mergeron 2025.739341.10__tar.gz → 2025.739355.1__tar.gz
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-2025.739341.10 → mergeron-2025.739355.1}/PKG-INFO +19 -20
- {mergeron-2025.739341.10 → mergeron-2025.739355.1}/README.rst +2 -2
- {mergeron-2025.739341.10 → mergeron-2025.739355.1}/pyproject.toml +43 -45
- {mergeron-2025.739341.10 → mergeron-2025.739355.1}/src/mergeron/__init__.py +1 -1
- {mergeron-2025.739341.10 → mergeron-2025.739355.1}/src/mergeron/core/__init__.py +1 -1
- {mergeron-2025.739341.10 → mergeron-2025.739355.1}/src/mergeron/core/empirical_margin_distribution.py +21 -20
- {mergeron-2025.739341.10 → mergeron-2025.739355.1}/src/mergeron/core/guidelines_boundaries.py +5 -5
- {mergeron-2025.739341.10 → mergeron-2025.739355.1}/src/mergeron/core/guidelines_boundary_functions.py +101 -115
- {mergeron-2025.739341.10 → mergeron-2025.739355.1}/src/mergeron/core/guidelines_boundary_functions_extra.py +13 -15
- {mergeron-2025.739341.10 → mergeron-2025.739355.1}/src/mergeron/gen/__init__.py +10 -2
- {mergeron-2025.739341.10 → mergeron-2025.739355.1}/src/mergeron/gen/data_generation.py +23 -20
- {mergeron-2025.739341.10 → mergeron-2025.739355.1}/src/mergeron/gen/data_generation_functions.py +5 -3
- {mergeron-2025.739341.10 → mergeron-2025.739355.1}/src/mergeron/core/ftc_merger_investigations_data.py +0 -0
- {mergeron-2025.739341.10 → mergeron-2025.739355.1}/src/mergeron/core/pseudorandom_numbers.py +0 -0
- {mergeron-2025.739341.10 → mergeron-2025.739355.1}/src/mergeron/data/__init__.py +0 -0
- {mergeron-2025.739341.10 → mergeron-2025.739355.1}/src/mergeron/data/damodaran_margin_data_serialized.zip +0 -0
- {mergeron-2025.739341.10 → mergeron-2025.739355.1}/src/mergeron/data/ftc_merger_investigations_data.zip +0 -0
- {mergeron-2025.739341.10 → mergeron-2025.739355.1}/src/mergeron/gen/enforcement_stats.py +0 -0
- {mergeron-2025.739341.10 → mergeron-2025.739355.1}/src/mergeron/gen/upp_tests.py +0 -0
- {mergeron-2025.739341.10 → mergeron-2025.739355.1}/src/mergeron/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: mergeron
|
|
3
|
-
Version: 2025.
|
|
3
|
+
Version: 2025.739355.1
|
|
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
|
|
@@ -18,23 +18,22 @@ Classifier: Programming Language :: Python :: 3
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
20
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
21
|
-
Requires-Dist: aenum (>=3.1.15
|
|
22
|
-
Requires-Dist: attrs (>=
|
|
23
|
-
Requires-Dist:
|
|
24
|
-
Requires-Dist: certifi (>=
|
|
25
|
-
Requires-Dist: h5py (>=3.13.0
|
|
26
|
-
Requires-Dist: jinja2 (>=3.1)
|
|
27
|
-
Requires-Dist: joblib (>=1.
|
|
28
|
-
Requires-Dist:
|
|
29
|
-
Requires-Dist:
|
|
30
|
-
Requires-Dist:
|
|
31
|
-
Requires-Dist:
|
|
32
|
-
Requires-Dist:
|
|
33
|
-
Requires-Dist:
|
|
34
|
-
Requires-Dist:
|
|
35
|
-
Requires-Dist:
|
|
36
|
-
Requires-Dist:
|
|
37
|
-
Requires-Dist: urllib3 (>=2.2.2,<3.0.0)
|
|
21
|
+
Requires-Dist: aenum (>=3.1.15)
|
|
22
|
+
Requires-Dist: attrs (>=25.3.0)
|
|
23
|
+
Requires-Dist: beautifulsoup4 (>=4.13.3)
|
|
24
|
+
Requires-Dist: certifi (>=2025.1.31)
|
|
25
|
+
Requires-Dist: h5py (>=3.13.0)
|
|
26
|
+
Requires-Dist: jinja2 (>=3.1.6)
|
|
27
|
+
Requires-Dist: joblib (>=1.4.2)
|
|
28
|
+
Requires-Dist: lxml (>=5.3.2)
|
|
29
|
+
Requires-Dist: matplotlib (>=3.10.1)
|
|
30
|
+
Requires-Dist: mpmath (>=1.3.0)
|
|
31
|
+
Requires-Dist: python-calamine (>=0.3.2)
|
|
32
|
+
Requires-Dist: ruamel-yaml (>=0.18.10)
|
|
33
|
+
Requires-Dist: scipy (>=1.15.2)
|
|
34
|
+
Requires-Dist: sympy (>=1.13.3)
|
|
35
|
+
Requires-Dist: types-beautifulsoup4 (>=4.12.0)
|
|
36
|
+
Requires-Dist: urllib3 (>=2.3.0)
|
|
38
37
|
Project-URL: Documentation, https://capeconomics.github.io/mergeron/
|
|
39
38
|
Project-URL: Repository, https://github.com/capeconomics/mergeron.git
|
|
40
39
|
Description-Content-Type: text/x-rst
|
|
@@ -87,8 +86,8 @@ To install the package, use the following shell command:
|
|
|
87
86
|
pip install mergeron
|
|
88
87
|
|
|
89
88
|
|
|
90
|
-
Documentation
|
|
89
|
+
Documentation
|
|
91
90
|
-------------
|
|
92
91
|
|
|
93
|
-
Usage guide and API reference
|
|
92
|
+
Usage guide and API reference are `available <https://capeconomics.github.io/mergeron/>`_.
|
|
94
93
|
|
|
@@ -46,7 +46,7 @@ To install the package, use the following shell command:
|
|
|
46
46
|
pip install mergeron
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
Documentation
|
|
49
|
+
Documentation
|
|
50
50
|
-------------
|
|
51
51
|
|
|
52
|
-
Usage guide and API reference
|
|
52
|
+
Usage guide and API reference are `available <https://capeconomics.github.io/mergeron/>`_.
|
|
@@ -4,6 +4,7 @@ authors = [{ name = "Murthy Kambhampaty", email = "smk@capeconomics.com" }]
|
|
|
4
4
|
description = "Python for analyzing merger enforcement policy"
|
|
5
5
|
readme = "README.rst"
|
|
6
6
|
license = "MIT"
|
|
7
|
+
license-files = ["./docs/source/license.rst"]
|
|
7
8
|
keywords = [
|
|
8
9
|
"merger enforcement policy",
|
|
9
10
|
"merger guidelines",
|
|
@@ -14,7 +15,7 @@ keywords = [
|
|
|
14
15
|
"upward pricing pressure",
|
|
15
16
|
"GUPPI",
|
|
16
17
|
]
|
|
17
|
-
version = "2025.
|
|
18
|
+
version = "2025.739355.1"
|
|
18
19
|
|
|
19
20
|
# Classifiers list: https://pypi.org/classifiers/
|
|
20
21
|
classifiers = [
|
|
@@ -33,57 +34,54 @@ classifiers = [
|
|
|
33
34
|
|
|
34
35
|
requires-python = ">=3.12"
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
dependencies = [
|
|
38
|
+
"aenum>=3.1.15",
|
|
39
|
+
"attrs>=25.3.0",
|
|
40
|
+
"beautifulsoup4>=4.13.3",
|
|
41
|
+
"certifi>=2025.1.31",
|
|
42
|
+
"h5py>=3.13.0",
|
|
43
|
+
"jinja2>=3.1.6",
|
|
44
|
+
"joblib>=1.4.2",
|
|
45
|
+
"lxml>=5.3.2",
|
|
46
|
+
"matplotlib>=3.10.1",
|
|
47
|
+
"mpmath>=1.3.0",
|
|
48
|
+
"python-calamine>=0.3.2",
|
|
49
|
+
"ruamel-yaml>=0.18.10",
|
|
50
|
+
"scipy>=1.15.2",
|
|
51
|
+
"sympy>=1.13.3",
|
|
52
|
+
"types-beautifulsoup4>=4.12.0",
|
|
53
|
+
"urllib3>=2.3.0",
|
|
54
|
+
]
|
|
41
55
|
|
|
42
56
|
[build-system]
|
|
43
57
|
requires = ["poetry-core"]
|
|
44
58
|
build-backend = "poetry.core.masonry.api"
|
|
45
59
|
|
|
46
60
|
|
|
47
|
-
[
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
aenum = "^3.1.15"
|
|
51
|
-
attrs = ">=23.2"
|
|
52
|
-
bs4 = ">=0.0.1"
|
|
53
|
-
jinja2 = ">=3.1"
|
|
54
|
-
joblib = ">=1.3"
|
|
55
|
-
matplotlib = ">=3.8"
|
|
56
|
-
mpmath = ">=1.3"
|
|
57
|
-
python = "^3.12"
|
|
58
|
-
scipy = ">=1.12"
|
|
59
|
-
sympy = ">=1.12"
|
|
60
|
-
certifi = ">=2023.11.17"
|
|
61
|
-
types-beautifulsoup4 = ">=4.11.2"
|
|
62
|
-
urllib3 = "^2.2.2"
|
|
63
|
-
ruamel-yaml = "^0.18.10"
|
|
64
|
-
h5py = "^3.13.0"
|
|
65
|
-
linuxdoc = "^20240924"
|
|
66
|
-
lxml = "^5.3.1"
|
|
67
|
-
python-calamine = "^0.3.1"
|
|
68
|
-
|
|
61
|
+
[project.urls]
|
|
62
|
+
Documentation = "https://capeconomics.github.io/mergeron/"
|
|
63
|
+
Repository = "https://github.com/capeconomics/mergeron.git"
|
|
69
64
|
|
|
70
65
|
[tool.poetry.group.dev.dependencies]
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
66
|
+
ipython = ">=9.1.0"
|
|
67
|
+
ipython-pygments-lexers = ">=1.1.1"
|
|
68
|
+
linuxdoc = ">=20240924"
|
|
69
|
+
mypy = ">=1.15.0"
|
|
70
|
+
pendulum = ">=3.0.0"
|
|
71
|
+
pipdeptree = ">=2.26.0"
|
|
72
|
+
coverage = ">=7.8.0"
|
|
73
|
+
pytest = ">=8.3.5"
|
|
74
|
+
pytest-cov = ">=6.1.1"
|
|
75
|
+
pytest-xdist = ">=3.6.1"
|
|
76
|
+
ruff = ">=0.11.4"
|
|
77
|
+
semver = ">=3.0.4"
|
|
78
|
+
twine = ">=6.1.0"
|
|
79
|
+
virtualenv = ">=20.30.0"
|
|
80
|
+
sphinx = ">=8.2.3"
|
|
79
81
|
sphinx-autoapi = ">=3.6.0"
|
|
80
|
-
sphinx-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
pytest-cov = "^6.0.0"
|
|
84
|
-
pendulum = "^3.0.0"
|
|
85
|
-
rstcheck = "^6.2.4"
|
|
86
|
-
ipython-pygments-lexers = "^1.1.1"
|
|
82
|
+
sphinx-autodoc-typehints = ">=3.1.0"
|
|
83
|
+
sphinx-immaterial = ">=0.13.5"
|
|
84
|
+
pkginfo = ">=1.12.1.2"
|
|
87
85
|
|
|
88
86
|
|
|
89
87
|
[tool.ruff]
|
|
@@ -113,7 +111,6 @@ exclude = [
|
|
|
113
111
|
"venv",
|
|
114
112
|
]
|
|
115
113
|
|
|
116
|
-
target-version = "py312"
|
|
117
114
|
fix = true
|
|
118
115
|
|
|
119
116
|
# Same as Black.
|
|
@@ -185,7 +182,8 @@ line-ending = "lf"
|
|
|
185
182
|
preview = true
|
|
186
183
|
|
|
187
184
|
[tool.mypy]
|
|
188
|
-
|
|
185
|
+
|
|
186
|
+
cache_fine_grained = true
|
|
189
187
|
ignore_missing_imports = false
|
|
190
188
|
strict = true
|
|
191
189
|
enable_incomplete_feature = ["PreciseTupleTypes"]
|
|
@@ -51,7 +51,7 @@ from scipy import stats # type: ignore
|
|
|
51
51
|
|
|
52
52
|
from .. import NTHREADS, VERSION, ArrayDouble, this_yaml # noqa: TID252
|
|
53
53
|
from .. import WORK_DIR as PKG_WORK_DIR # noqa: TID252
|
|
54
|
-
from . import DEFAULT_BITGENERATOR
|
|
54
|
+
from . import DEFAULT_BITGENERATOR, _mappingproxy_from_mapping
|
|
55
55
|
|
|
56
56
|
__version__ = VERSION
|
|
57
57
|
|
|
@@ -60,7 +60,9 @@ WORK_DIR = globals().get("WORK_DIR", PKG_WORK_DIR)
|
|
|
60
60
|
|
|
61
61
|
MGNDATA_ARCHIVE_PATH = WORK_DIR / "damodaran_margin_data_serialized.zip"
|
|
62
62
|
|
|
63
|
-
type DamodaranMarginData = MappingProxyType[
|
|
63
|
+
type DamodaranMarginData = MappingProxyType[
|
|
64
|
+
str, MappingProxyType[str, MappingProxyType[str, float | int]]
|
|
65
|
+
]
|
|
64
66
|
|
|
65
67
|
FINANCIAL_INDUSTRIES = {
|
|
66
68
|
_i.upper()
|
|
@@ -170,8 +172,8 @@ def margin_data_builder(
|
|
|
170
172
|
|
|
171
173
|
_missing = {"GROSS MARGIN": 0.0, "NUMBER OF FIRMS": 0.0}
|
|
172
174
|
gm, fc = zip(*[
|
|
173
|
-
[_v.get(_sk, _missing)
|
|
174
|
-
for
|
|
175
|
+
[_v.get(_sk, _missing)[_f] for _f in _missing]
|
|
176
|
+
for _v in _margin_data_dict.values()
|
|
175
177
|
])
|
|
176
178
|
|
|
177
179
|
average_margin, firm_count = np.array(gm, float), np.array(fc, int)
|
|
@@ -223,7 +225,7 @@ def margin_data_getter(
|
|
|
223
225
|
ws_pat = re.compile(r"\s+")
|
|
224
226
|
|
|
225
227
|
# Parse workbooks and save margin data dictionary
|
|
226
|
-
|
|
228
|
+
margin_data_: dict[str, dict[str, MappingProxyType[str, float]]] = {}
|
|
227
229
|
for _p in (WORK_DIR / "damodaran_margin_data_archive").iterdir():
|
|
228
230
|
xl_wbk = CalamineWorkbook.from_path(_p)
|
|
229
231
|
xl_wks = xl_wbk.get_sheet_by_index(
|
|
@@ -231,36 +233,35 @@ def margin_data_getter(
|
|
|
231
233
|
).to_python()
|
|
232
234
|
if xl_wks[8][2] != "Gross Margin":
|
|
233
235
|
raise ValueError("Worksheet does not match expected layout.")
|
|
236
|
+
row_keys: list[str] = [_c.upper() for _c in xl_wks[8][1:]] # type: ignore
|
|
234
237
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
238
|
+
_u = xl_wks[0][1]
|
|
239
|
+
if not isinstance(_u, datetime.datetime):
|
|
240
|
+
raise ValueError("Worksheet does not match expected layout.")
|
|
241
|
+
update: str = _u.isoformat()[:10]
|
|
242
|
+
|
|
243
|
+
margin_data_annual = margin_data_.setdefault(update, {})
|
|
244
|
+
for xl_row in xl_wks[9:]:
|
|
240
245
|
row_key = _s.upper() if isinstance((_s := xl_row[0]), str) else ""
|
|
241
246
|
|
|
242
|
-
if
|
|
243
|
-
read_row_flag = True
|
|
244
|
-
row_keys = [_c.upper() for _c in xl_row]
|
|
245
|
-
continue
|
|
246
|
-
elif not read_row_flag or not row_key or row_key.startswith("TOTAL"):
|
|
247
|
+
if not row_key or row_key.startswith("TOTAL"):
|
|
247
248
|
continue
|
|
248
249
|
else:
|
|
249
|
-
xl_row[1] = int(xl_row[1])
|
|
250
|
+
xl_row[1] = int(xl_row[1]) # type: ignore
|
|
250
251
|
margin_data_annual |= MappingProxyType({
|
|
251
252
|
row_key: MappingProxyType(
|
|
252
|
-
dict(zip(row_keys
|
|
253
|
+
dict(zip(row_keys, xl_row[1:], strict=True)) # type: ignore
|
|
253
254
|
)
|
|
254
255
|
})
|
|
255
256
|
|
|
256
|
-
|
|
257
|
+
margin_data_map: DamodaranMarginData = _mappingproxy_from_mapping(margin_data_)
|
|
257
258
|
with (
|
|
258
259
|
zipfile.ZipFile(data_archive_path, "w") as _yzp,
|
|
259
260
|
_yzp.open(f"{data_archive_path.stem}.yaml", "w") as _yfh,
|
|
260
261
|
):
|
|
261
|
-
this_yaml.dump(
|
|
262
|
+
this_yaml.dump(margin_data_map, _yfh)
|
|
262
263
|
|
|
263
|
-
return
|
|
264
|
+
return margin_data_map
|
|
264
265
|
|
|
265
266
|
|
|
266
267
|
def margin_data_downloader() -> None:
|
{mergeron-2025.739341.10 → mergeron-2025.739355.1}/src/mergeron/core/guidelines_boundaries.py
RENAMED
|
@@ -334,18 +334,18 @@ class DiversionRatioBoundary:
|
|
|
334
334
|
|
|
335
335
|
match self.agg_method:
|
|
336
336
|
case UPPAggrSelector.DIS:
|
|
337
|
-
upp_agg_fn = gbfn.
|
|
337
|
+
upp_agg_fn = gbfn.diversion_share_boundary_wtd_avg
|
|
338
338
|
upp_agg_kwargs |= {"agg_method": "distance", "weighting": None}
|
|
339
339
|
case UPPAggrSelector.AVG:
|
|
340
|
-
upp_agg_fn = gbfn.
|
|
340
|
+
upp_agg_fn = gbfn.diversion_share_boundary_xact_avg # type: ignore
|
|
341
341
|
case UPPAggrSelector.MAX:
|
|
342
|
-
upp_agg_fn = gbfn.
|
|
342
|
+
upp_agg_fn = gbfn.diversion_share_boundary_max # type: ignore
|
|
343
343
|
upp_agg_kwargs = {"dps": 10} # replace here
|
|
344
344
|
case UPPAggrSelector.MIN:
|
|
345
|
-
upp_agg_fn = gbfn.
|
|
345
|
+
upp_agg_fn = gbfn.diversion_share_boundary_min # type: ignore
|
|
346
346
|
upp_agg_kwargs |= {"dps": 10} # update here
|
|
347
347
|
case _:
|
|
348
|
-
upp_agg_fn = gbfn.
|
|
348
|
+
upp_agg_fn = gbfn.diversion_share_boundary_wtd_avg
|
|
349
349
|
|
|
350
350
|
aggregator_: Literal["arithmetic mean", "geometric mean", "distance"]
|
|
351
351
|
if self.agg_method.value.endswith("geometric mean"):
|
|
@@ -202,7 +202,7 @@ def hhi_post_contrib_boundary(
|
|
|
202
202
|
|
|
203
203
|
|
|
204
204
|
# hand-rolled root finding
|
|
205
|
-
def
|
|
205
|
+
def diversion_share_boundary_wtd_avg(
|
|
206
206
|
_delta_star: float = 0.075,
|
|
207
207
|
_r_val: float = DEFAULT_REC_RATIO,
|
|
208
208
|
/,
|
|
@@ -370,7 +370,7 @@ def shrratio_boundary_wtd_avg(
|
|
|
370
370
|
else:
|
|
371
371
|
s_2_oddsum -= s_1_pre
|
|
372
372
|
|
|
373
|
-
_s_intcpt =
|
|
373
|
+
_s_intcpt = _diversion_share_boundary_intcpt(
|
|
374
374
|
s_2_pre,
|
|
375
375
|
_delta_star,
|
|
376
376
|
_r_val,
|
|
@@ -406,7 +406,7 @@ def shrratio_boundary_wtd_avg(
|
|
|
406
406
|
)
|
|
407
407
|
|
|
408
408
|
|
|
409
|
-
def
|
|
409
|
+
def diversion_share_boundary_xact_avg(
|
|
410
410
|
_delta_star: float = 0.075,
|
|
411
411
|
_r_val: float = DEFAULT_REC_RATIO,
|
|
412
412
|
/,
|
|
@@ -467,47 +467,46 @@ def shrratio_boundary_xact_avg(
|
|
|
467
467
|
Array of share-pairs, area under boundary, area under boundary.
|
|
468
468
|
|
|
469
469
|
"""
|
|
470
|
-
_delta_star, _r_val = (mpf(f"{_v}") for _v in (_delta_star, _r_val))
|
|
471
470
|
_s_mid = _delta_star / (1 + _delta_star)
|
|
472
|
-
_step_size =
|
|
471
|
+
_step_size = 10**-dps
|
|
473
472
|
|
|
474
473
|
_bdry_start = np.array([(_s_mid, _s_mid)])
|
|
475
|
-
_s_1 = np.
|
|
474
|
+
_s_1 = np.arange(_s_mid - _step_size, 0, -_step_size)
|
|
476
475
|
if recapture_form == "inside-out":
|
|
477
|
-
_s_intcpt =
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
2 *
|
|
476
|
+
_s_intcpt: float = (
|
|
477
|
+
2 * _delta_star * _r_val + 1 - np.abs(2 * _delta_star * _r_val - 1)
|
|
478
|
+
) / (2 * _r_val)
|
|
479
|
+
nr_t1: ArrayDouble = (
|
|
480
|
+
1 + 2 * _delta_star * _r_val * (1 - _s_1) - _s_1 * (1 - _r_val)
|
|
482
481
|
)
|
|
483
|
-
nr_t1 = 1 + 2 * _delta_star * _r_val * (1 - _s_1) - _s_1 * (1 - _r_val)
|
|
484
482
|
|
|
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
|
|
483
|
+
nr_sqrt_mdr: float = 4 * _delta_star * _r_val
|
|
484
|
+
nr_sqrt_mdr2: float = nr_sqrt_mdr * _r_val
|
|
485
|
+
nr_sqrt_md2r2: float = nr_sqrt_mdr2 * _delta_star
|
|
488
486
|
|
|
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
|
|
487
|
+
nr_sqrt_t1: ArrayDouble = nr_sqrt_md2r2 * (_s_1**2 - 2 * _s_1 + 1)
|
|
488
|
+
nr_sqrt_t2: ArrayDouble = nr_sqrt_mdr2 * _s_1 * (_s_1 - 1)
|
|
489
|
+
nr_sqrt_t3: ArrayDouble = nr_sqrt_mdr * (2 * _s_1 - _s_1**2 - 1)
|
|
490
|
+
nr_sqrt_t4: ArrayDouble = (_s_1**2) * (_r_val**2 - 6 * _r_val + 1)
|
|
491
|
+
nr_sqrt_t5: ArrayDouble = _s_1 * (6 * _r_val - 2) + 1
|
|
494
492
|
|
|
495
|
-
nr_t2_mdr =
|
|
493
|
+
nr_t2_mdr: ArrayDouble = (
|
|
494
|
+
nr_sqrt_t1 + nr_sqrt_t2 + nr_sqrt_t3 + nr_sqrt_t4 + nr_sqrt_t5
|
|
495
|
+
)
|
|
496
496
|
|
|
497
497
|
# Alternative grouping of terms in np.sqrt
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
)
|
|
501
|
-
nr_sqrt_s1 = _s_1 * (
|
|
498
|
+
nr_sqrt_nos1: float = nr_sqrt_md2r2 - nr_sqrt_mdr + 1
|
|
499
|
+
nr_sqrt_s1: ArrayDouble = _s_1 * (
|
|
502
500
|
-2 * nr_sqrt_md2r2 - nr_sqrt_mdr2 + 2 * nr_sqrt_mdr + 6 * _r_val - 2
|
|
503
501
|
)
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
502
|
+
nr_sqrt_s1sq: ArrayDouble = (_s_1**2) * (
|
|
503
|
+
nr_sqrt_md2r2 + nr_sqrt_mdr2 - nr_sqrt_mdr + _r_val**2 - 6 * _r_val + 1
|
|
504
|
+
)
|
|
505
|
+
nr_t2_s1: ArrayDouble = nr_sqrt_s1sq + nr_sqrt_s1 + nr_sqrt_nos1
|
|
507
506
|
|
|
508
507
|
if not np.isclose(
|
|
509
|
-
np.einsum("i->", nr_t2_mdr
|
|
510
|
-
np.einsum("i->", nr_t2_s1
|
|
508
|
+
np.einsum("i->", nr_t2_mdr),
|
|
509
|
+
np.einsum("i->", nr_t2_s1),
|
|
511
510
|
rtol=0,
|
|
512
511
|
atol=0.5 * dps,
|
|
513
512
|
):
|
|
@@ -516,25 +515,28 @@ def shrratio_boundary_xact_avg(
|
|
|
516
515
|
f"with recapture spec, {f'"{recapture_form}"'} is incorrect."
|
|
517
516
|
)
|
|
518
517
|
|
|
519
|
-
s_2 = (nr_t1 -
|
|
518
|
+
s_2: ArrayDouble = (nr_t1 - nr_t2_s1**0.5) / (2 * _r_val)
|
|
520
519
|
|
|
521
520
|
else:
|
|
522
|
-
_s_intcpt =
|
|
523
|
-
s_2 = (
|
|
524
|
-
|
|
521
|
+
_s_intcpt: float = _delta_star + 1 / 2 - np.abs(_delta_star - 1 / 2)
|
|
522
|
+
s_2: ArrayDouble = (
|
|
523
|
+
0.5
|
|
525
524
|
+ _delta_star
|
|
526
525
|
- _delta_star * _s_1
|
|
527
|
-
-
|
|
528
|
-
(
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
526
|
+
- (
|
|
527
|
+
(
|
|
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)
|
|
533
|
+
)
|
|
534
|
+
** 0.5
|
|
533
535
|
)
|
|
534
536
|
)
|
|
535
537
|
|
|
536
538
|
bdry_inner = np.stack((_s_1, s_2), axis=1)
|
|
537
|
-
bdry_end = np.array([(
|
|
539
|
+
bdry_end = np.array([(0.0, _s_intcpt)], float)
|
|
538
540
|
|
|
539
541
|
bdry = np.vstack((
|
|
540
542
|
bdry_end,
|
|
@@ -542,14 +544,14 @@ def shrratio_boundary_xact_avg(
|
|
|
542
544
|
_bdry_start,
|
|
543
545
|
bdry_inner[:, ::-1],
|
|
544
546
|
bdry_end[:, ::-1],
|
|
545
|
-
))
|
|
547
|
+
))
|
|
546
548
|
s_2 = np.concatenate((np.array([_s_mid], float), s_2))
|
|
547
549
|
|
|
548
550
|
bdry_ends = [0, -1]
|
|
549
551
|
bdry_odds = np.array(range(1, len(s_2), 2), int)
|
|
550
552
|
bdry_evns = np.array(range(2, len(s_2), 2), int)
|
|
551
553
|
|
|
552
|
-
# Double the
|
|
554
|
+
# Double the area under the curve, and subtract the double counted bit.
|
|
553
555
|
bdry_area_simpson = 2 * _step_size * (
|
|
554
556
|
(4 / 3) * np.sum(s_2.take(bdry_odds))
|
|
555
557
|
+ (2 / 3) * np.sum(s_2.take(bdry_evns))
|
|
@@ -559,7 +561,7 @@ def shrratio_boundary_xact_avg(
|
|
|
559
561
|
return GuidelinesBoundary(bdry, round(float(bdry_area_simpson), dps))
|
|
560
562
|
|
|
561
563
|
|
|
562
|
-
def
|
|
564
|
+
def diversion_share_boundary_min(
|
|
563
565
|
_delta_star: float = 0.075,
|
|
564
566
|
_r_val: float = DEFAULT_REC_RATIO,
|
|
565
567
|
/,
|
|
@@ -615,7 +617,7 @@ def shrratio_boundary_min(
|
|
|
615
617
|
)
|
|
616
618
|
|
|
617
619
|
|
|
618
|
-
def
|
|
620
|
+
def diversion_share_boundary_max(
|
|
619
621
|
_delta_star: float = 0.075, _: float = DEFAULT_REC_RATIO, /, *, dps: int = 10
|
|
620
622
|
) -> GuidelinesBoundary:
|
|
621
623
|
R"""
|
|
@@ -647,7 +649,7 @@ def shrratio_boundary_max(
|
|
|
647
649
|
)
|
|
648
650
|
|
|
649
651
|
|
|
650
|
-
def
|
|
652
|
+
def _diversion_share_boundary_intcpt(
|
|
651
653
|
s_2_pre: float,
|
|
652
654
|
_delta_star: MPFloat,
|
|
653
655
|
_r_val: MPFloat,
|
|
@@ -783,7 +785,7 @@ def boundary_plot(
|
|
|
783
785
|
mktshare_plot_flag: bool = True,
|
|
784
786
|
mktshare_axes_flag: bool = True,
|
|
785
787
|
backend: Literal["pgf"] | str | None = "pgf",
|
|
786
|
-
) -> tuple[mpl.
|
|
788
|
+
) -> tuple[mpl.figure.Figure, Callable[..., None]]:
|
|
787
789
|
"""Set up basic figure and axes for plots of safe harbor boundaries.
|
|
788
790
|
|
|
789
791
|
See, https://matplotlib.org/stable/tutorials/text/pgf.html
|
|
@@ -807,9 +809,6 @@ def boundary_plot(
|
|
|
807
809
|
R' "luaotfload.patch_font", embedfull, "embedfull"'
|
|
808
810
|
R")",
|
|
809
811
|
R"\end{luacode}",
|
|
810
|
-
R"\usepackage{mathtools}",
|
|
811
|
-
R"\usepackage{unicode-math}",
|
|
812
|
-
R"\setmathfont[math-style=ISO]{STIX Two Math}",
|
|
813
812
|
R"\setmainfont{STIX Two Text}",
|
|
814
813
|
r"\setsansfont{Fira Sans Light}",
|
|
815
814
|
R"\setmonofont[Scale=MatchLowercase,]{Fira Mono}",
|
|
@@ -822,6 +821,9 @@ def boundary_plot(
|
|
|
822
821
|
R" Numbers={Monospaced, Lining},",
|
|
823
822
|
R" LetterSpace=0.50,",
|
|
824
823
|
R" }",
|
|
824
|
+
R"\usepackage{mathtools}",
|
|
825
|
+
R"\usepackage{unicode-math}",
|
|
826
|
+
R"\setmathfont[math-style=ISO]{STIX Two Math}",
|
|
825
827
|
R"\usepackage[",
|
|
826
828
|
R" activate={true, nocompatibility},",
|
|
827
829
|
R" tracking=true,",
|
|
@@ -831,57 +833,53 @@ def boundary_plot(
|
|
|
831
833
|
|
|
832
834
|
# Initialize a canvas with a single figure (set of axes)
|
|
833
835
|
fig_ = plt.figure(figsize=(5, 5), dpi=600)
|
|
834
|
-
|
|
836
|
+
ax_ = fig_.add_subplot()
|
|
837
|
+
# Set the width of axis grid lines, and tick marks:
|
|
838
|
+
# both axes, both major and minor ticks
|
|
839
|
+
# Frame, grid, and face color
|
|
840
|
+
for _spos0 in "left", "bottom":
|
|
841
|
+
ax_.spines[_spos0].set_linewidth(0.5)
|
|
842
|
+
ax_.spines[_spos0].set_zorder(5)
|
|
843
|
+
for _spos1 in "top", "right":
|
|
844
|
+
ax_.spines[_spos1].set_linewidth(0.0)
|
|
845
|
+
ax_.spines[_spos1].set_zorder(0)
|
|
846
|
+
ax_.spines[_spos1].set_visible(False)
|
|
847
|
+
ax_.set_facecolor("#E6E6E6")
|
|
848
|
+
|
|
849
|
+
ax_.grid(linewidth=0.5, linestyle=":", color="grey", zorder=1)
|
|
850
|
+
ax_.tick_params(axis="both", which="both", width=0.5)
|
|
851
|
+
|
|
852
|
+
# Tick marks skip, size, and rotation
|
|
853
|
+
# x-axis
|
|
854
|
+
for _t in ax_.get_xticklabels():
|
|
855
|
+
_t.update({"fontsize": 6, "rotation": 45, "ha": "right"})
|
|
856
|
+
# y-axis
|
|
857
|
+
for _t in ax_.get_yticklabels():
|
|
858
|
+
_t.update({"fontsize": 6, "rotation": 0, "ha": "right"})
|
|
835
859
|
|
|
836
860
|
def _set_axis_def(
|
|
837
|
-
|
|
861
|
+
ax0_: mpa.Axes,
|
|
838
862
|
/,
|
|
839
863
|
*,
|
|
840
864
|
mktshare_plot_flag: bool = False,
|
|
841
865
|
mktshare_axes_flag: bool = False,
|
|
842
|
-
) ->
|
|
843
|
-
# Set the width of axis grid lines, and tick marks:
|
|
844
|
-
# both axes, both major and minor ticks
|
|
845
|
-
# Frame, grid, and face color
|
|
846
|
-
for _spos0 in "left", "bottom":
|
|
847
|
-
ax1_.spines[_spos0].set_linewidth(0.5)
|
|
848
|
-
ax1_.spines[_spos0].set_zorder(5)
|
|
849
|
-
for _spos1 in "top", "right":
|
|
850
|
-
ax1_.spines[_spos1].set_linewidth(0.0)
|
|
851
|
-
ax1_.spines[_spos1].set_zorder(0)
|
|
852
|
-
ax1_.spines[_spos1].set_visible(False)
|
|
853
|
-
ax1_.set_facecolor("#E6E6E6")
|
|
854
|
-
|
|
855
|
-
ax1_.grid(linewidth=0.5, linestyle=":", color="grey", zorder=1)
|
|
856
|
-
ax1_.tick_params(axis="x", which="both", width=0.5)
|
|
857
|
-
ax1_.tick_params(axis="y", which="both", width=0.5)
|
|
858
|
-
|
|
859
|
-
# Tick marks skip, size, and rotation
|
|
860
|
-
# x-axis
|
|
861
|
-
plt.setp(
|
|
862
|
-
ax1_.xaxis.get_majorticklabels(),
|
|
863
|
-
horizontalalignment="right",
|
|
864
|
-
fontsize=6,
|
|
865
|
-
rotation=45,
|
|
866
|
-
)
|
|
867
|
-
# y-axis
|
|
868
|
-
plt.setp(
|
|
869
|
-
ax1_.yaxis.get_majorticklabels(), horizontalalignment="right", fontsize=6
|
|
870
|
-
)
|
|
871
|
-
|
|
866
|
+
) -> None:
|
|
872
867
|
if mktshare_plot_flag:
|
|
868
|
+
# Axis scale
|
|
869
|
+
ax0_.set_xlim(0, 1)
|
|
870
|
+
ax0_.set_ylim(0, 1)
|
|
871
|
+
ax0_.set_aspect(1.0)
|
|
872
|
+
|
|
873
873
|
# Plot the ray of symmetry
|
|
874
|
-
|
|
874
|
+
ax0_.plot(
|
|
875
875
|
[0, 1], [0, 1], linewidth=0.5, linestyle=":", color="grey", zorder=1
|
|
876
876
|
)
|
|
877
877
|
|
|
878
|
-
#
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
# Truncate the axis frame to a triangle:
|
|
884
|
-
ax1_.add_patch(
|
|
878
|
+
# Truncate the axis frame to a triangle bounded by the other diagonal:
|
|
879
|
+
ax0_.plot(
|
|
880
|
+
[0, 1], [1, 0], linestyle="-", linewidth=0.5, color="black", zorder=1
|
|
881
|
+
)
|
|
882
|
+
ax0_.add_patch(
|
|
885
883
|
mpp.Rectangle(
|
|
886
884
|
xy=(1.0025, 0.00),
|
|
887
885
|
width=1.1 * mp.sqrt(2),
|
|
@@ -894,48 +892,36 @@ def boundary_plot(
|
|
|
894
892
|
zorder=5,
|
|
895
893
|
)
|
|
896
894
|
)
|
|
897
|
-
# Feasible space is bounded by the other diagonal:
|
|
898
|
-
ax1_.plot(
|
|
899
|
-
[0, 1], [1, 0], linestyle="-", linewidth=0.5, color="black", zorder=1
|
|
900
|
-
)
|
|
901
895
|
|
|
902
896
|
# Axis Tick-mark locations
|
|
903
897
|
# One can supply an argument to mpt.AutoMinorLocator to
|
|
904
898
|
# specify a fixed number of minor intervals per major interval, e.g.:
|
|
905
899
|
# minorLocator = mpt.AutoMinorLocator(2)
|
|
906
900
|
# would lead to a single minor tick between major ticks.
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
if axs_ == ax1_.xaxis:
|
|
911
|
-
_majorticklabels_rot = 45
|
|
912
|
-
elif axs_ == ax1_.yaxis:
|
|
913
|
-
_majorticklabels_rot = 0
|
|
914
|
-
# x-axis
|
|
915
|
-
axs_.set_major_locator(major_locator)
|
|
916
|
-
axs_.set_minor_locator(minor_locator)
|
|
901
|
+
for axs_ in ax0_.xaxis, ax0_.yaxis:
|
|
902
|
+
axs_.set_major_locator(mpt.MultipleLocator(0.05))
|
|
903
|
+
axs_.set_minor_locator(mpt.AutoMinorLocator(5))
|
|
917
904
|
# It"s always x when specifying the format
|
|
918
905
|
axs_.set_major_formatter(mpt.StrMethodFormatter("{x:>3.0%}"))
|
|
919
906
|
|
|
920
907
|
# Hide every other tick-label
|
|
921
|
-
for axl_ in
|
|
922
|
-
|
|
908
|
+
for axl_ in ax0_.get_xticklabels(), ax0_.get_yticklabels():
|
|
909
|
+
for _t in axl_[::2]:
|
|
910
|
+
_t.set_visible(False)
|
|
923
911
|
|
|
924
912
|
# Axis labels
|
|
925
913
|
if mktshare_axes_flag:
|
|
926
914
|
# x-axis
|
|
927
|
-
|
|
928
|
-
|
|
915
|
+
ax0_.set_xlabel("Firm 1 Market Share, $s_1$", fontsize=10)
|
|
916
|
+
ax0_.xaxis.set_label_coords(0.75, -0.1)
|
|
929
917
|
# y-axis
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
return ax1_
|
|
918
|
+
ax0_.set_ylabel("Firm 2 Market Share, $s_2$", fontsize=10)
|
|
919
|
+
ax0_.yaxis.set_label_coords(-0.1, 0.75)
|
|
934
920
|
|
|
935
|
-
|
|
936
|
-
|
|
921
|
+
_set_axis_def(
|
|
922
|
+
ax_,
|
|
937
923
|
mktshare_plot_flag=mktshare_plot_flag,
|
|
938
924
|
mktshare_axes_flag=mktshare_axes_flag,
|
|
939
925
|
)
|
|
940
926
|
|
|
941
|
-
return
|
|
927
|
+
return fig_, _set_axis_def
|
|
@@ -7,11 +7,9 @@ poor performance
|
|
|
7
7
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
from collections.abc import Callable
|
|
11
10
|
from typing import Literal
|
|
12
11
|
|
|
13
12
|
import numpy as np
|
|
14
|
-
from attrs import frozen
|
|
15
13
|
from mpmath import mp, mpf # type: ignore
|
|
16
14
|
from scipy.spatial.distance import minkowski as distance_function # type: ignore
|
|
17
15
|
from sympy import lambdify, simplify, solve, symbols # type: ignore
|
|
@@ -96,7 +94,7 @@ def hhi_delta_boundary_qdtr(_dh_val: float = 0.01, /) -> GuidelinesBoundaryCalla
|
|
|
96
94
|
)
|
|
97
95
|
|
|
98
96
|
|
|
99
|
-
def
|
|
97
|
+
def diversion_share_boundary_qdtr_wtd_avg(
|
|
100
98
|
_delta_star: float = 0.075,
|
|
101
99
|
_r_val: float = DEFAULT_REC_RATIO,
|
|
102
100
|
/,
|
|
@@ -211,7 +209,7 @@ def shrratio_boundary_qdtr_wtd_avg(
|
|
|
211
209
|
)
|
|
212
210
|
|
|
213
211
|
|
|
214
|
-
def
|
|
212
|
+
def diversion_share_boundary_distance(
|
|
215
213
|
_delta_star: float = 0.075,
|
|
216
214
|
_r_val: float = DEFAULT_REC_RATIO,
|
|
217
215
|
/,
|
|
@@ -220,14 +218,14 @@ def shrratio_boundary_distance(
|
|
|
220
218
|
weighting: Literal["own-share", "cross-product-share"] | None = "own-share",
|
|
221
219
|
recapture_form: Literal["inside-out", "proportional"] = "inside-out",
|
|
222
220
|
dps: int = 5,
|
|
223
|
-
) ->
|
|
221
|
+
) -> GuidelinesBoundary:
|
|
224
222
|
R"""
|
|
225
223
|
Share combinations for the share-ratio boundaries using various aggregators.
|
|
226
224
|
|
|
227
225
|
Reimplements the arithmetic-averages and distance estimations from function,
|
|
228
|
-
`
|
|
226
|
+
`diversion_share_boundary_wtd_avg` but uses the Minkowski-distance function,
|
|
229
227
|
`scipy.spatial.distance.minkowski` for all aggregators. This reimplementation
|
|
230
|
-
is useful for testing the output of `
|
|
228
|
+
is useful for testing the output of `diversion_share_boundary_wtd_avg`
|
|
231
229
|
but runs considerably slower.
|
|
232
230
|
|
|
233
231
|
Parameters
|
|
@@ -330,7 +328,7 @@ def shrratio_boundary_distance(
|
|
|
330
328
|
else:
|
|
331
329
|
s_2_oddsum -= s_1_pre
|
|
332
330
|
|
|
333
|
-
s_intcpt = gbf.
|
|
331
|
+
s_intcpt = gbf._diversion_share_boundary_intcpt(
|
|
334
332
|
s_1_pre,
|
|
335
333
|
_delta_star,
|
|
336
334
|
_r_val,
|
|
@@ -360,20 +358,20 @@ def shrratio_boundary_distance(
|
|
|
360
358
|
|
|
361
359
|
bdry_points.append((mpf("0.0"), s_intcpt))
|
|
362
360
|
# Points defining boundary to point-of-symmetry
|
|
363
|
-
return
|
|
361
|
+
return GuidelinesBoundary(
|
|
364
362
|
np.vstack((bdry_points[::-1], np.flip(bdry_points[1:], 1))),
|
|
365
363
|
round(float(bdry_area_total), dps),
|
|
366
364
|
)
|
|
367
365
|
|
|
368
366
|
|
|
369
|
-
def
|
|
367
|
+
def diversion_share_boundary_xact_avg_mp(
|
|
370
368
|
_delta_star: float = 0.075,
|
|
371
369
|
_r_val: float = DEFAULT_REC_RATIO,
|
|
372
370
|
/,
|
|
373
371
|
*,
|
|
374
372
|
recapture_form: Literal["inside-out", "proportional"] = "inside-out",
|
|
375
373
|
dps: int = 5,
|
|
376
|
-
) ->
|
|
374
|
+
) -> GuidelinesBoundary:
|
|
377
375
|
R"""
|
|
378
376
|
Share combinations along the simple average diversion-ratio boundary.
|
|
379
377
|
|
|
@@ -515,12 +513,12 @@ def shrratio_boundary_xact_avg_mp(
|
|
|
515
513
|
+ (1 / 3) * np.sum(s_2.take(bdry_ends))
|
|
516
514
|
) - mp.power(_s_mid, 2)
|
|
517
515
|
|
|
518
|
-
return
|
|
516
|
+
return GuidelinesBoundary(bdry, float(mp.nstr(bdry_area_simpson, dps)))
|
|
519
517
|
|
|
520
518
|
|
|
521
|
-
#
|
|
519
|
+
# diversion_share_boundary_wtd_avg_autoroot
|
|
522
520
|
# this function is about half as fast as the manual one! ... and a touch less precise
|
|
523
|
-
def
|
|
521
|
+
def _diversion_share_boundary_wtd_avg_autoroot(
|
|
524
522
|
_delta_star: float = 0.075,
|
|
525
523
|
_r_val: float = DEFAULT_REC_RATIO,
|
|
526
524
|
/,
|
|
@@ -687,7 +685,7 @@ def _shrratio_boundary_wtd_avg_autoroot(
|
|
|
687
685
|
else:
|
|
688
686
|
s_2_oddsum -= s_1_pre
|
|
689
687
|
|
|
690
|
-
_s_intcpt = gbf.
|
|
688
|
+
_s_intcpt = gbf._diversion_share_boundary_intcpt(
|
|
691
689
|
s_2_pre,
|
|
692
690
|
_delta_star,
|
|
693
691
|
_r_val,
|
|
@@ -4,8 +4,10 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import enum
|
|
6
6
|
import io
|
|
7
|
+
import zipfile
|
|
7
8
|
from collections.abc import Sequence
|
|
8
9
|
from operator import attrgetter
|
|
10
|
+
from typing import IO
|
|
9
11
|
|
|
10
12
|
import h5py # type: ignore
|
|
11
13
|
import numpy as np
|
|
@@ -370,7 +372,11 @@ class PCMSpec:
|
|
|
370
372
|
_v: ArrayFloat | Sequence[ArrayDouble] | None,
|
|
371
373
|
) -> None:
|
|
372
374
|
if _i.dist_type.name.startswith("BETA"):
|
|
373
|
-
if
|
|
375
|
+
if (
|
|
376
|
+
_v is None
|
|
377
|
+
or not hasattr(_v, "len")
|
|
378
|
+
or (isinstance(_v, np.ndarray) and not any(_v.shape))
|
|
379
|
+
):
|
|
374
380
|
pass
|
|
375
381
|
elif np.array_equal(_v, DEFAULT_DIST_PARMS):
|
|
376
382
|
raise ValueError(
|
|
@@ -521,7 +527,9 @@ class MarketSampleData:
|
|
|
521
527
|
return byte_stream.getvalue()
|
|
522
528
|
|
|
523
529
|
@classmethod
|
|
524
|
-
def from_h5f(
|
|
530
|
+
def from_h5f(
|
|
531
|
+
cls, _hfh: io.BufferedReader | zipfile.ZipExtFile | IO[bytes]
|
|
532
|
+
) -> MarketSampleData:
|
|
525
533
|
"""Load market sample data from HDF5 file."""
|
|
526
534
|
with h5py.File(_hfh, "r") as _h5f:
|
|
527
535
|
_retval = cls(**{_a: _h5f[_a][:] for _a in _h5f})
|
|
@@ -445,20 +445,22 @@ class MarketSample:
|
|
|
445
445
|
this_yaml.dump(self, _yfh)
|
|
446
446
|
|
|
447
447
|
if save_dataset:
|
|
448
|
-
if
|
|
448
|
+
if self.dataset is None and self.enf_counts is None:
|
|
449
449
|
raise ValueError(
|
|
450
450
|
"No dataset and/or enforcement counts available for saving. "
|
|
451
451
|
"Generate some data or set save_dataset to False to proceed."
|
|
452
452
|
)
|
|
453
453
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
454
|
+
else:
|
|
455
|
+
if self.dataset is not None:
|
|
456
|
+
with (zpath / f"{name_root}_dataset.h5").open("wb") as _hfh:
|
|
457
|
+
_hfh.write(self.dataset.to_h5bin())
|
|
457
458
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
459
|
+
if self.enf_counts is not None:
|
|
460
|
+
with (zpath / f"{name_root}_enf_counts.yaml").open("w") as _yfh:
|
|
461
|
+
this_yaml.dump(self.enf_counts, _yfh)
|
|
461
462
|
|
|
463
|
+
@staticmethod
|
|
462
464
|
def from_archive(
|
|
463
465
|
zip_: zipfile.ZipFile, _subdir: str = "", /, *, restore_dataset: bool = False
|
|
464
466
|
) -> MarketSample:
|
|
@@ -466,27 +468,28 @@ class MarketSample:
|
|
|
466
468
|
zpath = zipfile.Path(zip_, at=_subdir)
|
|
467
469
|
name_root = f"{_PKG_NAME}_market_sample"
|
|
468
470
|
|
|
469
|
-
market_sample_ = this_yaml.load(
|
|
471
|
+
market_sample_: MarketSample = this_yaml.load(
|
|
472
|
+
(zpath / f"{name_root}.yaml").read_text()
|
|
473
|
+
)
|
|
470
474
|
|
|
471
475
|
if restore_dataset:
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
)):
|
|
476
|
+
_dt = (_dp := zpath / f"{name_root}_dataset.h5").is_file()
|
|
477
|
+
_et = (_ep := zpath / f"{name_root}_enf_counts.yaml").is_file()
|
|
478
|
+
if not (_dt or _et):
|
|
476
479
|
raise ValueError(
|
|
477
480
|
"Archive has no sample data to restore. "
|
|
478
481
|
"Delete second argument, or set it False, and rerun."
|
|
479
482
|
)
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
+
else:
|
|
484
|
+
if _dt:
|
|
485
|
+
with _dp.open("rb") as _hfh:
|
|
486
|
+
object.__setattr__(
|
|
487
|
+
market_sample_, "dataset", MarketSampleData.from_h5f(_hfh)
|
|
488
|
+
)
|
|
489
|
+
if _et:
|
|
483
490
|
object.__setattr__(
|
|
484
|
-
market_sample_, "
|
|
491
|
+
market_sample_, "enf_counts", this_yaml.load(_ep.read_text())
|
|
485
492
|
)
|
|
486
|
-
if _et:
|
|
487
|
-
object.__setattr__(
|
|
488
|
-
market_sample_, "enf_counts", this_yaml.load(_ep.read_text())
|
|
489
|
-
)
|
|
490
493
|
return market_sample_
|
|
491
494
|
|
|
492
495
|
@classmethod
|
{mergeron-2025.739341.10 → mergeron-2025.739355.1}/src/mergeron/gen/data_generation_functions.py
RENAMED
|
@@ -438,11 +438,13 @@ def gen_divr_array(
|
|
|
438
438
|
"""
|
|
439
439
|
divr_array: ArrayDouble
|
|
440
440
|
if _recapture_form == RECForm.FIXED:
|
|
441
|
-
divr_array =
|
|
441
|
+
divr_array = np.divide(
|
|
442
|
+
_recapture_ratio * _frmshr_array[:, ::-1], 1 - _frmshr_array
|
|
443
|
+
)
|
|
442
444
|
|
|
443
445
|
else:
|
|
444
446
|
purchprob_array = _aggregate_purchase_prob * _frmshr_array
|
|
445
|
-
divr_array = purchprob_array[:, ::-1]
|
|
447
|
+
divr_array = np.divide(purchprob_array[:, ::-1], 1 - purchprob_array)
|
|
446
448
|
|
|
447
449
|
divr_assert_test = (
|
|
448
450
|
(np.round(np.einsum("ij->i", _frmshr_array), 15) == 1)
|
|
@@ -616,7 +618,7 @@ def gen_margin_price_data(
|
|
|
616
618
|
# Revenue ratio has been 10-to-1 since inception
|
|
617
619
|
# Thus, a simple form of the HSR filing test would impose a 10-to-1
|
|
618
620
|
# ratio restriction on the merging firms' revenues
|
|
619
|
-
rev_ratio = (rev_array.min(axis=1)
|
|
621
|
+
rev_ratio = np.divide(rev_array.min(axis=1), rev_array.max(axis=1)).round(4)
|
|
620
622
|
hsr_filing_test = rev_ratio >= test_rev_ratio_inv
|
|
621
623
|
# del _rev_array, _rev_ratio
|
|
622
624
|
case SSZConstant.HSR_NTH:
|
|
File without changes
|
{mergeron-2025.739341.10 → mergeron-2025.739355.1}/src/mergeron/core/pseudorandom_numbers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|