Skip to content

Commit c86db7c

Browse files
committed
Allowing both bisect and Newton root finding algorithm in threshold finding function (for Landau damping)
1 parent 9db91ed commit c86db7c

File tree

2 files changed

+40
-18
lines changed

2 files changed

+40
-18
lines changed

pywit/landau_damping.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import numpy as np
22
from scipy.special import exp1
3-
from scipy.optimize import bisect
3+
from scipy.optimize import bisect, newton
44

55
from typing import Sequence, Tuple
66

@@ -62,7 +62,7 @@ def dispersion_integral_2d(tune_shift: np.ndarray, b_direct: float, b_cross: flo
6262
def find_detuning_coeffs_threshold(tune_shift: complex, q_s: float, reference_b_direct: float, reference_b_cross: float,
6363
added_b_direct: float = 0, added_b_cross: float = 0,
6464
fraction_of_qs_allowed_on_positive_side: float = 0.05,
65-
distribution: str = 'gaussian', tolerance=1e-10):
65+
distribution: str = 'gaussian', algorithm: str = 'newton', tolerance=1e-10):
6666
"""
6767
Compute the detuning coefficients (multiplied by sigma) corresponding to stability diagram threshold for a complex
6868
tune shift.
@@ -80,10 +80,11 @@ def find_detuning_coeffs_threshold(tune_shift: complex, q_s: float, reference_b_
8080
:param distribution: the transverse distribution of the beam. It can be 'gaussian' or 'parabolic'
8181
:param fraction_of_qs_allowed_on_positive_side: to determine azimuthal mode number l_mode (around which is drawn the
8282
stability diagram), one can consider positive tune shift up to this fraction of q_s (default=5%)
83-
:param tolerance: tolerance on difference w.r.t stability diagram, for Newton's root finding
83+
:param algorithm: root-solving algorithm ('bisect' or 'newton')
84+
:param tolerance: tolerance on difference w.r.t stability diagram, for root finding
8485
and for the final check that the roots are actually proper roots.
8586
:return: the detuning coefficients corresponding to the stability diagram threshold if the corresponding mode is
86-
unstable, 0 if the corresponding mode is stable or np.nan if the threshold cannot be found (failure of Newton's
87+
unstable, 0 if the corresponding mode is stable or np.nan if the threshold cannot be found (failure of root-solving
8788
algorithm).
8889
"""
8990
# evaluate azimuthal mode number
@@ -107,13 +108,20 @@ def f(b_direct):
107108
return tune_shift.imag - np.interp(tune_shift.real, np.real(stab)[::int(np.sign(b_direct_i))],
108109
np.imag(stab)[::int(np.sign(b_direct_i))])
109110

110-
# Newton root finding
111+
# root finding
111112
try:
112-
# we use 1e-15 as a bound instead of 0 because 0 would cause a divsion by 0 in dispersion_integral_2d
113-
if reference_b_direct > 0:
114-
b_direct_new = bisect(f, 1e-15, 100 * reference_b_direct)
113+
if algorithm=='bisect':
114+
# we use 1e-15 as a bound instead of 0 because 0 would cause a divsion by 0 in dispersion_integral_2d
115+
if reference_b_direct > 0:
116+
b_direct_new = bisect(f, 1e-15, 100 * reference_b_direct)
117+
else:
118+
b_direct_new = bisect(f, 100 * reference_b_direct, -1e-15)
119+
120+
elif algorithm=='newton':
121+
b_direct_new = newton(f, reference_b_direct, tol=tolerance)
122+
115123
else:
116-
b_direct_new = bisect(f, 100 * reference_b_direct, -1e-15)
124+
raise ValueError("algorithm must be either 'newton' or 'bisect'")
117125

118126
except RuntimeError:
119127
b_direct_new = np.nan
@@ -138,7 +146,7 @@ def find_detuning_coeffs_threshold_many_tune_shifts(tune_shifts: Sequence[comple
138146
added_b_direct: float = 0, added_b_cross: float = 0,
139147
distribution: str = 'gaussian',
140148
fraction_of_qs_allowed_on_positive_side: float = 0.05,
141-
tolerance=1e-10):
149+
algorithm: str = 'newton', tolerance = 1e-10):
142150
"""
143151
Compute the detuning coefficients corresponding to the most stringent stability diagram threshold for a sequence of
144152
complex tune shifts. It keeps fixed the ratio between reference_b_direct and reference_b_cross.
@@ -155,17 +163,18 @@ def find_detuning_coeffs_threshold_many_tune_shifts(tune_shifts: Sequence[comple
155163
:param distribution: the transverse distribution of the beam. It can be 'gaussian' or 'parabolic'
156164
:param fraction_of_qs_allowed_on_positive_side: to determine azimuthal mode number l_mode (around which is drawn the
157165
stability diagram), one can consider positive tuneshift up to this fraction of q_s (default=5%)
158-
:param tolerance: tolerance on difference w.r.t stability diagram, for Newton's root finding
166+
:param algorithm: root-solving algorithm ('bisect' or 'newton')
167+
:param tolerance: tolerance on difference w.r.t stability diagram, for root finding
159168
and for the final check that the roots are actually proper roots.
160169
:return: the detuning coefficients corresponding to the most stringent stability diagram threshold for all the
161170
given tune shifts if the corresponding mode is unstable, 0 if all modes are stable or np.nan if the
162-
no threshold can be found (failure of Newton's algorithm).
171+
no threshold can be found (failure of root-solving algorithm).
163172
"""
164173
# find max octupole current required from a list of modes, given their tuneshifts
165174
b_coefficients = np.array([find_detuning_coeffs_threshold(
166175
tune_shift=tune_shift, q_s=q_s, reference_b_direct=reference_b_direct,
167176
reference_b_cross=reference_b_cross, added_b_direct=added_b_direct, added_b_cross=added_b_cross, distribution=distribution,
168177
fraction_of_qs_allowed_on_positive_side=fraction_of_qs_allowed_on_positive_side,
169-
tolerance=tolerance) for tune_shift in tune_shifts if tune_shift is not np.nan])
178+
algorithm=algorithm, tolerance=tolerance) for tune_shift in tune_shifts if tune_shift is not np.nan])
170179

