mergeron 2025.739290.3__py3-none-any.whl → 2025.739290.5__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 +103 -48
- mergeron/core/__init__.py +105 -4
- mergeron/core/empirical_margin_distribution.py +100 -78
- mergeron/core/ftc_merger_investigations_data.py +309 -316
- mergeron/core/guidelines_boundaries.py +67 -138
- mergeron/core/guidelines_boundary_functions.py +202 -379
- mergeron/core/guidelines_boundary_functions_extra.py +264 -106
- mergeron/core/pseudorandom_numbers.py +73 -64
- mergeron/data/damodaran_margin_data_serialized.zip +0 -0
- mergeron/data/ftc_invdata.zip +0 -0
- mergeron/demo/visualize_empirical_margin_distribution.py +9 -7
- mergeron/gen/__init__.py +138 -161
- mergeron/gen/data_generation.py +181 -149
- mergeron/gen/data_generation_functions.py +220 -237
- mergeron/gen/enforcement_stats.py +78 -109
- mergeron/gen/upp_tests.py +119 -194
- {mergeron-2025.739290.3.dist-info → mergeron-2025.739290.5.dist-info}/METADATA +2 -3
- mergeron-2025.739290.5.dist-info/RECORD +24 -0
- {mergeron-2025.739290.3.dist-info → mergeron-2025.739290.5.dist-info}/WHEEL +1 -1
- mergeron/data/damodaran_margin_data_dict.msgpack +0 -0
- mergeron-2025.739290.3.dist-info/RECORD +0 -23
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import decimal
|
|
2
|
-
from dataclasses import dataclass
|
|
3
2
|
from typing import Any, Literal, TypedDict
|
|
4
3
|
|
|
4
|
+
import matplotlib as mpl
|
|
5
|
+
import matplotlib.axes as mpa
|
|
6
|
+
import matplotlib.patches as mpp
|
|
7
|
+
import matplotlib.pyplot as plt
|
|
8
|
+
import matplotlib.ticker as mpt
|
|
5
9
|
import numpy as np
|
|
10
|
+
from attrs import frozen
|
|
6
11
|
from mpmath import mp, mpf # type: ignore
|
|
7
12
|
|
|
8
13
|
from .. import DEFAULT_REC_RATIO, VERSION, ArrayBIGINT, ArrayDouble # noqa: TID252
|
|
@@ -23,7 +28,7 @@ class ShareRatioBoundaryKeywords(TypedDict, total=False):
|
|
|
23
28
|
weighting: Literal["own-share", "cross-product-share", None]
|
|
24
29
|
|
|
25
30
|
|
|
26
|
-
@
|
|
31
|
+
@frozen
|
|
27
32
|
class GuidelinesBoundary:
|
|
28
33
|
"""Output of a Guidelines boundary function."""
|
|
29
34
|
|
|
@@ -51,7 +56,7 @@ def dh_area(_delta_bound: float | MPFloat = 0.01, /, *, dps: int = 9) -> float:
|
|
|
51
56
|
.. math::
|
|
52
57
|
|
|
53
58
|
2 s1 s_2 &= ΔHHI\\
|
|
54
|
-
|
|
59
|
+
_s_1 + s_2 &= 1
|
|
55
60
|
|
|
56
61
|
Parameters
|
|
57
62
|
----------
|
|
@@ -100,17 +105,17 @@ def hhi_delta_boundary(
|
|
|
100
105
|
_s_naught = 1 / 2 * (1 - mp.sqrt(1 - 2 * _delta_bound))
|
|
101
106
|
_s_mid = mp.sqrt(_delta_bound / 2)
|
|
102
107
|
|
|
103
|
-
|
|
104
|
-
_s_1 = np.array(mp.arange(_s_mid, _s_naught - mp.eps, -
|
|
108
|
+
_step_size = mp.power(10, -6)
|
|
109
|
+
_s_1 = np.array(mp.arange(_s_mid, _s_naught - mp.eps, -_step_size))
|
|
105
110
|
|
|
106
111
|
# Boundary points
|
|
107
|
-
|
|
108
|
-
np.column_stack((_s_1, _delta_bound / (2 * _s_1))).astype(
|
|
109
|
-
np.array([(mpf("0.0"), mpf("1.0"))],
|
|
112
|
+
half_bdry = np.vstack((
|
|
113
|
+
np.column_stack((_s_1, _delta_bound / (2 * _s_1))).astype(float),
|
|
114
|
+
np.array([(mpf("0.0"), mpf("1.0"))], float),
|
|
110
115
|
))
|
|
111
|
-
|
|
116
|
+
bdry = np.vstack((half_bdry[::-1], half_bdry[1:, ::-1]))
|
|
112
117
|
|
|
113
|
-
return GuidelinesBoundary(
|
|
118
|
+
return GuidelinesBoundary(bdry, dh_area(_delta_bound, dps=dps))
|
|
114
119
|
|
|
115
120
|
|
|
116
121
|
def hhi_pre_contrib_boundary(
|
|
@@ -134,14 +139,14 @@ def hhi_pre_contrib_boundary(
|
|
|
134
139
|
_hhi_bound = mpf(f"{_hhi_bound}")
|
|
135
140
|
_s_mid = mp.sqrt(_hhi_bound / 2)
|
|
136
141
|
|
|
137
|
-
|
|
142
|
+
step_size = mp.power(10, -dps)
|
|
138
143
|
# Range-limit is 0 less a step, which is -1 * step-size
|
|
139
|
-
_s_1 = np.array(mp.arange(_s_mid, -
|
|
140
|
-
|
|
141
|
-
|
|
144
|
+
_s_1 = np.array(mp.arange(_s_mid, -step_size, -step_size))
|
|
145
|
+
s_2 = np.sqrt(_hhi_bound - _s_1**2)
|
|
146
|
+
half_bdry = np.column_stack((_s_1, s_2)).astype(float)
|
|
142
147
|
|
|
143
148
|
return GuidelinesBoundary(
|
|
144
|
-
np.vstack((
|
|
149
|
+
np.vstack((half_bdry[::-1], half_bdry[1:, ::-1])),
|
|
145
150
|
round(float(mp.pi * _hhi_bound / 4), dps),
|
|
146
151
|
)
|
|
147
152
|
|
|
@@ -171,9 +176,9 @@ def combined_share_boundary(
|
|
|
171
176
|
_s_intcpt = mpf(f"{_s_intcpt}")
|
|
172
177
|
_s_mid = _s_intcpt / 2
|
|
173
178
|
|
|
174
|
-
|
|
179
|
+
_s1 = np.array([0, _s_mid, _s_intcpt], float)
|
|
175
180
|
return GuidelinesBoundary(
|
|
176
|
-
np.column_stack((
|
|
181
|
+
np.column_stack((_s1, _s1[::-1])), round(float(_s_intcpt * _s_mid), dps)
|
|
177
182
|
)
|
|
178
183
|
|
|
179
184
|
|
|
@@ -206,8 +211,8 @@ def hhi_post_contrib_boundary(
|
|
|
206
211
|
)
|
|
207
212
|
|
|
208
213
|
|
|
209
|
-
def shrratio_boundary_wtd_avg(
|
|
210
|
-
_delta_star: float
|
|
214
|
+
def shrratio_boundary_wtd_avg( # noqa: PLR0914
|
|
215
|
+
_delta_star: float = 0.075,
|
|
211
216
|
_r_val: float = DEFAULT_REC_RATIO,
|
|
212
217
|
/,
|
|
213
218
|
*,
|
|
@@ -247,139 +252,137 @@ def shrratio_boundary_wtd_avg(
|
|
|
247
252
|
is derived and plotted from y-intercept to the ray of symmetry as follows::
|
|
248
253
|
|
|
249
254
|
from sympy import plot as symplot, solve, symbols
|
|
250
|
-
|
|
255
|
+
_s_1, s_2 = symbols("_s_1 s_2", positive=True)
|
|
251
256
|
|
|
252
257
|
g_val, r_val, m_val = 0.06, 0.80, 0.30
|
|
253
258
|
delta_star = g_val / (r_val * m_val)
|
|
254
259
|
|
|
255
260
|
# recapture_form == "inside-out"
|
|
256
261
|
oswag = solve(
|
|
257
|
-
|
|
258
|
-
+ s_2 *
|
|
259
|
-
- (
|
|
262
|
+
_s_1 * s_2 / (1 - _s_1)
|
|
263
|
+
+ s_2 * _s_1 / (1 - (r_val * s_2 + (1 - r_val) * _s_1))
|
|
264
|
+
- (_s_1 + s_2) * delta_star,
|
|
260
265
|
s_2
|
|
261
266
|
)[0]
|
|
262
267
|
symplot(
|
|
263
268
|
oswag,
|
|
264
|
-
(
|
|
269
|
+
(_s_1, 0., d_hat / (1 + d_hat)),
|
|
265
270
|
ylabel=s_2
|
|
266
271
|
)
|
|
267
272
|
|
|
268
273
|
cpswag = solve(
|
|
269
|
-
s_2 * s_2 / (1 -
|
|
270
|
-
+
|
|
271
|
-
- (
|
|
274
|
+
s_2 * s_2 / (1 - _s_1)
|
|
275
|
+
+ _s_1 * _s_1 / (1 - (r_val * s_2 + (1 - r_val) * _s_1))
|
|
276
|
+
- (_s_1 + s_2) * delta_star,
|
|
272
277
|
s_2
|
|
273
278
|
)[1]
|
|
274
279
|
symplot(
|
|
275
280
|
cpwag,
|
|
276
|
-
(
|
|
281
|
+
(_s_1, 0., d_hat / (1 + d_hat)),
|
|
277
282
|
ylabel=s_2
|
|
278
283
|
)
|
|
279
284
|
|
|
280
285
|
# recapture_form == "proportional"
|
|
281
286
|
oswag = solve(
|
|
282
|
-
|
|
283
|
-
+ s_2 *
|
|
284
|
-
- (
|
|
287
|
+
_s_1 * s_2 / (1 - _s_1)
|
|
288
|
+
+ s_2 * _s_1 / (1 - s_2)
|
|
289
|
+
- (_s_1 + s_2) * delta_star,
|
|
285
290
|
s_2
|
|
286
291
|
)[0]
|
|
287
292
|
symplot(
|
|
288
293
|
oswag,
|
|
289
|
-
(
|
|
294
|
+
(_s_1, 0., d_hat / (1 + d_hat)),
|
|
290
295
|
ylabel=s_2
|
|
291
296
|
)
|
|
292
297
|
|
|
293
298
|
cpswag = solve(
|
|
294
|
-
s_2 * s_2 / (1 -
|
|
295
|
-
+
|
|
296
|
-
- (
|
|
299
|
+
s_2 * s_2 / (1 - _s_1)
|
|
300
|
+
+ _s_1 * _s_1 / (1 - s_2)
|
|
301
|
+
- (_s_1 + s_2) * delta_star,
|
|
297
302
|
s_2
|
|
298
303
|
)[1]
|
|
299
304
|
symplot(
|
|
300
305
|
cpswag,
|
|
301
|
-
(
|
|
306
|
+
(_s_1, 0.0, d_hat / (1 + d_hat)),
|
|
302
307
|
ylabel=s_2
|
|
303
308
|
)
|
|
304
309
|
|
|
305
310
|
|
|
306
311
|
"""
|
|
307
312
|
|
|
308
|
-
_delta_star = mpf(f"{
|
|
313
|
+
_delta_star, _r_val = (mpf(f"{_v}") for _v in (_delta_star, _r_val))
|
|
309
314
|
_s_mid = mp.fdiv(_delta_star, 1 + _delta_star)
|
|
310
315
|
|
|
311
316
|
# initial conditions
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
317
|
+
bdry = [(_s_mid, _s_mid)]
|
|
318
|
+
s_1_pre, s_2_pre = _s_mid, _s_mid
|
|
319
|
+
s_2_oddval, s_2_oddsum, s_2_evnsum = True, 0.0, 0.0
|
|
315
320
|
|
|
316
321
|
# parameters for iteration
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
for _s_1 in mp.arange(_s_mid -
|
|
322
|
+
_step_size = mp.power(10, -dps)
|
|
323
|
+
theta_ = _step_size * (10 if weighting == "cross-product-share" else 1)
|
|
324
|
+
for _s_1 in mp.arange(_s_mid - _step_size, 0, -_step_size):
|
|
320
325
|
# The wtd. avg. GUPPI is not always convex to the origin, so we
|
|
321
|
-
# increment
|
|
326
|
+
# increment s_2 after each iteration in which our algorithm
|
|
322
327
|
# finds (s1, s2) on the boundary
|
|
323
|
-
|
|
328
|
+
s_2 = s_2_pre * (1 + theta_)
|
|
324
329
|
|
|
325
|
-
if (_s_1 +
|
|
330
|
+
if (_s_1 + s_2) > mpf("0.99875"):
|
|
326
331
|
# Loss of accuracy at 3-9s and up
|
|
327
332
|
break
|
|
328
333
|
|
|
329
334
|
while True:
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
_s_1 / (1 - lerp(_s_1,
|
|
335
|
+
de_1 = s_2 / (1 - _s_1)
|
|
336
|
+
de_2 = (
|
|
337
|
+
_s_1 / (1 - lerp(_s_1, s_2, _r_val))
|
|
333
338
|
if recapture_form == "inside-out"
|
|
334
|
-
else _s_1 / (1 -
|
|
339
|
+
else _s_1 / (1 - s_2)
|
|
335
340
|
)
|
|
336
341
|
|
|
337
|
-
|
|
338
|
-
mp.fdiv(
|
|
339
|
-
_s_1 if weighting == "cross-product-share" else _s_2, _s_1 + _s_2
|
|
340
|
-
)
|
|
342
|
+
r_ = (
|
|
343
|
+
mp.fdiv(_s_1 if weighting == "cross-product-share" else s_2, _s_1 + s_2)
|
|
341
344
|
if weighting
|
|
342
345
|
else 0.5
|
|
343
346
|
)
|
|
344
347
|
|
|
345
348
|
match agg_method:
|
|
346
349
|
case "geometric mean":
|
|
347
|
-
|
|
350
|
+
delta_test = mp.expm1(lerp(mp.log1p(de_1), mp.log1p(de_2), r_))
|
|
348
351
|
case "distance":
|
|
349
|
-
|
|
352
|
+
delta_test = mp.sqrt(lerp(de_1**2, de_2**2, r_))
|
|
350
353
|
case _:
|
|
351
|
-
|
|
354
|
+
delta_test = lerp(de_1, de_2, r_)
|
|
352
355
|
|
|
353
|
-
|
|
354
|
-
(
|
|
356
|
+
test_flag, incr_decr = (
|
|
357
|
+
(delta_test > _delta_star, -1)
|
|
355
358
|
if weighting == "cross-product-share"
|
|
356
|
-
else (
|
|
359
|
+
else (delta_test < _delta_star, 1)
|
|
357
360
|
)
|
|
358
361
|
|
|
359
|
-
if
|
|
360
|
-
|
|
362
|
+
if test_flag:
|
|
363
|
+
s_2 += incr_decr * _step_size
|
|
361
364
|
else:
|
|
362
365
|
break
|
|
363
366
|
|
|
364
367
|
# Build-up boundary points
|
|
365
|
-
|
|
368
|
+
bdry.append((_s_1, s_2))
|
|
366
369
|
|
|
367
370
|
# Build up area terms
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
+
s_2_oddsum += s_2 if s_2_oddval else 0
|
|
372
|
+
s_2_evnsum += s_2 if not s_2_oddval else 0
|
|
373
|
+
s_2_oddval = not s_2_oddval
|
|
371
374
|
|
|
372
375
|
# Hold share points
|
|
373
|
-
|
|
374
|
-
|
|
376
|
+
s_2_pre = s_2
|
|
377
|
+
s_1_pre = _s_1
|
|
375
378
|
|
|
376
|
-
if
|
|
377
|
-
|
|
379
|
+
if s_2_oddval:
|
|
380
|
+
s_2_evnsum -= s_2_pre
|
|
378
381
|
else:
|
|
379
|
-
|
|
382
|
+
s_2_oddsum -= s_1_pre
|
|
380
383
|
|
|
381
384
|
_s_intcpt = _shrratio_boundary_intcpt(
|
|
382
|
-
|
|
385
|
+
s_2_pre,
|
|
383
386
|
_delta_star,
|
|
384
387
|
_r_val,
|
|
385
388
|
recapture_form=recapture_form,
|
|
@@ -388,189 +391,34 @@ def shrratio_boundary_wtd_avg(
|
|
|
388
391
|
)
|
|
389
392
|
|
|
390
393
|
if weighting == "own-share":
|
|
391
|
-
|
|
392
|
-
|
|
394
|
+
gbd_prtlarea = (
|
|
395
|
+
_step_size * (4 * s_2_oddsum + 2 * s_2_evnsum + _s_mid + s_2_pre) / 3
|
|
393
396
|
)
|
|
394
397
|
# Area under boundary
|
|
395
|
-
|
|
396
|
-
2 * (
|
|
397
|
-
- (mp.power(_s_mid, "2") + mp.power(
|
|
398
|
+
bdry_area_total = float(
|
|
399
|
+
2 * (s_1_pre + gbd_prtlarea)
|
|
400
|
+
- (mp.power(_s_mid, "2") + mp.power(s_1_pre, "2"))
|
|
398
401
|
)
|
|
399
402
|
|
|
400
403
|
else:
|
|
401
|
-
|
|
402
|
-
|
|
404
|
+
gbd_prtlarea = (
|
|
405
|
+
_step_size * (4 * s_2_oddsum + 2 * s_2_evnsum + _s_mid + _s_intcpt) / 3
|
|
403
406
|
)
|
|
404
407
|
# Area under boundary
|
|
405
|
-
|
|
408
|
+
bdry_area_total = float(2 * gbd_prtlarea - mp.power(_s_mid, "2"))
|
|
406
409
|
|
|
407
|
-
|
|
408
|
-
|
|
410
|
+
bdry.append((mpf("0.0"), _s_intcpt))
|
|
411
|
+
bdry_array = np.array(bdry, float)
|
|
409
412
|
|
|
410
413
|
# Points defining boundary to point-of-symmetry
|
|
411
414
|
return GuidelinesBoundary(
|
|
412
|
-
np.vstack((
|
|
413
|
-
round(float(
|
|
415
|
+
np.vstack((bdry_array[::-1], bdry_array[1:, ::-1])),
|
|
416
|
+
round(float(bdry_area_total), dps),
|
|
414
417
|
)
|
|
415
418
|
|
|
416
419
|
|
|
417
|
-
def shrratio_boundary_xact_avg(
|
|
418
|
-
_delta_star: float
|
|
419
|
-
_r_val: float = DEFAULT_REC_RATIO,
|
|
420
|
-
/,
|
|
421
|
-
*,
|
|
422
|
-
recapture_form: Literal["inside-out", "proportional"] = "inside-out",
|
|
423
|
-
dps: int = 5,
|
|
424
|
-
) -> GuidelinesBoundary:
|
|
425
|
-
"""
|
|
426
|
-
Share combinations for the simple average GUPPI boundary with symmetric
|
|
427
|
-
merging-firm margins.
|
|
428
|
-
|
|
429
|
-
Notes
|
|
430
|
-
-----
|
|
431
|
-
An analytical expression for the exact average boundary is derived
|
|
432
|
-
and plotted from the y-intercept to the ray of symmetry as follows::
|
|
433
|
-
|
|
434
|
-
from sympy import latex, plot as symplot, solve, symbols
|
|
435
|
-
|
|
436
|
-
s_1, s_2 = symbols("s_1 s_2")
|
|
437
|
-
|
|
438
|
-
g_val, r_val, m_val = 0.06, 0.80, 0.30
|
|
439
|
-
d_hat = g_val / (r_val * m_val)
|
|
440
|
-
|
|
441
|
-
# recapture_form = "inside-out"
|
|
442
|
-
sag = solve(
|
|
443
|
-
(s_2 / (1 - s_1))
|
|
444
|
-
+ (s_1 / (1 - (r_val * s_2 + (1 - r_val) * s_1)))
|
|
445
|
-
- 2 * d_hat,
|
|
446
|
-
s_2
|
|
447
|
-
)[0]
|
|
448
|
-
symplot(
|
|
449
|
-
sag,
|
|
450
|
-
(s_1, 0., d_hat / (1 + d_hat)),
|
|
451
|
-
ylabel=s_2
|
|
452
|
-
)
|
|
453
|
-
|
|
454
|
-
# recapture_form = "proportional"
|
|
455
|
-
sag = solve((s_2/(1 - s_1)) + (s_1/(1 - s_2)) - 2 * d_hat, s_2)[0]
|
|
456
|
-
symplot(
|
|
457
|
-
sag,
|
|
458
|
-
(s_1, 0., d_hat / (1 + d_hat)),
|
|
459
|
-
ylabel=s_2
|
|
460
|
-
)
|
|
461
|
-
|
|
462
|
-
Parameters
|
|
463
|
-
----------
|
|
464
|
-
_delta_star
|
|
465
|
-
Share ratio (:math:`\\overline{d} / \\overline{r}`).
|
|
466
|
-
_r_val
|
|
467
|
-
Recapture ratio
|
|
468
|
-
recapture_form
|
|
469
|
-
Whether recapture-ratio is MNL-consistent ("inside-out") or has fixed
|
|
470
|
-
value for both merging firms ("proportional").
|
|
471
|
-
dps
|
|
472
|
-
Number of decimal places for rounding returned shares.
|
|
473
|
-
|
|
474
|
-
Returns
|
|
475
|
-
-------
|
|
476
|
-
Array of share-pairs, area under boundary, area under boundary.
|
|
477
|
-
|
|
478
|
-
"""
|
|
479
|
-
|
|
480
|
-
_delta_star = mpf(f"{_delta_star}")
|
|
481
|
-
_s_mid = _delta_star / (1 + _delta_star)
|
|
482
|
-
_gbd_step_sz = mp.power(10, -dps)
|
|
483
|
-
|
|
484
|
-
_gbdry_points_start = np.array([(_s_mid, _s_mid)])
|
|
485
|
-
_s_1 = np.array(mp.arange(_s_mid - _gbd_step_sz, 0, -_gbd_step_sz), np.float64)
|
|
486
|
-
if recapture_form == "inside-out":
|
|
487
|
-
_s_intcpt = mp.fdiv(
|
|
488
|
-
mp.fsub(
|
|
489
|
-
2 * _delta_star * _r_val + 1, mp.fabs(2 * _delta_star * _r_val - 1)
|
|
490
|
-
),
|
|
491
|
-
2 * mpf(f"{_r_val}"),
|
|
492
|
-
)
|
|
493
|
-
_nr_t1 = 1 + 2 * _delta_star * _r_val * (1 - _s_1) - _s_1 * (1 - _r_val)
|
|
494
|
-
|
|
495
|
-
_nr_sqrt_mdr = 4 * _delta_star * _r_val
|
|
496
|
-
_nr_sqrt_mdr2 = _nr_sqrt_mdr * _r_val
|
|
497
|
-
_nr_sqrt_md2r2 = _nr_sqrt_mdr2 * _delta_star
|
|
498
|
-
|
|
499
|
-
_nr_sqrt_t1 = _nr_sqrt_md2r2 * (_s_1**2 - 2 * _s_1 + 1)
|
|
500
|
-
_nr_sqrt_t2 = _nr_sqrt_mdr2 * _s_1 * (_s_1 - 1)
|
|
501
|
-
_nr_sqrt_t3 = _nr_sqrt_mdr * (2 * _s_1 - _s_1**2 - 1)
|
|
502
|
-
_nr_sqrt_t4 = (_s_1**2) * (_r_val**2 - 6 * _r_val + 1)
|
|
503
|
-
_nr_sqrt_t5 = _s_1 * (6 * _r_val - 2) + 1
|
|
504
|
-
|
|
505
|
-
_nr_t2_mdr = _nr_sqrt_t1 + _nr_sqrt_t2 + _nr_sqrt_t3 + _nr_sqrt_t4 + _nr_sqrt_t5
|
|
506
|
-
|
|
507
|
-
# Alternative grouping of terms in np.sqrt
|
|
508
|
-
_nr_sqrt_s1sq = (_s_1**2) * (
|
|
509
|
-
_nr_sqrt_md2r2 + _nr_sqrt_mdr2 - _nr_sqrt_mdr + _r_val**2 - 6 * _r_val + 1
|
|
510
|
-
)
|
|
511
|
-
_nr_sqrt_s1 = _s_1 * (
|
|
512
|
-
-2 * _nr_sqrt_md2r2 - _nr_sqrt_mdr2 + 2 * _nr_sqrt_mdr + 6 * _r_val - 2
|
|
513
|
-
)
|
|
514
|
-
_nr_sqrt_nos1 = _nr_sqrt_md2r2 - _nr_sqrt_mdr + 1
|
|
515
|
-
|
|
516
|
-
_nr_t2_s1 = _nr_sqrt_s1sq + _nr_sqrt_s1 + _nr_sqrt_nos1
|
|
517
|
-
|
|
518
|
-
if not np.isclose(
|
|
519
|
-
np.einsum("i->", _nr_t2_mdr.astype(np.float64)),
|
|
520
|
-
np.einsum("i->", _nr_t2_s1.astype(np.float64)),
|
|
521
|
-
rtol=0,
|
|
522
|
-
atol=0.5 * dps,
|
|
523
|
-
):
|
|
524
|
-
raise RuntimeError(
|
|
525
|
-
"Calculation of sq. root term in exact average GUPPI"
|
|
526
|
-
f"with recapture spec, {f'"{recapture_form}"'} is incorrect."
|
|
527
|
-
)
|
|
528
|
-
|
|
529
|
-
_s_2 = (_nr_t1 - np.sqrt(_nr_t2_s1)) / (2 * _r_val)
|
|
530
|
-
|
|
531
|
-
else:
|
|
532
|
-
_s_intcpt = mp.fsub(_delta_star + 1 / 2, mp.fabs(_delta_star - 1 / 2))
|
|
533
|
-
_s_2 = (
|
|
534
|
-
(1 / 2)
|
|
535
|
-
+ _delta_star
|
|
536
|
-
- _delta_star * _s_1
|
|
537
|
-
- np.sqrt(
|
|
538
|
-
((_delta_star**2) - 1) * (_s_1**2)
|
|
539
|
-
+ (-2 * (_delta_star**2) + _delta_star + 1) * _s_1
|
|
540
|
-
+ (_delta_star**2)
|
|
541
|
-
- _delta_star
|
|
542
|
-
+ (1 / 4)
|
|
543
|
-
)
|
|
544
|
-
)
|
|
545
|
-
|
|
546
|
-
_gbdry_points_inner = np.column_stack((_s_1, _s_2))
|
|
547
|
-
_gbdry_points_end = np.array([(mpf("0.0"), _s_intcpt)], np.float64)
|
|
548
|
-
|
|
549
|
-
_gbdry_points = np.vstack((
|
|
550
|
-
_gbdry_points_end,
|
|
551
|
-
_gbdry_points_inner[::-1],
|
|
552
|
-
_gbdry_points_start,
|
|
553
|
-
_gbdry_points_inner[:, ::-1],
|
|
554
|
-
_gbdry_points_end[:, ::-1],
|
|
555
|
-
)).astype(np.float64)
|
|
556
|
-
_s_2 = np.concatenate((np.array([_s_mid], np.float64), _s_2))
|
|
557
|
-
|
|
558
|
-
_gbdry_ends = [0, -1]
|
|
559
|
-
_gbdry_odds = np.array(range(1, len(_s_2), 2), np.int64)
|
|
560
|
-
_gbdry_evns = np.array(range(2, len(_s_2), 2), np.int64)
|
|
561
|
-
|
|
562
|
-
# Double the are under the curve, and subtract the double counted bit.
|
|
563
|
-
_gbdry_area_simpson = 2 * _gbd_step_sz * (
|
|
564
|
-
(4 / 3) * np.sum(_s_2.take(_gbdry_odds))
|
|
565
|
-
+ (2 / 3) * np.sum(_s_2.take(_gbdry_evns))
|
|
566
|
-
+ (1 / 3) * np.sum(_s_2.take(_gbdry_ends))
|
|
567
|
-
) - np.power(_s_mid, 2)
|
|
568
|
-
|
|
569
|
-
return GuidelinesBoundary(_gbdry_points, round(float(_gbdry_area_simpson), dps))
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
def shrratio_boundary_xact_avg_mp(
|
|
573
|
-
_delta_star: float | decimal.Decimal | MPFloat = 0.075,
|
|
420
|
+
def shrratio_boundary_xact_avg( # noqa: PLR0914
|
|
421
|
+
_delta_star: float = 0.075,
|
|
574
422
|
_r_val: float = DEFAULT_REC_RATIO,
|
|
575
423
|
/,
|
|
576
424
|
*,
|
|
@@ -588,29 +436,29 @@ def shrratio_boundary_xact_avg_mp(
|
|
|
588
436
|
|
|
589
437
|
from sympy import latex, plot as symplot, solve, symbols
|
|
590
438
|
|
|
591
|
-
|
|
439
|
+
_s_1, s_2 = symbols("_s_1 s_2")
|
|
592
440
|
|
|
593
441
|
g_val, r_val, m_val = 0.06, 0.80, 0.30
|
|
594
442
|
d_hat = g_val / (r_val * m_val)
|
|
595
443
|
|
|
596
444
|
# recapture_form = "inside-out"
|
|
597
445
|
sag = solve(
|
|
598
|
-
(s_2 / (1 -
|
|
599
|
-
+ (
|
|
446
|
+
(s_2 / (1 - _s_1))
|
|
447
|
+
+ (_s_1 / (1 - (r_val * s_2 + (1 - r_val) * _s_1)))
|
|
600
448
|
- 2 * d_hat,
|
|
601
449
|
s_2
|
|
602
450
|
)[0]
|
|
603
451
|
symplot(
|
|
604
452
|
sag,
|
|
605
|
-
(
|
|
453
|
+
(_s_1, 0., d_hat / (1 + d_hat)),
|
|
606
454
|
ylabel=s_2
|
|
607
455
|
)
|
|
608
456
|
|
|
609
457
|
# recapture_form = "proportional"
|
|
610
|
-
sag = solve((s_2/(1 -
|
|
458
|
+
sag = solve((s_2/(1 - _s_1)) + (_s_1/(1 - s_2)) - 2 * d_hat, s_2)[0]
|
|
611
459
|
symplot(
|
|
612
460
|
sag,
|
|
613
|
-
(
|
|
461
|
+
(_s_1, 0., d_hat / (1 + d_hat)),
|
|
614
462
|
ylabel=s_2
|
|
615
463
|
)
|
|
616
464
|
|
|
@@ -632,12 +480,12 @@ def shrratio_boundary_xact_avg_mp(
|
|
|
632
480
|
|
|
633
481
|
"""
|
|
634
482
|
|
|
635
|
-
_delta_star = mpf(f"{
|
|
483
|
+
_delta_star, _r_val = (mpf(f"{_v}") for _v in (_delta_star, _r_val))
|
|
636
484
|
_s_mid = _delta_star / (1 + _delta_star)
|
|
637
|
-
|
|
485
|
+
_step_size = mp.power(10, -dps)
|
|
638
486
|
|
|
639
|
-
|
|
640
|
-
_s_1 = np.array(mp.arange(_s_mid -
|
|
487
|
+
_bdry_start = np.array([(_s_mid, _s_mid)])
|
|
488
|
+
_s_1 = np.array(mp.arange(_s_mid - _step_size, 0, -_step_size))
|
|
641
489
|
if recapture_form == "inside-out":
|
|
642
490
|
_s_intcpt = mp.fdiv(
|
|
643
491
|
mp.fsub(
|
|
@@ -645,34 +493,34 @@ def shrratio_boundary_xact_avg_mp(
|
|
|
645
493
|
),
|
|
646
494
|
2 * mpf(f"{_r_val}"),
|
|
647
495
|
)
|
|
648
|
-
|
|
496
|
+
nr_t1 = 1 + 2 * _delta_star * _r_val * (1 - _s_1) - _s_1 * (1 - _r_val)
|
|
649
497
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
498
|
+
nr_sqrt_mdr = 4 * _delta_star * _r_val
|
|
499
|
+
nr_sqrt_mdr2 = nr_sqrt_mdr * _r_val
|
|
500
|
+
nr_sqrt_md2r2 = nr_sqrt_mdr2 * _delta_star
|
|
653
501
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
502
|
+
nr_sqrt_t1 = nr_sqrt_md2r2 * (_s_1**2 - 2 * _s_1 + 1)
|
|
503
|
+
nr_sqrt_t2 = nr_sqrt_mdr2 * _s_1 * (_s_1 - 1)
|
|
504
|
+
nr_sqrt_t3 = nr_sqrt_mdr * (2 * _s_1 - _s_1**2 - 1)
|
|
505
|
+
nr_sqrt_t4 = (_s_1**2) * (_r_val**2 - 6 * _r_val + 1)
|
|
506
|
+
nr_sqrt_t5 = _s_1 * (6 * _r_val - 2) + 1
|
|
659
507
|
|
|
660
|
-
|
|
508
|
+
nr_t2_mdr = nr_sqrt_t1 + nr_sqrt_t2 + nr_sqrt_t3 + nr_sqrt_t4 + nr_sqrt_t5
|
|
661
509
|
|
|
662
510
|
# Alternative grouping of terms in np.sqrt
|
|
663
|
-
|
|
664
|
-
|
|
511
|
+
nr_sqrt_s1sq = (_s_1**2) * (
|
|
512
|
+
nr_sqrt_md2r2 + nr_sqrt_mdr2 - nr_sqrt_mdr + _r_val**2 - 6 * _r_val + 1
|
|
665
513
|
)
|
|
666
|
-
|
|
667
|
-
-2 *
|
|
514
|
+
nr_sqrt_s1 = _s_1 * (
|
|
515
|
+
-2 * nr_sqrt_md2r2 - nr_sqrt_mdr2 + 2 * nr_sqrt_mdr + 6 * _r_val - 2
|
|
668
516
|
)
|
|
669
|
-
|
|
517
|
+
nr_sqrt_nos1 = nr_sqrt_md2r2 - nr_sqrt_mdr + 1
|
|
670
518
|
|
|
671
|
-
|
|
519
|
+
nr_t2_s1 = nr_sqrt_s1sq + nr_sqrt_s1 + nr_sqrt_nos1
|
|
672
520
|
|
|
673
521
|
if not np.isclose(
|
|
674
|
-
np.einsum("i->",
|
|
675
|
-
np.einsum("i->",
|
|
522
|
+
np.einsum("i->", nr_t2_mdr.astype(float)),
|
|
523
|
+
np.einsum("i->", nr_t2_s1.astype(float)),
|
|
676
524
|
rtol=0,
|
|
677
525
|
atol=0.5 * dps,
|
|
678
526
|
):
|
|
@@ -681,11 +529,11 @@ def shrratio_boundary_xact_avg_mp(
|
|
|
681
529
|
f"with recapture spec, {f'"{recapture_form}"'} is incorrect."
|
|
682
530
|
)
|
|
683
531
|
|
|
684
|
-
|
|
532
|
+
s_2 = (nr_t1 - np.sqrt(nr_t2_s1)) / (2 * _r_val)
|
|
685
533
|
|
|
686
534
|
else:
|
|
687
535
|
_s_intcpt = mp.fsub(_delta_star + 1 / 2, mp.fabs(_delta_star - 1 / 2))
|
|
688
|
-
|
|
536
|
+
s_2 = (
|
|
689
537
|
(1 / 2)
|
|
690
538
|
+ _delta_star
|
|
691
539
|
- _delta_star * _s_1
|
|
@@ -698,34 +546,34 @@ def shrratio_boundary_xact_avg_mp(
|
|
|
698
546
|
)
|
|
699
547
|
)
|
|
700
548
|
|
|
701
|
-
|
|
702
|
-
|
|
549
|
+
bdry_inner = np.column_stack((_s_1, s_2))
|
|
550
|
+
bdry_end = np.array([(mpf("0.0"), _s_intcpt)], float)
|
|
703
551
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
)).astype(
|
|
711
|
-
|
|
552
|
+
bdry = np.vstack((
|
|
553
|
+
bdry_end,
|
|
554
|
+
bdry_inner[::-1],
|
|
555
|
+
_bdry_start,
|
|
556
|
+
bdry_inner[:, ::-1],
|
|
557
|
+
bdry_end[:, ::-1],
|
|
558
|
+
)).astype(float)
|
|
559
|
+
s_2 = np.concatenate((np.array([_s_mid], float), s_2))
|
|
712
560
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
561
|
+
bdry_ends = [0, -1]
|
|
562
|
+
bdry_odds = np.array(range(1, len(s_2), 2), int)
|
|
563
|
+
bdry_evns = np.array(range(2, len(s_2), 2), int)
|
|
716
564
|
|
|
717
565
|
# Double the are under the curve, and subtract the double counted bit.
|
|
718
|
-
|
|
719
|
-
(4 / 3) * np.sum(
|
|
720
|
-
+ (2 / 3) * np.sum(
|
|
721
|
-
+ (1 / 3) * np.sum(
|
|
566
|
+
bdry_area_simpson = 2 * _step_size * (
|
|
567
|
+
(4 / 3) * np.sum(s_2.take(bdry_odds))
|
|
568
|
+
+ (2 / 3) * np.sum(s_2.take(bdry_evns))
|
|
569
|
+
+ (1 / 3) * np.sum(s_2.take(bdry_ends))
|
|
722
570
|
) - np.power(_s_mid, 2)
|
|
723
571
|
|
|
724
|
-
return GuidelinesBoundary(
|
|
572
|
+
return GuidelinesBoundary(bdry, round(float(bdry_area_simpson), dps))
|
|
725
573
|
|
|
726
574
|
|
|
727
575
|
def shrratio_boundary_min(
|
|
728
|
-
_delta_star: float
|
|
576
|
+
_delta_star: float = 0.075,
|
|
729
577
|
_r_val: float = DEFAULT_REC_RATIO,
|
|
730
578
|
/,
|
|
731
579
|
*,
|
|
@@ -761,42 +609,29 @@ def shrratio_boundary_min(
|
|
|
761
609
|
|
|
762
610
|
"""
|
|
763
611
|
|
|
764
|
-
_delta_star = mpf(f"{
|
|
612
|
+
_delta_star, _r_val = (mpf(f"{_v}") for _v in (_delta_star, _r_val))
|
|
765
613
|
_s_intcpt = mpf("1.00")
|
|
766
614
|
_s_mid = _delta_star / (1 + _delta_star)
|
|
767
615
|
|
|
768
616
|
if recapture_form == "inside-out":
|
|
769
|
-
# ## Plot envelope of GUPPI boundaries with
|
|
770
|
-
# ## See (
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
_smin_nr / _guppi_bdry_env_dr,
|
|
778
|
-
_s_mid,
|
|
779
|
-
_smax_nr / _guppi_bdry_env_dr,
|
|
780
|
-
_s_intcpt,
|
|
781
|
-
),
|
|
782
|
-
np.float64,
|
|
783
|
-
)
|
|
784
|
-
|
|
785
|
-
_gbd_area = _s_mid + _s1_pts[1] * (1 - 2 * _s_mid)
|
|
617
|
+
# ## Plot envelope of GUPPI boundaries with rk_ = r_bar if sk_ = min(_s_1, s_2)
|
|
618
|
+
# ## See (si_, sj_) in equation~(44), or thereabouts, in paper
|
|
619
|
+
smin_nr = _delta_star * (1 - _r_val)
|
|
620
|
+
smax_nr = 1 - _delta_star * _r_val
|
|
621
|
+
dr = smin_nr + smax_nr
|
|
622
|
+
s_1 = np.array((0, smin_nr / dr, _s_mid, smax_nr / dr, _s_intcpt), float)
|
|
623
|
+
|
|
624
|
+
bdry_area = _s_mid + s_1[1] * (1 - 2 * _s_mid)
|
|
786
625
|
else:
|
|
787
|
-
|
|
626
|
+
s_1, bdry_area = np.array((0, _s_mid, _s_intcpt), float), _s_mid
|
|
788
627
|
|
|
789
628
|
return GuidelinesBoundary(
|
|
790
|
-
np.column_stack((
|
|
629
|
+
np.column_stack((s_1, s_1[::-1])), round(float(bdry_area), dps)
|
|
791
630
|
)
|
|
792
631
|
|
|
793
632
|
|
|
794
633
|
def shrratio_boundary_max(
|
|
795
|
-
_delta_star: float
|
|
796
|
-
_r_val: float = DEFAULT_REC_RATIO,
|
|
797
|
-
/,
|
|
798
|
-
*,
|
|
799
|
-
dps: int = 10,
|
|
634
|
+
_delta_star: float = 0.075, _: float = DEFAULT_REC_RATIO, /, *, dps: int = 10
|
|
800
635
|
) -> GuidelinesBoundary:
|
|
801
636
|
"""
|
|
802
637
|
Share combinations on the minimum GUPPI boundary with symmetric
|
|
@@ -806,8 +641,9 @@ def shrratio_boundary_max(
|
|
|
806
641
|
----------
|
|
807
642
|
_delta_star
|
|
808
643
|
Share ratio (:math:`\\overline{d} / \\overline{r}`).
|
|
809
|
-
|
|
810
|
-
|
|
644
|
+
_
|
|
645
|
+
Placeholder for recapture ratio included for consistency with other
|
|
646
|
+
share-ratio boundary functions.
|
|
811
647
|
dps
|
|
812
648
|
Number of decimal places for rounding returned shares.
|
|
813
649
|
|
|
@@ -817,26 +653,19 @@ def shrratio_boundary_max(
|
|
|
817
653
|
|
|
818
654
|
"""
|
|
819
655
|
|
|
820
|
-
# _r_val is not needed for max boundary, but is specified for consistency
|
|
821
|
-
# of function call with other share-ratio boundary functions
|
|
822
|
-
del _r_val
|
|
823
656
|
_delta_star = mpf(f"{_delta_star}")
|
|
824
657
|
_s_intcpt = _delta_star
|
|
825
658
|
_s_mid = _delta_star / (1 + _delta_star)
|
|
826
|
-
|
|
827
659
|
_s1_pts = (0, _s_mid, _s_intcpt)
|
|
828
660
|
|
|
829
661
|
return GuidelinesBoundary(
|
|
830
|
-
np.column_stack((
|
|
831
|
-
np.array(_s1_pts, np.float64),
|
|
832
|
-
np.array(_s1_pts[::-1], np.float64),
|
|
833
|
-
)),
|
|
662
|
+
np.column_stack((np.array(_s1_pts, float), np.array(_s1_pts[::-1], float))),
|
|
834
663
|
round(float(_s_intcpt * _s_mid), dps), # simplified calculation
|
|
835
664
|
)
|
|
836
665
|
|
|
837
666
|
|
|
838
667
|
def _shrratio_boundary_intcpt(
|
|
839
|
-
|
|
668
|
+
s_2_pre: float,
|
|
840
669
|
_delta_star: MPFloat,
|
|
841
670
|
_r_val: MPFloat,
|
|
842
671
|
/,
|
|
@@ -864,13 +693,13 @@ def _shrratio_boundary_intcpt(
|
|
|
864
693
|
):
|
|
865
694
|
_s_intcpt = mp.fsub(_delta_star + 1 / 2, mp.fabs(_delta_star - 1 / 2))
|
|
866
695
|
case _:
|
|
867
|
-
_s_intcpt =
|
|
696
|
+
_s_intcpt = s_2_pre
|
|
868
697
|
|
|
869
698
|
return _s_intcpt
|
|
870
699
|
|
|
871
700
|
|
|
872
701
|
def lerp[LerpT: (float, MPFloat, ArrayDouble, ArrayBIGINT)](
|
|
873
|
-
_x1: LerpT, _x2: LerpT, _r: float = 0.25, /
|
|
702
|
+
_x1: LerpT, _x2: LerpT, _r: float | MPFloat = 0.25, /
|
|
874
703
|
) -> LerpT:
|
|
875
704
|
"""
|
|
876
705
|
From the function of the same name in the C++ standard [2]_
|
|
@@ -918,7 +747,7 @@ def round_cust(
|
|
|
918
747
|
*,
|
|
919
748
|
frac: float = 0.005,
|
|
920
749
|
rounding_mode: str = "ROUND_HALF_UP",
|
|
921
|
-
) ->
|
|
750
|
+
) -> float:
|
|
922
751
|
"""
|
|
923
752
|
Custom rounding, to the nearest 0.5% by default.
|
|
924
753
|
|
|
@@ -948,7 +777,7 @@ def round_cust(
|
|
|
948
777
|
|
|
949
778
|
"""
|
|
950
779
|
|
|
951
|
-
if rounding_mode not in
|
|
780
|
+
if rounding_mode not in {
|
|
952
781
|
decimal.ROUND_05UP,
|
|
953
782
|
decimal.ROUND_CEILING,
|
|
954
783
|
decimal.ROUND_DOWN,
|
|
@@ -957,15 +786,15 @@ def round_cust(
|
|
|
957
786
|
decimal.ROUND_HALF_EVEN,
|
|
958
787
|
decimal.ROUND_HALF_UP,
|
|
959
788
|
decimal.ROUND_UP,
|
|
960
|
-
|
|
789
|
+
}:
|
|
961
790
|
raise ValueError(
|
|
962
791
|
f"Value, {f'"{rounding_mode}"'} is invalid for rounding_mode."
|
|
963
792
|
'Documentation for the, "decimal" built-in lists valid rounding modes.'
|
|
964
793
|
)
|
|
965
794
|
|
|
966
|
-
|
|
795
|
+
n_, f_, e_ = (decimal.Decimal(f"{g_}") for g_ in [_num, frac, 1])
|
|
967
796
|
|
|
968
|
-
return
|
|
797
|
+
return float(f_ * (n_ / f_).quantize(e_, rounding=rounding_mode))
|
|
969
798
|
|
|
970
799
|
|
|
971
800
|
def boundary_plot(*, mktshares_plot_flag: bool = True) -> tuple[Any, ...]:
|
|
@@ -974,15 +803,9 @@ def boundary_plot(*, mktshares_plot_flag: bool = True) -> tuple[Any, ...]:
|
|
|
974
803
|
See, https://matplotlib.org/stable/tutorials/text/pgf.html
|
|
975
804
|
"""
|
|
976
805
|
|
|
977
|
-
import matplotlib as mpl
|
|
978
|
-
import matplotlib.axes as mpa
|
|
979
|
-
import matplotlib.patches as mpp
|
|
980
|
-
import matplotlib.ticker as mpt
|
|
981
|
-
|
|
982
806
|
mpl.use("pgf")
|
|
983
|
-
import matplotlib.pyplot as _plt # noqa: ICN001
|
|
984
807
|
|
|
985
|
-
|
|
808
|
+
plt.rcParams.update({
|
|
986
809
|
"pgf.rcfonts": False,
|
|
987
810
|
"pgf.texsystem": "lualatex",
|
|
988
811
|
"pgf.preamble": "\n".join([
|
|
@@ -1021,11 +844,11 @@ def boundary_plot(*, mktshares_plot_flag: bool = True) -> tuple[Any, ...]:
|
|
|
1021
844
|
})
|
|
1022
845
|
|
|
1023
846
|
# Initialize a canvas with a single figure (set of axes)
|
|
1024
|
-
|
|
1025
|
-
|
|
847
|
+
fig_ = plt.figure(figsize=(5, 5), dpi=600)
|
|
848
|
+
ax_out = fig_.add_subplot()
|
|
1026
849
|
|
|
1027
850
|
def _set_axis_def(
|
|
1028
|
-
|
|
851
|
+
ax1_: mpa.Axes,
|
|
1029
852
|
/,
|
|
1030
853
|
*,
|
|
1031
854
|
mktshares_plot_flag: bool = False,
|
|
@@ -1035,53 +858,53 @@ def boundary_plot(*, mktshares_plot_flag: bool = True) -> tuple[Any, ...]:
|
|
|
1035
858
|
# both axes, both major and minor ticks
|
|
1036
859
|
# Frame, grid, and face color
|
|
1037
860
|
for _spos0 in "left", "bottom":
|
|
1038
|
-
|
|
1039
|
-
|
|
861
|
+
ax1_.spines[_spos0].set_linewidth(0.5)
|
|
862
|
+
ax1_.spines[_spos0].set_zorder(5)
|
|
1040
863
|
for _spos1 in "top", "right":
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
864
|
+
ax1_.spines[_spos1].set_linewidth(0.0)
|
|
865
|
+
ax1_.spines[_spos1].set_zorder(0)
|
|
866
|
+
ax1_.spines[_spos1].set_visible(False)
|
|
867
|
+
ax1_.set_facecolor("#E6E6E6")
|
|
1045
868
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
869
|
+
ax1_.grid(linewidth=0.5, linestyle=":", color="grey", zorder=1)
|
|
870
|
+
ax1_.tick_params(axis="x", which="both", width=0.5)
|
|
871
|
+
ax1_.tick_params(axis="y", which="both", width=0.5)
|
|
1049
872
|
|
|
1050
873
|
# Tick marks skip, size, and rotation
|
|
1051
874
|
# x-axis
|
|
1052
|
-
|
|
1053
|
-
|
|
875
|
+
plt.setp(
|
|
876
|
+
ax1_.xaxis.get_majorticklabels(),
|
|
1054
877
|
horizontalalignment="right",
|
|
1055
878
|
fontsize=6,
|
|
1056
879
|
rotation=45,
|
|
1057
880
|
)
|
|
1058
881
|
# y-axis
|
|
1059
|
-
|
|
1060
|
-
|
|
882
|
+
plt.setp(
|
|
883
|
+
ax1_.yaxis.get_majorticklabels(), horizontalalignment="right", fontsize=6
|
|
1061
884
|
)
|
|
1062
885
|
|
|
1063
886
|
if mktshares_plot_flag:
|
|
1064
887
|
# Axis labels
|
|
1065
888
|
if mktshares_axlbls_flag:
|
|
1066
889
|
# x-axis
|
|
1067
|
-
|
|
1068
|
-
|
|
890
|
+
ax1_.set_xlabel("Firm 1 Market Share, $_s_1$", fontsize=10)
|
|
891
|
+
ax1_.xaxis.set_label_coords(0.75, -0.1)
|
|
1069
892
|
# y-axis
|
|
1070
|
-
|
|
1071
|
-
|
|
893
|
+
ax1_.set_ylabel("Firm 2 Market Share, $s_2$", fontsize=10)
|
|
894
|
+
ax1_.yaxis.set_label_coords(-0.1, 0.75)
|
|
1072
895
|
|
|
1073
896
|
# Plot the ray of symmetry
|
|
1074
|
-
|
|
897
|
+
ax1_.plot(
|
|
1075
898
|
[0, 1], [0, 1], linewidth=0.5, linestyle=":", color="grey", zorder=1
|
|
1076
899
|
)
|
|
1077
900
|
|
|
1078
901
|
# Axis scale
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
902
|
+
ax1_.set_xlim(0, 1)
|
|
903
|
+
ax1_.set_ylim(0, 1)
|
|
904
|
+
ax1_.set_aspect(1.0)
|
|
1082
905
|
|
|
1083
906
|
# Truncate the axis frame to a triangle:
|
|
1084
|
-
|
|
907
|
+
ax1_.add_patch(
|
|
1085
908
|
mpp.Rectangle(
|
|
1086
909
|
xy=(1.0025, 0.00),
|
|
1087
910
|
width=1.1 * mp.sqrt(2),
|
|
@@ -1095,7 +918,7 @@ def boundary_plot(*, mktshares_plot_flag: bool = True) -> tuple[Any, ...]:
|
|
|
1095
918
|
)
|
|
1096
919
|
)
|
|
1097
920
|
# Feasible space is bounded by the other diagonal:
|
|
1098
|
-
|
|
921
|
+
ax1_.plot(
|
|
1099
922
|
[0, 1], [1, 0], linestyle="-", linewidth=0.5, color="black", zorder=1
|
|
1100
923
|
)
|
|
1101
924
|
|
|
@@ -1104,25 +927,25 @@ def boundary_plot(*, mktshares_plot_flag: bool = True) -> tuple[Any, ...]:
|
|
|
1104
927
|
# specify a fixed number of minor intervals per major interval, e.g.:
|
|
1105
928
|
# minorLocator = mpt.AutoMinorLocator(2)
|
|
1106
929
|
# would lead to a single minor tick between major ticks.
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
for
|
|
1110
|
-
if
|
|
930
|
+
minor_locator = mpt.AutoMinorLocator(5)
|
|
931
|
+
major_locator = mpt.MultipleLocator(0.05)
|
|
932
|
+
for axs_ in ax1_.xaxis, ax1_.yaxis:
|
|
933
|
+
if axs_ == ax1_.xaxis:
|
|
1111
934
|
_majorticklabels_rot = 45
|
|
1112
|
-
elif
|
|
935
|
+
elif axs_ == ax1_.yaxis:
|
|
1113
936
|
_majorticklabels_rot = 0
|
|
1114
937
|
# x-axis
|
|
1115
|
-
|
|
1116
|
-
|
|
938
|
+
axs_.set_major_locator(major_locator)
|
|
939
|
+
axs_.set_minor_locator(minor_locator)
|
|
1117
940
|
# It"s always x when specifying the format
|
|
1118
|
-
|
|
941
|
+
axs_.set_major_formatter(mpt.StrMethodFormatter("{x:>3.0%}"))
|
|
1119
942
|
|
|
1120
943
|
# Hide every other tick-label
|
|
1121
|
-
for
|
|
1122
|
-
|
|
944
|
+
for axl_ in ax1_.get_xticklabels(), ax1_.get_yticklabels():
|
|
945
|
+
plt.setp(axl_[::2], visible=False)
|
|
1123
946
|
|
|
1124
|
-
return
|
|
947
|
+
return ax1_
|
|
1125
948
|
|
|
1126
|
-
|
|
949
|
+
ax_out = _set_axis_def(ax_out, mktshares_plot_flag=mktshares_plot_flag)
|
|
1127
950
|
|
|
1128
|
-
return
|
|
951
|
+
return plt, fig_, ax_out, _set_axis_def
|