mergeron 2025.739290.6__py3-none-any.whl → 2025.739290.7__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 +1 -1
- mergeron/core/__init__.py +30 -32
- mergeron/core/ftc_merger_investigations_data.py +5 -1
- mergeron/core/guidelines_boundaries.py +11 -11
- mergeron/core/guidelines_boundary_functions.py +113 -113
- mergeron/core/guidelines_boundary_functions_extra.py +207 -0
- mergeron/data/__init__.py +4 -7
- mergeron/data/ftc_merger_investigations_data.zip +0 -0
- mergeron/gen/__init__.py +29 -35
- mergeron/gen/data_generation.py +1 -13
- mergeron/gen/enforcement_stats.py +18 -7
- mergeron/gen/upp_tests.py +50 -144
- {mergeron-2025.739290.6.dist-info → mergeron-2025.739290.7.dist-info}/METADATA +1 -1
- mergeron-2025.739290.7.dist-info/RECORD +22 -0
- mergeron-2025.739290.6.dist-info/RECORD +0 -22
- {mergeron-2025.739290.6.dist-info → mergeron-2025.739290.7.dist-info}/WHEEL +0 -0
mergeron/__init__.py
CHANGED
mergeron/core/__init__.py
CHANGED
|
@@ -9,7 +9,14 @@ import mpmath # type: ignore
|
|
|
9
9
|
import numpy as np
|
|
10
10
|
from attrs import cmp_using, field, frozen
|
|
11
11
|
|
|
12
|
-
from .. import
|
|
12
|
+
from .. import ( # noqa: TID252
|
|
13
|
+
VERSION,
|
|
14
|
+
ArrayBIGINT,
|
|
15
|
+
ArrayDouble,
|
|
16
|
+
this_yaml,
|
|
17
|
+
yamelize_attrs,
|
|
18
|
+
yaml_rt_mapper,
|
|
19
|
+
)
|
|
13
20
|
|
|
14
21
|
__version__ = VERSION
|
|
15
22
|
|
|
@@ -17,6 +24,17 @@ type MPFloat = mpmath.ctx_mp_python.mpf
|
|
|
17
24
|
type MPMatrix = mpmath.matrix # type: ignore
|
|
18
25
|
|
|
19
26
|
|
|
27
|
+
@frozen
|
|
28
|
+
class GuidelinesBoundary:
|
|
29
|
+
"""Output of a Guidelines boundary function."""
|
|
30
|
+
|
|
31
|
+
coordinates: ArrayDouble
|
|
32
|
+
"""Market-share pairs as Cartesian coordinates of points on the boundary."""
|
|
33
|
+
|
|
34
|
+
area: float
|
|
35
|
+
"""Area under the boundary."""
|
|
36
|
+
|
|
37
|
+
|
|
20
38
|
@frozen
|
|
21
39
|
class INVTableData:
|
|
22
40
|
industry_group: str
|
|
@@ -29,6 +47,7 @@ type INVData = MappingProxyType[
|
|
|
29
47
|
]
|
|
30
48
|
type INVData_in = Mapping[str, Mapping[str, Mapping[str, INVTableData]]]
|
|
31
49
|
|
|
50
|
+
yamelize_attrs(INVTableData)
|
|
32
51
|
|
|
33
52
|
(_, _) = (
|
|
34
53
|
this_yaml.representer.add_representer(
|
|
@@ -59,6 +78,16 @@ type INVData_in = Mapping[str, Mapping[str, Mapping[str, INVTableData]]]
|
|
|
59
78
|
),
|
|
60
79
|
)
|
|
61
80
|
|
|
81
|
+
_, _ = (
|
|
82
|
+
this_yaml.representer.add_representer(
|
|
83
|
+
MappingProxyType,
|
|
84
|
+
lambda _r, _d: _r.represent_mapping("!mappingproxy", dict(_d.items())),
|
|
85
|
+
),
|
|
86
|
+
this_yaml.constructor.add_constructor(
|
|
87
|
+
"!mappingproxy", lambda _c, _n: MappingProxyType(dict(**yaml_rt_mapper(_c, _n)))
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
|
|
62
91
|
|
|
63
92
|
def _dict_from_mapping(_p: Mapping[Any, Any], /) -> dict[Any, Any]:
|
|
64
93
|
retval: dict[Any, Any] = {}
|
|
@@ -76,34 +105,3 @@ def _mappingproxy_from_mapping(_p: Mapping[Any, Any], /) -> MappingProxyType[Any
|
|
|
76
105
|
else {_k: _v}
|
|
77
106
|
)
|
|
78
107
|
return MappingProxyType(retval)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
_, _ = (
|
|
82
|
-
this_yaml.representer.add_representer(
|
|
83
|
-
MappingProxyType,
|
|
84
|
-
lambda _r, _d: _r.represent_mapping("!mappingproxy", dict(_d.items())),
|
|
85
|
-
),
|
|
86
|
-
this_yaml.constructor.add_constructor(
|
|
87
|
-
"!mappingproxy", lambda _c, _n: MappingProxyType(yaml_rt_mapper(_c, _n))
|
|
88
|
-
),
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
for _typ in (INVTableData,):
|
|
93
|
-
_, _ = (
|
|
94
|
-
this_yaml.representer.add_representer(
|
|
95
|
-
_typ,
|
|
96
|
-
lambda _r, _d: _r.represent_mapping(
|
|
97
|
-
f"!{_d.__class__.__name__}",
|
|
98
|
-
{
|
|
99
|
-
_a.name: getattr(_d, _a.name)
|
|
100
|
-
for _a in _d.__attrs_attrs__
|
|
101
|
-
if _a.name not in {"coordinates", "area"}
|
|
102
|
-
},
|
|
103
|
-
),
|
|
104
|
-
),
|
|
105
|
-
this_yaml.constructor.add_constructor(
|
|
106
|
-
f"!{_typ.__name__}",
|
|
107
|
-
lambda _c, _n: globals()[_n.tag.lstrip("!")](**yaml_rt_mapper(_c, _n)),
|
|
108
|
-
),
|
|
109
|
-
)
|
|
@@ -12,7 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
|
|
13
13
|
import re
|
|
14
14
|
import shutil
|
|
15
|
-
from collections.abc import Sequence
|
|
15
|
+
from collections.abc import Mapping, Sequence
|
|
16
16
|
from operator import itemgetter
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
from types import MappingProxyType
|
|
@@ -94,6 +94,10 @@ CNT_FCOUNT_DICT = {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
|
|
97
|
+
def reverse_map(_dict: Mapping[Any, Any]) -> Mapping[Any, Any]:
|
|
98
|
+
return {_v: _k for _k, _v in _dict.items()}
|
|
99
|
+
|
|
100
|
+
|
|
97
101
|
def construct_data(
|
|
98
102
|
_archive_path: Path = INVDATA_ARCHIVE_PATH,
|
|
99
103
|
*,
|
|
@@ -146,6 +146,15 @@ class GuidelinesThresholds:
|
|
|
146
146
|
class ConcentrationBoundary:
|
|
147
147
|
"""Concentration parameters, boundary coordinates, and area under concentration boundary."""
|
|
148
148
|
|
|
149
|
+
threshold: float = field(kw_only=False, default=0.01)
|
|
150
|
+
|
|
151
|
+
@threshold.validator
|
|
152
|
+
def _tv(
|
|
153
|
+
_instance: ConcentrationBoundary, _attribute: Attribute[float], _value: float, /
|
|
154
|
+
) -> None:
|
|
155
|
+
if not 0 <= _value <= 1:
|
|
156
|
+
raise ValueError("Concentration threshold must lie between 0 and 1.")
|
|
157
|
+
|
|
149
158
|
measure_name: Literal[
|
|
150
159
|
"ΔHHI",
|
|
151
160
|
"Combined share",
|
|
@@ -165,17 +174,8 @@ class ConcentrationBoundary:
|
|
|
165
174
|
}:
|
|
166
175
|
raise ValueError(f"Invalid name for a concentration measure, {_value!r}.")
|
|
167
176
|
|
|
168
|
-
threshold: float = field(kw_only=False, default=0.01)
|
|
169
|
-
|
|
170
|
-
@threshold.validator
|
|
171
|
-
def _tv(
|
|
172
|
-
_instance: ConcentrationBoundary, _attribute: Attribute[float], _value: float, /
|
|
173
|
-
) -> None:
|
|
174
|
-
if not 0 <= _value <= 1:
|
|
175
|
-
raise ValueError("Concentration threshold must lie between 0 and 1.")
|
|
176
|
-
|
|
177
177
|
precision: int = field(
|
|
178
|
-
kw_only=
|
|
178
|
+
kw_only=True, default=5, validator=validators.instance_of(int)
|
|
179
179
|
)
|
|
180
180
|
|
|
181
181
|
area: float = field(init=False, kw_only=True)
|
|
@@ -284,7 +284,7 @@ class DiversionRatioBoundary:
|
|
|
284
284
|
|
|
285
285
|
agg_method: UPPAggrSelector = field(
|
|
286
286
|
kw_only=True,
|
|
287
|
-
default=UPPAggrSelector.
|
|
287
|
+
default=UPPAggrSelector.MIN,
|
|
288
288
|
validator=validators.instance_of(UPPAggrSelector),
|
|
289
289
|
)
|
|
290
290
|
"""
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import decimal
|
|
2
|
-
from
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from typing import Literal, TypedDict
|
|
3
4
|
|
|
4
5
|
import matplotlib as mpl
|
|
5
6
|
import matplotlib.axes as mpa
|
|
@@ -7,11 +8,10 @@ import matplotlib.patches as mpp
|
|
|
7
8
|
import matplotlib.pyplot as plt
|
|
8
9
|
import matplotlib.ticker as mpt
|
|
9
10
|
import numpy as np
|
|
10
|
-
from attrs import frozen
|
|
11
11
|
from mpmath import mp, mpf # type: ignore
|
|
12
12
|
|
|
13
13
|
from .. import DEFAULT_REC_RATIO, VERSION, ArrayBIGINT, ArrayDouble # noqa: TID252
|
|
14
|
-
from . import MPFloat
|
|
14
|
+
from . import GuidelinesBoundary, MPFloat
|
|
15
15
|
|
|
16
16
|
__version__ = VERSION
|
|
17
17
|
|
|
@@ -28,17 +28,6 @@ class ShareRatioBoundaryKeywords(TypedDict, total=False):
|
|
|
28
28
|
weighting: Literal["own-share", "cross-product-share", None]
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
@frozen
|
|
32
|
-
class GuidelinesBoundary:
|
|
33
|
-
"""Output of a Guidelines boundary function."""
|
|
34
|
-
|
|
35
|
-
coordinates: ArrayDouble
|
|
36
|
-
"""Market-share pairs as Cartesian coordinates of points on the boundary."""
|
|
37
|
-
|
|
38
|
-
area: float
|
|
39
|
-
"""Area under the boundary."""
|
|
40
|
-
|
|
41
|
-
|
|
42
31
|
def dh_area(_delta_bound: float | MPFloat = 0.01, /, *, dps: int = 9) -> float:
|
|
43
32
|
R"""
|
|
44
33
|
Area under the ΔHHI boundary.
|
|
@@ -56,7 +45,7 @@ def dh_area(_delta_bound: float | MPFloat = 0.01, /, *, dps: int = 9) -> float:
|
|
|
56
45
|
.. math::
|
|
57
46
|
|
|
58
47
|
2 s1 s_2 &= ΔHHI\\
|
|
59
|
-
|
|
48
|
+
s_1 + s_2 &= 1
|
|
60
49
|
|
|
61
50
|
Parameters
|
|
62
51
|
----------
|
|
@@ -113,7 +102,7 @@ def hhi_delta_boundary(
|
|
|
113
102
|
np.column_stack((_s_1, _delta_bound / (2 * _s_1))).astype(float),
|
|
114
103
|
np.array([(mpf("0.0"), mpf("1.0"))], float),
|
|
115
104
|
))
|
|
116
|
-
bdry = np.vstack((half_bdry[::-1], half_bdry[1:, ::-1]))
|
|
105
|
+
bdry = np.vstack((half_bdry[::-1], half_bdry[1:, ::-1]), dtype=float)
|
|
117
106
|
|
|
118
107
|
return GuidelinesBoundary(bdry, dh_area(_delta_bound, dps=dps))
|
|
119
108
|
|
|
@@ -141,12 +130,12 @@ def hhi_pre_contrib_boundary(
|
|
|
141
130
|
|
|
142
131
|
step_size = mp.power(10, -dps)
|
|
143
132
|
# Range-limit is 0 less a step, which is -1 * step-size
|
|
144
|
-
|
|
145
|
-
s_2 = np.sqrt(_hhi_bound -
|
|
146
|
-
half_bdry = np.column_stack((
|
|
133
|
+
s_1 = np.array(mp.arange(_s_mid, -step_size, -step_size))
|
|
134
|
+
s_2 = np.sqrt(_hhi_bound - s_1**2)
|
|
135
|
+
half_bdry = np.column_stack((s_1, s_2)).astype(float)
|
|
147
136
|
|
|
148
137
|
return GuidelinesBoundary(
|
|
149
|
-
np.vstack((half_bdry[::-1], half_bdry[1:, ::-1])),
|
|
138
|
+
np.vstack((half_bdry[::-1], half_bdry[1:, ::-1]), dtype=float),
|
|
150
139
|
round(float(mp.pi * _hhi_bound / 4), dps),
|
|
151
140
|
)
|
|
152
141
|
|
|
@@ -178,7 +167,8 @@ def combined_share_boundary(
|
|
|
178
167
|
|
|
179
168
|
_s1 = np.array([0, _s_mid, _s_intcpt], float)
|
|
180
169
|
return GuidelinesBoundary(
|
|
181
|
-
np.
|
|
170
|
+
np.array(list(zip(_s1, _s1[::-1])), float),
|
|
171
|
+
round(float(_s_intcpt * _s_mid), dps),
|
|
182
172
|
)
|
|
183
173
|
|
|
184
174
|
|
|
@@ -211,6 +201,7 @@ def hhi_post_contrib_boundary(
|
|
|
211
201
|
)
|
|
212
202
|
|
|
213
203
|
|
|
204
|
+
# hand-rolled root finding
|
|
214
205
|
def shrratio_boundary_wtd_avg( # noqa: PLR0914
|
|
215
206
|
_delta_star: float = 0.075,
|
|
216
207
|
_r_val: float = DEFAULT_REC_RATIO,
|
|
@@ -252,58 +243,57 @@ def shrratio_boundary_wtd_avg( # noqa: PLR0914
|
|
|
252
243
|
is derived and plotted from y-intercept to the ray of symmetry as follows::
|
|
253
244
|
|
|
254
245
|
from sympy import plot as symplot, solve, symbols
|
|
255
|
-
|
|
246
|
+
s_1, s_2 = symbols("s_1 s_2", positive=True)
|
|
256
247
|
|
|
257
248
|
g_val, r_val, m_val = 0.06, 0.80, 0.30
|
|
258
249
|
delta_star = g_val / (r_val * m_val)
|
|
259
250
|
|
|
260
251
|
# recapture_form == "inside-out"
|
|
261
252
|
oswag = solve(
|
|
262
|
-
|
|
263
|
-
+ s_2 *
|
|
264
|
-
- (
|
|
253
|
+
s_1 * s_2 / (1 - s_1)
|
|
254
|
+
+ s_2 * s_1 / (1 - (r_val * s_2 + (1 - r_val) * s_1))
|
|
255
|
+
- (s_1 + s_2) * delta_star,
|
|
265
256
|
s_2
|
|
266
257
|
)[0]
|
|
267
258
|
symplot(
|
|
268
259
|
oswag,
|
|
269
|
-
(
|
|
260
|
+
(s_1, 0., d_hat / (1 + d_hat)),
|
|
270
261
|
ylabel=s_2
|
|
271
262
|
)
|
|
272
263
|
|
|
273
264
|
cpswag = solve(
|
|
274
|
-
s_2 * s_2 / (1 -
|
|
275
|
-
+
|
|
276
|
-
- (
|
|
265
|
+
s_2 * s_2 / (1 - s_1)
|
|
266
|
+
+ s_1 * s_1 / (1 - (r_val * s_2 + (1 - r_val) * s_1))
|
|
267
|
+
- (s_1 + s_2) * delta_star,
|
|
277
268
|
s_2
|
|
278
269
|
)[1]
|
|
279
270
|
symplot(
|
|
280
271
|
cpwag,
|
|
281
|
-
(
|
|
282
|
-
ylabel=s_2
|
|
272
|
+
(s_1, 0.0, d_hat / (1 + d_hat)), ylabel=s_2
|
|
283
273
|
)
|
|
284
274
|
|
|
285
275
|
# recapture_form == "proportional"
|
|
286
276
|
oswag = solve(
|
|
287
|
-
|
|
288
|
-
+ s_2 *
|
|
289
|
-
- (
|
|
277
|
+
s_1 * s_2 / (1 - s_1)
|
|
278
|
+
+ s_2 * s_1 / (1 - s_2)
|
|
279
|
+
- (s_1 + s_2) * delta_star,
|
|
290
280
|
s_2
|
|
291
281
|
)[0]
|
|
292
282
|
symplot(
|
|
293
283
|
oswag,
|
|
294
|
-
(
|
|
284
|
+
(s_1, 0., d_hat / (1 + d_hat)),
|
|
295
285
|
ylabel=s_2
|
|
296
286
|
)
|
|
297
287
|
|
|
298
288
|
cpswag = solve(
|
|
299
|
-
s_2 * s_2 / (1 -
|
|
300
|
-
+
|
|
301
|
-
- (
|
|
289
|
+
s_2 * s_2 / (1 - s_1)
|
|
290
|
+
+ s_1 * s_1 / (1 - s_2)
|
|
291
|
+
- (s_1 + s_2) * delta_star,
|
|
302
292
|
s_2
|
|
303
293
|
)[1]
|
|
304
294
|
symplot(
|
|
305
295
|
cpswag,
|
|
306
|
-
(
|
|
296
|
+
(s_1, 0.0, d_hat / (1 + d_hat)),
|
|
307
297
|
ylabel=s_2
|
|
308
298
|
)
|
|
309
299
|
|
|
@@ -321,26 +311,22 @@ def shrratio_boundary_wtd_avg( # noqa: PLR0914
|
|
|
321
311
|
# parameters for iteration
|
|
322
312
|
_step_size = mp.power(10, -dps)
|
|
323
313
|
theta_ = _step_size * (10 if weighting == "cross-product-share" else 1)
|
|
324
|
-
for
|
|
314
|
+
for s_1 in mp.arange(_s_mid - _step_size, 0, -_step_size):
|
|
325
315
|
# The wtd. avg. GUPPI is not always convex to the origin, so we
|
|
326
316
|
# increment s_2 after each iteration in which our algorithm
|
|
327
317
|
# finds (s1, s2) on the boundary
|
|
328
318
|
s_2 = s_2_pre * (1 + theta_)
|
|
329
319
|
|
|
330
|
-
if (_s_1 + s_2) > mpf("0.99875"):
|
|
331
|
-
# Loss of accuracy at 3-9s and up
|
|
332
|
-
break
|
|
333
|
-
|
|
334
320
|
while True:
|
|
335
|
-
de_1 = s_2 / (1 -
|
|
321
|
+
de_1 = s_2 / (1 - s_1)
|
|
336
322
|
de_2 = (
|
|
337
|
-
|
|
323
|
+
s_1 / (1 - lerp(s_1, s_2, _r_val))
|
|
338
324
|
if recapture_form == "inside-out"
|
|
339
|
-
else
|
|
325
|
+
else s_1 / (1 - s_2)
|
|
340
326
|
)
|
|
341
327
|
|
|
342
328
|
r_ = (
|
|
343
|
-
mp.fdiv(
|
|
329
|
+
mp.fdiv(s_1 if weighting == "cross-product-share" else s_2, s_1 + s_2)
|
|
344
330
|
if weighting
|
|
345
331
|
else 0.5
|
|
346
332
|
)
|
|
@@ -365,7 +351,7 @@ def shrratio_boundary_wtd_avg( # noqa: PLR0914
|
|
|
365
351
|
break
|
|
366
352
|
|
|
367
353
|
# Build-up boundary points
|
|
368
|
-
bdry.append((
|
|
354
|
+
bdry.append((s_1, s_2))
|
|
369
355
|
|
|
370
356
|
# Build up area terms
|
|
371
357
|
s_2_oddsum += s_2 if s_2_oddval else 0
|
|
@@ -374,7 +360,11 @@ def shrratio_boundary_wtd_avg( # noqa: PLR0914
|
|
|
374
360
|
|
|
375
361
|
# Hold share points
|
|
376
362
|
s_2_pre = s_2
|
|
377
|
-
s_1_pre =
|
|
363
|
+
s_1_pre = s_1
|
|
364
|
+
|
|
365
|
+
if (s_1 + s_2) > mpf("0.99875"):
|
|
366
|
+
# Loss of accuracy at 3-9s and up
|
|
367
|
+
break
|
|
378
368
|
|
|
379
369
|
if s_2_oddval:
|
|
380
370
|
s_2_evnsum -= s_2_pre
|
|
@@ -412,7 +402,7 @@ def shrratio_boundary_wtd_avg( # noqa: PLR0914
|
|
|
412
402
|
|
|
413
403
|
# Points defining boundary to point-of-symmetry
|
|
414
404
|
return GuidelinesBoundary(
|
|
415
|
-
np.vstack((bdry_array[::-1], bdry_array[1:, ::-1])),
|
|
405
|
+
np.vstack((bdry_array[::-1], bdry_array[1:, ::-1]), dtype=float),
|
|
416
406
|
round(float(bdry_area_total), dps),
|
|
417
407
|
)
|
|
418
408
|
|
|
@@ -436,29 +426,29 @@ def shrratio_boundary_xact_avg( # noqa: PLR0914
|
|
|
436
426
|
|
|
437
427
|
from sympy import latex, plot as symplot, solve, symbols
|
|
438
428
|
|
|
439
|
-
|
|
429
|
+
s_1, s_2 = symbols("s_1 s_2")
|
|
440
430
|
|
|
441
431
|
g_val, r_val, m_val = 0.06, 0.80, 0.30
|
|
442
432
|
d_hat = g_val / (r_val * m_val)
|
|
443
433
|
|
|
444
434
|
# recapture_form = "inside-out"
|
|
445
435
|
sag = solve(
|
|
446
|
-
(s_2 / (1 -
|
|
447
|
-
+ (
|
|
436
|
+
(s_2 / (1 - s_1))
|
|
437
|
+
+ (s_1 / (1 - (r_val * s_2 + (1 - r_val) * s_1)))
|
|
448
438
|
- 2 * d_hat,
|
|
449
439
|
s_2
|
|
450
440
|
)[0]
|
|
451
441
|
symplot(
|
|
452
442
|
sag,
|
|
453
|
-
(
|
|
443
|
+
(s_1, 0., d_hat / (1 + d_hat)),
|
|
454
444
|
ylabel=s_2
|
|
455
445
|
)
|
|
456
446
|
|
|
457
447
|
# recapture_form = "proportional"
|
|
458
|
-
sag = solve((s_2/(1 -
|
|
448
|
+
sag = solve((s_2/(1 - s_1)) + (s_1/(1 - s_2)) - 2 * d_hat, s_2)[0]
|
|
459
449
|
symplot(
|
|
460
450
|
sag,
|
|
461
|
-
(
|
|
451
|
+
(s_1, 0., d_hat / (1 + d_hat)),
|
|
462
452
|
ylabel=s_2
|
|
463
453
|
)
|
|
464
454
|
|
|
@@ -614,7 +604,7 @@ def shrratio_boundary_min(
|
|
|
614
604
|
_s_mid = _delta_star / (1 + _delta_star)
|
|
615
605
|
|
|
616
606
|
if recapture_form == "inside-out":
|
|
617
|
-
# ## Plot envelope of GUPPI boundaries with rk_ = r_bar if sk_ = min(
|
|
607
|
+
# ## Plot envelope of GUPPI boundaries with rk_ = r_bar if sk_ = min(s_1, s_2)
|
|
618
608
|
# ## See (si_, sj_) in equation~(44), or thereabouts, in paper
|
|
619
609
|
smin_nr = _delta_star * (1 - _r_val)
|
|
620
610
|
smax_nr = 1 - _delta_star * _r_val
|
|
@@ -626,7 +616,7 @@ def shrratio_boundary_min(
|
|
|
626
616
|
s_1, bdry_area = np.array((0, _s_mid, _s_intcpt), float), _s_mid
|
|
627
617
|
|
|
628
618
|
return GuidelinesBoundary(
|
|
629
|
-
np.
|
|
619
|
+
np.array(list(zip(s_1, s_1[::-1])), float), round(float(bdry_area), dps)
|
|
630
620
|
)
|
|
631
621
|
|
|
632
622
|
|
|
@@ -659,7 +649,7 @@ def shrratio_boundary_max(
|
|
|
659
649
|
_s1_pts = (0, _s_mid, _s_intcpt)
|
|
660
650
|
|
|
661
651
|
return GuidelinesBoundary(
|
|
662
|
-
np.
|
|
652
|
+
np.array(list(zip(_s1_pts, _s1_pts[::-1])), float),
|
|
663
653
|
round(float(_s_intcpt * _s_mid), dps), # simplified calculation
|
|
664
654
|
)
|
|
665
655
|
|
|
@@ -797,51 +787,57 @@ def round_cust(
|
|
|
797
787
|
return float(f_ * (n_ / f_).quantize(e_, rounding=rounding_mode))
|
|
798
788
|
|
|
799
789
|
|
|
800
|
-
def boundary_plot(
|
|
790
|
+
def boundary_plot(
|
|
791
|
+
*,
|
|
792
|
+
mktshare_plot_flag: bool = True,
|
|
793
|
+
mktshare_axes_flag: bool = True,
|
|
794
|
+
backend: Literal["pgf"] | str | None = "pgf",
|
|
795
|
+
) -> tuple[mpl.pyplot, mpl.pyplot.Figure, mpl.axes.Axes, Callable[..., mpl.axes.Axes]]:
|
|
801
796
|
"""Setup basic figure and axes for plots of safe harbor boundaries.
|
|
802
797
|
|
|
803
798
|
See, https://matplotlib.org/stable/tutorials/text/pgf.html
|
|
804
799
|
"""
|
|
805
800
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
801
|
+
if backend == "pgf":
|
|
802
|
+
mpl.use("pgf")
|
|
803
|
+
|
|
804
|
+
plt.rcParams.update({
|
|
805
|
+
"pgf.rcfonts": False,
|
|
806
|
+
"pgf.texsystem": "lualatex",
|
|
807
|
+
"pgf.preamble": "\n".join([
|
|
808
|
+
R"\pdfvariable minorversion=7",
|
|
809
|
+
R"\usepackage{fontspec}",
|
|
810
|
+
R"\usepackage{luacode}",
|
|
811
|
+
R"\begin{luacode}",
|
|
812
|
+
R"local function embedfull(tfmdata)",
|
|
813
|
+
R' tfmdata.embedding = "full"',
|
|
814
|
+
R"end",
|
|
815
|
+
R"",
|
|
816
|
+
R"luatexbase.add_to_callback("
|
|
817
|
+
R' "luaotfload.patch_font", embedfull, "embedfull"'
|
|
818
|
+
R")",
|
|
819
|
+
R"\end{luacode}",
|
|
820
|
+
R"\usepackage{mathtools}",
|
|
821
|
+
R"\usepackage{unicode-math}",
|
|
822
|
+
R"\setmathfont[math-style=ISO]{STIX Two Math}",
|
|
823
|
+
R"\setmainfont{STIX Two Text}",
|
|
824
|
+
r"\setsansfont{Fira Sans Light}",
|
|
825
|
+
R"\setmonofont[Scale=MatchLowercase,]{Fira Mono}",
|
|
826
|
+
R"\defaultfontfeatures[\rmfamily]{",
|
|
827
|
+
R" Ligatures={TeX, Common},",
|
|
828
|
+
R" Numbers={Proportional, Lining},",
|
|
829
|
+
R" }",
|
|
830
|
+
R"\defaultfontfeatures[\sffamily]{",
|
|
831
|
+
R" Ligatures={TeX, Common},",
|
|
832
|
+
R" Numbers={Monospaced, Lining},",
|
|
833
|
+
R" LetterSpace=0.50,",
|
|
834
|
+
R" }",
|
|
835
|
+
R"\usepackage[",
|
|
836
|
+
R" activate={true, nocompatibility},",
|
|
837
|
+
R" tracking=true,",
|
|
838
|
+
R" ]{microtype}",
|
|
839
|
+
]),
|
|
840
|
+
})
|
|
845
841
|
|
|
846
842
|
# Initialize a canvas with a single figure (set of axes)
|
|
847
843
|
fig_ = plt.figure(figsize=(5, 5), dpi=600)
|
|
@@ -851,8 +847,8 @@ def boundary_plot(*, mktshares_plot_flag: bool = True) -> tuple[Any, ...]:
|
|
|
851
847
|
ax1_: mpa.Axes,
|
|
852
848
|
/,
|
|
853
849
|
*,
|
|
854
|
-
|
|
855
|
-
|
|
850
|
+
mktshare_plot_flag: bool = False,
|
|
851
|
+
mktshare_axes_flag: bool = False,
|
|
856
852
|
) -> mpa.Axes:
|
|
857
853
|
# Set the width of axis grid lines, and tick marks:
|
|
858
854
|
# both axes, both major and minor ticks
|
|
@@ -883,16 +879,7 @@ def boundary_plot(*, mktshares_plot_flag: bool = True) -> tuple[Any, ...]:
|
|
|
883
879
|
ax1_.yaxis.get_majorticklabels(), horizontalalignment="right", fontsize=6
|
|
884
880
|
)
|
|
885
881
|
|
|
886
|
-
if
|
|
887
|
-
# Axis labels
|
|
888
|
-
if mktshares_axlbls_flag:
|
|
889
|
-
# x-axis
|
|
890
|
-
ax1_.set_xlabel("Firm 1 Market Share, $_s_1$", fontsize=10)
|
|
891
|
-
ax1_.xaxis.set_label_coords(0.75, -0.1)
|
|
892
|
-
# y-axis
|
|
893
|
-
ax1_.set_ylabel("Firm 2 Market Share, $s_2$", fontsize=10)
|
|
894
|
-
ax1_.yaxis.set_label_coords(-0.1, 0.75)
|
|
895
|
-
|
|
882
|
+
if mktshare_plot_flag:
|
|
896
883
|
# Plot the ray of symmetry
|
|
897
884
|
ax1_.plot(
|
|
898
885
|
[0, 1], [0, 1], linewidth=0.5, linestyle=":", color="grey", zorder=1
|
|
@@ -944,8 +931,21 @@ def boundary_plot(*, mktshares_plot_flag: bool = True) -> tuple[Any, ...]:
|
|
|
944
931
|
for axl_ in ax1_.get_xticklabels(), ax1_.get_yticklabels():
|
|
945
932
|
plt.setp(axl_[::2], visible=False)
|
|
946
933
|
|
|
934
|
+
# Axis labels
|
|
935
|
+
if mktshare_axes_flag:
|
|
936
|
+
# x-axis
|
|
937
|
+
ax1_.set_xlabel("Firm 1 Market Share, $s_1$", fontsize=10)
|
|
938
|
+
ax1_.xaxis.set_label_coords(0.75, -0.1)
|
|
939
|
+
# y-axis
|
|
940
|
+
ax1_.set_ylabel("Firm 2 Market Share, $s_2$", fontsize=10)
|
|
941
|
+
ax1_.yaxis.set_label_coords(-0.1, 0.75)
|
|
942
|
+
|
|
947
943
|
return ax1_
|
|
948
944
|
|
|
949
|
-
ax_out = _set_axis_def(
|
|
945
|
+
ax_out = _set_axis_def(
|
|
946
|
+
ax_out,
|
|
947
|
+
mktshare_plot_flag=mktshare_plot_flag,
|
|
948
|
+
mktshare_axes_flag=mktshare_axes_flag,
|
|
949
|
+
)
|
|
950
950
|
|
|
951
951
|
return plt, fig_, ax_out, _set_axis_def
|