171180
return max(b_coefficients, key=abs_first_item_or_nan)

test/test_landau_damping.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import pytest
2-
from pytest import mark
2+
from pytest import mark, raises
33
import numpy as np
44

55
from pywit.landau_damping import dispersion_integral_2d, find_detuning_coeffs_threshold
@@ -23,6 +23,15 @@ def test_dispersion_integral_2d(distribution: str, expected_value: complex):
2323
assert np.isclose(np.imag(expected_value), np.imag(test_value))
2424

2525

26+
def test_wrong_algo_in_find_detuning_coeffs_threshold():
27+
with raises(ValueError, match="algorithm must be either 'newton' or 'bisect'"):
28+
_ = find_detuning_coeffs_threshold(tune_shift=1e-5-1j*1e-5, q_s=2e-3,
29+
reference_b_direct=1e-15,
30+
reference_b_cross=1e-15,
31+
algorithm='newto')
32+
33+
34+
@mark.parametrize('algorithm',['newton','bisect'])
2635
@mark.parametrize('b_direct, b_cross, added_b_direct, added_b_cross',
2736
[[1.980315192200037e-05, -1.4139287608495406e-05, 0, 0],
2837
[-1.2718161244917965e-05, 9.08066253298482e-06, 0, 0],
@@ -31,7 +40,8 @@ def test_dispersion_integral_2d(distribution: str, expected_value: complex):
3140
[-1.2718161244917965e-05, 9.08066253298482e-06, -1.2718161244917965e-05 / 3,
3241
9.08066253298482e-06 / 3],
3342
])
34-
def test_find_detuning_coeffs_threshold(b_direct: float, b_cross: float, added_b_direct: float, added_b_cross: float):
43+
def test_find_detuning_coeffs_threshold(algorithm: str, b_direct: float,
44+
b_cross: float, added_b_direct: float, added_b_cross: float):
3545
# reference values obtained with the old impedance wake model https://gitlab.cern.ch/IRIS/HLLHC_IW_model
3646
# test positive octupole polarity
3747
tune_shift = -7.3622423693e-05 - 3.0188372754e-06j
@@ -41,25 +51,28 @@ def test_find_detuning_coeffs_threshold(b_direct: float, b_cross: float, added_b
4151
reference_b_direct=b_direct,
4252
reference_b_cross=b_cross,
4353
added_b_direct=added_b_direct,
44-
added_b_cross=added_b_cross)
54+
added_b_cross=added_b_cross,
55+
algorithm=algorithm)
4556

4657
assert np.isclose(b_direct - added_b_direct, b_direct_test)
4758
assert np.isclose(b_cross - added_b_cross, b_cross_test)
4859

4960

61+
@mark.parametrize('algorithm',['newton','bisect'])
5062
@mark.parametrize('b_direct, b_cross',
5163
[[3.960630384598084e-05, -2.8278575218404585e-05],
5264
[-2.5436322491034757e-05, 1.816132506682559e-05],
5365
])
54-
def test_find_detuning_coeffs_threshold_many_tune_shifts(b_direct: float, b_cross: float):
66+
def test_find_detuning_coeffs_threshold_many_tune_shifts(algorithm: str, b_direct: float, b_cross: float):
5567
# reference values obtained with the old impedance wake model https://gitlab.cern.ch/IRIS/HLLHC_IW_model
5668
tune_shift = -7.3622423693e-05 - 3.0188372754e-06j
5769
q_s = 2e-3
5870
tune_shifts = [np.nan, tune_shift, 2 * tune_shift]
5971

6072
b_direct_test, b_cross_test = find_detuning_coeffs_threshold_many_tune_shifts(tune_shifts=tune_shifts, q_s=q_s,
6173
reference_b_direct=b_direct,
62-
reference_b_cross=b_cross)
74+
reference_b_cross=b_cross,
75+
algorithm=algorithm)
6376
assert np.isclose(b_direct, b_direct_test)
6477
assert np.isclose(b_cross, b_cross_test)
6578

0 commit comments

Comments
 (0)