Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature constant detuning term #3

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 34 additions & 18 deletions pywit/landau_damping.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import numpy as np
from scipy.special import exp1
from scipy.optimize import newton
from scipy.optimize import bisect

from typing import Sequence, Tuple

Expand Down Expand Up @@ -59,19 +59,24 @@ def dispersion_integral_2d(tune_shift: np.ndarray, b_direct: float, b_cross: flo
return -i


def find_detuning_coeffs_threshold(tune_shift: complex, q_s: float, b_direct_ref: float, b_cross_ref: float,
def find_detuning_coeffs_threshold(tune_shift: complex, q_s: float, reference_b_direct: float, reference_b_cross: float,
added_b_direct: float = 0, added_b_cross: float = 0,
fraction_of_qs_allowed_on_positive_side: float = 0.05,
distribution: str = 'gaussian', tolerance=1e-10):
"""
Compute the detuning coefficients (multiplied by sigma) corresponding to stability diagram threshold for a complex
tune shift.
It keeps fixed the ratio between b_direct_ref and b_cross_ref.
It keeps fixed the ratio between reference_b_direct and reference_b_cross.
:param tune_shift: the tune shift for which the octupole threshold is computed
:param q_s: the synchrotron tune
:param b_direct_ref: the direct detuning coefficient multiplied by sigma (i.e. $\alpha_x \sigma_x$ if working in
:param reference_b_direct: the direct detuning coefficient multiplied by sigma (i.e. $\alpha_x \sigma_x$ if working in
the x plane or $\alpha_y \sigma_y$ if working in the y plane)
:param b_cross_ref: the cross detuning coefficient multiplied by sigma (i.e. $\alpha_{xy} \sigma_y$ if working in
:param reference_b_cross: the cross detuning coefficient multiplied by sigma (i.e. $\alpha_{xy} \sigma_y$ if working in
the x plane or $\alpha_{yx} \sigma_x$ if working in the y plane)
:param added_b_direct: an additional contribution to the direct detuning coefficient which is kept fixed in the
procedure to find the stability threshold
:param added_b_cross: an additional contribution to the cross detuning coefficient which is kept fixed in the
procedure to find the stability threshold
:param distribution: the transverse distribution of the beam. It can be 'gaussian' or 'parabolic'
:param fraction_of_qs_allowed_on_positive_side: to determine azimuthal mode number l_mode (around which is drawn the
stability diagram), one can consider positive tune shift up to this fraction of q_s (default=5%)
Expand All @@ -88,23 +93,28 @@ def find_detuning_coeffs_threshold(tune_shift: complex, q_s: float, b_direct_ref
# take away the shift from azimuthal mode number
tune_shift -= q_s * l_mode

b_ratio = b_cross_ref/b_direct_ref
b_ratio = reference_b_cross / reference_b_direct
if tune_shift.imag < 0.:

# function to solve (distance in imag. part w.r.t stab. diagram, as a function of oct. current)
def f(b_direct):
b_direct_i = b_direct
b_cross_i = b_ratio * b_direct
b_direct_i = b_direct + added_b_direct
b_cross_i = b_ratio * b_direct + added_b_cross
stab = [-1. / dispersion_integral_2d(t_s, b_direct_i, b_cross_i, distribution=distribution) for e in (-1, 1)
for t_s in b_direct_i * e * 10. ** np.arange(-3, 2, 0.01)[::e]]
# note: one has to reverse the table to get the interpolation right, for negative polarity (np.interp always
# wants monotonically increasing abscissae)
return tune_shift.imag - np.interp(tune_shift.real, np.real(stab)[::int(np.sign(b_direct_ref))],
np.imag(stab)[::int(np.sign(b_direct_ref))])
return tune_shift.imag - np.interp(tune_shift.real, np.real(stab)[::int(np.sign(b_direct_i))],
np.imag(stab)[::int(np.sign(b_direct_i))])

# Newton root finding
try:
b_direct_new = newton(f, b_direct_ref, tol=tolerance)
# we use 1e-15 as a bound instead of 0 because 0 would cause a divsion by 0 in dispersion_integral_2d
if reference_b_direct > 0:
b_direct_new = bisect(f, 1e-15, 100 * reference_b_direct)
else:
b_direct_new = bisect(f, 100 * reference_b_direct, -1e-15)

except RuntimeError:
b_direct_new = np.nan
else:
Expand All @@ -123,19 +133,25 @@ def abs_first_item_or_nan(tup: Tuple):
return np.nan


def find_detuning_coeffs_threshold_many_tune_shifts(tune_shifts: Sequence[complex], q_s: float, b_direct_ref: float,
b_cross_ref: float, distribution: str = 'gaussian',
def find_detuning_coeffs_threshold_many_tune_shifts(tune_shifts: Sequence[complex], q_s: float,
reference_b_direct: float, reference_b_cross: float,
added_b_direct: float = 0, added_b_cross: float = 0,
distribution: str = 'gaussian',
fraction_of_qs_allowed_on_positive_side: float = 0.05,
tolerance=1e-10):
"""
Compute the detuning coefficients corresponding to the most stringent stability diagram threshold for a sequence of
complex tune shifts. It keeps fixed the ratio between b_direct_ref and b_cross_ref.
complex tune shifts. It keeps fixed the ratio between reference_b_direct and reference_b_cross.
:param tune_shifts: the sequence of complex tune shifts
:param q_s: the synchrotron tune
:param b_direct_ref: the direct detuning coefficient multiplied by sigma (i.e. $\alpha_x \sigma_x$ if working in
:param reference_b_direct: the direct detuning coefficient multiplied by sigma (i.e. $\alpha_x \sigma_x$ if working in
the x plane or $\alpha_y \sigma_y$ if working in the y plane)
:param b_cross_ref: the cross detuning coefficient multiplied by sigma (i.e. $\alpha_{xy} \sigma_y$ if working in
:param reference_b_cross: the cross detuning coefficient multiplied by sigma (i.e. $\alpha_{xy} \sigma_y$ if working in
the x plane or $\alpha_{yx} \sigma_x$ if working in the y plane)
:param added_b_direct: an additional contribution to the direct detuning coefficient which is kept fixed in the
procedure to find the stability threshold
:param added_b_cross: an additional contribution to the cross detuning coefficient which is kept fixed in the
procedure to find the stability threshold
:param distribution: the transverse distribution of the beam. It can be 'gaussian' or 'parabolic'
:param fraction_of_qs_allowed_on_positive_side: to determine azimuthal mode number l_mode (around which is drawn the
stability diagram), one can consider positive tuneshift up to this fraction of q_s (default=5%)
Expand All @@ -147,8 +163,8 @@ def find_detuning_coeffs_threshold_many_tune_shifts(tune_shifts: Sequence[comple
"""
# find max octupole current required from a list of modes, given their tuneshifts
b_coefficients = np.array([find_detuning_coeffs_threshold(
tune_shift=tune_shift, q_s=q_s, b_direct_ref=b_direct_ref,
b_cross_ref=b_cross_ref, distribution=distribution,
tune_shift=tune_shift, q_s=q_s, reference_b_direct=reference_b_direct,
reference_b_cross=reference_b_cross, added_b_direct=added_b_direct, added_b_cross=added_b_cross, distribution=distribution,
fraction_of_qs_allowed_on_positive_side=fraction_of_qs_allowed_on_positive_side,
tolerance=tolerance) for tune_shift in tune_shifts if tune_shift is not np.nan])

Expand Down
104 changes: 42 additions & 62 deletions test/test_landau_damping.py
Original file line number Diff line number Diff line change
@@ -1,86 +1,66 @@
import pytest
from pytest import mark
import numpy as np

from pywit.landau_damping import dispersion_integral_2d, find_detuning_coeffs_threshold
from pywit.landau_damping import find_detuning_coeffs_threshold_many_tune_shifts


def test_dispersion_integral_2d():

distribution = 'gaussian'
tune_shift = -7.3622423693e-05-3.0188372754e-06j
@mark.parametrize('distribution, expected_value',
[['gaussian', 7153.859519171599 - 2722.966574677259j],
['parabolic', 6649.001778623455 - 3168.4257879737443j],
])
def test_dispersion_integral_2d(distribution: str, expected_value: complex):
# reference values obtained with DELPHI
tune_shift = -7.3622423693e-05 - 3.0188372754e-06j
b_direct = 7.888357197059519e-05
b_cross = -5.632222163778799e-05

# reference value obtained with DELPHI
expected_value = 7153.859519171599-2722.966574677259j

assert np.isclose(np.real(expected_value), np.real(dispersion_integral_2d(tune_shift=tune_shift,
b_direct=b_direct,
b_cross=b_cross,
distribution=distribution)))
assert np.isclose(np.imag(expected_value), np.imag(dispersion_integral_2d(tune_shift=tune_shift,
b_direct=b_direct,
b_cross=b_cross,
distribution=distribution)))

distribution = 'parabolic'

# reference value obtained with DELPHI
expected_value = 6649.001778623455-3168.4257879737443j
test_value = dispersion_integral_2d(tune_shift=tune_shift, b_direct=b_direct, b_cross=b_cross,
distribution=distribution)

assert np.isclose(np.real(expected_value), np.real(dispersion_integral_2d(tune_shift=tune_shift,
b_direct=b_direct,
b_cross=b_cross,
distribution=distribution)))
assert np.isclose(np.imag(expected_value), np.imag(dispersion_integral_2d(tune_shift=tune_shift,
b_direct=b_direct,
b_cross=b_cross,
distribution=distribution)))
assert np.isclose(np.real(expected_value), np.real(test_value))
assert np.isclose(np.imag(expected_value), np.imag(test_value))


def test_find_detuning_coeffs_threshold():
@mark.parametrize('b_direct, b_cross, added_b_direct, added_b_cross',
[[1.980315192200037e-05, -1.4139287608495406e-05, 0, 0],
[-1.2718161244917965e-05, 9.08066253298482e-06, 0, 0],
[1.980315192200037e-05, -1.4139287608495406e-05, 1.980315192200037e-05 / 3,
-1.4139287608495406e-05 / 3],
[-1.2718161244917965e-05, 9.08066253298482e-06, -1.2718161244917965e-05 / 3,
9.08066253298482e-06 / 3],
])
def test_find_detuning_coeffs_threshold(b_direct: float, b_cross: float, added_b_direct: float, added_b_cross: float):
# reference values obtained with the old impedance wake model https://gitlab.cern.ch/IRIS/HLLHC_IW_model
# test positive octupole polarity
tune_shift = -7.3622423693e-05 - 3.0188372754e-06j
q_s = 2e-3
b_direct = 1.980315192200037e-05
b_cross = -1.4139287608495406e-05
assert np.isclose(b_direct, find_detuning_coeffs_threshold(tune_shift=tune_shift, q_s=q_s, b_direct_ref=b_direct,
b_cross_ref=b_cross)[0])
assert np.isclose(b_cross, find_detuning_coeffs_threshold(tune_shift=tune_shift, q_s=q_s, b_direct_ref=b_direct,
b_cross_ref=b_cross)[1])
# test negative octupole polarity
b_direct = -1.2718161244917965e-05
b_cross = 9.08066253298482e-06

assert np.isclose(b_direct, find_detuning_coeffs_threshold(tune_shift=tune_shift, q_s=q_s, b_direct_ref=b_direct,
b_cross_ref=b_cross)[0])
assert np.isclose(b_cross, find_detuning_coeffs_threshold(tune_shift=tune_shift, q_s=q_s, b_direct_ref=b_direct,
b_cross_ref=b_cross)[1])
b_direct_test, b_cross_test = find_detuning_coeffs_threshold(tune_shift=tune_shift, q_s=q_s,
reference_b_direct=b_direct,
reference_b_cross=b_cross,
added_b_direct=added_b_direct,
added_b_cross=added_b_cross)

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

def test_find_detuning_coeffs_threshold_many_tune_shifts():

@mark.parametrize('b_direct, b_cross',
[[3.960630384598084e-05, -2.8278575218404585e-05],
[-2.5436322491034757e-05, 1.816132506682559e-05],
])
def test_find_detuning_coeffs_threshold_many_tune_shifts(b_direct: float, b_cross: float):
# reference values obtained with the old impedance wake model https://gitlab.cern.ch/IRIS/HLLHC_IW_model
tune_shift = -7.3622423693e-05 - 3.0188372754e-06j
q_s = 2e-3
tune_shifts = [np.nan, tune_shift, 2*tune_shift]
tune_shifts = [np.nan, tune_shift, 2 * tune_shift]

b_direct_test, b_cross_test = find_detuning_coeffs_threshold_many_tune_shifts(tune_shifts=tune_shifts, q_s=q_s,
reference_b_direct=b_direct,
reference_b_cross=b_cross)
assert np.isclose(b_direct, b_direct_test)
assert np.isclose(b_cross, b_cross_test)


# test positive octupole polarity
b_direct = 3.960630384598084e-05
b_cross = -2.8278575218404585e-05
assert np.isclose(b_direct, find_detuning_coeffs_threshold_many_tune_shifts(tune_shifts=tune_shifts, q_s=q_s,
b_direct_ref=b_direct,
b_cross_ref=b_cross)[0])
assert np.isclose(b_cross, find_detuning_coeffs_threshold_many_tune_shifts(tune_shifts=tune_shifts, q_s=q_s,
b_direct_ref=b_direct,
b_cross_ref=b_cross)[1])
# test negative octupole polarity
b_direct = -2.5436322491034757e-05
b_cross = 1.816132506682559e-05
assert np.isclose(b_direct, find_detuning_coeffs_threshold_many_tune_shifts(tune_shifts=tune_shifts, q_s=q_s,
b_direct_ref=b_direct,
b_cross_ref=b_cross)[0])
assert np.isclose(b_cross, find_detuning_coeffs_threshold_many_tune_shifts(tune_shifts=tune_shifts, q_s=q_s,
b_direct_ref=b_direct,
b_cross_ref=b_cross)[1])