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