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
|
@@ -8,16 +8,16 @@ poor performance
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
from collections.abc import Callable
|
|
11
|
-
from dataclasses import dataclass
|
|
12
11
|
from typing import Literal
|
|
13
12
|
|
|
14
13
|
import numpy as np
|
|
14
|
+
from attrs import frozen
|
|
15
15
|
from mpmath import mp, mpf # type: ignore
|
|
16
16
|
from scipy.spatial.distance import minkowski as distance_function # type: ignore
|
|
17
17
|
from sympy import lambdify, simplify, solve, symbols # type: ignore
|
|
18
18
|
|
|
19
19
|
from .. import DEFAULT_REC_RATIO, VERSION, ArrayDouble # noqa: TID252
|
|
20
|
-
from . import guidelines_boundary_functions as
|
|
20
|
+
from . import guidelines_boundary_functions as gbf
|
|
21
21
|
|
|
22
22
|
__version__ = VERSION
|
|
23
23
|
|
|
@@ -26,14 +26,14 @@ mp.dps = 32
|
|
|
26
26
|
mp.trap_complex = True
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
@
|
|
29
|
+
@frozen
|
|
30
30
|
class GuidelinesBoundaryCallable:
|
|
31
31
|
boundary_function: Callable[[ArrayDouble], ArrayDouble]
|
|
32
32
|
area: float
|
|
33
33
|
s_naught: float = 0
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def dh_area_quad(_dh_val: float = 0.01,
|
|
36
|
+
def dh_area_quad(_dh_val: float = 0.01, /) -> float:
|
|
37
37
|
"""
|
|
38
38
|
Area under the ΔHHI boundary.
|
|
39
39
|
|
|
@@ -45,8 +45,6 @@ def dh_area_quad(_dh_val: float = 0.01, /, *, dps: int = 9) -> float:
|
|
|
45
45
|
----------
|
|
46
46
|
_dh_val
|
|
47
47
|
Merging-firms' ΔHHI bound.
|
|
48
|
-
dps
|
|
49
|
-
Specified precision in decimal places.
|
|
50
48
|
|
|
51
49
|
Returns
|
|
52
50
|
-------
|
|
@@ -57,11 +55,12 @@ def dh_area_quad(_dh_val: float = 0.01, /, *, dps: int = 9) -> float:
|
|
|
57
55
|
_dh_val = mpf(f"{_dh_val}")
|
|
58
56
|
_s_naught = (1 - mp.sqrt(1 - 2 * _dh_val)) / 2
|
|
59
57
|
|
|
60
|
-
return
|
|
61
|
-
|
|
62
|
-
_s_naught
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
return float(
|
|
59
|
+
mp.nstr(
|
|
60
|
+
_s_naught
|
|
61
|
+
+ mp.quad(lambda x: _dh_val / (2 * x), [_s_naught, 1 - _s_naught]),
|
|
62
|
+
mp.prec,
|
|
63
|
+
)
|
|
65
64
|
)
|
|
66
65
|
|
|
67
66
|
|
|
@@ -84,18 +83,24 @@ def hhi_delta_boundary_qdtr(_dh_val: float = 0.01, /) -> GuidelinesBoundaryCalla
|
|
|
84
83
|
|
|
85
84
|
_s_1, _s_2 = symbols("s_1, s_2", positive=True)
|
|
86
85
|
|
|
87
|
-
_hhi_eqn = _s_2 -
|
|
86
|
+
_hhi_eqn = _s_2 - _dh_val / (2 * _s_1)
|
|
88
87
|
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
hhi_bdry = solve(_hhi_eqn, _s_2)[0]
|
|
89
|
+
s_nought = float(solve(_hhi_eqn.subs({_s_2: 1 - _s_1}), _s_1)[0])
|
|
91
90
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
hhi_bdry_area = float(
|
|
92
|
+
mp.nstr(
|
|
93
|
+
2
|
|
94
|
+
* (
|
|
95
|
+
s_nought
|
|
96
|
+
+ mp.quad(lambdify(_s_1, hhi_bdry, "mpmath"), (s_nought, 1 - s_nought))
|
|
97
|
+
),
|
|
98
|
+
mp.prec,
|
|
99
|
+
)
|
|
95
100
|
)
|
|
96
101
|
|
|
97
102
|
return GuidelinesBoundaryCallable(
|
|
98
|
-
lambdify(_s_1,
|
|
103
|
+
lambdify(_s_1, hhi_bdry, "numpy"), hhi_bdry_area, s_nought
|
|
99
104
|
)
|
|
100
105
|
|
|
101
106
|
|
|
@@ -131,62 +136,60 @@ def shrratio_boundary_qdtr_wtd_avg(
|
|
|
131
136
|
|
|
132
137
|
_delta_star = mpf(f"{_delta_star}")
|
|
133
138
|
_s_mid = _delta_star / (1 + _delta_star)
|
|
134
|
-
|
|
139
|
+
s_naught = 0
|
|
135
140
|
|
|
136
|
-
|
|
141
|
+
s_1, s_2 = symbols("s_1:3", positive=True)
|
|
137
142
|
|
|
138
143
|
match weighting:
|
|
139
144
|
case "own-share":
|
|
140
145
|
_bdry_eqn = (
|
|
141
|
-
|
|
142
|
-
+
|
|
143
|
-
*
|
|
146
|
+
s_1 * s_2 / (1 - s_1)
|
|
147
|
+
+ s_2
|
|
148
|
+
* s_1
|
|
144
149
|
/ (
|
|
145
|
-
(1 - (_r_val *
|
|
150
|
+
(1 - (_r_val * s_2 + (1 - _r_val) * s_1))
|
|
146
151
|
if recapture_form == "inside-out"
|
|
147
|
-
else (1 -
|
|
152
|
+
else (1 - s_2)
|
|
148
153
|
)
|
|
149
|
-
- (
|
|
154
|
+
- (s_1 + s_2) * _delta_star
|
|
150
155
|
)
|
|
151
156
|
|
|
152
|
-
_bdry_func = solve(_bdry_eqn,
|
|
153
|
-
|
|
154
|
-
float(solve(simplify(_bdry_eqn.subs({
|
|
157
|
+
_bdry_func = solve(_bdry_eqn, s_2)[0]
|
|
158
|
+
s_naught = (
|
|
159
|
+
float(solve(simplify(_bdry_eqn.subs({s_2: 1 - s_1})), s_1)[0]) # type: ignore
|
|
155
160
|
if recapture_form == "inside-out"
|
|
156
161
|
else 0
|
|
157
162
|
)
|
|
158
|
-
|
|
163
|
+
bdry_area = float(
|
|
159
164
|
2
|
|
160
165
|
* (
|
|
161
|
-
|
|
162
|
-
+ mp.quad(lambdify(
|
|
166
|
+
s_naught
|
|
167
|
+
+ mp.quad(lambdify(s_1, _bdry_func, "mpmath"), (s_naught, _s_mid))
|
|
163
168
|
)
|
|
164
|
-
- (_s_mid**2 +
|
|
169
|
+
- (_s_mid**2 + s_naught**2)
|
|
165
170
|
)
|
|
166
171
|
|
|
167
172
|
case "cross-product-share":
|
|
168
173
|
mp.trap_complex = False
|
|
169
|
-
|
|
174
|
+
d_star = symbols("d", positive=True)
|
|
170
175
|
_bdry_eqn = (
|
|
171
|
-
|
|
172
|
-
+
|
|
173
|
-
*
|
|
176
|
+
s_2 * s_2 / (1 - s_1)
|
|
177
|
+
+ s_1
|
|
178
|
+
* s_1
|
|
174
179
|
/ (
|
|
175
|
-
(1 - (_r_val *
|
|
180
|
+
(1 - (_r_val * s_2 + (1 - _r_val) * s_1))
|
|
176
181
|
if recapture_form == "inside-out"
|
|
177
|
-
else (1 -
|
|
182
|
+
else (1 - s_2)
|
|
178
183
|
)
|
|
179
|
-
- (
|
|
184
|
+
- (s_1 + s_2) * d_star
|
|
180
185
|
)
|
|
181
186
|
|
|
182
|
-
_bdry_func = solve(_bdry_eqn,
|
|
183
|
-
|
|
187
|
+
_bdry_func = solve(_bdry_eqn, s_2)[1]
|
|
188
|
+
bdry_area = float(
|
|
184
189
|
2
|
|
185
190
|
* (
|
|
186
191
|
mp.quad(
|
|
187
|
-
lambdify(
|
|
188
|
-
_s_1, _bdry_func.subs({_d_star: _delta_star}), "mpmath"
|
|
189
|
-
),
|
|
192
|
+
lambdify(s_1, _bdry_func.subs({d_star: _delta_star}), "mpmath"),
|
|
190
193
|
(0, _s_mid),
|
|
191
194
|
)
|
|
192
195
|
).real
|
|
@@ -195,30 +198,30 @@ def shrratio_boundary_qdtr_wtd_avg(
|
|
|
195
198
|
|
|
196
199
|
case _:
|
|
197
200
|
_bdry_eqn = (
|
|
198
|
-
1 / 2 *
|
|
201
|
+
1 / 2 * s_2 / (1 - s_1)
|
|
199
202
|
+ 1
|
|
200
203
|
/ 2
|
|
201
|
-
*
|
|
204
|
+
* s_1
|
|
202
205
|
/ (
|
|
203
|
-
(1 - (_r_val *
|
|
206
|
+
(1 - (_r_val * s_2 + (1 - _r_val) * s_1))
|
|
204
207
|
if recapture_form == "inside-out"
|
|
205
|
-
else (1 -
|
|
208
|
+
else (1 - s_2)
|
|
206
209
|
)
|
|
207
210
|
- _delta_star
|
|
208
211
|
)
|
|
209
212
|
|
|
210
|
-
_bdry_func = solve(_bdry_eqn,
|
|
211
|
-
|
|
212
|
-
2 * (mp.quad(lambdify(
|
|
213
|
+
_bdry_func = solve(_bdry_eqn, s_2)[0]
|
|
214
|
+
bdry_area = float(
|
|
215
|
+
2 * (mp.quad(lambdify(s_1, _bdry_func, "mpmath"), (0, _s_mid)))
|
|
213
216
|
- _s_mid**2
|
|
214
217
|
)
|
|
215
218
|
|
|
216
219
|
return GuidelinesBoundaryCallable(
|
|
217
|
-
lambdify(
|
|
220
|
+
lambdify(s_1, _bdry_func, "numpy"), bdry_area, s_naught
|
|
218
221
|
)
|
|
219
222
|
|
|
220
223
|
|
|
221
|
-
def shrratio_boundary_distance(
|
|
224
|
+
def shrratio_boundary_distance( # noqa: PLR0914
|
|
222
225
|
_delta_star: float = 0.075,
|
|
223
226
|
_r_val: float = DEFAULT_REC_RATIO,
|
|
224
227
|
/,
|
|
@@ -227,7 +230,7 @@ def shrratio_boundary_distance(
|
|
|
227
230
|
weighting: Literal["own-share", "cross-product-share"] | None = "own-share",
|
|
228
231
|
recapture_form: Literal["inside-out", "proportional"] = "inside-out",
|
|
229
232
|
dps: int = 5,
|
|
230
|
-
) ->
|
|
233
|
+
) -> gbf.GuidelinesBoundary:
|
|
231
234
|
"""
|
|
232
235
|
Share combinations for the GUPPI boundaries using various aggregators with
|
|
233
236
|
symmetric merging-firm margins.
|
|
@@ -261,42 +264,41 @@ def shrratio_boundary_distance(
|
|
|
261
264
|
"""
|
|
262
265
|
|
|
263
266
|
_delta_star = mpf(f"{_delta_star}")
|
|
264
|
-
_s_mid = _delta_star / (1 + _delta_star)
|
|
265
|
-
|
|
266
|
-
# initial conditions
|
|
267
|
-
_gbdry_points = [(_s_mid, _s_mid)]
|
|
268
|
-
_s_1_pre, _s_2_pre = _s_mid, _s_mid
|
|
269
|
-
_s_2_oddval, _s_2_oddsum, _s_2_evnsum = True, 0.0, 0.0
|
|
270
267
|
|
|
271
268
|
# parameters for iteration
|
|
269
|
+
_s_mid = mp.fdiv(_delta_star, 1 + _delta_star)
|
|
272
270
|
_weights_base = (mpf("0.5"),) * 2
|
|
273
|
-
|
|
274
|
-
_theta =
|
|
275
|
-
|
|
271
|
+
_bdry_step_sz = mp.power(10, -dps)
|
|
272
|
+
_theta = _bdry_step_sz * (10 if weighting == "cross-product-share" else 1)
|
|
273
|
+
|
|
274
|
+
# initial conditions
|
|
275
|
+
bdry_points = [(_s_mid, _s_mid)]
|
|
276
|
+
s_1_pre, s_2_pre = _s_mid, _s_mid
|
|
277
|
+
s_2_oddval, s_2_oddsum, s_2_evnsum = True, 0.0, 0.0
|
|
278
|
+
for s_1 in mp.arange(_s_mid - _bdry_step_sz, 0, -_bdry_step_sz):
|
|
276
279
|
# The wtd. avg. GUPPI is not always convex to the origin, so we
|
|
277
|
-
# increment
|
|
280
|
+
# increment s_2 after each iteration in which our algorithm
|
|
278
281
|
# finds (s1, s2) on the boundary
|
|
279
|
-
|
|
282
|
+
s_2 = s_2_pre * (1 + _theta)
|
|
280
283
|
|
|
281
|
-
if (
|
|
282
|
-
# 1: #
|
|
284
|
+
if (s_1 + s_2) > mpf("0.99875"):
|
|
285
|
+
# 1: # inaccuracy at 3-9s and up
|
|
283
286
|
break
|
|
284
287
|
|
|
285
288
|
while True:
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
+
de_1 = s_2 / (1 - s_1)
|
|
290
|
+
de_2 = (
|
|
291
|
+
s_1 / (1 - gbf.lerp(s_1, s_2, _r_val))
|
|
289
292
|
if recapture_form == "inside-out"
|
|
290
|
-
else
|
|
293
|
+
else s_1 / (1 - s_2)
|
|
291
294
|
)
|
|
292
295
|
|
|
293
|
-
|
|
296
|
+
weights_i = (
|
|
294
297
|
(
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
_s_1 + _s_2,
|
|
298
|
+
_w := mp.fdiv(
|
|
299
|
+
s_2 if weighting == "cross-product-share" else s_1, s_1 + s_2
|
|
298
300
|
),
|
|
299
|
-
1 -
|
|
301
|
+
1 - _w,
|
|
300
302
|
)
|
|
301
303
|
if weighting
|
|
302
304
|
else _weights_base
|
|
@@ -304,70 +306,226 @@ def shrratio_boundary_distance(
|
|
|
304
306
|
|
|
305
307
|
match agg_method:
|
|
306
308
|
case "arithmetic mean":
|
|
307
|
-
|
|
308
|
-
(
|
|
309
|
+
delta_test = distance_function(
|
|
310
|
+
(de_1, de_2), (0.0, 0.0), p=1, w=weights_i
|
|
309
311
|
)
|
|
310
312
|
case "distance":
|
|
311
|
-
|
|
312
|
-
(
|
|
313
|
+
delta_test = distance_function(
|
|
314
|
+
(de_1, de_2), (0.0, 0.0), p=2, w=weights_i
|
|
313
315
|
)
|
|
314
316
|
|
|
315
317
|
_test_flag, _incr_decr = (
|
|
316
|
-
(
|
|
318
|
+
(delta_test > _delta_star, -1)
|
|
317
319
|
if weighting == "cross-product-share"
|
|
318
|
-
else (
|
|
320
|
+
else (delta_test < _delta_star, 1)
|
|
319
321
|
)
|
|
320
322
|
|
|
321
323
|
if _test_flag:
|
|
322
|
-
|
|
324
|
+
s_2 += _incr_decr * _bdry_step_sz
|
|
323
325
|
else:
|
|
324
326
|
break
|
|
325
327
|
|
|
326
328
|
# Build-up boundary points
|
|
327
|
-
|
|
329
|
+
bdry_points.append((s_1, s_2))
|
|
328
330
|
|
|
329
331
|
# Build up area terms
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
332
|
+
s_2_oddsum += s_2 if s_2_oddval else 0
|
|
333
|
+
s_2_evnsum += s_2 if not s_2_oddval else 0
|
|
334
|
+
s_2_oddval = not s_2_oddval
|
|
333
335
|
|
|
334
336
|
# Hold share points
|
|
335
|
-
|
|
336
|
-
|
|
337
|
+
s_2_pre = s_2
|
|
338
|
+
s_1_pre = s_1
|
|
337
339
|
|
|
338
|
-
if
|
|
339
|
-
|
|
340
|
+
if s_2_oddval:
|
|
341
|
+
s_2_evnsum -= s_2_pre
|
|
340
342
|
else:
|
|
341
|
-
|
|
343
|
+
s_2_oddsum -= s_1_pre
|
|
342
344
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
+
s_intcpt = gbf._shrratio_boundary_intcpt(
|
|
346
|
+
s_1_pre,
|
|
345
347
|
_delta_star,
|
|
346
348
|
_r_val,
|
|
347
349
|
recapture_form=recapture_form,
|
|
348
350
|
agg_method=agg_method,
|
|
349
351
|
weighting=weighting,
|
|
350
352
|
)
|
|
353
|
+
print(s_1_pre, _delta_star, _r_val, s_intcpt)
|
|
354
|
+
print(type(s_intcpt))
|
|
351
355
|
|
|
352
356
|
if weighting == "own-share":
|
|
353
|
-
|
|
354
|
-
|
|
357
|
+
bdry_prtlarea = (
|
|
358
|
+
_bdry_step_sz * (4 * s_2_oddsum + 2 * s_2_evnsum + _s_mid + s_2_pre) / 3
|
|
355
359
|
)
|
|
356
360
|
# Area under boundary
|
|
357
|
-
|
|
358
|
-
mp.power(_s_mid, "2") + mp.power(
|
|
361
|
+
bdry_area_total = 2 * (s_1_pre + bdry_prtlarea) - (
|
|
362
|
+
mp.power(_s_mid, "2") + mp.power(s_1_pre, "2")
|
|
359
363
|
)
|
|
360
364
|
|
|
361
365
|
else:
|
|
362
|
-
|
|
363
|
-
|
|
366
|
+
print([type(_f) for _f in (_s_mid, s_2_oddsum, s_2_evnsum, s_intcpt)])
|
|
367
|
+
bdry_prtlarea = (
|
|
368
|
+
_bdry_step_sz * (4 * s_2_oddsum + 2 * s_2_evnsum + _s_mid + s_intcpt) / 3
|
|
364
369
|
)
|
|
365
370
|
# Area under boundary
|
|
366
|
-
|
|
371
|
+
bdry_area_total = 2 * bdry_prtlarea - mp.power(_s_mid, "2")
|
|
367
372
|
|
|
368
|
-
|
|
373
|
+
bdry_points.append((mpf("0.0"), s_intcpt))
|
|
369
374
|
# Points defining boundary to point-of-symmetry
|
|
370
|
-
return
|
|
371
|
-
np.vstack((
|
|
372
|
-
round(float(
|
|
375
|
+
return gbf.GuidelinesBoundary(
|
|
376
|
+
np.vstack((bdry_points[::-1], np.flip(bdry_points[1:], 1))),
|
|
377
|
+
round(float(bdry_area_total), dps),
|
|
373
378
|
)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def shrratio_boundary_xact_avg_mp( # noqa: PLR0914
|
|
382
|
+
_delta_star: float = 0.075,
|
|
383
|
+
_r_val: float = DEFAULT_REC_RATIO,
|
|
384
|
+
/,
|
|
385
|
+
*,
|
|
386
|
+
recapture_form: Literal["inside-out", "proportional"] = "inside-out",
|
|
387
|
+
dps: int = 5,
|
|
388
|
+
) -> gbf.GuidelinesBoundary:
|
|
389
|
+
"""
|
|
390
|
+
Share combinations along the simple average diversion-ratio boundary.
|
|
391
|
+
|
|
392
|
+
Notes
|
|
393
|
+
-----
|
|
394
|
+
An analytical expression for the exact average boundary is derived
|
|
395
|
+
and plotted from the y-intercept to the ray of symmetry as follows::
|
|
396
|
+
|
|
397
|
+
from sympy import latex, plot as symplot, solve, symbols
|
|
398
|
+
|
|
399
|
+
s_1, s_2 = symbols("s_1 s_2")
|
|
400
|
+
|
|
401
|
+
g_val, r_val, m_val = 0.06, 0.80, 0.30
|
|
402
|
+
d_hat = g_val / (r_val * m_val)
|
|
403
|
+
|
|
404
|
+
# recapture_form = "inside-out"
|
|
405
|
+
sag = solve(
|
|
406
|
+
(s_2 / (1 - s_1))
|
|
407
|
+
+ (s_1 / (1 - (r_val * s_2 + (1 - r_val) * s_1)))
|
|
408
|
+
- 2 * d_hat,
|
|
409
|
+
s_2
|
|
410
|
+
)[0]
|
|
411
|
+
symplot(
|
|
412
|
+
sag,
|
|
413
|
+
(s_1, 0., d_hat / (1 + d_hat)),
|
|
414
|
+
ylabel=s_2
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
# recapture_form = "proportional"
|
|
418
|
+
sag = solve((s_2/(1 - s_1)) + (s_1/(1 - s_2)) - 2 * d_hat, s_2)[0]
|
|
419
|
+
symplot(
|
|
420
|
+
sag,
|
|
421
|
+
(s_1, 0., d_hat / (1 + d_hat)),
|
|
422
|
+
ylabel=s_2
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
Parameters
|
|
426
|
+
----------
|
|
427
|
+
_delta_star
|
|
428
|
+
Share ratio (:math:`\\overline{d} / \\overline{r}`).
|
|
429
|
+
_r_val
|
|
430
|
+
Recapture ratio
|
|
431
|
+
recapture_form
|
|
432
|
+
Whether recapture-ratio is MNL-consistent ("inside-out") or has fixed
|
|
433
|
+
value for both merging firms ("proportional").
|
|
434
|
+
dps
|
|
435
|
+
Number of decimal places for rounding returned shares.
|
|
436
|
+
|
|
437
|
+
Returns
|
|
438
|
+
-------
|
|
439
|
+
Array of share-pairs, area under boundary, area under boundary.
|
|
440
|
+
|
|
441
|
+
"""
|
|
442
|
+
|
|
443
|
+
_delta_star = mpf(f"{_delta_star}")
|
|
444
|
+
_s_mid = _delta_star / (1 + _delta_star)
|
|
445
|
+
_bdry_step_sz = 10**-dps
|
|
446
|
+
_bdry_start = np.array([(_s_mid, _s_mid)])
|
|
447
|
+
_s_1 = np.array(mp.arange(_s_mid - _bdry_step_sz, 0, -_bdry_step_sz))
|
|
448
|
+
if recapture_form == "inside-out":
|
|
449
|
+
s_intcpt = mp.fdiv(
|
|
450
|
+
mp.fsub(
|
|
451
|
+
2 * _delta_star * _r_val + 1, mp.fabs(2 * _delta_star * _r_val - 1)
|
|
452
|
+
),
|
|
453
|
+
2 * mpf(f"{_r_val}"),
|
|
454
|
+
)
|
|
455
|
+
nr_t1 = 1 + 2 * _delta_star * _r_val * (1 - _s_1) - _s_1 * (1 - _r_val)
|
|
456
|
+
|
|
457
|
+
nr_sqrt_mdr = 4 * _delta_star * _r_val
|
|
458
|
+
nr_sqrt_mdr2 = nr_sqrt_mdr * _r_val
|
|
459
|
+
nr_sqrt_md2r2 = nr_sqrt_mdr2 * _delta_star
|
|
460
|
+
|
|
461
|
+
nr_sqrt_t1 = nr_sqrt_md2r2 * (_s_1**2 - 2 * _s_1 + 1)
|
|
462
|
+
nr_sqrt_t2 = nr_sqrt_mdr2 * _s_1 * (_s_1 - 1)
|
|
463
|
+
nr_sqrt_t3 = nr_sqrt_mdr * (2 * _s_1 - _s_1**2 - 1)
|
|
464
|
+
nr_sqrt_t4 = (_s_1**2) * (_r_val**2 - 6 * _r_val + 1)
|
|
465
|
+
nr_sqrt_t5 = _s_1 * (6 * _r_val - 2) + 1
|
|
466
|
+
|
|
467
|
+
nr_t2_mdr = nr_sqrt_t1 + nr_sqrt_t2 + nr_sqrt_t3 + nr_sqrt_t4 + nr_sqrt_t5
|
|
468
|
+
|
|
469
|
+
# Alternative grouping of terms in np.sqrt
|
|
470
|
+
nr_sqrt_s1sq = (_s_1**2) * (
|
|
471
|
+
nr_sqrt_md2r2 + nr_sqrt_mdr2 - nr_sqrt_mdr + _r_val**2 - 6 * _r_val + 1
|
|
472
|
+
)
|
|
473
|
+
nr_sqrt_s1 = _s_1 * (
|
|
474
|
+
-2 * nr_sqrt_md2r2 - nr_sqrt_mdr2 + 2 * nr_sqrt_mdr + 6 * _r_val - 2
|
|
475
|
+
)
|
|
476
|
+
nr_sqrt_nos1 = nr_sqrt_md2r2 - nr_sqrt_mdr + 1
|
|
477
|
+
|
|
478
|
+
nr_t2_s1 = nr_sqrt_s1sq + nr_sqrt_s1 + nr_sqrt_nos1
|
|
479
|
+
|
|
480
|
+
if not np.isclose(
|
|
481
|
+
np.einsum("i->", nr_t2_mdr.astype(float)),
|
|
482
|
+
np.einsum("i->", nr_t2_s1.astype(float)),
|
|
483
|
+
rtol=0,
|
|
484
|
+
atol=0.5 * dps,
|
|
485
|
+
):
|
|
486
|
+
raise RuntimeError(
|
|
487
|
+
"Calculation of sq. root term in exact average GUPPI"
|
|
488
|
+
f"with recapture spec, {f'"{recapture_form}"'} is incorrect."
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
s_2 = (nr_t1 - np.sqrt(nr_t2_s1)) / (2 * _r_val)
|
|
492
|
+
|
|
493
|
+
else:
|
|
494
|
+
s_intcpt = mp.fsub(_delta_star + 1 / 2, mp.fabs(_delta_star - 1 / 2))
|
|
495
|
+
s_2 = (
|
|
496
|
+
(1 / 2)
|
|
497
|
+
+ _delta_star
|
|
498
|
+
- _delta_star * _s_1
|
|
499
|
+
- np.sqrt(
|
|
500
|
+
((_delta_star**2) - 1) * (_s_1**2)
|
|
501
|
+
+ (-2 * (_delta_star**2) + _delta_star + 1) * _s_1
|
|
502
|
+
+ (_delta_star**2)
|
|
503
|
+
- _delta_star
|
|
504
|
+
+ (1 / 4)
|
|
505
|
+
)
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
bdry_inner = np.column_stack((_s_1, s_2))
|
|
509
|
+
bdry_end = np.array([(mpf("0.0"), s_intcpt)])
|
|
510
|
+
|
|
511
|
+
bdry = np.vstack((
|
|
512
|
+
bdry_end,
|
|
513
|
+
bdry_inner[::-1],
|
|
514
|
+
_bdry_start,
|
|
515
|
+
np.flip(bdry_inner, 1),
|
|
516
|
+
np.flip(bdry_end, 1),
|
|
517
|
+
))
|
|
518
|
+
s_2 = np.concatenate((np.array([_s_mid]), s_2))
|
|
519
|
+
|
|
520
|
+
bdry_ends = [0, -1]
|
|
521
|
+
bdry_odds = np.array(range(1, len(s_2), 2), int)
|
|
522
|
+
bdry_evns = np.array(range(2, len(s_2), 2), int)
|
|
523
|
+
|
|
524
|
+
# Double the area under the half-curve, and subtract the double-counted bit.
|
|
525
|
+
bdry_area_simpson = 2 * _bdry_step_sz * (
|
|
526
|
+
(4 / 3) * np.sum(s_2.take(bdry_odds))
|
|
527
|
+
+ (2 / 3) * np.sum(s_2.take(bdry_evns))
|
|
528
|
+
+ (1 / 3) * np.sum(s_2.take(bdry_ends))
|
|
529
|
+
) - mp.power(_s_mid, 2)
|
|
530
|
+
|
|
531
|
+
return gbf.GuidelinesBoundary(bdry, float(mp.nstr(bdry_area_simpson, dps)))
|