From 9d60d14ce173d9cc2df9eed40badb6536d1863bc Mon Sep 17 00:00:00 2001 From: SebastienJoly Date: Fri, 3 Jan 2025 12:09:04 +0100 Subject: [PATCH 001/137] Add function to compute the equilibrium emittance in presence of SR and IBS. --- xfields/ibs/__init__.py | 2 + xfields/ibs/_equilibrium_emittance.py | 349 ++++++++++++++++++++++++++ 2 files changed, 351 insertions(+) create mode 100644 xfields/ibs/_equilibrium_emittance.py diff --git a/xfields/ibs/__init__.py b/xfields/ibs/__init__.py index 427cc7fe..277b190e 100644 --- a/xfields/ibs/__init__.py +++ b/xfields/ibs/__init__.py @@ -6,11 +6,13 @@ from ._analytical import BjorkenMtingwaIBS, IBSGrowthRates, NagaitsevIBS from ._api import configure_intrabeam_scattering, get_intrabeam_scattering_growth_rates from ._kicks import IBSAnalyticalKick, IBSKineticKick +from ._equilibrium_emittance import ibs_rates, compute_emittance_evolution __all__ = [ "BjorkenMtingwaIBS", "configure_intrabeam_scattering", "get_intrabeam_scattering_growth_rates", + "compute_emittance_evolution", "IBSGrowthRates", "IBSAnalyticalKick", "IBSKineticKick", diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py new file mode 100644 index 00000000..b869d939 --- /dev/null +++ b/xfields/ibs/_equilibrium_emittance.py @@ -0,0 +1,349 @@ +# copyright ################################# # +# This file is part of the Xfields Package. # +# Copyright (c) CERN, 2021. # +# ########################################### # + +import sys +import warnings +#from logging import getLogger +from typing import Tuple, Literal + +import numpy as np +import xtrack as xt + +#LOGGER = getLogger(__name__) + +def ibs_rates( + twiss: xt.TwissTable, + bunch_intensity: float, + input_emittances: Tuple, + emittance_coupling_factor: float = 0, + emittance_constraint: Literal["Coupling", "Excitation"] = "Coupling", + formalism: Literal["Nagaitsev", "Bjorken-Mtingwa", "B&M"] = "Nagaitsev", + natural_emittances: Tuple = None, + longitudinal_emittance_ratio: float = None, + damping_rates: Tuple = None, + **kwargs, +): + """ + Compute the IBS growth rates and emittance evolution rates. + + Parameters + ---------- + twiss : object + Twiss object of the ring. + bunch_intensity : float + Bunch intensity [particles per bunch]. + input_emittances : tuple of floats + Tuple containing the equilibrium transverse emittances (horizontal and + vertical). + emittance_coupling_factor : float, optional + Emittance coupling factor, defined as the ratio of vertical to + horizontal emittance. + Default is 0. + emittance_constraint : str, optional + Can enforces constraints on the transverse emittance based on the + emittance coupling factor. + "Coupling" corresponds to the case where the + vertical emittance is the result of linear coupling. + "Excitation" corresponds to the case where the vertical emittance is + the result of an excitation (e.g. from a feedback system). + Default is "Coupling". + formalism : str, optional + Which formalism to use for the computation. Can be ``Nagaitsev`` + or ``Bjorken-Mtingwa`` (also accepts ``B&M``), case-insensitively. + natural_emittances : tuple of floats, optional + Natural emittances (horizontal, vertical, longitudinal). + If None, they are taken from the `twiss` object. Default is None. + longitudinal_emittance_ratio : float, optional + Ratio of the RMS bunch length to the RMS momentum spread. Used if + an user specified input_sigma_zeta or input_sigma_delta are given. + It allows accounting for a perturbed longitudinal distrubtion due to + bunch lengthening or a microwave instability. Default is None. + damping_rates : tuple of floats, optional + SR damping rates (horizontal, vertical, longitudinal). + If None, they are taken from the `twiss` object. Default is None. + + Returns + ------- + emittance_rates : tuple of floats + Time variations of the emittances (horizontal, vertical, longitudinal). + ibs_growth_rates : tuple of floats + IBS growth rates (horizontal, vertical, longitudinal). + """ + input_emittance_x, input_emittance_y, input_emittance_z = input_emittances + + if natural_emittances is not None: + natural_emittance_x, natural_emittance_y, natural_emittance_z = ( + natural_emittances + ) + else: + natural_emittance_x, natural_emittance_y, natural_emittance_z = ( + twiss.eq_gemitt_x, + twiss.eq_gemitt_y, + twiss.eq_gemitt_zeta, + ) + # If emittance_coupling_factor is non-zero, then natural emittance is + # modified accordingly. + if ( + emittance_coupling_factor != 0 + and emittance_constraint.lower() == "coupling" + ): + # The convention used enforces a total transverse emittance + # conservation for any emittance_coupling_factor. + natural_emittance_x *= 1 / (1 + emittance_coupling_factor) + natural_emittance_y = ( + natural_emittance_x * emittance_coupling_factor + / (1 + emittance_coupling_factor) + ) + if ( + emittance_coupling_factor != 0 + and emittance_constraint.lower() == "excitation" + ): + # The convention used only enforce a constraint on the vertical + # emittance. + natural_emittance_y = ( + natural_emittance_x * emittance_coupling_factor + ) + + if damping_rates is None: + damping_rate_x, damping_rate_y, damping_rate_z = ( + twiss.damping_constants_s + ) + + sigma_zeta = (input_emittance_z * longitudinal_emittance_ratio) ** 0.5 + sigma_delta = (input_emittance_z / longitudinal_emittance_ratio) ** 0.5 + + assert input_emittance_x > 0., ("'input_emittance_x' should be larger than" + " zero, try providing 'initial_emittances'") + assert input_emittance_y > 0., ("'input_emittance_y' should be larger than" + " zero, try providing 'initial_emittances'") + ibs_growth_rates = twiss.get_ibs_growth_rates( + formalism=formalism, + total_beam_intensity=bunch_intensity, + gemitt_x=input_emittance_x, + gemitt_y=input_emittance_y, + sigma_delta=sigma_delta, + bunch_length=sigma_zeta, # 1 sigma_{zeta,RMS} bunch length + bunched=True, + ) + + depsilon_x_dt = ( + -2 * damping_rate_x * (input_emittance_x - natural_emittance_x) + + ibs_growth_rates.Tx * input_emittance_x + ) + depsilon_y_dt = ( + -2 * damping_rate_y * (input_emittance_y - natural_emittance_y) + + ibs_growth_rates.Ty * input_emittance_y + ) + depsilon_z_dt = ( + -2 * damping_rate_z * (input_emittance_z - natural_emittance_z) + + ibs_growth_rates.Tz * input_emittance_z + ) + return ( + (depsilon_x_dt, depsilon_y_dt, depsilon_z_dt), + (ibs_growth_rates.Tx, ibs_growth_rates.Ty, ibs_growth_rates.Tz), + ) + +def compute_emittance_evolution( + twiss: xt.TwissTable, + bunch_intensity: float, + initial_emittances: Tuple = None, + emittance_coupling_factor: float = 0, + emittance_constraint: Literal["Coupling", "Excitation"] = "Coupling", + input_sigma_zeta: float = None, + input_sigma_delta: float = None, + natural_emittances: Tuple = None, + rtol: float = 1e-6, + **kwargs, +): + """ + Compute the evolution of beam emittances due to IBS until convergence. + By default, the function assumes the emittances from the Twiss object. + They can also be specified as well as different natural emittances. + The emittance evolution can be constrained to follow two scenarios: + - A vertical emittance originating from linear coupling. + - A vertical emittance originating from an excitation. + The impact from the longitudinal impedance (e.g. bunch lengthening or + microwave instability) can be accounted for by specifying the RMS bunch + length and momentum spread. + + Parameters + ---------- + twiss : object + Twiss object of the ring. + bunch_intensity : float + Bunch intensity [particles per bunch]. + initial_emittances : tuple of floats, optional + Initial values for the horizontal, vertical, and longitudinal + emittances. If None, the equilibrium emittances from the Twiss object + are used. Default is None. + emittance_coupling_factor : float, optional + Emittance coupling factor, defined as the ratio of vertical to + horizontal emittance. Default is 0. + emittance_constraint : str, optional + Can enforces constraints on the transverse emittance based on the + emittance coupling factor. + "Coupling" corresponds to the case where the + vertical emittance is the result of linear coupling. + "Excitation" corresponds to the case where the vertical emittance is + the result of an excitation (e.g. from a feedback system). + Default is "Coupling". + input_sigma_zeta : float + Used specified RMS momentum spread overwriting the natural one from + the `twiss` object. Default is None. + input_sigma_delta : float + Used specified RMS momentum spread overwriting the natural one from + the `twiss` object. Default is None. + natural_emittances : tuple of floats, optional + Natural emittances (horizontal, vertical, longitudinal). + If None, they are taken from the `twiss` object. Default is None. + rtol : float, optional + Relative tolerance for equilibrium emittance convergence. + Default is 1e-6. + + Returns + ------- + time : numpy.ndarray + Computed time steps [s]. + emittances_x : list of float + Horizontal emittance values computed over all the time steps. + emittances_y : list of float + Vertical emittance values computed over all the time steps. + emittances_z : list of float + Longitudinal emittance values computed over all the time steps. + T_x : list of float + Horizontal IBS growth rates computed over all the time steps. + T_y : list of float + Vertical IBS growth rates computed over all the time steps. + T_z : list of float + Longitudinal IBS growth rates computed over all the time steps. + """ + if initial_emittances is None: + print("Emittances from the Twiss object are being used.") + emittance_x, emittance_y, emittance_z = ( + twiss.eq_gemitt_x, twiss.eq_gemitt_y, twiss.eq_gemitt_zeta + ) + # If emittance_coupling_factor is non zero, then natural emittance is + # modified accordingly + if ( + emittance_coupling_factor != 0 + and emittance_constraint.lower() == "coupling" + ): + # The convention used enforces a total transverse emittance + # conservation for any emittance_coupling_factor + emittance_x *= 1 / (1 + emittance_coupling_factor) + emittance_y = ( + emittance_x * emittance_coupling_factor + / (1 + emittance_coupling_factor) + ) + if ( + emittance_coupling_factor != 0 + and emittance_constraint.lower() == "excitation" + ): + # The convention used only enforce a constraint on the vertical + # emittance + emittance_y = emittance_x * emittance_coupling_factor + else: + emittance_x, emittance_y, emittance_z = initial_emittances + if natural_emittances is None: + warnings.warn("'initial_emittances' is specified but not " + "'natural_emittances', proceed with caution.") + + if input_sigma_zeta is not None: + warnings.warn("'input_sigma_zeta' is specified, make sure it remains " + "consistent with 'initial_emittances'.") + sigma_zeta = input_sigma_zeta + sigma_delta = (emittance_z / twiss.bets0) ** 0.5 + elif input_sigma_delta is not None: + warnings.warn("'input_sigma_delta' is specified, make sure it remains " + "consistent with 'initial_emittances'.") + sigma_zeta = (emittance_z * twiss.bets0) ** 0.5 + sigma_delta = input_sigma_delta + else: + # Default longitudinal_emittance_ratio is twiss.bets0 + sigma_zeta = (emittance_z * twiss.bets0) ** 0.5 + sigma_delta = (emittance_z / twiss.bets0) ** 0.5 + longitudinal_emittance_ratio = sigma_zeta / sigma_delta + if (input_sigma_zeta is not None or input_sigma_delta is not None): + assert initial_emittances is not None, ( + "Input of 'input_sigma_zeta' or 'input_sigma_delta' provided, but " + "not of 'initial_emittances'. Please provide 'initial_emittances'." + ) + + time = [] + emittances_x_list, emittances_y_list, emittances_z_list = [], [], [] + T_x, T_y, T_z = [], [], [] + + time_step = twiss.T_rev0 # Initial time step is the revolution period + tol = np.inf + + current_emittances = np.array([emittance_x, emittance_y, emittance_z]) + it = 0 # Iteration counter + + while tol > rtol: + # Print convergence progress + sys.stdout.write("\rConvergence = {:.1f}%".format(100 * rtol / tol)) + + # Compute IBS emittance rates and growth rates + ibs_emittance_rates, ibs_growth_rates = ibs_rates( + twiss, bunch_intensity, current_emittances, + initial_emittances=initial_emittances, + emittance_coupling_factor=emittance_coupling_factor, + emittance_constraint=emittance_constraint, + longitudinal_emittance_ratio=longitudinal_emittance_ratio, + natural_emittances=natural_emittances, + ) + + # Update emittances + current_emittances += np.array(ibs_emittance_rates) * time_step + + # Enforce constraints if specified + if emittance_constraint.lower() == "coupling": + forced_emittance_x = ( + (current_emittances[0] + current_emittances[1]) + / (1 + emittance_coupling_factor) + ) + forced_emittance_y = forced_emittance_x * emittance_coupling_factor + current_emittances[0] = forced_emittance_x + current_emittances[1] = forced_emittance_y + + if emittance_constraint.lower() == "excitation": + forced_emittance_y = forced_emittance_x * emittance_coupling_factor + current_emittances[1] = forced_emittance_y + + # Append current values to lists + time.append(time_step) + emittances_x_list.append(current_emittances[0]) + emittances_y_list.append(current_emittances[1]) + emittances_z_list.append(current_emittances[2]) + T_x.append(ibs_growth_rates[0]) + T_y.append(ibs_growth_rates[1]) + T_z.append(ibs_growth_rates[2]) + + # Compute tolerance + if it > 0: + tol = np.max( + np.abs((current_emittances - previous_emittances) + / previous_emittances) + ) + # Store current emittances for the next iteration + previous_emittances = current_emittances.copy() + + # Update time step for the next iteration + time_step = 0.01 / np.max( + (ibs_growth_rates, twiss.damping_constants_s) + ) + + it += 1 + + print("\nConverged!") + return ( + np.cumsum(time), + emittances_x_list, + emittances_y_list, + emittances_z_list, + T_x, + T_y, + T_z, + ) \ No newline at end of file From 3d495420bd163433c32034e93c2126b525af6be4 Mon Sep 17 00:00:00 2001 From: SebastienJoly Date: Fri, 3 Jan 2025 15:51:09 +0100 Subject: [PATCH 002/137] Fix small bug with 'excitation' constraint. --- xfields/ibs/_equilibrium_emittance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index b869d939..f3107e93 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -309,7 +309,7 @@ def compute_emittance_evolution( current_emittances[1] = forced_emittance_y if emittance_constraint.lower() == "excitation": - forced_emittance_y = forced_emittance_x * emittance_coupling_factor + forced_emittance_y = current_emittances[0] * emittance_coupling_factor current_emittances[1] = forced_emittance_y # Append current values to lists From 5aed27db622a0ccdb28f445d4b96f79c9813830a Mon Sep 17 00:00:00 2001 From: SebastienJoly Date: Fri, 3 Jan 2025 15:51:26 +0100 Subject: [PATCH 003/137] Add tests. --- tests/test_ibs_equilibrium_emittances.py | 154 +++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 tests/test_ibs_equilibrium_emittances.py diff --git a/tests/test_ibs_equilibrium_emittances.py b/tests/test_ibs_equilibrium_emittances.py new file mode 100644 index 00000000..dd4bc8dd --- /dev/null +++ b/tests/test_ibs_equilibrium_emittances.py @@ -0,0 +1,154 @@ +import pytest +import xfields as xf +import xobjects as xo +import xtrack as xt +from ibs_conftest import XTRACK_TEST_DATA +from numpy.testing import assert_allclose + +bunch_intensity = 6.2e9 # 1C bunch intensity + +bessy3_dir = XTRACK_TEST_DATA / "bessy3" +line = xt.Line.from_json(str(bessy3_dir / "line.json")) +line.build_tracker(_context=xo.ContextCpu()) +line.matrix_stability_tol = 1e-2 +line.configure_radiation(model='mean') +line.compensate_radiation_energy_loss() + +twiss = line.twiss(eneloss_and_damping=True) + +@pytest.mark.parametrize('emittance_constraint', ['coupling', 'excitation']) +def test_ibs_emittance_constraints(emittance_constraint): + + ####################################### + # Equilibrium emittances calculations # + ####################################### + + emittance_coupling_factor = 0.02 + + time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = xf.ibs.compute_emittance_evolution( + twiss, bunch_intensity, + emittance_coupling_factor=emittance_coupling_factor, + emittance_constraint=emittance_constraint, + ) + + if emittance_constraint == 'coupling': + # Check equilibrium emittance + assert_allclose( + emittances_x_list[-1], + twiss.eq_gemitt_x / (1 + emittance_coupling_factor) / (1 - T_x[-1] / 2 / twiss.damping_constants_s[0]), + rtol=5e-2 + ) + # Check equilibrium emittance + assert_allclose( + emittances_z_list[-1], + twiss.eq_gemitt_zeta / (1 - T_z[-1] / 2 / twiss.damping_constants_s[2]), + rtol=2e-2 + ) + # Check emittance coupling constraint + assert_allclose( + emittances_y_list[-1] / emittances_x_list[-1], + emittance_coupling_factor, + rtol=2e-2 + ) + + else: + # Check equilibrium emittance + assert_allclose( + emittances_x_list[-1], + twiss.eq_gemitt_x / (1 - T_x[-1] / 2 / twiss.damping_constants_s[0]), + rtol=5e-2 + ) + # Check equilibrium emittance + assert_allclose( + emittances_z_list[-1], + twiss.eq_gemitt_zeta / (1 - T_z[-1] / 2 / twiss.damping_constants_s[2]), + rtol=2e-2 + ) + # Check emittance coupling constraint + assert_allclose( + emittances_y_list[-1] / emittances_x_list[-1], + emittance_coupling_factor, + ) + +@pytest.mark.parametrize('emittance_coupling_factor', [0.02, 0.1, 0.2]) +def test_ibs_emittance_coupling_factor(emittance_coupling_factor): + """ + As the emittance coupling factor increases, the equilibrium emittance + cannot be compared anymore to the solution of the differential equation + describing the emittance evolution in presence of IBS and SR if a + constraint on the emittance is enforced. + """ + ####################################### + # Equilibrium emittances calculations # + ####################################### + + time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = xf.ibs.compute_emittance_evolution( + twiss, bunch_intensity, + emittance_coupling_factor=emittance_coupling_factor, + ) + + # Check equilibrium emittance + assert_allclose( + emittances_x_list[-1], + twiss.eq_gemitt_x / (1 + emittance_coupling_factor) / (1 - T_x[-1] / 2 / twiss.damping_constants_s[0]), + rtol=1e-1 + ) + # Check equilibrium emittance + assert_allclose( + emittances_z_list[-1], + twiss.eq_gemitt_zeta / (1 - T_z[-1] / 2 / twiss.damping_constants_s[2]), + rtol=2e-2 + ) + # Check emittance coupling constraint + assert_allclose( + emittances_y_list[-1] / emittances_x_list[-1], + emittance_coupling_factor, + ) + +@pytest.mark.parametrize('emittance_coupling_factor', [0.02, 0.1, 1.]) +def test_ibs_emittance_no_constraint(emittance_coupling_factor): + """ + Without any emittance constraint, the equilibrium emittance becomes + almost identical to the solution of the differential equation describing + the emittance evolution in presence of IBS and SR. + """ + initial_emittances=( + twiss.eq_gemitt_x, emittance_coupling_factor*twiss.eq_gemitt_x, + twiss.eq_gemitt_zeta + ) + emittance_constraint="" + natural_emittances=( + twiss.eq_gemitt_x, emittance_coupling_factor*twiss.eq_gemitt_x, + twiss.eq_gemitt_zeta + ) + + ####################################### + # Equilibrium emittances calculations # + ####################################### + + time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = xf.ibs.compute_emittance_evolution( + twiss, bunch_intensity, + initial_emittances=initial_emittances, + emittance_coupling_factor=emittance_coupling_factor, + emittance_constraint=emittance_constraint, + natural_emittances=natural_emittances, + ) + + # Check equilibrium emittance + assert_allclose( + emittances_x_list[-1], + twiss.eq_gemitt_x / (1 - T_x[-1] / 2 / twiss.damping_constants_s[0]), + rtol=2e-2 + ) + # Check equilibrium emittance + assert_allclose( + emittances_y_list[-1], + emittance_coupling_factor*twiss.eq_gemitt_x / (1 - T_y[-1] / 2 / twiss.damping_constants_s[1]), + rtol=2e-2 + ) + # Check equilibrium emittance + assert_allclose( + emittances_z_list[-1], + twiss.eq_gemitt_zeta / (1 - T_z[-1] / 2 / twiss.damping_constants_s[2]), + rtol=2e-2 + ) \ No newline at end of file From e76d4a2aebf26e8b7fbb424e143f0968e308bc3a Mon Sep 17 00:00:00 2001 From: SebastienJoly Date: Thu, 9 Jan 2025 11:00:34 +0100 Subject: [PATCH 004/137] Additional fixes and simplifications. --- xfields/ibs/_equilibrium_emittance.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index f3107e93..6cd60f66 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -91,11 +91,11 @@ def ibs_rates( ): # The convention used enforces a total transverse emittance # conservation for any emittance_coupling_factor. - natural_emittance_x *= 1 / (1 + emittance_coupling_factor) natural_emittance_y = ( natural_emittance_x * emittance_coupling_factor / (1 + emittance_coupling_factor) ) + natural_emittance_x *= 1 / (1 + emittance_coupling_factor) if ( emittance_coupling_factor != 0 and emittance_constraint.lower() == "excitation" @@ -140,6 +140,7 @@ def ibs_rates( -2 * damping_rate_z * (input_emittance_z - natural_emittance_z) + ibs_growth_rates.Tz * input_emittance_z ) + return ( (depsilon_x_dt, depsilon_y_dt, depsilon_z_dt), (ibs_growth_rates.Tx, ibs_growth_rates.Ty, ibs_growth_rates.Tz), @@ -232,11 +233,11 @@ def compute_emittance_evolution( ): # The convention used enforces a total transverse emittance # conservation for any emittance_coupling_factor - emittance_x *= 1 / (1 + emittance_coupling_factor) emittance_y = ( emittance_x * emittance_coupling_factor / (1 + emittance_coupling_factor) ) + emittance_x *= 1 / (1 + emittance_coupling_factor) if ( emittance_coupling_factor != 0 and emittance_constraint.lower() == "excitation" @@ -250,20 +251,16 @@ def compute_emittance_evolution( warnings.warn("'initial_emittances' is specified but not " "'natural_emittances', proceed with caution.") + sigma_zeta = (emittance_z * twiss.bets0) ** 0.5 + sigma_delta = (emittance_z / twiss.bets0) ** 0.5 if input_sigma_zeta is not None: warnings.warn("'input_sigma_zeta' is specified, make sure it remains " "consistent with 'initial_emittances'.") sigma_zeta = input_sigma_zeta - sigma_delta = (emittance_z / twiss.bets0) ** 0.5 elif input_sigma_delta is not None: warnings.warn("'input_sigma_delta' is specified, make sure it remains " "consistent with 'initial_emittances'.") - sigma_zeta = (emittance_z * twiss.bets0) ** 0.5 - sigma_delta = input_sigma_delta - else: - # Default longitudinal_emittance_ratio is twiss.bets0 - sigma_zeta = (emittance_z * twiss.bets0) ** 0.5 - sigma_delta = (emittance_z / twiss.bets0) ** 0.5 + sigma_delta = input_sigma_delta longitudinal_emittance_ratio = sigma_zeta / sigma_delta if (input_sigma_zeta is not None or input_sigma_delta is not None): assert initial_emittances is not None, ( From 60584ccc7900f64980338043882ab9d678dd1811 Mon Sep 17 00:00:00 2001 From: SebastienJoly Date: Fri, 10 Jan 2025 18:06:01 +0100 Subject: [PATCH 005/137] Proper initialization of the emittances when forcing an emittance coupling factor through linear coupling. --- xfields/ibs/_equilibrium_emittance.py | 33 +++++++++++++++++++-------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 6cd60f66..bf30d802 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -89,13 +89,17 @@ def ibs_rates( emittance_coupling_factor != 0 and emittance_constraint.lower() == "coupling" ): - # The convention used enforces a total transverse emittance - # conservation for any emittance_coupling_factor. + # The convention used is valid for arbitrary damping partition + # numbers and emittance_coupling_factor. natural_emittance_y = ( natural_emittance_x * emittance_coupling_factor - / (1 + emittance_coupling_factor) + / (1 + emittance_coupling_factor * + twiss.partition_numbers[1] / twiss.partition_numbers[0]) + ) + natural_emittance_x *= ( + 1 / (1 + emittance_coupling_factor * + twiss.partition_numbers[1] / twiss.partition_numbers[0]) ) - natural_emittance_x *= 1 / (1 + emittance_coupling_factor) if ( emittance_coupling_factor != 0 and emittance_constraint.lower() == "excitation" @@ -110,6 +114,10 @@ def ibs_rates( damping_rate_x, damping_rate_y, damping_rate_z = ( twiss.damping_constants_s ) + else: + damping_rate_x, damping_rate_y, damping_rate_z = ( + damping_rates + ) sigma_zeta = (input_emittance_z * longitudinal_emittance_ratio) ** 0.5 sigma_delta = (input_emittance_z / longitudinal_emittance_ratio) ** 0.5 @@ -126,7 +134,7 @@ def ibs_rates( sigma_delta=sigma_delta, bunch_length=sigma_zeta, # 1 sigma_{zeta,RMS} bunch length bunched=True, - ) + ) depsilon_x_dt = ( -2 * damping_rate_x * (input_emittance_x - natural_emittance_x) @@ -139,7 +147,7 @@ def ibs_rates( depsilon_z_dt = ( -2 * damping_rate_z * (input_emittance_z - natural_emittance_z) + ibs_growth_rates.Tz * input_emittance_z - ) + ) return ( (depsilon_x_dt, depsilon_y_dt, depsilon_z_dt), @@ -231,13 +239,18 @@ def compute_emittance_evolution( emittance_coupling_factor != 0 and emittance_constraint.lower() == "coupling" ): - # The convention used enforces a total transverse emittance - # conservation for any emittance_coupling_factor + # The convention used is valid for arbitrary damping partition + # numbers and emittance_coupling_factor. emittance_y = ( emittance_x * emittance_coupling_factor - / (1 + emittance_coupling_factor) + / (1 + emittance_coupling_factor * + twiss.partition_numbers[1] / twiss.partition_numbers[0]) ) - emittance_x *= 1 / (1 + emittance_coupling_factor) + emittance_x *= ( + 1 / (1 + emittance_coupling_factor * + twiss.partition_numbers[1] / twiss.partition_numbers[0]) + ) + if ( emittance_coupling_factor != 0 and emittance_constraint.lower() == "excitation" From a104d832799c818dec81879b0386cfbda9b56ebe Mon Sep 17 00:00:00 2001 From: SebastienJoly Date: Mon, 13 Jan 2025 09:54:16 +0100 Subject: [PATCH 006/137] Remove 'natural_emittances' from 'ibs_rates' to avoid potential confusion. --- xfields/ibs/_equilibrium_emittance.py | 48 ++++----------------------- 1 file changed, 6 insertions(+), 42 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index bf30d802..eeae1bb5 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -20,7 +20,6 @@ def ibs_rates( emittance_coupling_factor: float = 0, emittance_constraint: Literal["Coupling", "Excitation"] = "Coupling", formalism: Literal["Nagaitsev", "Bjorken-Mtingwa", "B&M"] = "Nagaitsev", - natural_emittances: Tuple = None, longitudinal_emittance_ratio: float = None, damping_rates: Tuple = None, **kwargs, @@ -52,9 +51,6 @@ def ibs_rates( formalism : str, optional Which formalism to use for the computation. Can be ``Nagaitsev`` or ``Bjorken-Mtingwa`` (also accepts ``B&M``), case-insensitively. - natural_emittances : tuple of floats, optional - Natural emittances (horizontal, vertical, longitudinal). - If None, they are taken from the `twiss` object. Default is None. longitudinal_emittance_ratio : float, optional Ratio of the RMS bunch length to the RMS momentum spread. Used if an user specified input_sigma_zeta or input_sigma_delta are given. @@ -72,43 +68,12 @@ def ibs_rates( IBS growth rates (horizontal, vertical, longitudinal). """ input_emittance_x, input_emittance_y, input_emittance_z = input_emittances - - if natural_emittances is not None: - natural_emittance_x, natural_emittance_y, natural_emittance_z = ( - natural_emittances - ) - else: - natural_emittance_x, natural_emittance_y, natural_emittance_z = ( - twiss.eq_gemitt_x, - twiss.eq_gemitt_y, - twiss.eq_gemitt_zeta, - ) - # If emittance_coupling_factor is non-zero, then natural emittance is - # modified accordingly. - if ( - emittance_coupling_factor != 0 - and emittance_constraint.lower() == "coupling" - ): - # The convention used is valid for arbitrary damping partition - # numbers and emittance_coupling_factor. - natural_emittance_y = ( - natural_emittance_x * emittance_coupling_factor - / (1 + emittance_coupling_factor * - twiss.partition_numbers[1] / twiss.partition_numbers[0]) - ) - natural_emittance_x *= ( - 1 / (1 + emittance_coupling_factor * - twiss.partition_numbers[1] / twiss.partition_numbers[0]) - ) - if ( - emittance_coupling_factor != 0 - and emittance_constraint.lower() == "excitation" - ): - # The convention used only enforce a constraint on the vertical - # emittance. - natural_emittance_y = ( - natural_emittance_x * emittance_coupling_factor - ) + + natural_emittance_x, natural_emittance_y, natural_emittance_z = ( + twiss.eq_gemitt_x, + twiss.eq_gemitt_y, + twiss.eq_gemitt_zeta, + ) if damping_rates is None: damping_rate_x, damping_rate_y, damping_rate_z = ( @@ -302,7 +267,6 @@ def compute_emittance_evolution( emittance_coupling_factor=emittance_coupling_factor, emittance_constraint=emittance_constraint, longitudinal_emittance_ratio=longitudinal_emittance_ratio, - natural_emittances=natural_emittances, ) # Update emittances From 2e706f028d82bbad2eff3b442e583214f5572efb Mon Sep 17 00:00:00 2001 From: SebastienJoly Date: Mon, 13 Jan 2025 14:10:38 +0100 Subject: [PATCH 007/137] Remove last 'natural_emittances' traces --- xfields/ibs/_equilibrium_emittance.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index eeae1bb5..6d014f8d 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -127,7 +127,6 @@ def compute_emittance_evolution( emittance_constraint: Literal["Coupling", "Excitation"] = "Coupling", input_sigma_zeta: float = None, input_sigma_delta: float = None, - natural_emittances: Tuple = None, rtol: float = 1e-6, **kwargs, ): @@ -225,9 +224,6 @@ def compute_emittance_evolution( emittance_y = emittance_x * emittance_coupling_factor else: emittance_x, emittance_y, emittance_z = initial_emittances - if natural_emittances is None: - warnings.warn("'initial_emittances' is specified but not " - "'natural_emittances', proceed with caution.") sigma_zeta = (emittance_z * twiss.bets0) ** 0.5 sigma_delta = (emittance_z / twiss.bets0) ** 0.5 From 9a5d910d788360c98606c85cf7973bdefe03bb95 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 13 Jan 2025 17:35:36 +0100 Subject: [PATCH 008/137] imports shuffle, xo class for derivatives to simplify passing data around: --- xfields/ibs/_equilibrium_emittance.py | 185 ++++++++++++++++---------- 1 file changed, 115 insertions(+), 70 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 6d014f8d..925d4a65 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -3,27 +3,70 @@ # Copyright (c) CERN, 2021. # # ########################################### # +from __future__ import annotations + +import logging import sys import warnings -#from logging import getLogger -from typing import Tuple, Literal +from typing import Literal import numpy as np +import xobjects as xo import xtrack as xt +from xtrack import Table + +from xfields.ibs._analytical import IBSGrowthRates + +LOGGER = logging.getLogger(__name__) + +# ----- Some classes to store results (as xo.HybridClass) ----- # + + +class _EmittanceTimeDerivatives(xo.HybridClass): + """ + Holds emittance evolution rates named ``dex``, + ``dey``, and ``dez``. The values are expressed + in [m.s^-1]. + + Attributes + ---------- + dex : float + Horizontal geometric emittance time + derivative, in [m.s^-1]. + dey : float + Vertical geometric emittance time + derivative, in [m.s^-1]. + dez : float + Longitudinal geometric emittance time + derivative, in [m.s^-1]. + """ + + _xofields = { + "dex": xo.Float64, + "dey": xo.Float64, + "dez": xo.Float64, + } + + def __init__(self, dex: float, dey: float, dez: float) -> None: + """Init with given values.""" + self.xoinitialize(dex=dex, dey=dey, dez=dez) + + def as_tuple(self) -> tuple[float, float, float]: + """Return the growth rates as a tuple.""" + return float(self.dex), float(self.dey), float(self.dez) -#LOGGER = getLogger(__name__) def ibs_rates( twiss: xt.TwissTable, bunch_intensity: float, - input_emittances: Tuple, + input_emittances: tuple, emittance_coupling_factor: float = 0, emittance_constraint: Literal["Coupling", "Excitation"] = "Coupling", formalism: Literal["Nagaitsev", "Bjorken-Mtingwa", "B&M"] = "Nagaitsev", longitudinal_emittance_ratio: float = None, - damping_rates: Tuple = None, + damping_rates: tuple = None, **kwargs, -): +) -> tuple[IBSGrowthRates, _EmittanceTimeDerivatives]: """ Compute the IBS growth rates and emittance evolution rates. @@ -34,19 +77,19 @@ def ibs_rates( bunch_intensity : float Bunch intensity [particles per bunch]. input_emittances : tuple of floats - Tuple containing the equilibrium transverse emittances (horizontal and + Tuple containing the equilibrium transverse emittances (horizontal and vertical). emittance_coupling_factor : float, optional - Emittance coupling factor, defined as the ratio of vertical to + Emittance coupling factor, defined as the ratio of vertical to horizontal emittance. Default is 0. emittance_constraint : str, optional Can enforces constraints on the transverse emittance based on the - emittance coupling factor. + emittance coupling factor. "Coupling" corresponds to the case where the vertical emittance is the result of linear coupling. - "Excitation" corresponds to the case where the vertical emittance is - the result of an excitation (e.g. from a feedback system). + "Excitation" corresponds to the case where the vertical emittance is + the result of an excitation (e.g. from a feedback system). Default is "Coupling". formalism : str, optional Which formalism to use for the computation. Can be ``Nagaitsev`` @@ -76,21 +119,19 @@ def ibs_rates( ) if damping_rates is None: - damping_rate_x, damping_rate_y, damping_rate_z = ( - twiss.damping_constants_s - ) + damping_rate_x, damping_rate_y, damping_rate_z = twiss.damping_constants_s else: - damping_rate_x, damping_rate_y, damping_rate_z = ( - damping_rates - ) + damping_rate_x, damping_rate_y, damping_rate_z = damping_rates sigma_zeta = (input_emittance_z * longitudinal_emittance_ratio) ** 0.5 sigma_delta = (input_emittance_z / longitudinal_emittance_ratio) ** 0.5 - assert input_emittance_x > 0., ("'input_emittance_x' should be larger than" - " zero, try providing 'initial_emittances'") - assert input_emittance_y > 0., ("'input_emittance_y' should be larger than" - " zero, try providing 'initial_emittances'") + assert input_emittance_x > 0.0, ( + "'input_emittance_x' should be larger than" " zero, try providing 'initial_emittances'" + ) + assert input_emittance_y > 0.0, ( + "'input_emittance_y' should be larger than" " zero, try providing 'initial_emittances'" + ) ibs_growth_rates = twiss.get_ibs_growth_rates( formalism=formalism, total_beam_intensity=bunch_intensity, @@ -99,7 +140,7 @@ def ibs_rates( sigma_delta=sigma_delta, bunch_length=sigma_zeta, # 1 sigma_{zeta,RMS} bunch length bunched=True, - ) + ) depsilon_x_dt = ( -2 * damping_rate_x * (input_emittance_x - natural_emittance_x) @@ -112,13 +153,14 @@ def ibs_rates( depsilon_z_dt = ( -2 * damping_rate_z * (input_emittance_z - natural_emittance_z) + ibs_growth_rates.Tz * input_emittance_z - ) - + ) + return ( (depsilon_x_dt, depsilon_y_dt, depsilon_z_dt), (ibs_growth_rates.Tx, ibs_growth_rates.Ty, ibs_growth_rates.Tz), ) + def compute_emittance_evolution( twiss: xt.TwissTable, bunch_intensity: float, @@ -148,19 +190,19 @@ def compute_emittance_evolution( bunch_intensity : float Bunch intensity [particles per bunch]. initial_emittances : tuple of floats, optional - Initial values for the horizontal, vertical, and longitudinal - emittances. If None, the equilibrium emittances from the Twiss object + Initial values for the horizontal, vertical, and longitudinal + emittances. If None, the equilibrium emittances from the Twiss object are used. Default is None. emittance_coupling_factor : float, optional - Emittance coupling factor, defined as the ratio of vertical to + Emittance coupling factor, defined as the ratio of vertical to horizontal emittance. Default is 0. emittance_constraint : str, optional Can enforces constraints on the transverse emittance based on the - emittance coupling factor. + emittance coupling factor. "Coupling" corresponds to the case where the vertical emittance is the result of linear coupling. - "Excitation" corresponds to the case where the vertical emittance is - the result of an excitation (e.g. from a feedback system). + "Excitation" corresponds to the case where the vertical emittance is + the result of an excitation (e.g. from a feedback system). Default is "Coupling". input_sigma_zeta : float Used specified RMS momentum spread overwriting the natural one from @@ -172,7 +214,7 @@ def compute_emittance_evolution( Natural emittances (horizontal, vertical, longitudinal). If None, they are taken from the `twiss` object. Default is None. rtol : float, optional - Relative tolerance for equilibrium emittance convergence. + Relative tolerance for equilibrium emittance convergence. Default is 1e-6. Returns @@ -195,53 +237,60 @@ def compute_emittance_evolution( if initial_emittances is None: print("Emittances from the Twiss object are being used.") emittance_x, emittance_y, emittance_z = ( - twiss.eq_gemitt_x, twiss.eq_gemitt_y, twiss.eq_gemitt_zeta + twiss.eq_gemitt_x, + twiss.eq_gemitt_y, + twiss.eq_gemitt_zeta, ) - # If emittance_coupling_factor is non zero, then natural emittance is + # If emittance_coupling_factor is non zero, then natural emittance is # modified accordingly - if ( - emittance_coupling_factor != 0 - and emittance_constraint.lower() == "coupling" - ): + if emittance_coupling_factor != 0 and emittance_constraint.lower() == "coupling": # The convention used is valid for arbitrary damping partition # numbers and emittance_coupling_factor. emittance_y = ( - emittance_x * emittance_coupling_factor - / (1 + emittance_coupling_factor * - twiss.partition_numbers[1] / twiss.partition_numbers[0]) + emittance_x + * emittance_coupling_factor + / ( + 1 + + emittance_coupling_factor + * twiss.partition_numbers[1] + / twiss.partition_numbers[0] + ) ) - emittance_x *= ( - 1 / (1 + emittance_coupling_factor * - twiss.partition_numbers[1] / twiss.partition_numbers[0]) + emittance_x *= 1 / ( + 1 + + emittance_coupling_factor + * twiss.partition_numbers[1] + / twiss.partition_numbers[0] ) - - if ( - emittance_coupling_factor != 0 - and emittance_constraint.lower() == "excitation" - ): + + if emittance_coupling_factor != 0 and emittance_constraint.lower() == "excitation": # The convention used only enforce a constraint on the vertical # emittance emittance_y = emittance_x * emittance_coupling_factor else: emittance_x, emittance_y, emittance_z = initial_emittances - + sigma_zeta = (emittance_z * twiss.bets0) ** 0.5 sigma_delta = (emittance_z / twiss.bets0) ** 0.5 if input_sigma_zeta is not None: - warnings.warn("'input_sigma_zeta' is specified, make sure it remains " - "consistent with 'initial_emittances'.") + warnings.warn( + "'input_sigma_zeta' is specified, make sure it remains " + "consistent with 'initial_emittances'." + ) sigma_zeta = input_sigma_zeta elif input_sigma_delta is not None: - warnings.warn("'input_sigma_delta' is specified, make sure it remains " - "consistent with 'initial_emittances'.") - sigma_delta = input_sigma_delta + warnings.warn( + "'input_sigma_delta' is specified, make sure it remains " + "consistent with 'initial_emittances'." + ) + sigma_delta = input_sigma_delta longitudinal_emittance_ratio = sigma_zeta / sigma_delta - if (input_sigma_zeta is not None or input_sigma_delta is not None): + if input_sigma_zeta is not None or input_sigma_delta is not None: assert initial_emittances is not None, ( "Input of 'input_sigma_zeta' or 'input_sigma_delta' provided, but " "not of 'initial_emittances'. Please provide 'initial_emittances'." ) - + time = [] emittances_x_list, emittances_y_list, emittances_z_list = [], [], [] T_x, T_y, T_z = [], [], [] @@ -250,7 +299,7 @@ def compute_emittance_evolution( tol = np.inf current_emittances = np.array([emittance_x, emittance_y, emittance_z]) - it = 0 # Iteration counter + it = 0 # Iteration counter while tol > rtol: # Print convergence progress @@ -258,7 +307,9 @@ def compute_emittance_evolution( # Compute IBS emittance rates and growth rates ibs_emittance_rates, ibs_growth_rates = ibs_rates( - twiss, bunch_intensity, current_emittances, + twiss, + bunch_intensity, + current_emittances, initial_emittances=initial_emittances, emittance_coupling_factor=emittance_coupling_factor, emittance_constraint=emittance_constraint, @@ -270,14 +321,13 @@ def compute_emittance_evolution( # Enforce constraints if specified if emittance_constraint.lower() == "coupling": - forced_emittance_x = ( - (current_emittances[0] + current_emittances[1]) - / (1 + emittance_coupling_factor) + forced_emittance_x = (current_emittances[0] + current_emittances[1]) / ( + 1 + emittance_coupling_factor ) forced_emittance_y = forced_emittance_x * emittance_coupling_factor current_emittances[0] = forced_emittance_x current_emittances[1] = forced_emittance_y - + if emittance_constraint.lower() == "excitation": forced_emittance_y = current_emittances[0] * emittance_coupling_factor current_emittances[1] = forced_emittance_y @@ -293,17 +343,12 @@ def compute_emittance_evolution( # Compute tolerance if it > 0: - tol = np.max( - np.abs((current_emittances - previous_emittances) - / previous_emittances) - ) + tol = np.max(np.abs((current_emittances - previous_emittances) / previous_emittances)) # Store current emittances for the next iteration previous_emittances = current_emittances.copy() # Update time step for the next iteration - time_step = 0.01 / np.max( - (ibs_growth_rates, twiss.damping_constants_s) - ) + time_step = 0.01 / np.max((ibs_growth_rates, twiss.damping_constants_s)) it += 1 @@ -316,4 +361,4 @@ def compute_emittance_evolution( T_x, T_y, T_z, - ) \ No newline at end of file + ) From 98eed04bab7f231090df9514da55b18c8d581c30 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 13 Jan 2025 17:36:38 +0100 Subject: [PATCH 009/137] rename function to explicit name and make private --- xfields/ibs/_equilibrium_emittance.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 925d4a65..52e98bec 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -19,6 +19,7 @@ LOGGER = logging.getLogger(__name__) + # ----- Some classes to store results (as xo.HybridClass) ----- # @@ -56,7 +57,7 @@ def as_tuple(self) -> tuple[float, float, float]: return float(self.dex), float(self.dey), float(self.dez) -def ibs_rates( +def _ibs_rates_and_emittance_derivatives( twiss: xt.TwissTable, bunch_intensity: float, input_emittances: tuple, @@ -164,7 +165,7 @@ def ibs_rates( def compute_emittance_evolution( twiss: xt.TwissTable, bunch_intensity: float, - initial_emittances: Tuple = None, + initial_emittances: tuple = None, emittance_coupling_factor: float = 0, emittance_constraint: Literal["Coupling", "Excitation"] = "Coupling", input_sigma_zeta: float = None, @@ -306,7 +307,7 @@ def compute_emittance_evolution( sys.stdout.write("\rConvergence = {:.1f}%".format(100 * rtol / tol)) # Compute IBS emittance rates and growth rates - ibs_emittance_rates, ibs_growth_rates = ibs_rates( + ibs_emittance_rates, ibs_growth_rates = _ibs_rates_and_emittance_derivatives( twiss, bunch_intensity, current_emittances, From e6468ce777be612a45fa2adc21b1e7c80766219d Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 13 Jan 2025 17:38:37 +0100 Subject: [PATCH 010/137] return as classes and beware of order in call line --- xfields/ibs/_equilibrium_emittance.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 52e98bec..7a5a42c0 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -157,8 +157,8 @@ def _ibs_rates_and_emittance_derivatives( ) return ( - (depsilon_x_dt, depsilon_y_dt, depsilon_z_dt), - (ibs_growth_rates.Tx, ibs_growth_rates.Ty, ibs_growth_rates.Tz), + ibs_growth_rates, + _EmittanceTimeDerivatives(depsilon_x_dt, depsilon_y_dt, depsilon_z_dt), ) @@ -307,7 +307,7 @@ def compute_emittance_evolution( sys.stdout.write("\rConvergence = {:.1f}%".format(100 * rtol / tol)) # Compute IBS emittance rates and growth rates - ibs_emittance_rates, ibs_growth_rates = _ibs_rates_and_emittance_derivatives( + ibs_growth_rates, ibs_emittance_rates = _ibs_rates_and_emittance_derivatives( twiss, bunch_intensity, current_emittances, From 717f6acfc76340ba918337ab53b54058ae8ccbfb Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 13 Jan 2025 17:40:28 +0100 Subject: [PATCH 011/137] unpack as tuple, specify comment --- xfields/ibs/_equilibrium_emittance.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 7a5a42c0..a0bf9594 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -306,7 +306,7 @@ def compute_emittance_evolution( # Print convergence progress sys.stdout.write("\rConvergence = {:.1f}%".format(100 * rtol / tol)) - # Compute IBS emittance rates and growth rates + # Compute IBS growth rates and emittance derivatives ibs_growth_rates, ibs_emittance_rates = _ibs_rates_and_emittance_derivatives( twiss, bunch_intensity, @@ -316,6 +316,9 @@ def compute_emittance_evolution( emittance_constraint=emittance_constraint, longitudinal_emittance_ratio=longitudinal_emittance_ratio, ) + # Make sure we have them as tuples for below + ibs_growth_rates = ibs_growth_rates.as_tuple() + ibs_emittance_rates = ibs_emittance_rates.as_tuple() # Update emittances current_emittances += np.array(ibs_emittance_rates) * time_step From 896060d9f1f8062606585a4038cbb3aa152acd17 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 13 Jan 2025 17:40:57 +0100 Subject: [PATCH 012/137] rename to emittance_derivatives --- xfields/ibs/_equilibrium_emittance.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index a0bf9594..8ac9c30d 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -307,7 +307,7 @@ def compute_emittance_evolution( sys.stdout.write("\rConvergence = {:.1f}%".format(100 * rtol / tol)) # Compute IBS growth rates and emittance derivatives - ibs_growth_rates, ibs_emittance_rates = _ibs_rates_and_emittance_derivatives( + ibs_growth_rates, emittance_derivatives = _ibs_rates_and_emittance_derivatives( twiss, bunch_intensity, current_emittances, @@ -318,10 +318,10 @@ def compute_emittance_evolution( ) # Make sure we have them as tuples for below ibs_growth_rates = ibs_growth_rates.as_tuple() - ibs_emittance_rates = ibs_emittance_rates.as_tuple() + emittance_derivatives = emittance_derivatives.as_tuple() # Update emittances - current_emittances += np.array(ibs_emittance_rates) * time_step + current_emittances += np.array(emittance_derivatives) * time_step # Enforce constraints if specified if emittance_constraint.lower() == "coupling": From 87caa658965d18bbb7259de9fdc8756f21556535 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 13 Jan 2025 17:50:32 +0100 Subject: [PATCH 013/137] commented out code to return a Table for the user's enjoyment --- xfields/ibs/_equilibrium_emittance.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 8ac9c30d..fd037e6a 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -366,3 +366,15 @@ def compute_emittance_evolution( T_y, T_z, ) + # return Table( + # data={ + # "time": np.cumsum(time), + # "gemitt_x": emittances_x_list, + # "gemitt_y": emittances_y_list, + # "gemitt_z": emittances_z_list, + # "Tx": Tx, + # "Ty": Ty, + # "Tz": Tz, + # }, + # index="time", + # ) From e1fd86a5e78c0f4b89896fe9b252687e5bdf96ca Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 13 Jan 2025 17:53:39 +0100 Subject: [PATCH 014/137] do not export private function --- xfields/ibs/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xfields/ibs/__init__.py b/xfields/ibs/__init__.py index 277b190e..2f9356a9 100644 --- a/xfields/ibs/__init__.py +++ b/xfields/ibs/__init__.py @@ -6,7 +6,7 @@ from ._analytical import BjorkenMtingwaIBS, IBSGrowthRates, NagaitsevIBS from ._api import configure_intrabeam_scattering, get_intrabeam_scattering_growth_rates from ._kicks import IBSAnalyticalKick, IBSKineticKick -from ._equilibrium_emittance import ibs_rates, compute_emittance_evolution +from ._equilibrium_emittance import compute_emittance_evolution __all__ = [ "BjorkenMtingwaIBS", From 4b3deafad766ac5942c8c6f1f7c34602e61bd09c Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 13 Jan 2025 17:54:43 +0100 Subject: [PATCH 015/137] rename params that are not used in private function anymore - emittance_coupling_factor and emittance_constraint --- xfields/ibs/_equilibrium_emittance.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index fd037e6a..6d574304 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -61,8 +61,6 @@ def _ibs_rates_and_emittance_derivatives( twiss: xt.TwissTable, bunch_intensity: float, input_emittances: tuple, - emittance_coupling_factor: float = 0, - emittance_constraint: Literal["Coupling", "Excitation"] = "Coupling", formalism: Literal["Nagaitsev", "Bjorken-Mtingwa", "B&M"] = "Nagaitsev", longitudinal_emittance_ratio: float = None, damping_rates: tuple = None, From 25b2c6409759a24f340b7c465ee4c6b3c3f11046 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 13 Jan 2025 17:59:14 +0100 Subject: [PATCH 016/137] explicitely name attributions --- xfields/ibs/_equilibrium_emittance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 6d574304..bcd0e0b6 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -156,7 +156,7 @@ def _ibs_rates_and_emittance_derivatives( return ( ibs_growth_rates, - _EmittanceTimeDerivatives(depsilon_x_dt, depsilon_y_dt, depsilon_z_dt), + _EmittanceTimeDerivatives(dex=depsilon_x_dt, dey=depsilon_y_dt, dez=depsilon_z_dt), ) From 6b4357dfc174e0c259bbb60b5c607baba1f970aa Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 13 Jan 2025 18:08:31 +0100 Subject: [PATCH 017/137] docstring rework and TODOs --- xfields/ibs/_equilibrium_emittance.py | 41 +++++++++++---------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index bcd0e0b6..a1017767 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -60,45 +60,35 @@ def as_tuple(self) -> tuple[float, float, float]: def _ibs_rates_and_emittance_derivatives( twiss: xt.TwissTable, bunch_intensity: float, - input_emittances: tuple, + input_emittances: tuple[float, float, float], formalism: Literal["Nagaitsev", "Bjorken-Mtingwa", "B&M"] = "Nagaitsev", longitudinal_emittance_ratio: float = None, damping_rates: tuple = None, **kwargs, ) -> tuple[IBSGrowthRates, _EmittanceTimeDerivatives]: """ - Compute the IBS growth rates and emittance evolution rates. + Compute the IBS growth rates and emittance time derivatives from + the effect of both IBS and SR. Parameters ---------- - twiss : object - Twiss object of the ring. + twiss : xtrack.TwissTable + Twiss results of the `xtrack.Line` configuration. bunch_intensity : float Bunch intensity [particles per bunch]. - input_emittances : tuple of floats - Tuple containing the equilibrium transverse emittances (horizontal and - vertical). - emittance_coupling_factor : float, optional - Emittance coupling factor, defined as the ratio of vertical to - horizontal emittance. - Default is 0. - emittance_constraint : str, optional - Can enforces constraints on the transverse emittance based on the - emittance coupling factor. - "Coupling" corresponds to the case where the - vertical emittance is the result of linear coupling. - "Excitation" corresponds to the case where the vertical emittance is - the result of an excitation (e.g. from a feedback system). - Default is "Coupling". - formalism : str, optional - Which formalism to use for the computation. Can be ``Nagaitsev`` - or ``Bjorken-Mtingwa`` (also accepts ``B&M``), case-insensitively. + input_emittances : tuple[float, float, float] + The bunch's starting geometric emittances in the horizontal, + vertical and longitudinal planes, in [m]. + formalism : str + Which formalism to use for the computation of the IBS growth rates. + Can be ``Nagaitsev`` or ``Bjorken-Mtingwa`` (also accepts ``B&M``), + case-insensitively. longitudinal_emittance_ratio : float, optional Ratio of the RMS bunch length to the RMS momentum spread. Used if - an user specified input_sigma_zeta or input_sigma_delta are given. + a user specified input_sigma_zeta or input_sigma_delta are given. It allows accounting for a perturbed longitudinal distrubtion due to bunch lengthening or a microwave instability. Default is None. - damping_rates : tuple of floats, optional + damping_rates : tuple[float, float, float], optional SR damping rates (horizontal, vertical, longitudinal). If None, they are taken from the `twiss` object. Default is None. @@ -109,14 +99,17 @@ def _ibs_rates_and_emittance_derivatives( ibs_growth_rates : tuple of floats IBS growth rates (horizontal, vertical, longitudinal). """ + # TODO: bunch emittances - ask for the three separately and update docstring input_emittance_x, input_emittance_y, input_emittance_z = input_emittances + # TODO: just take from twiss parameter in calculations and remove this attribution natural_emittance_x, natural_emittance_y, natural_emittance_z = ( twiss.eq_gemitt_x, twiss.eq_gemitt_y, twiss.eq_gemitt_zeta, ) + # TODO: just take the damping constants from twiss and be done (remove param and docstring) if damping_rates is None: damping_rate_x, damping_rate_y, damping_rate_z = twiss.damping_constants_s else: From 59c9a009efbcf07864b0616bc62ddea7d1354c0a Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 13 Jan 2025 18:10:28 +0100 Subject: [PATCH 018/137] input_sigma_zeta and input_sigma_delta are not in this function, remove from docstring --- xfields/ibs/_equilibrium_emittance.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index a1017767..65273119 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -84,9 +84,8 @@ def _ibs_rates_and_emittance_derivatives( Can be ``Nagaitsev`` or ``Bjorken-Mtingwa`` (also accepts ``B&M``), case-insensitively. longitudinal_emittance_ratio : float, optional - Ratio of the RMS bunch length to the RMS momentum spread. Used if - a user specified input_sigma_zeta or input_sigma_delta are given. - It allows accounting for a perturbed longitudinal distrubtion due to + Ratio of the RMS bunch length to the RMS momentum spread. If provided, + allows accounting for a perturbed longitudinal distrubtion due to bunch lengthening or a microwave instability. Default is None. damping_rates : tuple[float, float, float], optional SR damping rates (horizontal, vertical, longitudinal). From 53d3511a9db910b88191f6483152b36780d25f54 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 13 Jan 2025 18:11:54 +0100 Subject: [PATCH 019/137] replace call of intermediate badly named natural_emittance_[xyz] to directly relevant attribute in TwissTable --- xfields/ibs/_equilibrium_emittance.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 65273119..f0e3e9da 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -134,15 +134,15 @@ def _ibs_rates_and_emittance_derivatives( ) depsilon_x_dt = ( - -2 * damping_rate_x * (input_emittance_x - natural_emittance_x) + -2 * damping_rate_x * (input_emittance_x - twiss.eq_gemitt_x) + ibs_growth_rates.Tx * input_emittance_x ) depsilon_y_dt = ( - -2 * damping_rate_y * (input_emittance_y - natural_emittance_y) + -2 * damping_rate_y * (input_emittance_y - twiss.eq_gemitt_y) + ibs_growth_rates.Ty * input_emittance_y ) depsilon_z_dt = ( - -2 * damping_rate_z * (input_emittance_z - natural_emittance_z) + -2 * damping_rate_z * (input_emittance_z - twiss.eq_gemitt_zeta) + ibs_growth_rates.Tz * input_emittance_z ) From 692b66430a22b3b7db0ce804bbb9526f99044a77 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 13 Jan 2025 18:12:54 +0100 Subject: [PATCH 020/137] also remove attribution to vars, could add later a check for presence (or maybe in public function?) --- xfields/ibs/_equilibrium_emittance.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index f0e3e9da..1587a147 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -101,12 +101,8 @@ def _ibs_rates_and_emittance_derivatives( # TODO: bunch emittances - ask for the three separately and update docstring input_emittance_x, input_emittance_y, input_emittance_z = input_emittances - # TODO: just take from twiss parameter in calculations and remove this attribution - natural_emittance_x, natural_emittance_y, natural_emittance_z = ( - twiss.eq_gemitt_x, - twiss.eq_gemitt_y, - twiss.eq_gemitt_zeta, - ) + # TODO: can check that the SR eq emittances are present in twiss object (or in public func?) + # TODO: just take the damping constants from twiss and be done (remove param and docstring) if damping_rates is None: From 57270e477d2c89ba793b6bb24bb843d374df2449 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 13 Jan 2025 18:15:10 +0100 Subject: [PATCH 021/137] remove damping rates from parameters and take it directly from the Twiss Table --- xfields/ibs/_equilibrium_emittance.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 1587a147..b9ce76d1 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -63,7 +63,6 @@ def _ibs_rates_and_emittance_derivatives( input_emittances: tuple[float, float, float], formalism: Literal["Nagaitsev", "Bjorken-Mtingwa", "B&M"] = "Nagaitsev", longitudinal_emittance_ratio: float = None, - damping_rates: tuple = None, **kwargs, ) -> tuple[IBSGrowthRates, _EmittanceTimeDerivatives]: """ @@ -87,9 +86,6 @@ def _ibs_rates_and_emittance_derivatives( Ratio of the RMS bunch length to the RMS momentum spread. If provided, allows accounting for a perturbed longitudinal distrubtion due to bunch lengthening or a microwave instability. Default is None. - damping_rates : tuple[float, float, float], optional - SR damping rates (horizontal, vertical, longitudinal). - If None, they are taken from the `twiss` object. Default is None. Returns ------- @@ -103,13 +99,6 @@ def _ibs_rates_and_emittance_derivatives( # TODO: can check that the SR eq emittances are present in twiss object (or in public func?) - - # TODO: just take the damping constants from twiss and be done (remove param and docstring) - if damping_rates is None: - damping_rate_x, damping_rate_y, damping_rate_z = twiss.damping_constants_s - else: - damping_rate_x, damping_rate_y, damping_rate_z = damping_rates - sigma_zeta = (input_emittance_z * longitudinal_emittance_ratio) ** 0.5 sigma_delta = (input_emittance_z / longitudinal_emittance_ratio) ** 0.5 @@ -130,15 +119,15 @@ def _ibs_rates_and_emittance_derivatives( ) depsilon_x_dt = ( - -2 * damping_rate_x * (input_emittance_x - twiss.eq_gemitt_x) + -2 * twiss.damping_constants_s[0] * (input_emittance_x - twiss.eq_gemitt_x) + ibs_growth_rates.Tx * input_emittance_x ) depsilon_y_dt = ( - -2 * damping_rate_y * (input_emittance_y - twiss.eq_gemitt_y) + -2 * twiss.damping_constants_s[1] * (input_emittance_y - twiss.eq_gemitt_y) + ibs_growth_rates.Ty * input_emittance_y ) depsilon_z_dt = ( - -2 * damping_rate_z * (input_emittance_z - twiss.eq_gemitt_zeta) + -2 * twiss.damping_constants_s[2] * (input_emittance_z - twiss.eq_gemitt_zeta) + ibs_growth_rates.Tz * input_emittance_z ) From 2e4688404700ca3b726b33249b386fac349f8ee8 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 13 Jan 2025 18:20:24 +0100 Subject: [PATCH 022/137] and rework the return statement in docstring --- xfields/ibs/_equilibrium_emittance.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index b9ce76d1..d69be764 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -23,7 +23,7 @@ # ----- Some classes to store results (as xo.HybridClass) ----- # -class _EmittanceTimeDerivatives(xo.HybridClass): +class EmittanceTimeDerivatives(xo.HybridClass): """ Holds emittance evolution rates named ``dex``, ``dey``, and ``dez``. The values are expressed @@ -64,7 +64,7 @@ def _ibs_rates_and_emittance_derivatives( formalism: Literal["Nagaitsev", "Bjorken-Mtingwa", "B&M"] = "Nagaitsev", longitudinal_emittance_ratio: float = None, **kwargs, -) -> tuple[IBSGrowthRates, _EmittanceTimeDerivatives]: +) -> tuple[IBSGrowthRates, EmittanceTimeDerivatives]: """ Compute the IBS growth rates and emittance time derivatives from the effect of both IBS and SR. @@ -89,10 +89,11 @@ def _ibs_rates_and_emittance_derivatives( Returns ------- - emittance_rates : tuple of floats - Time variations of the emittances (horizontal, vertical, longitudinal). - ibs_growth_rates : tuple of floats - IBS growth rates (horizontal, vertical, longitudinal). + tuple[IBSGrowthRates, EmittanceTimeDerivatives] + Both the computed IBS growth rates and the emittance time derivatives + from the contributions of SR and IBS, each in a specific container + object (namely ``IBSGrowthRates`` and ``EmittanceTimeDerivatives``, + respectively). """ # TODO: bunch emittances - ask for the three separately and update docstring input_emittance_x, input_emittance_y, input_emittance_z = input_emittances @@ -133,7 +134,7 @@ def _ibs_rates_and_emittance_derivatives( return ( ibs_growth_rates, - _EmittanceTimeDerivatives(dex=depsilon_x_dt, dey=depsilon_y_dt, dez=depsilon_z_dt), + EmittanceTimeDerivatives(dex=depsilon_x_dt, dey=depsilon_y_dt, dez=depsilon_z_dt), ) From 8c20bfce35b0736e3b02517d1175053058450fdd Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 13 Jan 2025 18:25:24 +0100 Subject: [PATCH 023/137] commented out potential check for presence of expected attributes in Twiss Table --- xfields/ibs/_equilibrium_emittance.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index d69be764..28198014 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -98,7 +98,17 @@ def _ibs_rates_and_emittance_derivatives( # TODO: bunch emittances - ask for the three separately and update docstring input_emittance_x, input_emittance_y, input_emittance_z = input_emittances - # TODO: can check that the SR eq emittances are present in twiss object (or in public func?) + # TODO: can check that the SR eq emittances are present in twiss object (or in public func?) Could be: + # if None in ( + # getattr(twiss, "eq_gemitt_x", None), + # getattr(twiss, "eq_gemitt_y", None), + # getattr(twiss, "eq_gemitt_zeta", None), + # getattr(twiss, "damping_constants_s", None), + # ): + # raise AttributeError( + # "The TwissTable must contain SR equilibrium emittances and damping constants. " + # "Did you activate radiation and twiss with eneloss_and_damping=True?" + # ) sigma_zeta = (input_emittance_z * longitudinal_emittance_ratio) ** 0.5 sigma_delta = (input_emittance_z / longitudinal_emittance_ratio) ** 0.5 From 7363448481705557d786a327386830619365e6d3 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 13 Jan 2025 18:25:53 +0100 Subject: [PATCH 024/137] asserts and checks as high as possible --- xfields/ibs/_equilibrium_emittance.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 28198014..acf619f4 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -97,6 +97,12 @@ def _ibs_rates_and_emittance_derivatives( """ # TODO: bunch emittances - ask for the three separately and update docstring input_emittance_x, input_emittance_y, input_emittance_z = input_emittances + assert input_emittance_x > 0.0, ( + "'input_emittance_x' should be larger than" " zero, try providing 'initial_emittances'" + ) + assert input_emittance_y > 0.0, ( + "'input_emittance_y' should be larger than" " zero, try providing 'initial_emittances'" + ) # TODO: can check that the SR eq emittances are present in twiss object (or in public func?) Could be: # if None in ( @@ -113,12 +119,7 @@ def _ibs_rates_and_emittance_derivatives( sigma_zeta = (input_emittance_z * longitudinal_emittance_ratio) ** 0.5 sigma_delta = (input_emittance_z / longitudinal_emittance_ratio) ** 0.5 - assert input_emittance_x > 0.0, ( - "'input_emittance_x' should be larger than" " zero, try providing 'initial_emittances'" - ) - assert input_emittance_y > 0.0, ( - "'input_emittance_y' should be larger than" " zero, try providing 'initial_emittances'" - ) + ibs_growth_rates = twiss.get_ibs_growth_rates( formalism=formalism, total_beam_intensity=bunch_intensity, From 25f5b994cf8559a4177b6477fd08fd2dd572d3e7 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 13 Jan 2025 18:31:53 +0100 Subject: [PATCH 025/137] comments to walk through the function, pretty clean already --- xfields/ibs/_equilibrium_emittance.py | 28 ++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index acf619f4..52472fab 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -95,6 +95,8 @@ def _ibs_rates_and_emittance_derivatives( object (namely ``IBSGrowthRates`` and ``EmittanceTimeDerivatives``, respectively). """ + LOGGER.debug("Computing IBS growth rates and emittance time derivatives.") + # ---------------------------------------------------------------------------------------------- # TODO: bunch emittances - ask for the three separately and update docstring input_emittance_x, input_emittance_y, input_emittance_z = input_emittances assert input_emittance_x > 0.0, ( @@ -103,23 +105,26 @@ def _ibs_rates_and_emittance_derivatives( assert input_emittance_y > 0.0, ( "'input_emittance_y' should be larger than" " zero, try providing 'initial_emittances'" ) - - # TODO: can check that the SR eq emittances are present in twiss object (or in public func?) Could be: + # ---------------------------------------------------------------------------------------------- + # TODO: check for SR eq emittances etc in twiss table (or in public func?) Could be: # if None in ( # getattr(twiss, "eq_gemitt_x", None), # getattr(twiss, "eq_gemitt_y", None), # getattr(twiss, "eq_gemitt_zeta", None), # getattr(twiss, "damping_constants_s", None), # ): + # LOGGER.error("Invalid TwissTable, does not have SR equilibrium properties.") # raise AttributeError( # "The TwissTable must contain SR equilibrium emittances and damping constants. " # "Did you activate radiation and twiss with eneloss_and_damping=True?" # ) - - sigma_zeta = (input_emittance_z * longitudinal_emittance_ratio) ** 0.5 - sigma_delta = (input_emittance_z / longitudinal_emittance_ratio) ** 0.5 - - + # ---------------------------------------------------------------------------------------------- + # Compute relevant longitudinal parameters for the bunch (needed for IBS growth rates) + LOGGER.debug("Computing longitudinal parameters for the bunch.") + sigma_zeta = (input_emittance_z * longitudinal_emittance_ratio) ** 0.5 # in [m] + sigma_delta = (input_emittance_z / longitudinal_emittance_ratio) ** 0.5 # in [-] + # ---------------------------------------------------------------------------------------------- + # Ask to compute the IBS growth rates (this function logs so no need to do it here) ibs_growth_rates = twiss.get_ibs_growth_rates( formalism=formalism, total_beam_intensity=bunch_intensity, @@ -129,7 +134,11 @@ def _ibs_rates_and_emittance_derivatives( bunch_length=sigma_zeta, # 1 sigma_{zeta,RMS} bunch length bunched=True, ) - + # ---------------------------------------------------------------------------------------------- + # Computing the emittance time derivatives analytically. + # TODO: ADD A REF TO THE FORMULA HERE + # TODO: replace input_emittance_[xyz] by gemitt_[xyz] once they are parameters + LOGGER.debug("Computing emittance time derivatives analytically.") depsilon_x_dt = ( -2 * twiss.damping_constants_s[0] * (input_emittance_x - twiss.eq_gemitt_x) + ibs_growth_rates.Tx * input_emittance_x @@ -142,7 +151,8 @@ def _ibs_rates_and_emittance_derivatives( -2 * twiss.damping_constants_s[2] * (input_emittance_z - twiss.eq_gemitt_zeta) + ibs_growth_rates.Tz * input_emittance_z ) - + # ---------------------------------------------------------------------------------------------- + # And return the results return ( ibs_growth_rates, EmittanceTimeDerivatives(dex=depsilon_x_dt, dey=depsilon_y_dt, dez=depsilon_z_dt), From eb8c687ba0c3301f8fe0f858dffe6f1d8db7a420 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 13 Jan 2025 18:38:22 +0100 Subject: [PATCH 026/137] one last todo --- xfields/ibs/_equilibrium_emittance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 52472fab..71f21a7c 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -59,7 +59,7 @@ def as_tuple(self) -> tuple[float, float, float]: def _ibs_rates_and_emittance_derivatives( twiss: xt.TwissTable, - bunch_intensity: float, + bunch_intensity: float, # TODO: rename total_beam_intensity like in rest of APIs, confirm Seb is ok input_emittances: tuple[float, float, float], formalism: Literal["Nagaitsev", "Bjorken-Mtingwa", "B&M"] = "Nagaitsev", longitudinal_emittance_ratio: float = None, From da11a191e6ecad01c6d877656a3fd224ebc0c7d0 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 14:35:33 +0100 Subject: [PATCH 027/137] bunch_intensity -> total_beam_intensity to mimic the rest of the APIs --- xfields/ibs/_equilibrium_emittance.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 71f21a7c..ee87efe1 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -59,7 +59,7 @@ def as_tuple(self) -> tuple[float, float, float]: def _ibs_rates_and_emittance_derivatives( twiss: xt.TwissTable, - bunch_intensity: float, # TODO: rename total_beam_intensity like in rest of APIs, confirm Seb is ok + total_beam_intensity: int, input_emittances: tuple[float, float, float], formalism: Literal["Nagaitsev", "Bjorken-Mtingwa", "B&M"] = "Nagaitsev", longitudinal_emittance_ratio: float = None, @@ -73,8 +73,8 @@ def _ibs_rates_and_emittance_derivatives( ---------- twiss : xtrack.TwissTable Twiss results of the `xtrack.Line` configuration. - bunch_intensity : float - Bunch intensity [particles per bunch]. + total_beam_intensity : int + The beam or bunch intensity, in [particles per bunch]. input_emittances : tuple[float, float, float] The bunch's starting geometric emittances in the horizontal, vertical and longitudinal planes, in [m]. @@ -127,7 +127,7 @@ def _ibs_rates_and_emittance_derivatives( # Ask to compute the IBS growth rates (this function logs so no need to do it here) ibs_growth_rates = twiss.get_ibs_growth_rates( formalism=formalism, - total_beam_intensity=bunch_intensity, + total_beam_intensity=total_beam_intensity, gemitt_x=input_emittance_x, gemitt_y=input_emittance_y, sigma_delta=sigma_delta, @@ -161,7 +161,7 @@ def _ibs_rates_and_emittance_derivatives( def compute_emittance_evolution( twiss: xt.TwissTable, - bunch_intensity: float, + total_beam_intensity: int, initial_emittances: tuple = None, emittance_coupling_factor: float = 0, emittance_constraint: Literal["Coupling", "Excitation"] = "Coupling", @@ -185,8 +185,8 @@ def compute_emittance_evolution( ---------- twiss : object Twiss object of the ring. - bunch_intensity : float - Bunch intensity [particles per bunch]. + total_beam_intensity : int + The beam or bunch intensity, in [particles per bunch]. initial_emittances : tuple of floats, optional Initial values for the horizontal, vertical, and longitudinal emittances. If None, the equilibrium emittances from the Twiss object @@ -306,7 +306,7 @@ def compute_emittance_evolution( # Compute IBS growth rates and emittance derivatives ibs_growth_rates, emittance_derivatives = _ibs_rates_and_emittance_derivatives( twiss, - bunch_intensity, + total_beam_intensity, current_emittances, initial_emittances=initial_emittances, emittance_coupling_factor=emittance_coupling_factor, From 02124e50b09c7f3d54d594b5992edc8e7a96fa98 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 14:36:37 +0100 Subject: [PATCH 028/137] pass kwargs to IBS growth rates method, document in docstring --- xfields/ibs/_equilibrium_emittance.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index ee87efe1..941db3fc 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -86,6 +86,10 @@ def _ibs_rates_and_emittance_derivatives( Ratio of the RMS bunch length to the RMS momentum spread. If provided, allows accounting for a perturbed longitudinal distrubtion due to bunch lengthening or a microwave instability. Default is None. + **kwargs : dict + Keyword arguments are passed to the growth rates computation method of + the chosen IBS formalism implementation. See the formalism classes in + the ``xfields.ibs._analytical`` for more details. Returns ------- @@ -133,6 +137,7 @@ def _ibs_rates_and_emittance_derivatives( sigma_delta=sigma_delta, bunch_length=sigma_zeta, # 1 sigma_{zeta,RMS} bunch length bunched=True, + **kwargs, ) # ---------------------------------------------------------------------------------------------- # Computing the emittance time derivatives analytically. From 4d66bb899cb92e136448bc887668694633a69c86 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 14:36:57 +0100 Subject: [PATCH 029/137] remove bunched=True as it is default, and with kwargs passing the user can choose it --- xfields/ibs/_equilibrium_emittance.py | 1 - 1 file changed, 1 deletion(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 941db3fc..d684dcf1 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -136,7 +136,6 @@ def _ibs_rates_and_emittance_derivatives( gemitt_y=input_emittance_y, sigma_delta=sigma_delta, bunch_length=sigma_zeta, # 1 sigma_{zeta,RMS} bunch length - bunched=True, **kwargs, ) # ---------------------------------------------------------------------------------------------- From ff816241261de5df3317dcc678edef5f0d1ac2d9 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 14:38:08 +0100 Subject: [PATCH 030/137] no default for formalism, place it as second arg to mimic API pf IBS grates --- xfields/ibs/_equilibrium_emittance.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index d684dcf1..1649c214 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -59,9 +59,9 @@ def as_tuple(self) -> tuple[float, float, float]: def _ibs_rates_and_emittance_derivatives( twiss: xt.TwissTable, + formalism: Literal["Nagaitsev", "Bjorken-Mtingwa", "B&M"], total_beam_intensity: int, input_emittances: tuple[float, float, float], - formalism: Literal["Nagaitsev", "Bjorken-Mtingwa", "B&M"] = "Nagaitsev", longitudinal_emittance_ratio: float = None, **kwargs, ) -> tuple[IBSGrowthRates, EmittanceTimeDerivatives]: @@ -73,15 +73,15 @@ def _ibs_rates_and_emittance_derivatives( ---------- twiss : xtrack.TwissTable Twiss results of the `xtrack.Line` configuration. + formalism : str + Which formalism to use for the computation of the IBS growth rates. + Can be ``Nagaitsev`` or ``Bjorken-Mtingwa`` (also accepts ``B&M``), + case-insensitively. total_beam_intensity : int The beam or bunch intensity, in [particles per bunch]. input_emittances : tuple[float, float, float] The bunch's starting geometric emittances in the horizontal, vertical and longitudinal planes, in [m]. - formalism : str - Which formalism to use for the computation of the IBS growth rates. - Can be ``Nagaitsev`` or ``Bjorken-Mtingwa`` (also accepts ``B&M``), - case-insensitively. longitudinal_emittance_ratio : float, optional Ratio of the RMS bunch length to the RMS momentum spread. If provided, allows accounting for a perturbed longitudinal distrubtion due to From d94f1a9c15050c62ff8a9c25dd35a7ac23815eda Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 14:39:14 +0100 Subject: [PATCH 031/137] add formalism to main function as the user NEEDS to be able to choose this --- xfields/ibs/_equilibrium_emittance.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 1649c214..beda8d11 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -165,6 +165,7 @@ def _ibs_rates_and_emittance_derivatives( def compute_emittance_evolution( twiss: xt.TwissTable, + formalism: Literal["Nagaitsev", "Bjorken-Mtingwa", "B&M"], total_beam_intensity: int, initial_emittances: tuple = None, emittance_coupling_factor: float = 0, @@ -189,6 +190,10 @@ def compute_emittance_evolution( ---------- twiss : object Twiss object of the ring. + formalism : str + Which formalism to use for the computation of the IBS growth rates. + Can be ``Nagaitsev`` or ``Bjorken-Mtingwa`` (also accepts ``B&M``), + case-insensitively. total_beam_intensity : int The beam or bunch intensity, in [particles per bunch]. initial_emittances : tuple of floats, optional From f4fa9db831085f6d446debf02361c6b5d8e47e07 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 14:48:11 +0100 Subject: [PATCH 032/137] some walkthrough --- xfields/ibs/_equilibrium_emittance.py | 47 +++++++++++++-------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index beda8d11..855dcb97 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -188,8 +188,8 @@ def compute_emittance_evolution( Parameters ---------- - twiss : object - Twiss object of the ring. + twiss : xtrack.TwissTable + Twiss results of the `xtrack.Line` configuration. formalism : str Which formalism to use for the computation of the IBS growth rates. Can be ``Nagaitsev`` or ``Bjorken-Mtingwa`` (also accepts ``B&M``), @@ -241,6 +241,9 @@ def compute_emittance_evolution( T_z : list of float Longitudinal IBS growth rates computed over all the time steps. """ + # ---------------------------------------------------------------------------------------------- + # Handle initial transverse emittances and potential effect of coupling / excitation constraints + # TODO: I don't like this, would rather force the user to provide gemitt_x, gemitt_y & gemitt_zeta if initial_emittances is None: print("Emittances from the Twiss object are being used.") emittance_x, emittance_y, emittance_z = ( @@ -250,25 +253,11 @@ def compute_emittance_evolution( ) # If emittance_coupling_factor is non zero, then natural emittance is # modified accordingly + # fmt: off if emittance_coupling_factor != 0 and emittance_constraint.lower() == "coupling": - # The convention used is valid for arbitrary damping partition - # numbers and emittance_coupling_factor. - emittance_y = ( - emittance_x - * emittance_coupling_factor - / ( - 1 - + emittance_coupling_factor - * twiss.partition_numbers[1] - / twiss.partition_numbers[0] - ) - ) - emittance_x *= 1 / ( - 1 - + emittance_coupling_factor - * twiss.partition_numbers[1] - / twiss.partition_numbers[0] - ) + # The convention used is valid for arbitrary damping partition numbers and emittance_coupling_factor. + emittance_y = emittance_x * emittance_coupling_factor / (1 + emittance_coupling_factor * twiss.partition_numbers[1] / twiss.partition_numbers[0]) + emittance_x *= 1 / (1 + emittance_coupling_factor * twiss.partition_numbers[1] / twiss.partition_numbers[0]) if emittance_coupling_factor != 0 and emittance_constraint.lower() == "excitation": # The convention used only enforce a constraint on the vertical @@ -276,7 +265,9 @@ def compute_emittance_evolution( emittance_y = emittance_x * emittance_coupling_factor else: emittance_x, emittance_y, emittance_z = initial_emittances - + # fmt: on + # ---------------------------------------------------------------------------------------------- + # Handle initial longitudinal emittance and potential effect of bunch lengthening sigma_zeta = (emittance_z * twiss.bets0) ** 0.5 sigma_delta = (emittance_z / twiss.bets0) ** 0.5 if input_sigma_zeta is not None: @@ -297,7 +288,8 @@ def compute_emittance_evolution( "Input of 'input_sigma_zeta' or 'input_sigma_delta' provided, but " "not of 'initial_emittances'. Please provide 'initial_emittances'." ) - + # ---------------------------------------------------------------------------------------------- + # Start structures to store the iterative results until convergence time = [] emittances_x_list, emittances_y_list, emittances_z_list = [], [], [] T_x, T_y, T_z = [], [], [] @@ -307,7 +299,13 @@ def compute_emittance_evolution( current_emittances = np.array([emittance_x, emittance_y, emittance_z]) it = 0 # Iteration counter - + # ---------------------------------------------------------------------------------------------- + # Start the iterative process until convergence: + # - Compute IBS rates and emittance time derivatives + # - Update emittances using the time derivatives and time step + # - Enforce transverse / longitudinal constraints if specified + # - Store all intermediate results for this time step + # - Compute tolerance and check for convergence while tol > rtol: # Print convergence progress sys.stdout.write("\rConvergence = {:.1f}%".format(100 * rtol / tol)) @@ -361,7 +359,8 @@ def compute_emittance_evolution( time_step = 0.01 / np.max((ibs_growth_rates, twiss.damping_constants_s)) it += 1 - + # ---------------------------------------------------------------------------------------------- + # Return the results print("\nConverged!") return ( np.cumsum(time), From 447a1040af717d9f0d4fa2344a53fee2a51428ec Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 14:55:40 +0100 Subject: [PATCH 033/137] reworking of the docstring, part1 --- xfields/ibs/_equilibrium_emittance.py | 33 +++++++++++++++------------ 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 855dcb97..04ca6505 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -167,7 +167,7 @@ def compute_emittance_evolution( twiss: xt.TwissTable, formalism: Literal["Nagaitsev", "Bjorken-Mtingwa", "B&M"], total_beam_intensity: int, - initial_emittances: tuple = None, + initial_emittances: tuple[float, float, float] = None, emittance_coupling_factor: float = 0, emittance_constraint: Literal["Coupling", "Excitation"] = "Coupling", input_sigma_zeta: float = None, @@ -196,21 +196,26 @@ def compute_emittance_evolution( case-insensitively. total_beam_intensity : int The beam or bunch intensity, in [particles per bunch]. - initial_emittances : tuple of floats, optional - Initial values for the horizontal, vertical, and longitudinal - emittances. If None, the equilibrium emittances from the Twiss object - are used. Default is None. + initial_emittances : tuple[float, float, float], optional + The bunch's starting geometric emittances in the horizontal, + vertical and longitudinal planes, in [m]. If not provided, the + SR equilibrium emittances from the TwissTable are used. Defaults + to `None`. emittance_coupling_factor : float, optional - Emittance coupling factor, defined as the ratio of vertical to - horizontal emittance. Default is 0. + The ratio of vertical to horizontal emittances. If a value is provided + and `emittance_constraint` is set to `coupling`, then this ratio is + preserved. Defaults to 0. emittance_constraint : str, optional - Can enforces constraints on the transverse emittance based on the - emittance coupling factor. - "Coupling" corresponds to the case where the - vertical emittance is the result of linear coupling. - "Excitation" corresponds to the case where the vertical emittance is - the result of an excitation (e.g. from a feedback system). - Default is "Coupling". + If an accepted value is provided, enforces constraints on the transverse + emittances. Can be either "coupling" or "excitation", case-insensitively. + If `coupling`, vertical emittance is the result of linear coupling and is + determined from the horizontal one based on the `emittance_coupling_factor`. + If `excitation`, vertical emittance is the result of an excitation (e.g. from + a feedback system). + Defaults to "coupling", with no effect as `emittance_coupling_factor` defaults to 0. + + + input_sigma_zeta : float Used specified RMS momentum spread overwriting the natural one from the `twiss` object. Default is None. From 3e44bf52d6bddded4caa6f4a5afd73672ebb9124 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 15:02:15 +0100 Subject: [PATCH 034/137] reworking of the docstring, part2 --- xfields/ibs/_equilibrium_emittance.py | 28 +++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 04ca6505..8b166efc 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -170,8 +170,9 @@ def compute_emittance_evolution( initial_emittances: tuple[float, float, float] = None, emittance_coupling_factor: float = 0, emittance_constraint: Literal["Coupling", "Excitation"] = "Coupling", - input_sigma_zeta: float = None, - input_sigma_delta: float = None, + # TODO: move these two (still optional) below gemitt_x, gemitt_y, gemitt_zeta when modify that + sigma_zeta: float = None, + sigma_delta: float = None, rtol: float = 1e-6, **kwargs, ): @@ -213,15 +214,14 @@ def compute_emittance_evolution( If `excitation`, vertical emittance is the result of an excitation (e.g. from a feedback system). Defaults to "coupling", with no effect as `emittance_coupling_factor` defaults to 0. + sigma_zeta : float, optional + The RMS bunch length. If provided, overwrites the one computed from + the longitudinal emittance. Defaults to `None`. + sigma_delta : float, optional + The RMS momentum spread of the bunch. If provided, overwrites the one + computed from the longitudinal emittance. Defaults to `None`. - - input_sigma_zeta : float - Used specified RMS momentum spread overwriting the natural one from - the `twiss` object. Default is None. - input_sigma_delta : float - Used specified RMS momentum spread overwriting the natural one from - the `twiss` object. Default is None. natural_emittances : tuple of floats, optional Natural emittances (horizontal, vertical, longitudinal). If None, they are taken from the `twiss` object. Default is None. @@ -275,20 +275,20 @@ def compute_emittance_evolution( # Handle initial longitudinal emittance and potential effect of bunch lengthening sigma_zeta = (emittance_z * twiss.bets0) ** 0.5 sigma_delta = (emittance_z / twiss.bets0) ** 0.5 - if input_sigma_zeta is not None: + if sigma_zeta is not None: warnings.warn( "'input_sigma_zeta' is specified, make sure it remains " "consistent with 'initial_emittances'." ) - sigma_zeta = input_sigma_zeta - elif input_sigma_delta is not None: + sigma_zeta = sigma_zeta + elif sigma_delta is not None: warnings.warn( "'input_sigma_delta' is specified, make sure it remains " "consistent with 'initial_emittances'." ) - sigma_delta = input_sigma_delta + sigma_delta = sigma_delta longitudinal_emittance_ratio = sigma_zeta / sigma_delta - if input_sigma_zeta is not None or input_sigma_delta is not None: + if sigma_zeta is not None or sigma_delta is not None: assert initial_emittances is not None, ( "Input of 'input_sigma_zeta' or 'input_sigma_delta' provided, but " "not of 'initial_emittances'. Please provide 'initial_emittances'." From 6f89e9804c8a245235e3c67a8fa9fc656eb559c0 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 15:03:12 +0100 Subject: [PATCH 035/137] natural emittances is not a param anymore, remove from docstring --- xfields/ibs/_equilibrium_emittance.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 8b166efc..912c6eaf 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -222,9 +222,8 @@ def compute_emittance_evolution( computed from the longitudinal emittance. Defaults to `None`. - natural_emittances : tuple of floats, optional - Natural emittances (horizontal, vertical, longitudinal). - If None, they are taken from the `twiss` object. Default is None. + + rtol : float, optional Relative tolerance for equilibrium emittance convergence. Default is 1e-6. From 7a48a4c5109bcd3434c228ce2635e982e31a11e0 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 15:04:40 +0100 Subject: [PATCH 036/137] clarify rtol --- xfields/ibs/_equilibrium_emittance.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 912c6eaf..d6bcfd5e 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -220,13 +220,11 @@ def compute_emittance_evolution( sigma_delta : float, optional The RMS momentum spread of the bunch. If provided, overwrites the one computed from the longitudinal emittance. Defaults to `None`. - - - - rtol : float, optional - Relative tolerance for equilibrium emittance convergence. - Default is 1e-6. + Relative tolerance to determine when convergence is reached: if the relative + difference between the computed emittances and those at the previous step is + below `rtol`, then convergence is considered achieved. Defaults to 1e-6. + Returns ------- From 369f660dfcf77e43aafd3f24e1602c270dc6a636 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 15:07:26 +0100 Subject: [PATCH 037/137] clear up call to _ibs_rates_and_emittance_derivatives which was using disappeared parameters lost as kwargs were not used, not the case anymore --- xfields/ibs/_equilibrium_emittance.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index d6bcfd5e..cf707e20 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -176,6 +176,7 @@ def compute_emittance_evolution( rtol: float = 1e-6, **kwargs, ): + # TODO: rework this main chunk of docstring """ Compute the evolution of beam emittances due to IBS until convergence. By default, the function assumes the emittances from the Twiss object. @@ -224,7 +225,10 @@ def compute_emittance_evolution( Relative tolerance to determine when convergence is reached: if the relative difference between the computed emittances and those at the previous step is below `rtol`, then convergence is considered achieved. Defaults to 1e-6. - + **kwargs : dict + Keyword arguments are passed to the growth rates computation method of + the chosen IBS formalism implementation. See the formalism classes in + the ``xfields.ibs._analytical`` for more details. Returns ------- @@ -314,12 +318,10 @@ def compute_emittance_evolution( # Compute IBS growth rates and emittance derivatives ibs_growth_rates, emittance_derivatives = _ibs_rates_and_emittance_derivatives( - twiss, - total_beam_intensity, - current_emittances, - initial_emittances=initial_emittances, - emittance_coupling_factor=emittance_coupling_factor, - emittance_constraint=emittance_constraint, + twiss=twiss, + formalism=formalism, + total_beam_intensity=total_beam_intensity, + input_emittances=current_emittances, longitudinal_emittance_ratio=longitudinal_emittance_ratio, ) # Make sure we have them as tuples for below From d5b3650859e43ec764071ae7041297efb25f5f52 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 15:07:42 +0100 Subject: [PATCH 038/137] propagate kwargs to inner function --- xfields/ibs/_equilibrium_emittance.py | 1 + 1 file changed, 1 insertion(+) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index cf707e20..d725acb8 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -323,6 +323,7 @@ def compute_emittance_evolution( total_beam_intensity=total_beam_intensity, input_emittances=current_emittances, longitudinal_emittance_ratio=longitudinal_emittance_ratio, + **kwargs, ) # Make sure we have them as tuples for below ibs_growth_rates = ibs_growth_rates.as_tuple() From f514a23efb1af4f698dadc1ac6bc1874ee1bcba9 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 15:16:27 +0100 Subject: [PATCH 039/137] rework the whole emittance constraint thing in docstring, should be clearer --- xfields/ibs/_equilibrium_emittance.py | 32 ++++++++++++--------------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index d725acb8..30738984 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -78,7 +78,7 @@ def _ibs_rates_and_emittance_derivatives( Can be ``Nagaitsev`` or ``Bjorken-Mtingwa`` (also accepts ``B&M``), case-insensitively. total_beam_intensity : int - The beam or bunch intensity, in [particles per bunch]. + The bunch intensity, in [particles per bunch]. input_emittances : tuple[float, float, float] The bunch's starting geometric emittances in the horizontal, vertical and longitudinal planes, in [m]. @@ -178,15 +178,6 @@ def compute_emittance_evolution( ): # TODO: rework this main chunk of docstring """ - Compute the evolution of beam emittances due to IBS until convergence. - By default, the function assumes the emittances from the Twiss object. - They can also be specified as well as different natural emittances. - The emittance evolution can be constrained to follow two scenarios: - - A vertical emittance originating from linear coupling. - - A vertical emittance originating from an excitation. - The impact from the longitudinal impedance (e.g. bunch lengthening or - microwave instability) can be accounted for by specifying the RMS bunch - length and momentum spread. Parameters ---------- @@ -197,7 +188,7 @@ def compute_emittance_evolution( Can be ``Nagaitsev`` or ``Bjorken-Mtingwa`` (also accepts ``B&M``), case-insensitively. total_beam_intensity : int - The beam or bunch intensity, in [particles per bunch]. + The bunch intensity, in [particles per bunch]. initial_emittances : tuple[float, float, float], optional The bunch's starting geometric emittances in the horizontal, vertical and longitudinal planes, in [m]. If not provided, the @@ -205,16 +196,21 @@ def compute_emittance_evolution( to `None`. emittance_coupling_factor : float, optional The ratio of vertical to horizontal emittances. If a value is provided - and `emittance_constraint` is set to `coupling`, then this ratio is - preserved. Defaults to 0. + it is taken into account for the evolution of emittances. See the next + parameter for possible scenarios. Defaults to 0. emittance_constraint : str, optional If an accepted value is provided, enforces constraints on the transverse emittances. Can be either "coupling" or "excitation", case-insensitively. - If `coupling`, vertical emittance is the result of linear coupling and is - determined from the horizontal one based on the `emittance_coupling_factor`. - If `excitation`, vertical emittance is the result of an excitation (e.g. from - a feedback system). - Defaults to "coupling", with no effect as `emittance_coupling_factor` defaults to 0. + Defaults to "coupling". + - If `coupling`, vertical emittance is the result of linear coupling. In + this case both the vertical and horizontal emittances are altered and + determined based on the value of `emittance_coupling_factor` such that + the total transverse IS preserved. + - If `excitation`, vertical emittance is the result of an excitation + (e.g. from a feedback system) and is determined from the horizontal + emittance based on the value of `emittance_coupling_factor`. In this + case the total transverse emittance is NOT preserved. + This has no effect by default as `emittance_coupling_factor` defaults to 0. sigma_zeta : float, optional The RMS bunch length. If provided, overwrites the one computed from the longitudinal emittance. Defaults to `None`. From 7dcbfe6cf90870f564a769ceedaa3046485af832 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 15:24:23 +0100 Subject: [PATCH 040/137] commented out code for Table return, and global quantities included --- xfields/ibs/_equilibrium_emittance.py | 35 ++++++++++++++++++--------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 30738984..fd63fcf3 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -361,18 +361,9 @@ def compute_emittance_evolution( it += 1 # ---------------------------------------------------------------------------------------------- - # Return the results + # Return a table with the results and return it print("\nConverged!") - return ( - np.cumsum(time), - emittances_x_list, - emittances_y_list, - emittances_z_list, - T_x, - T_y, - T_z, - ) - # return Table( + # result_table = Table( # data={ # "time": np.cumsum(time), # "gemitt_x": emittances_x_list, @@ -384,3 +375,25 @@ def compute_emittance_evolution( # }, # index="time", # ) + # Provide global quantities as well + # result_table._data.update( + # { + # "damping_constants_s": twiss.damping_constants_s, + # "eq_gemitt_x": emittances_x_list[-1], + # "eq_gemitt_x": emittances_y_list[-1], + # "eq_gemitt_x": emittances_z_list[-1], + # "sr_eq_gemitt_x": twiss.eq_gemitt_x, + # "sr_eq_gemitt_y": twiss.eq_gemitt_y, + # "eq_nemitt_zeta": twiss.eq_nemitt_zeta, + # } + # ) + # return result_table + return ( + np.cumsum(time), + emittances_x_list, + emittances_y_list, + emittances_z_list, + T_x, + T_y, + T_z, + ) From d73c041149ffd54dfb98aacb5fb2477ccf4119a2 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 15:34:18 +0100 Subject: [PATCH 041/137] commented out docstring for when we return a table --- xfields/ibs/_equilibrium_emittance.py | 31 ++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index fd63fcf3..01d54113 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -167,7 +167,14 @@ def compute_emittance_evolution( twiss: xt.TwissTable, formalism: Literal["Nagaitsev", "Bjorken-Mtingwa", "B&M"], total_beam_intensity: int, + # TODO: want to force providing gemitt_x, gemitt_y & gemitt_zeta instead of this initial_emittances (can allow nemitt_x and add checks / conversions) initial_emittances: tuple[float, float, float] = None, + # gemitt_x: float = None, + # nemitt_x: float = None, + # gemitt_y: float = None, + # nemitt_y: float = None, + # gemitt_zeta: float = None, + # nemitt_zeta: float = None, emittance_coupling_factor: float = 0, emittance_constraint: Literal["Coupling", "Excitation"] = "Coupling", # TODO: move these two (still optional) below gemitt_x, gemitt_y, gemitt_zeta when modify that @@ -243,6 +250,27 @@ def compute_emittance_evolution( T_z : list of float Longitudinal IBS growth rates computed over all the time steps. """ + # Returns + # ------- + # xtrack.TwissTable + # The convergence calculations results. The table contains the following + # columns, as time-step by time-step quantities: + # - time: time values at which quantities are computed, in [s] + # - gemitt_x: horizontal geometric emittance values, in [m] + # - gemitt_y: vertical geometric emittance values, in [m] + # - gemitt_zeta: longitudinal geometric emittance values, in [m] + # - Tx: horizontal IBS growth rate, in [s^-1] + # - Ty: vertical IBS growth rate, in [s^-1] + # - Tz: longitudinal IBS growth rate, in [s^-1] + # The table also contains the following global quantities: + # - damping_constants_s: radiation damping constants per second used for the calculations + # - partition_numbers: damping partition numbers used for the calculations + # - eq_gemitt_x: final horizontal equilibrium geometric emittance converged to, in [m] + # - eq_gemitt_y: final vertical equilibrium geometric emittance converged to, in [m] + # - eq_gemitt_zeta: final longitudinal equilibrium geometric emittance converged to, in [m] + # - sr_eq_gemitt_x: horizontal equilibrium geometric emittance from synchrotron radiation used, in [m] + # - sr_eq_gemitt_y: vertical equilibrium geometric emittance from synchrotron radiation used, in [m] + # - eq_nemitt_zeta: longitudinal equilibrium normalized emittance from synchrotron radiation used, in [m] # ---------------------------------------------------------------------------------------------- # Handle initial transverse emittances and potential effect of coupling / excitation constraints # TODO: I don't like this, would rather force the user to provide gemitt_x, gemitt_y & gemitt_zeta @@ -368,7 +396,7 @@ def compute_emittance_evolution( # "time": np.cumsum(time), # "gemitt_x": emittances_x_list, # "gemitt_y": emittances_y_list, - # "gemitt_z": emittances_z_list, + # "gemitt_zeta": emittances_z_list, # "Tx": Tx, # "Ty": Ty, # "Tz": Tz, @@ -379,6 +407,7 @@ def compute_emittance_evolution( # result_table._data.update( # { # "damping_constants_s": twiss.damping_constants_s, + # "partition_numbers": twiss.partition_numbers, # "eq_gemitt_x": emittances_x_list[-1], # "eq_gemitt_x": emittances_y_list[-1], # "eq_gemitt_x": emittances_z_list[-1], From 6effcb5abf57e0deb8dbc44abb4802abb8335cde Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 15:36:16 +0100 Subject: [PATCH 042/137] use new parameter names in warning --- xfields/ibs/_equilibrium_emittance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 01d54113..11c12b55 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -302,13 +302,13 @@ def compute_emittance_evolution( sigma_delta = (emittance_z / twiss.bets0) ** 0.5 if sigma_zeta is not None: warnings.warn( - "'input_sigma_zeta' is specified, make sure it remains " + "'sigma_zeta' is specified, make sure it remains " "consistent with 'initial_emittances'." ) sigma_zeta = sigma_zeta elif sigma_delta is not None: warnings.warn( - "'input_sigma_delta' is specified, make sure it remains " + "'sigma_delta' is specified, make sure it remains " "consistent with 'initial_emittances'." ) sigma_delta = sigma_delta From 69c577af4bafcf9af50fb07d1e591dbbc42a0f1e Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 15:40:58 +0100 Subject: [PATCH 043/137] some harmless renaming --- xfields/ibs/_equilibrium_emittance.py | 44 +++++++++++++-------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 11c12b55..f3da64d3 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -320,15 +320,14 @@ def compute_emittance_evolution( ) # ---------------------------------------------------------------------------------------------- # Start structures to store the iterative results until convergence - time = [] - emittances_x_list, emittances_y_list, emittances_z_list = [], [], [] - T_x, T_y, T_z = [], [], [] - + tolerance = np.inf time_step = twiss.T_rev0 # Initial time step is the revolution period - tol = np.inf + time_deltas = [] + res_gemitt_x, res_gemitt_y, res_gemitt_zeta = [], [], [] + T_x, T_y, T_z = [], [], [] current_emittances = np.array([emittance_x, emittance_y, emittance_z]) - it = 0 # Iteration counter + iterations = 0 # Iteration counter # ---------------------------------------------------------------------------------------------- # Start the iterative process until convergence: # - Compute IBS rates and emittance time derivatives @@ -336,9 +335,9 @@ def compute_emittance_evolution( # - Enforce transverse / longitudinal constraints if specified # - Store all intermediate results for this time step # - Compute tolerance and check for convergence - while tol > rtol: + while tolerance > rtol: # Print convergence progress - sys.stdout.write("\rConvergence = {:.1f}%".format(100 * rtol / tol)) + sys.stdout.write(f"\rIteration {iterations} - convergence = {100 * rtol / tolerance:.1f}%") # Compute IBS growth rates and emittance derivatives ibs_growth_rates, emittance_derivatives = _ibs_rates_and_emittance_derivatives( @@ -353,7 +352,7 @@ def compute_emittance_evolution( ibs_growth_rates = ibs_growth_rates.as_tuple() emittance_derivatives = emittance_derivatives.as_tuple() - # Update emittances + # Update current emittances - add the time step * emittance time derivatives current_emittances += np.array(emittance_derivatives) * time_step # Enforce constraints if specified @@ -370,26 +369,27 @@ def compute_emittance_evolution( current_emittances[1] = forced_emittance_y # Append current values to lists - time.append(time_step) - emittances_x_list.append(current_emittances[0]) - emittances_y_list.append(current_emittances[1]) - emittances_z_list.append(current_emittances[2]) + time_deltas.append(time_step) + res_gemitt_x.append(current_emittances[0]) + res_gemitt_y.append(current_emittances[1]) + res_gemitt_zeta.append(current_emittances[2]) T_x.append(ibs_growth_rates[0]) T_y.append(ibs_growth_rates[1]) T_z.append(ibs_growth_rates[2]) - # Compute tolerance - if it > 0: - tol = np.max(np.abs((current_emittances - previous_emittances) / previous_emittances)) + # Compute tolerance (but not at first step since there is no previous value) + if iterations > 0: + tolerance = np.max(np.abs((current_emittances - previous_emittances) / previous_emittances)) + # Store current emittances for the next iteration previous_emittances = current_emittances.copy() # Update time step for the next iteration time_step = 0.01 / np.max((ibs_growth_rates, twiss.damping_constants_s)) - it += 1 + iterations += 1 # ---------------------------------------------------------------------------------------------- - # Return a table with the results and return it + # We have exited the loop, we have converged. Construct a Table with the results and return it print("\nConverged!") # result_table = Table( # data={ @@ -418,10 +418,10 @@ def compute_emittance_evolution( # ) # return result_table return ( - np.cumsum(time), - emittances_x_list, - emittances_y_list, - emittances_z_list, + np.cumsum(time_deltas), + res_gemitt_x, + res_gemitt_y, + res_gemitt_zeta, T_x, T_y, T_z, From e8f6948f373f4ad1e67d8b560e05cc186cce40c6 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 15:43:43 +0100 Subject: [PATCH 044/137] some todo --- xfields/ibs/_equilibrium_emittance.py | 1 + 1 file changed, 1 insertion(+) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index f3da64d3..8b7cf541 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -163,6 +163,7 @@ def _ibs_rates_and_emittance_derivatives( ) +# TODO: find a better, more explicit name for this def compute_emittance_evolution( twiss: xt.TwissTable, formalism: Literal["Nagaitsev", "Bjorken-Mtingwa", "B&M"], From b876bb1386c863573811caf6943cb5440c8f497c Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 15:46:38 +0100 Subject: [PATCH 045/137] put back this piece of docstring --- xfields/ibs/_equilibrium_emittance.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 8b7cf541..6d1d8bda 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -186,6 +186,15 @@ def compute_emittance_evolution( ): # TODO: rework this main chunk of docstring """ + Compute the evolution of beam emittances due to IBS until convergence. + By default, the function assumes the emittances from the Twiss object. + They can also be specified as well as different natural emittances. + The emittance evolution can be constrained to follow two scenarios: + - A vertical emittance originating from linear coupling. + - A vertical emittance originating from an excitation. + The impact from the longitudinal impedance (e.g. bunch lengthening or + microwave instability) can be accounted for by specifying the RMS bunch + length and momentum spread. Parameters ---------- From cbde1b3dfe2875be20f0bcb7a632ac6aec805847 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 15:54:42 +0100 Subject: [PATCH 046/137] fix my mess of overriding names --- xfields/ibs/_equilibrium_emittance.py | 32 +++++++++++---------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 6d1d8bda..688b6ed2 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -177,10 +177,10 @@ def compute_emittance_evolution( # gemitt_zeta: float = None, # nemitt_zeta: float = None, emittance_coupling_factor: float = 0, - emittance_constraint: Literal["Coupling", "Excitation"] = "Coupling", + emittance_constraint: Literal["coupling", "excitation"] = "coupling", # TODO: move these two (still optional) below gemitt_x, gemitt_y, gemitt_zeta when modify that - sigma_zeta: float = None, - sigma_delta: float = None, + overwrite_sigma_zeta: float = None, + overwrite_sigma_delta: float = None, rtol: float = 1e-6, **kwargs, ): @@ -228,10 +228,10 @@ def compute_emittance_evolution( emittance based on the value of `emittance_coupling_factor`. In this case the total transverse emittance is NOT preserved. This has no effect by default as `emittance_coupling_factor` defaults to 0. - sigma_zeta : float, optional + overwrite_sigma_zeta : float, optional The RMS bunch length. If provided, overwrites the one computed from the longitudinal emittance. Defaults to `None`. - sigma_delta : float, optional + overwrite_sigma_delta : float, optional The RMS momentum spread of the bunch. If provided, overwrites the one computed from the longitudinal emittance. Defaults to `None`. rtol : float, optional @@ -310,22 +310,16 @@ def compute_emittance_evolution( # Handle initial longitudinal emittance and potential effect of bunch lengthening sigma_zeta = (emittance_z * twiss.bets0) ** 0.5 sigma_delta = (emittance_z / twiss.bets0) ** 0.5 - if sigma_zeta is not None: - warnings.warn( - "'sigma_zeta' is specified, make sure it remains " - "consistent with 'initial_emittances'." - ) - sigma_zeta = sigma_zeta - elif sigma_delta is not None: - warnings.warn( - "'sigma_delta' is specified, make sure it remains " - "consistent with 'initial_emittances'." - ) - sigma_delta = sigma_delta + if overwrite_sigma_zeta is not None: + warnings.warn("'overwrite_sigma_zeta' is specified, make sure it remains consistent with 'initial_emittances'.") + sigma_zeta = overwrite_sigma_zeta + elif overwrite_sigma_delta is not None: + warnings.warn("'sigma_delta' is specified, make sure it remains consistent with 'initial_emittances'.") + sigma_delta = overwrite_sigma_delta longitudinal_emittance_ratio = sigma_zeta / sigma_delta - if sigma_zeta is not None or sigma_delta is not None: + if overwrite_sigma_zeta is not None or overwrite_sigma_delta is not None: assert initial_emittances is not None, ( - "Input of 'input_sigma_zeta' or 'input_sigma_delta' provided, but " + "Input of 'overwrite_sigma_zeta' or 'overwrite_sigma_delta' provided, but " "not of 'initial_emittances'. Please provide 'initial_emittances'." ) # ---------------------------------------------------------------------------------------------- From 7234e332785e54def04e123350acd995d8604834 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 16:08:41 +0100 Subject: [PATCH 047/137] small correction to emittance_coupling_factor --- xfields/ibs/_equilibrium_emittance.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 688b6ed2..14687ba1 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -212,9 +212,10 @@ def compute_emittance_evolution( SR equilibrium emittances from the TwissTable are used. Defaults to `None`. emittance_coupling_factor : float, optional - The ratio of vertical to horizontal emittances. If a value is provided - it is taken into account for the evolution of emittances. See the next - parameter for possible scenarios. Defaults to 0. + A factor relating vertical to horizontal emittances. If a value is + provided, it is taken into account for the evolution of emittances. + See the next parameter for possible scenarios and how this value is + used. Defaults to 0. emittance_constraint : str, optional If an accepted value is provided, enforces constraints on the transverse emittances. Can be either "coupling" or "excitation", case-insensitively. From 6ae0a8f6810a24445d8210a72f36c1ad1c3be7d7 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 14 Jan 2025 16:16:13 +0100 Subject: [PATCH 048/137] typos --- xfields/ibs/_equilibrium_emittance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 14687ba1..9968e516 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -104,10 +104,10 @@ def _ibs_rates_and_emittance_derivatives( # TODO: bunch emittances - ask for the three separately and update docstring input_emittance_x, input_emittance_y, input_emittance_z = input_emittances assert input_emittance_x > 0.0, ( - "'input_emittance_x' should be larger than" " zero, try providing 'initial_emittances'" + "'input_emittance_x' should be larger than zero, try providing 'initial_emittances'" ) assert input_emittance_y > 0.0, ( - "'input_emittance_y' should be larger than" " zero, try providing 'initial_emittances'" + "'input_emittance_y' should be larger than zero, try providing 'initial_emittances'" ) # ---------------------------------------------------------------------------------------------- # TODO: check for SR eq emittances etc in twiss table (or in public func?) Could be: From 42a1b942becdf129b6cd6d080019abaf2ee5bc43 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 11:17:18 +0100 Subject: [PATCH 049/137] verbose flag and xobjects print --- xfields/ibs/_equilibrium_emittance.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 9968e516..7de8fabc 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -6,7 +6,6 @@ from __future__ import annotations import logging -import sys import warnings from typing import Literal @@ -182,6 +181,7 @@ def compute_emittance_evolution( overwrite_sigma_zeta: float = None, overwrite_sigma_delta: float = None, rtol: float = 1e-6, + verbose: bool = True, **kwargs, ): # TODO: rework this main chunk of docstring @@ -239,6 +239,9 @@ def compute_emittance_evolution( Relative tolerance to determine when convergence is reached: if the relative difference between the computed emittances and those at the previous step is below `rtol`, then convergence is considered achieved. Defaults to 1e-6. + verbose : bool, optional + Whether to print out information on the current iteration step and estimated + convergence progress. Defaults to `False`. **kwargs : dict Keyword arguments are passed to the growth rates computation method of the chosen IBS formalism implementation. See the formalism classes in @@ -341,8 +344,8 @@ def compute_emittance_evolution( # - Store all intermediate results for this time step # - Compute tolerance and check for convergence while tolerance > rtol: - # Print convergence progress - sys.stdout.write(f"\rIteration {iterations} - convergence = {100 * rtol / tolerance:.1f}%") + if verbose is True: # Display estimated convergence progress if asked + xo.general._print(f"Iteration {iterations} - convergence = {100 * rtol / tolerance:.1f}%", end="\r") # Compute IBS growth rates and emittance derivatives ibs_growth_rates, emittance_derivatives = _ibs_rates_and_emittance_derivatives( From 2619c33c5ffa4431a9c3de0032c7dba2a8e172ab Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 11:24:23 +0100 Subject: [PATCH 050/137] todo for emittance constraint checks --- xfields/ibs/_equilibrium_emittance.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index 7de8fabc..e18798d8 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -212,8 +212,9 @@ def compute_emittance_evolution( SR equilibrium emittances from the TwissTable are used. Defaults to `None`. emittance_coupling_factor : float, optional - A factor relating vertical to horizontal emittances. If a value is - provided, it is taken into account for the evolution of emittances. + The ratio of perturbed transverse emittances due to betatron coupling. + If a value is provided, it is taken into account for the evolution of + emittances and induced an emittance sharing between the two planes. See the next parameter for possible scenarios and how this value is used. Defaults to 0. emittance_constraint : str, optional @@ -286,6 +287,8 @@ def compute_emittance_evolution( # - sr_eq_gemitt_y: vertical equilibrium geometric emittance from synchrotron radiation used, in [m] # - eq_nemitt_zeta: longitudinal equilibrium normalized emittance from synchrotron radiation used, in [m] # ---------------------------------------------------------------------------------------------- + # TODO: Perform check for valid value of emittance_constraint but no value of emittance coupling factor + # ---------------------------------------------------------------------------------------------- # Handle initial transverse emittances and potential effect of coupling / excitation constraints # TODO: I don't like this, would rather force the user to provide gemitt_x, gemitt_y & gemitt_zeta if initial_emittances is None: From b7d9270022dcdc0efaf1905d0f21e1e6ddb1eda0 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 11:29:44 +0100 Subject: [PATCH 051/137] review changes for emittance constraint --- xfields/ibs/_equilibrium_emittance.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index e18798d8..a14defee 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -223,13 +223,16 @@ def compute_emittance_evolution( Defaults to "coupling". - If `coupling`, vertical emittance is the result of linear coupling. In this case both the vertical and horizontal emittances are altered and - determined based on the value of `emittance_coupling_factor` such that - the total transverse IS preserved. + determined based on the value of `emittance_coupling_factor` and the + damping partition numbers. If the horizontal and vertical partition + numbers are equal then the total transverse emittance is preserved. - If `excitation`, vertical emittance is the result of an excitation (e.g. from a feedback system) and is determined from the horizontal emittance based on the value of `emittance_coupling_factor`. In this case the total transverse emittance is NOT preserved. - This has no effect by default as `emittance_coupling_factor` defaults to 0. + Providing `None` or an empty string allows one to study a scenarion without + constraint. Note as `emittance_coupling_factor` defaults to 0, this parameter + has no effect unless a non-zero value is provided. overwrite_sigma_zeta : float, optional The RMS bunch length. If provided, overwrites the one computed from the longitudinal emittance. Defaults to `None`. From 34f953cecf6472af10c36ab1ee98933dacd84986 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 11:30:54 +0100 Subject: [PATCH 052/137] only transverse constraints enforced in loop --- xfields/ibs/_equilibrium_emittance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index a14defee..cd948db1 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -346,7 +346,7 @@ def compute_emittance_evolution( # Start the iterative process until convergence: # - Compute IBS rates and emittance time derivatives # - Update emittances using the time derivatives and time step - # - Enforce transverse / longitudinal constraints if specified + # - Enforce transverse constraints if specified # - Store all intermediate results for this time step # - Compute tolerance and check for convergence while tolerance > rtol: From 77f4008fe40aca4b67de833b4566e80f33cae622 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 11:32:44 +0100 Subject: [PATCH 053/137] correct info on default value --- xfields/ibs/_equilibrium_emittance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index cd948db1..b404dff0 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -245,7 +245,7 @@ def compute_emittance_evolution( below `rtol`, then convergence is considered achieved. Defaults to 1e-6. verbose : bool, optional Whether to print out information on the current iteration step and estimated - convergence progress. Defaults to `False`. + convergence progress. Defaults to `True`. **kwargs : dict Keyword arguments are passed to the growth rates computation method of the chosen IBS formalism implementation. See the formalism classes in From 51749da0d3319488cfabb301d27807df6548c92d Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 11:37:27 +0100 Subject: [PATCH 054/137] typo --- xfields/ibs/_equilibrium_emittance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index b404dff0..e93173e4 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -288,7 +288,7 @@ def compute_emittance_evolution( # - eq_gemitt_zeta: final longitudinal equilibrium geometric emittance converged to, in [m] # - sr_eq_gemitt_x: horizontal equilibrium geometric emittance from synchrotron radiation used, in [m] # - sr_eq_gemitt_y: vertical equilibrium geometric emittance from synchrotron radiation used, in [m] - # - eq_nemitt_zeta: longitudinal equilibrium normalized emittance from synchrotron radiation used, in [m] + # - sr_eq_nemitt_zeta: longitudinal equilibrium normalized emittance from synchrotron radiation used, in [m] # ---------------------------------------------------------------------------------------------- # TODO: Perform check for valid value of emittance_constraint but no value of emittance coupling factor # ---------------------------------------------------------------------------------------------- From 39f95045b4e0aa1af269ece274655eebd1d4557f Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 11:40:09 +0100 Subject: [PATCH 055/137] add todo for api changes --- xfields/ibs/_equilibrium_emittance.py | 1 + 1 file changed, 1 insertion(+) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index e93173e4..abb8c777 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -332,6 +332,7 @@ def compute_emittance_evolution( "Input of 'overwrite_sigma_zeta' or 'overwrite_sigma_delta' provided, but " "not of 'initial_emittances'. Please provide 'initial_emittances'." ) + # TODO: potentially here we redefine the emittance_z here as the ratio after the change here (product of the two sigmas) # ---------------------------------------------------------------------------------------------- # Start structures to store the iterative results until convergence tolerance = np.inf From 5d77c42fdb910fbde32a1ecfa2dc681fdf7abd96 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 11:40:38 +0100 Subject: [PATCH 056/137] should be elif --- xfields/ibs/_equilibrium_emittance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium_emittance.py index abb8c777..4e12801c 100644 --- a/xfields/ibs/_equilibrium_emittance.py +++ b/xfields/ibs/_equilibrium_emittance.py @@ -379,7 +379,7 @@ def compute_emittance_evolution( current_emittances[0] = forced_emittance_x current_emittances[1] = forced_emittance_y - if emittance_constraint.lower() == "excitation": + elif emittance_constraint.lower() == "excitation": forced_emittance_y = current_emittances[0] * emittance_coupling_factor current_emittances[1] = forced_emittance_y From db29a81bd16f25cf304dc7ac3a6aafa08dd605b4 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 14:31:23 +0100 Subject: [PATCH 057/137] file rename --- xfields/ibs/__init__.py | 2 +- xfields/ibs/{_equilibrium_emittance.py => _equilibrium.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename xfields/ibs/{_equilibrium_emittance.py => _equilibrium.py} (100%) diff --git a/xfields/ibs/__init__.py b/xfields/ibs/__init__.py index 2f9356a9..e5ac967a 100644 --- a/xfields/ibs/__init__.py +++ b/xfields/ibs/__init__.py @@ -6,7 +6,7 @@ from ._analytical import BjorkenMtingwaIBS, IBSGrowthRates, NagaitsevIBS from ._api import configure_intrabeam_scattering, get_intrabeam_scattering_growth_rates from ._kicks import IBSAnalyticalKick, IBSKineticKick -from ._equilibrium_emittance import compute_emittance_evolution +from ._equilibrium import compute_emittance_evolution __all__ = [ "BjorkenMtingwaIBS", diff --git a/xfields/ibs/_equilibrium_emittance.py b/xfields/ibs/_equilibrium.py similarity index 100% rename from xfields/ibs/_equilibrium_emittance.py rename to xfields/ibs/_equilibrium.py From 21b85a0de8fab08173d358e8f448fa716cf0df69 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 14:36:33 +0100 Subject: [PATCH 058/137] return a Table, add return docstring and build --- xfields/ibs/_equilibrium.py | 115 ++++++++++++++---------------------- 1 file changed, 45 insertions(+), 70 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 4e12801c..16652789 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -183,7 +183,7 @@ def compute_emittance_evolution( rtol: float = 1e-6, verbose: bool = True, **kwargs, -): +) -> Table: # TODO: rework this main chunk of docstring """ Compute the evolution of beam emittances due to IBS until convergence. @@ -253,42 +253,26 @@ def compute_emittance_evolution( Returns ------- - time : numpy.ndarray - Computed time steps [s]. - emittances_x : list of float - Horizontal emittance values computed over all the time steps. - emittances_y : list of float - Vertical emittance values computed over all the time steps. - emittances_z : list of float - Longitudinal emittance values computed over all the time steps. - T_x : list of float - Horizontal IBS growth rates computed over all the time steps. - T_y : list of float - Vertical IBS growth rates computed over all the time steps. - T_z : list of float - Longitudinal IBS growth rates computed over all the time steps. + xtrack.TwissTable + The convergence calculations results. The table contains the following + columns, as time-step by time-step quantities: + - time: time values at which quantities are computed, in [s] + - gemitt_x: horizontal geometric emittance values, in [m] + - gemitt_y: vertical geometric emittance values, in [m] + - gemitt_zeta: longitudinal geometric emittance values, in [m] + - Tx: horizontal IBS growth rate, in [s^-1] + - Ty: vertical IBS growth rate, in [s^-1] + - Tz: longitudinal IBS growth rate, in [s^-1] + The table also contains the following global quantities: + - damping_constants_s: radiation damping constants per second used for the calculations + - partition_numbers: damping partition numbers used for the calculations + - eq_gemitt_x: final horizontal equilibrium geometric emittance converged to, in [m] + - eq_gemitt_y: final vertical equilibrium geometric emittance converged to, in [m] + - eq_gemitt_zeta: final longitudinal equilibrium geometric emittance converged to, in [m] + - sr_eq_gemitt_x: horizontal equilibrium geometric emittance from synchrotron radiation used, in [m] + - sr_eq_gemitt_y: vertical equilibrium geometric emittance from synchrotron radiation used, in [m] + - sr_eq_nemitt_zeta: longitudinal equilibrium normalized emittance from synchrotron radiation used, in [m] """ - # Returns - # ------- - # xtrack.TwissTable - # The convergence calculations results. The table contains the following - # columns, as time-step by time-step quantities: - # - time: time values at which quantities are computed, in [s] - # - gemitt_x: horizontal geometric emittance values, in [m] - # - gemitt_y: vertical geometric emittance values, in [m] - # - gemitt_zeta: longitudinal geometric emittance values, in [m] - # - Tx: horizontal IBS growth rate, in [s^-1] - # - Ty: vertical IBS growth rate, in [s^-1] - # - Tz: longitudinal IBS growth rate, in [s^-1] - # The table also contains the following global quantities: - # - damping_constants_s: radiation damping constants per second used for the calculations - # - partition_numbers: damping partition numbers used for the calculations - # - eq_gemitt_x: final horizontal equilibrium geometric emittance converged to, in [m] - # - eq_gemitt_y: final vertical equilibrium geometric emittance converged to, in [m] - # - eq_gemitt_zeta: final longitudinal equilibrium geometric emittance converged to, in [m] - # - sr_eq_gemitt_x: horizontal equilibrium geometric emittance from synchrotron radiation used, in [m] - # - sr_eq_gemitt_y: vertical equilibrium geometric emittance from synchrotron radiation used, in [m] - # - sr_eq_nemitt_zeta: longitudinal equilibrium normalized emittance from synchrotron radiation used, in [m] # ---------------------------------------------------------------------------------------------- # TODO: Perform check for valid value of emittance_constraint but no value of emittance coupling factor # ---------------------------------------------------------------------------------------------- @@ -405,39 +389,30 @@ def compute_emittance_evolution( iterations += 1 # ---------------------------------------------------------------------------------------------- # We have exited the loop, we have converged. Construct a Table with the results and return it - print("\nConverged!") - # result_table = Table( - # data={ - # "time": np.cumsum(time), - # "gemitt_x": emittances_x_list, - # "gemitt_y": emittances_y_list, - # "gemitt_zeta": emittances_z_list, - # "Tx": Tx, - # "Ty": Ty, - # "Tz": Tz, - # }, - # index="time", - # ) + xo.general._print(f"Converged to equilibrium with a tolerance of {tolerance:.4e}") + result_table = Table( + data={ + "time": np.cumsum(time), + "gemitt_x": res_gemitt_x, + "gemitt_y": res_gemitt_y, + "gemitt_zeta": res_gemitt_zeta, + "Tx": T_x, + "Ty": T_y, + "Tz": T_z, + }, + index="time", + ) # Provide global quantities as well - # result_table._data.update( - # { - # "damping_constants_s": twiss.damping_constants_s, - # "partition_numbers": twiss.partition_numbers, - # "eq_gemitt_x": emittances_x_list[-1], - # "eq_gemitt_x": emittances_y_list[-1], - # "eq_gemitt_x": emittances_z_list[-1], - # "sr_eq_gemitt_x": twiss.eq_gemitt_x, - # "sr_eq_gemitt_y": twiss.eq_gemitt_y, - # "eq_nemitt_zeta": twiss.eq_nemitt_zeta, - # } - # ) - # return result_table - return ( - np.cumsum(time_deltas), - res_gemitt_x, - res_gemitt_y, - res_gemitt_zeta, - T_x, - T_y, - T_z, + result_table._data.update( + { + "damping_constants_s": twiss.damping_constants_s, + "partition_numbers": twiss.partition_numbers, + "eq_gemitt_x": res_gemitt_x[-1], + "eq_gemitt_x": res_gemitt_y[-1], + "eq_gemitt_x": res_gemitt_zeta[-1], + "sr_eq_gemitt_x": twiss.eq_gemitt_x, + "sr_eq_gemitt_y": twiss.eq_gemitt_y, + "eq_nemitt_zeta": twiss.eq_nemitt_zeta, + } ) + return result_table From d43cf4b4754e1fc8d314fee4c139a1353e051e0b Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 14:38:00 +0100 Subject: [PATCH 059/137] do not change names from the TwissTable, be explicit in those we return for global quantities --- xfields/ibs/_equilibrium.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 16652789..8fd37cfc 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -266,12 +266,12 @@ def compute_emittance_evolution( The table also contains the following global quantities: - damping_constants_s: radiation damping constants per second used for the calculations - partition_numbers: damping partition numbers used for the calculations - - eq_gemitt_x: final horizontal equilibrium geometric emittance converged to, in [m] - - eq_gemitt_y: final vertical equilibrium geometric emittance converged to, in [m] - - eq_gemitt_zeta: final longitudinal equilibrium geometric emittance converged to, in [m] - - sr_eq_gemitt_x: horizontal equilibrium geometric emittance from synchrotron radiation used, in [m] - - sr_eq_gemitt_y: vertical equilibrium geometric emittance from synchrotron radiation used, in [m] - - sr_eq_nemitt_zeta: longitudinal equilibrium normalized emittance from synchrotron radiation used, in [m] + - eq_gemitt_x: horizontal equilibrium geometric emittance from synchrotron radiation used, in [m] + - eq_gemitt_y: vertical equilibrium geometric emittance from synchrotron radiation used, in [m] + - eq_nemitt_zeta: longitudinal equilibrium normalized emittance from synchrotron radiation used, in [m] + - sr_ibs_eq_gemitt_x: final horizontal equilibrium geometric emittance converged to, in [m] + - sr_ibs_eq_gemitt_y: final vertical equilibrium geometric emittance converged to, in [m] + - sr_ibs_eq_gemitt_zeta: final longitudinal equilibrium geometric emittance converged to, in [m] """ # ---------------------------------------------------------------------------------------------- # TODO: Perform check for valid value of emittance_constraint but no value of emittance coupling factor @@ -407,12 +407,12 @@ def compute_emittance_evolution( { "damping_constants_s": twiss.damping_constants_s, "partition_numbers": twiss.partition_numbers, - "eq_gemitt_x": res_gemitt_x[-1], - "eq_gemitt_x": res_gemitt_y[-1], - "eq_gemitt_x": res_gemitt_zeta[-1], - "sr_eq_gemitt_x": twiss.eq_gemitt_x, - "sr_eq_gemitt_y": twiss.eq_gemitt_y, - "eq_nemitt_zeta": twiss.eq_nemitt_zeta, + "sr_ibs_eq_gemitt_x": res_gemitt_x[-1], + "sr_ibs_eq_gemitt_y": res_gemitt_y[-1], + "sr_ibs_eq_gemitt_zeta": res_gemitt_zeta[-1], + "eq_gemitt_x": twiss.eq_gemitt_x, + "eq_gemitt_y": twiss.eq_gemitt_y, + "eq_gemitt_zeta": twiss.eq_gemitt_zeta, } ) return result_table From f508b00e7e45d5f0d77e3bec703e91913ff2d92b Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 14:55:46 +0100 Subject: [PATCH 060/137] proper numpy arrays for columns, fix varname not updated --- xfields/ibs/_equilibrium.py | 44 ++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 8fd37cfc..97f83090 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -100,16 +100,12 @@ def _ibs_rates_and_emittance_derivatives( """ LOGGER.debug("Computing IBS growth rates and emittance time derivatives.") # ---------------------------------------------------------------------------------------------- - # TODO: bunch emittances - ask for the three separately and update docstring + # TODO: bunch emittances - ask for the three separately and update docstring - keep checks here input_emittance_x, input_emittance_y, input_emittance_z = input_emittances - assert input_emittance_x > 0.0, ( - "'input_emittance_x' should be larger than zero, try providing 'initial_emittances'" - ) - assert input_emittance_y > 0.0, ( - "'input_emittance_y' should be larger than zero, try providing 'initial_emittances'" - ) + assert input_emittance_x > 0.0, "'input_emittance_x' should be larger than zero" + assert input_emittance_y > 0.0, "'input_emittance_y' should be larger than zero" # ---------------------------------------------------------------------------------------------- - # TODO: check for SR eq emittances etc in twiss table (or in public func?) Could be: + # TODO: check for SR eq emittances etc in twiss table (or in public func?) (move to main func) # if None in ( # getattr(twiss, "eq_gemitt_x", None), # getattr(twiss, "eq_gemitt_y", None), @@ -305,10 +301,14 @@ def compute_emittance_evolution( sigma_zeta = (emittance_z * twiss.bets0) ** 0.5 sigma_delta = (emittance_z / twiss.bets0) ** 0.5 if overwrite_sigma_zeta is not None: - warnings.warn("'overwrite_sigma_zeta' is specified, make sure it remains consistent with 'initial_emittances'.") + warnings.warn( + "'overwrite_sigma_zeta' is specified, make sure it remains consistent with 'initial_emittances'." + ) sigma_zeta = overwrite_sigma_zeta elif overwrite_sigma_delta is not None: - warnings.warn("'sigma_delta' is specified, make sure it remains consistent with 'initial_emittances'.") + warnings.warn( + "'sigma_delta' is specified, make sure it remains consistent with 'initial_emittances'." + ) sigma_delta = overwrite_sigma_delta longitudinal_emittance_ratio = sigma_zeta / sigma_delta if overwrite_sigma_zeta is not None or overwrite_sigma_delta is not None: @@ -336,7 +336,9 @@ def compute_emittance_evolution( # - Compute tolerance and check for convergence while tolerance > rtol: if verbose is True: # Display estimated convergence progress if asked - xo.general._print(f"Iteration {iterations} - convergence = {100 * rtol / tolerance:.1f}%", end="\r") + xo.general._print( + f"Iteration {iterations} - convergence = {100 * rtol / tolerance:.1f}%", end="\r" + ) # Compute IBS growth rates and emittance derivatives ibs_growth_rates, emittance_derivatives = _ibs_rates_and_emittance_derivatives( @@ -378,7 +380,9 @@ def compute_emittance_evolution( # Compute tolerance (but not at first step since there is no previous value) if iterations > 0: - tolerance = np.max(np.abs((current_emittances - previous_emittances) / previous_emittances)) + tolerance = np.max( + np.abs((current_emittances - previous_emittances) / previous_emittances) + ) # Store current emittances for the next iteration previous_emittances = current_emittances.copy() @@ -389,16 +393,16 @@ def compute_emittance_evolution( iterations += 1 # ---------------------------------------------------------------------------------------------- # We have exited the loop, we have converged. Construct a Table with the results and return it - xo.general._print(f"Converged to equilibrium with a tolerance of {tolerance:.4e}") + xo.general._print(f"Reached equilibrium with tolerance={tolerance:.2e} (vs rtol={rtol:.2e})") result_table = Table( data={ - "time": np.cumsum(time), - "gemitt_x": res_gemitt_x, - "gemitt_y": res_gemitt_y, - "gemitt_zeta": res_gemitt_zeta, - "Tx": T_x, - "Ty": T_y, - "Tz": T_z, + "time": np.cumsum(time_deltas), + "gemitt_x": np.array(res_gemitt_x), + "gemitt_y": np.array(res_gemitt_y), + "gemitt_zeta": np.array(res_gemitt_zeta), + "Tx": np.array(T_x), + "Ty": np.array(T_y), + "Tz": np.array(T_z), }, index="time", ) From e89f85904bdcfe81fe2f2bb5a7e583c2d15aeb1a Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 15:02:24 +0100 Subject: [PATCH 061/137] function rename, and fix in docstring --- tests/test_ibs_equilibrium_emittances.py | 6 ++-- xfields/ibs/__init__.py | 4 +-- xfields/ibs/_equilibrium.py | 42 +++++++++++++----------- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/tests/test_ibs_equilibrium_emittances.py b/tests/test_ibs_equilibrium_emittances.py index dd4bc8dd..d8831858 100644 --- a/tests/test_ibs_equilibrium_emittances.py +++ b/tests/test_ibs_equilibrium_emittances.py @@ -25,7 +25,7 @@ def test_ibs_emittance_constraints(emittance_constraint): emittance_coupling_factor = 0.02 - time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = xf.ibs.compute_emittance_evolution( + time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( twiss, bunch_intensity, emittance_coupling_factor=emittance_coupling_factor, emittance_constraint=emittance_constraint, @@ -82,7 +82,7 @@ def test_ibs_emittance_coupling_factor(emittance_coupling_factor): # Equilibrium emittances calculations # ####################################### - time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = xf.ibs.compute_emittance_evolution( + time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( twiss, bunch_intensity, emittance_coupling_factor=emittance_coupling_factor, ) @@ -126,7 +126,7 @@ def test_ibs_emittance_no_constraint(emittance_coupling_factor): # Equilibrium emittances calculations # ####################################### - time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = xf.ibs.compute_emittance_evolution( + time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( twiss, bunch_intensity, initial_emittances=initial_emittances, emittance_coupling_factor=emittance_coupling_factor, diff --git a/xfields/ibs/__init__.py b/xfields/ibs/__init__.py index e5ac967a..d033e5a2 100644 --- a/xfields/ibs/__init__.py +++ b/xfields/ibs/__init__.py @@ -6,13 +6,13 @@ from ._analytical import BjorkenMtingwaIBS, IBSGrowthRates, NagaitsevIBS from ._api import configure_intrabeam_scattering, get_intrabeam_scattering_growth_rates from ._kicks import IBSAnalyticalKick, IBSKineticKick -from ._equilibrium import compute_emittance_evolution +from ._equilibrium import compute_equilibrium_emittances_from_sr_and_ibs __all__ = [ "BjorkenMtingwaIBS", "configure_intrabeam_scattering", "get_intrabeam_scattering_growth_rates", - "compute_emittance_evolution", + "compute_equilibrium_emittances_from_sr_and_ibs", "IBSGrowthRates", "IBSAnalyticalKick", "IBSKineticKick", diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 97f83090..f063e7ec 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -158,8 +158,10 @@ def _ibs_rates_and_emittance_derivatives( ) -# TODO: find a better, more explicit name for this -def compute_emittance_evolution( +# ----- Public API (to be integrated as method in TwissTable) ----- # + + +def compute_equilibrium_emittances_from_sr_and_ibs( twiss: xt.TwissTable, formalism: Literal["Nagaitsev", "Bjorken-Mtingwa", "B&M"], total_beam_intensity: int, @@ -252,22 +254,22 @@ def compute_emittance_evolution( xtrack.TwissTable The convergence calculations results. The table contains the following columns, as time-step by time-step quantities: - - time: time values at which quantities are computed, in [s] - - gemitt_x: horizontal geometric emittance values, in [m] - - gemitt_y: vertical geometric emittance values, in [m] - - gemitt_zeta: longitudinal geometric emittance values, in [m] - - Tx: horizontal IBS growth rate, in [s^-1] - - Ty: vertical IBS growth rate, in [s^-1] - - Tz: longitudinal IBS growth rate, in [s^-1] + - time: time values at which quantities are computed, in [s]. + - gemitt_x: horizontal geometric emittance values, in [m]. + - gemitt_y: vertical geometric emittance values, in [m]. + - gemitt_zeta: longitudinal geometric emittance values, in [m]. + - Tx: horizontal IBS growth rate, in [s^-1]. + - Ty: vertical IBS growth rate, in [s^-1]. + - Tz: longitudinal IBS growth rate, in [s^-1]. The table also contains the following global quantities: - - damping_constants_s: radiation damping constants per second used for the calculations - - partition_numbers: damping partition numbers used for the calculations - - eq_gemitt_x: horizontal equilibrium geometric emittance from synchrotron radiation used, in [m] - - eq_gemitt_y: vertical equilibrium geometric emittance from synchrotron radiation used, in [m] - - eq_nemitt_zeta: longitudinal equilibrium normalized emittance from synchrotron radiation used, in [m] - - sr_ibs_eq_gemitt_x: final horizontal equilibrium geometric emittance converged to, in [m] - - sr_ibs_eq_gemitt_y: final vertical equilibrium geometric emittance converged to, in [m] - - sr_ibs_eq_gemitt_zeta: final longitudinal equilibrium geometric emittance converged to, in [m] + - damping_constants_s: radiation damping constants per second used. + - partition_numbers: damping partition numbers used. + - eq_gemitt_x: horizontal equilibrium geometric emittance from synchrotron radiation used, in [m]. + - eq_gemitt_y: vertical equilibrium geometric emittance from synchrotron radiation used, in [m]. + - eq_gemitt_zeta: longitudinal equilibrium geometric emittance from synchrotron radiation used, in [m]. + - sr_ibs_eq_gemitt_x: final horizontal equilibrium geometric emittance converged to, in [m]. + - sr_ibs_eq_gemitt_y: final vertical equilibrium geometric emittance converged to, in [m]. + - sr_ibs_eq_gemitt_zeta: final longitudinal equilibrium geometric emittance converged to, in [m]. """ # ---------------------------------------------------------------------------------------------- # TODO: Perform check for valid value of emittance_constraint but no value of emittance coupling factor @@ -411,12 +413,12 @@ def compute_emittance_evolution( { "damping_constants_s": twiss.damping_constants_s, "partition_numbers": twiss.partition_numbers, - "sr_ibs_eq_gemitt_x": res_gemitt_x[-1], - "sr_ibs_eq_gemitt_y": res_gemitt_y[-1], - "sr_ibs_eq_gemitt_zeta": res_gemitt_zeta[-1], "eq_gemitt_x": twiss.eq_gemitt_x, "eq_gemitt_y": twiss.eq_gemitt_y, "eq_gemitt_zeta": twiss.eq_gemitt_zeta, + "sr_ibs_eq_gemitt_x": res_gemitt_x[-1], + "sr_ibs_eq_gemitt_y": res_gemitt_y[-1], + "sr_ibs_eq_gemitt_zeta": res_gemitt_zeta[-1], } ) return result_table From 8b43266d091f3d7578a1dc00664062e3675669d7 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 15:08:01 +0100 Subject: [PATCH 062/137] update main docstring chunk, fix return type --- xfields/ibs/_equilibrium.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index f063e7ec..11ab17f9 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -182,17 +182,23 @@ def compute_equilibrium_emittances_from_sr_and_ibs( verbose: bool = True, **kwargs, ) -> Table: - # TODO: rework this main chunk of docstring """ - Compute the evolution of beam emittances due to IBS until convergence. - By default, the function assumes the emittances from the Twiss object. - They can also be specified as well as different natural emittances. - The emittance evolution can be constrained to follow two scenarios: - - A vertical emittance originating from linear coupling. + Compute the evolution of emittances due to Synchrotron Radiation + and Intra-Beam Scattering until convergence to equilibrium values. + The equilibrium state is determined by an iterative process which + consists in computing the IBS growth rates and the emittance time + derivaties, then computing the emittances at the next time step, + potentially including the effect of transverse constraints, and + checking for convergence. The convergence criteria can be chosen + by the user. + + Transverse emittances can be constrained to follow two scenarios: + - An emittance exchange originating from betatron coupling. - A vertical emittance originating from an excitation. - The impact from the longitudinal impedance (e.g. bunch lengthening or - microwave instability) can be accounted for by specifying the RMS bunch - length and momentum spread. + + The impact from the longitudinal impedance (e.g. bunch lengthening + or microwave instability) can be accounted for by specifying the RMS + bunch length and momentum spread. Parameters ---------- @@ -251,7 +257,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( Returns ------- - xtrack.TwissTable + xtrack.Table The convergence calculations results. The table contains the following columns, as time-step by time-step quantities: - time: time values at which quantities are computed, in [s]. From c97746dc703b35408307af81e3f9112b2da497c3 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 15:09:44 +0100 Subject: [PATCH 063/137] imports order --- xfields/ibs/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xfields/ibs/__init__.py b/xfields/ibs/__init__.py index d033e5a2..b4782d99 100644 --- a/xfields/ibs/__init__.py +++ b/xfields/ibs/__init__.py @@ -5,8 +5,8 @@ from ._analytical import BjorkenMtingwaIBS, IBSGrowthRates, NagaitsevIBS from ._api import configure_intrabeam_scattering, get_intrabeam_scattering_growth_rates -from ._kicks import IBSAnalyticalKick, IBSKineticKick from ._equilibrium import compute_equilibrium_emittances_from_sr_and_ibs +from ._kicks import IBSAnalyticalKick, IBSKineticKick __all__ = [ "BjorkenMtingwaIBS", From 71bfb168f41c3ce26b7187b8cd6bc30428c1daa4 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 15:10:17 +0100 Subject: [PATCH 064/137] also here --- xfields/ibs/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xfields/ibs/__init__.py b/xfields/ibs/__init__.py index b4782d99..feda843b 100644 --- a/xfields/ibs/__init__.py +++ b/xfields/ibs/__init__.py @@ -10,9 +10,9 @@ __all__ = [ "BjorkenMtingwaIBS", + "compute_equilibrium_emittances_from_sr_and_ibs", "configure_intrabeam_scattering", "get_intrabeam_scattering_growth_rates", - "compute_equilibrium_emittances_from_sr_and_ibs", "IBSGrowthRates", "IBSAnalyticalKick", "IBSKineticKick", From c97cdde2ac646b405aee88ae6573ebd9ef4a4237 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 15:21:38 +0100 Subject: [PATCH 065/137] private function explicitely requires individual (geometric) emittances, public function passes those --- xfields/ibs/_equilibrium.py | 45 +++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 11ab17f9..fba5a8d1 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -60,7 +60,9 @@ def _ibs_rates_and_emittance_derivatives( twiss: xt.TwissTable, formalism: Literal["Nagaitsev", "Bjorken-Mtingwa", "B&M"], total_beam_intensity: int, - input_emittances: tuple[float, float, float], + gemitt_x: float, + gemitt_y: float, + gemitt_zeta: float, longitudinal_emittance_ratio: float = None, **kwargs, ) -> tuple[IBSGrowthRates, EmittanceTimeDerivatives]: @@ -78,9 +80,12 @@ def _ibs_rates_and_emittance_derivatives( case-insensitively. total_beam_intensity : int The bunch intensity, in [particles per bunch]. - input_emittances : tuple[float, float, float] - The bunch's starting geometric emittances in the horizontal, - vertical and longitudinal planes, in [m]. + gemitt_x : float + Horizontal geometric emittance in [m]. + gemitt_y : float + Vertical geometric emittance in [m]. + gemitt_zeta : float + Longitudinal geometric emittance in [m]. longitudinal_emittance_ratio : float, optional Ratio of the RMS bunch length to the RMS momentum spread. If provided, allows accounting for a perturbed longitudinal distrubtion due to @@ -100,10 +105,10 @@ def _ibs_rates_and_emittance_derivatives( """ LOGGER.debug("Computing IBS growth rates and emittance time derivatives.") # ---------------------------------------------------------------------------------------------- - # TODO: bunch emittances - ask for the three separately and update docstring - keep checks here - input_emittance_x, input_emittance_y, input_emittance_z = input_emittances - assert input_emittance_x > 0.0, "'input_emittance_x' should be larger than zero" - assert input_emittance_y > 0.0, "'input_emittance_y' should be larger than zero" + # Check for valid emittance values + assert gemitt_x > 0.0, "Horizontal emittance should be larger than zero" + assert gemitt_y > 0.0, "Vertical emittance should be larger than zero" + assert gemitt_zeta > 0.0, "Longitudinal emittance should be larger than zero" # ---------------------------------------------------------------------------------------------- # TODO: check for SR eq emittances etc in twiss table (or in public func?) (move to main func) # if None in ( @@ -120,15 +125,15 @@ def _ibs_rates_and_emittance_derivatives( # ---------------------------------------------------------------------------------------------- # Compute relevant longitudinal parameters for the bunch (needed for IBS growth rates) LOGGER.debug("Computing longitudinal parameters for the bunch.") - sigma_zeta = (input_emittance_z * longitudinal_emittance_ratio) ** 0.5 # in [m] - sigma_delta = (input_emittance_z / longitudinal_emittance_ratio) ** 0.5 # in [-] + sigma_zeta = (gemitt_zeta * longitudinal_emittance_ratio) ** 0.5 # in [m] + sigma_delta = (gemitt_zeta / longitudinal_emittance_ratio) ** 0.5 # in [-] # ---------------------------------------------------------------------------------------------- # Ask to compute the IBS growth rates (this function logs so no need to do it here) ibs_growth_rates = twiss.get_ibs_growth_rates( formalism=formalism, total_beam_intensity=total_beam_intensity, - gemitt_x=input_emittance_x, - gemitt_y=input_emittance_y, + gemitt_x=gemitt_x, + gemitt_y=gemitt_y, sigma_delta=sigma_delta, bunch_length=sigma_zeta, # 1 sigma_{zeta,RMS} bunch length **kwargs, @@ -139,16 +144,16 @@ def _ibs_rates_and_emittance_derivatives( # TODO: replace input_emittance_[xyz] by gemitt_[xyz] once they are parameters LOGGER.debug("Computing emittance time derivatives analytically.") depsilon_x_dt = ( - -2 * twiss.damping_constants_s[0] * (input_emittance_x - twiss.eq_gemitt_x) - + ibs_growth_rates.Tx * input_emittance_x + -2 * twiss.damping_constants_s[0] * (gemitt_x - twiss.eq_gemitt_x) + + ibs_growth_rates.Tx * gemitt_x ) depsilon_y_dt = ( - -2 * twiss.damping_constants_s[1] * (input_emittance_y - twiss.eq_gemitt_y) - + ibs_growth_rates.Ty * input_emittance_y + -2 * twiss.damping_constants_s[1] * (gemitt_y - twiss.eq_gemitt_y) + + ibs_growth_rates.Ty * gemitt_y ) depsilon_z_dt = ( - -2 * twiss.damping_constants_s[2] * (input_emittance_z - twiss.eq_gemitt_zeta) - + ibs_growth_rates.Tz * input_emittance_z + -2 * twiss.damping_constants_s[2] * (gemitt_zeta - twiss.eq_gemitt_zeta) + + ibs_growth_rates.Tz * gemitt_zeta ) # ---------------------------------------------------------------------------------------------- # And return the results @@ -353,7 +358,9 @@ def compute_equilibrium_emittances_from_sr_and_ibs( twiss=twiss, formalism=formalism, total_beam_intensity=total_beam_intensity, - input_emittances=current_emittances, + gemitt_x=current_emittances[0], + gemitt_y=current_emittances[1], + gemitt_zeta=current_emittances[2], longitudinal_emittance_ratio=longitudinal_emittance_ratio, **kwargs, ) From cb0e623aec12a27d23bdb0a68e875a629ef1e5d6 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 15:24:58 +0100 Subject: [PATCH 066/137] public function checks for correct radiation state of TwissTable first thing --- xfields/ibs/_equilibrium.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index fba5a8d1..62333d8c 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -68,7 +68,8 @@ def _ibs_rates_and_emittance_derivatives( ) -> tuple[IBSGrowthRates, EmittanceTimeDerivatives]: """ Compute the IBS growth rates and emittance time derivatives from - the effect of both IBS and SR. + the effect of both IBS and SR. TODO: Include a ref here to the + analytical formula used. Parameters ---------- @@ -110,19 +111,6 @@ def _ibs_rates_and_emittance_derivatives( assert gemitt_y > 0.0, "Vertical emittance should be larger than zero" assert gemitt_zeta > 0.0, "Longitudinal emittance should be larger than zero" # ---------------------------------------------------------------------------------------------- - # TODO: check for SR eq emittances etc in twiss table (or in public func?) (move to main func) - # if None in ( - # getattr(twiss, "eq_gemitt_x", None), - # getattr(twiss, "eq_gemitt_y", None), - # getattr(twiss, "eq_gemitt_zeta", None), - # getattr(twiss, "damping_constants_s", None), - # ): - # LOGGER.error("Invalid TwissTable, does not have SR equilibrium properties.") - # raise AttributeError( - # "The TwissTable must contain SR equilibrium emittances and damping constants. " - # "Did you activate radiation and twiss with eneloss_and_damping=True?" - # ) - # ---------------------------------------------------------------------------------------------- # Compute relevant longitudinal parameters for the bunch (needed for IBS growth rates) LOGGER.debug("Computing longitudinal parameters for the bunch.") sigma_zeta = (gemitt_zeta * longitudinal_emittance_ratio) ** 0.5 # in [m] @@ -283,6 +271,20 @@ def compute_equilibrium_emittances_from_sr_and_ibs( - sr_ibs_eq_gemitt_zeta: final longitudinal equilibrium geometric emittance converged to, in [m]. """ # ---------------------------------------------------------------------------------------------- + # Check for SR equilibrium emittances, damping constants and partition numbers in the TwissTable + if None in ( + getattr(twiss, "eq_gemitt_x", None), + getattr(twiss, "eq_gemitt_y", None), + getattr(twiss, "eq_gemitt_zeta", None), + getattr(twiss, "damping_constants_s", None), + getattr(twiss, "partition_numbers", None), + ): + LOGGER.error("Invalid TwissTable, does not have SR equilibrium properties. Did you configure radiation?") + raise AttributeError( + "The TwissTable must contain SR equilibrium emittances and damping constants. " + "Did you activate radiation and twiss with `eneloss_and_damping=True?`" + ) + # ---------------------------------------------------------------------------------------------- # TODO: Perform check for valid value of emittance_constraint but no value of emittance coupling factor # ---------------------------------------------------------------------------------------------- # Handle initial transverse emittances and potential effect of coupling / excitation constraints From 9dbc7353d331638361dcfe460d05af7aed115206 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 15:38:02 +0100 Subject: [PATCH 067/137] check for valid emittance_constraint and if it is, warn if coupling factor is 0 --- xfields/ibs/_equilibrium.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 62333d8c..102c062c 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -227,9 +227,9 @@ def compute_equilibrium_emittances_from_sr_and_ibs( (e.g. from a feedback system) and is determined from the horizontal emittance based on the value of `emittance_coupling_factor`. In this case the total transverse emittance is NOT preserved. - Providing `None` or an empty string allows one to study a scenarion without - constraint. Note as `emittance_coupling_factor` defaults to 0, this parameter - has no effect unless a non-zero value is provided. + Providing `None` allows one to study a scenario without constraint. Note + that as `emittance_coupling_factor` defaults to 0, the constraint has no + effect unless a non-zero factor is provided. overwrite_sigma_zeta : float, optional The RMS bunch length. If provided, overwrites the one computed from the longitudinal emittance. Defaults to `None`. @@ -272,20 +272,20 @@ def compute_equilibrium_emittances_from_sr_and_ibs( """ # ---------------------------------------------------------------------------------------------- # Check for SR equilibrium emittances, damping constants and partition numbers in the TwissTable - if None in ( - getattr(twiss, "eq_gemitt_x", None), - getattr(twiss, "eq_gemitt_y", None), - getattr(twiss, "eq_gemitt_zeta", None), - getattr(twiss, "damping_constants_s", None), - getattr(twiss, "partition_numbers", None), - ): + # fmt: off + required_twiss_attributes = ["eq_gemitt_x", "eq_gemitt_y", "eq_gemitt_zeta", "damping_constants_s", "partition_numbers"] + if any(getattr(twiss, attr, None) is None for attr in required_twiss_attributes): LOGGER.error("Invalid TwissTable, does not have SR equilibrium properties. Did you configure radiation?") raise AttributeError( "The TwissTable must contain SR equilibrium emittances and damping constants. " "Did you activate radiation and twiss with `eneloss_and_damping=True?`" ) # ---------------------------------------------------------------------------------------------- - # TODO: Perform check for valid value of emittance_constraint but no value of emittance coupling factor + # Check for valid value of emittance_constraint and warn if constraint provided but factor is 0 + if emittance_constraint is not None: + assert emittance_constraint.lower() in ("coupling", "excitation"), "Invalid 'emittance_constraint', accepted values are 'coupling' or 'excitation'." + if emittance_coupling_factor == 0: + LOGGER.warning("As 'emittance_coupling_factor` is zero, providing 'emittance_constraint' has no effect!") # ---------------------------------------------------------------------------------------------- # Handle initial transverse emittances and potential effect of coupling / excitation constraints # TODO: I don't like this, would rather force the user to provide gemitt_x, gemitt_y & gemitt_zeta @@ -296,11 +296,9 @@ def compute_equilibrium_emittances_from_sr_and_ibs( twiss.eq_gemitt_y, twiss.eq_gemitt_zeta, ) - # If emittance_coupling_factor is non zero, then natural emittance is - # modified accordingly - # fmt: off + # If emittance_coupling_factor is non zero, then natural emittance is modified accordingly (used + # convention is valid for arbitrary damping partition numbers and emittance_coupling_factor). if emittance_coupling_factor != 0 and emittance_constraint.lower() == "coupling": - # The convention used is valid for arbitrary damping partition numbers and emittance_coupling_factor. emittance_y = emittance_x * emittance_coupling_factor / (1 + emittance_coupling_factor * twiss.partition_numbers[1] / twiss.partition_numbers[0]) emittance_x *= 1 / (1 + emittance_coupling_factor * twiss.partition_numbers[1] / twiss.partition_numbers[0]) @@ -310,7 +308,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( emittance_y = emittance_x * emittance_coupling_factor else: emittance_x, emittance_y, emittance_z = initial_emittances - # fmt: on + # fmt: on # ---------------------------------------------------------------------------------------------- # Handle initial longitudinal emittance and potential effect of bunch lengthening sigma_zeta = (emittance_z * twiss.bets0) ** 0.5 From 8d9cc79596d65237780a8f9c03acf8038c4f673c Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 15:39:15 +0100 Subject: [PATCH 068/137] move these up --- xfields/ibs/_equilibrium.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 102c062c..e849c201 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -166,11 +166,10 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # nemitt_y: float = None, # gemitt_zeta: float = None, # nemitt_zeta: float = None, - emittance_coupling_factor: float = 0, - emittance_constraint: Literal["coupling", "excitation"] = "coupling", - # TODO: move these two (still optional) below gemitt_x, gemitt_y, gemitt_zeta when modify that overwrite_sigma_zeta: float = None, overwrite_sigma_delta: float = None, + emittance_coupling_factor: float = 0, + emittance_constraint: Literal["coupling", "excitation"] = "coupling", rtol: float = 1e-6, verbose: bool = True, **kwargs, From cac2b79be81fcc8384926b2ce2f86b718994c5a9 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 15:39:49 +0100 Subject: [PATCH 069/137] this todo has been done --- xfields/ibs/_equilibrium.py | 1 - 1 file changed, 1 deletion(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index e849c201..9ed7718a 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -129,7 +129,6 @@ def _ibs_rates_and_emittance_derivatives( # ---------------------------------------------------------------------------------------------- # Computing the emittance time derivatives analytically. # TODO: ADD A REF TO THE FORMULA HERE - # TODO: replace input_emittance_[xyz] by gemitt_[xyz] once they are parameters LOGGER.debug("Computing emittance time derivatives analytically.") depsilon_x_dt = ( -2 * twiss.damping_constants_s[0] * (gemitt_x - twiss.eq_gemitt_x) From 7d8ebfca63f54d529c413336a6f45ec9d0f4b827 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 15:55:59 +0100 Subject: [PATCH 070/137] commented out check for when we require emittances --- xfields/ibs/_equilibrium.py | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 9ed7718a..d2c41c97 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -150,7 +150,7 @@ def _ibs_rates_and_emittance_derivatives( ) -# ----- Public API (to be integrated as method in TwissTable) ----- # +# ----- Public API (integrated as method in TwissTable) ----- # def compute_equilibrium_emittances_from_sr_and_ibs( @@ -268,17 +268,36 @@ def compute_equilibrium_emittances_from_sr_and_ibs( - sr_ibs_eq_gemitt_y: final vertical equilibrium geometric emittance converged to, in [m]. - sr_ibs_eq_gemitt_zeta: final longitudinal equilibrium geometric emittance converged to, in [m]. """ + # fmt: off # ---------------------------------------------------------------------------------------------- # Check for SR equilibrium emittances, damping constants and partition numbers in the TwissTable - # fmt: off - required_twiss_attributes = ["eq_gemitt_x", "eq_gemitt_y", "eq_gemitt_zeta", "damping_constants_s", "partition_numbers"] - if any(getattr(twiss, attr, None) is None for attr in required_twiss_attributes): + _required_attrs = ["damping_constants_s", "partition_numbers", "eq_gemitt_x", "eq_gemitt_y", "eq_gemitt_zeta"] + if any(getattr(twiss, attr, None) is None for attr in _required_attrs): LOGGER.error("Invalid TwissTable, does not have SR equilibrium properties. Did you configure radiation?") raise AttributeError( "The TwissTable must contain SR equilibrium emittances and damping constants. " "Did you activate radiation and twiss with `eneloss_and_damping=True?`" ) # ---------------------------------------------------------------------------------------------- + # Perform checks on required & exclusive parameters, and convert emittances to geometric if needed + # assert total_beam_intensity is not None, "Must provide 'total_beam_intensity'" + # assert any([gemitt_x, nemitt_x]), "Must provide either 'gemitt_x' or 'nemitt_x'" + # assert any([gemitt_y, nemitt_y]), "Must provide either 'gemitt_y' or 'nemitt_y'" + # assert any([gemitt_zeta, nemitt_zeta]), "Must provide either 'gemitt_zeta' or 'nemitt_zeta'" + # if gemitt_x is not None: + # assert nemitt_x is None, "Cannot provide both 'gemitt_x' and 'nemitt_x'" + # if gemitt_y is not None: + # assert nemitt_y is None, "Cannot provide both 'gemitt_y' and 'nemitt_y'" + # if nemitt_x is not None: + # assert gemitt_x is None, "Cannot provide both 'gemitt_x' and 'nemitt_x'" + # gemitt_x = nemitt_x / (twiss.beta0 * twiss.gamma0) + # if nemitt_y is not None: + # assert gemitt_y is None, "Cannot provide both 'gemitt_y' and 'nemitt_y'" + # gemitt_y = nemitt_y / (twiss.beta0 * twiss.gamma0) + # if nemitt_zeta is not None: + # assert gemitt_zeta is None, "Cannot provide both 'gemitt_zeta' and 'nemitt_zeta'" + # gemitt_zeta = nemitt_zeta / (twiss.beta0 * twiss.gamma0) + # ---------------------------------------------------------------------------------------------- # Check for valid value of emittance_constraint and warn if constraint provided but factor is 0 if emittance_constraint is not None: assert emittance_constraint.lower() in ("coupling", "excitation"), "Invalid 'emittance_constraint', accepted values are 'coupling' or 'excitation'." @@ -327,7 +346,8 @@ def compute_equilibrium_emittances_from_sr_and_ibs( "Input of 'overwrite_sigma_zeta' or 'overwrite_sigma_delta' provided, but " "not of 'initial_emittances'. Please provide 'initial_emittances'." ) - # TODO: potentially here we redefine the emittance_z here as the ratio after the change here (product of the two sigmas) + # Since a longitudinal property was overwritten we recompute the emittance_z + emittance_z = sigma_zeta * sigma_delta # ---------------------------------------------------------------------------------------------- # Start structures to store the iterative results until convergence tolerance = np.inf @@ -337,11 +357,10 @@ def compute_equilibrium_emittances_from_sr_and_ibs( res_gemitt_x, res_gemitt_y, res_gemitt_zeta = [], [], [] T_x, T_y, T_z = [], [], [] current_emittances = np.array([emittance_x, emittance_y, emittance_z]) - iterations = 0 # Iteration counter # ---------------------------------------------------------------------------------------------- # Start the iterative process until convergence: # - Compute IBS rates and emittance time derivatives - # - Update emittances using the time derivatives and time step + # - Compute new emittances using the time derivatives and time step # - Enforce transverse constraints if specified # - Store all intermediate results for this time step # - Compute tolerance and check for convergence From 95a814564d0700f5e7dd860a0e832a8655697c00 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 15:56:18 +0100 Subject: [PATCH 071/137] no umpacking, let's be verbose --- xfields/ibs/_equilibrium.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index d2c41c97..be4f897d 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -349,13 +349,19 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # Since a longitudinal property was overwritten we recompute the emittance_z emittance_z = sigma_zeta * sigma_delta # ---------------------------------------------------------------------------------------------- - # Start structures to store the iterative results until convergence + # Initialize values for the iterative process (first time step is revolution period) + iterations = 0 tolerance = np.inf - time_step = twiss.T_rev0 # Initial time step is the revolution period - - time_deltas = [] - res_gemitt_x, res_gemitt_y, res_gemitt_zeta = [], [], [] - T_x, T_y, T_z = [], [], [] + time_step = twiss.T_rev0 + # Structures for iterative results (time, IBS growth rates, computed emittances) + time_deltas = [] # stores the deltas (!), we do a cumsum at the end + T_x = [] + T_y = [] + T_z = [] + res_gemitt_x = [] + res_gemitt_y = [] + res_gemitt_zeta = [] + # Starting emittances (numpy array since we compute the next ones from these) current_emittances = np.array([emittance_x, emittance_y, emittance_z]) # ---------------------------------------------------------------------------------------------- # Start the iterative process until convergence: From c661d708c97b6a57b5323de5fb60e22a3e7349c1 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 16:10:37 +0100 Subject: [PATCH 072/137] type hint --- xfields/ibs/_equilibrium.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index be4f897d..7a1b1eb2 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -12,6 +12,7 @@ import numpy as np import xobjects as xo import xtrack as xt +from numpy.typing import ArrayLike from xtrack import Table from xfields.ibs._analytical import IBSGrowthRates @@ -350,19 +351,19 @@ def compute_equilibrium_emittances_from_sr_and_ibs( emittance_z = sigma_zeta * sigma_delta # ---------------------------------------------------------------------------------------------- # Initialize values for the iterative process (first time step is revolution period) - iterations = 0 - tolerance = np.inf - time_step = twiss.T_rev0 + iterations: float = 0 + tolerance: float = np.inf + time_step: float = twiss.T_rev0 # Structures for iterative results (time, IBS growth rates, computed emittances) - time_deltas = [] # stores the deltas (!), we do a cumsum at the end - T_x = [] - T_y = [] - T_z = [] - res_gemitt_x = [] - res_gemitt_y = [] - res_gemitt_zeta = [] + time_deltas: list[float] = [] # stores the deltas (!), we do a cumsum at the end + T_x: list[float] = [] + T_y: list[float] = [] + T_z: list[float] = [] + res_gemitt_x: list[float] = [] + res_gemitt_y: list[float] = [] + res_gemitt_zeta: list[float] = [] # Starting emittances (numpy array since we compute the next ones from these) - current_emittances = np.array([emittance_x, emittance_y, emittance_z]) + current_emittances: ArrayLike = np.array([emittance_x, emittance_y, emittance_z]) # ---------------------------------------------------------------------------------------------- # Start the iterative process until convergence: # - Compute IBS rates and emittance time derivatives @@ -409,12 +410,12 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # Append current values to lists time_deltas.append(time_step) - res_gemitt_x.append(current_emittances[0]) - res_gemitt_y.append(current_emittances[1]) - res_gemitt_zeta.append(current_emittances[2]) T_x.append(ibs_growth_rates[0]) T_y.append(ibs_growth_rates[1]) T_z.append(ibs_growth_rates[2]) + res_gemitt_x.append(current_emittances[0]) + res_gemitt_y.append(current_emittances[1]) + res_gemitt_zeta.append(current_emittances[2]) # Compute tolerance (but not at first step since there is no previous value) if iterations > 0: From 41da9b9f4d9ddbbd0beafdb481406da61666464e Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 16:15:48 +0100 Subject: [PATCH 073/137] type hints and tweaks --- xfields/ibs/_equilibrium.py | 60 +++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 7a1b1eb2..39a1484c 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -106,17 +106,17 @@ def _ibs_rates_and_emittance_derivatives( respectively). """ LOGGER.debug("Computing IBS growth rates and emittance time derivatives.") - # ---------------------------------------------------------------------------------------------- + # --------------------------------------------------------------------------------------------- # Check for valid emittance values assert gemitt_x > 0.0, "Horizontal emittance should be larger than zero" assert gemitt_y > 0.0, "Vertical emittance should be larger than zero" assert gemitt_zeta > 0.0, "Longitudinal emittance should be larger than zero" - # ---------------------------------------------------------------------------------------------- + # --------------------------------------------------------------------------------------------- # Compute relevant longitudinal parameters for the bunch (needed for IBS growth rates) LOGGER.debug("Computing longitudinal parameters for the bunch.") sigma_zeta = (gemitt_zeta * longitudinal_emittance_ratio) ** 0.5 # in [m] sigma_delta = (gemitt_zeta / longitudinal_emittance_ratio) ** 0.5 # in [-] - # ---------------------------------------------------------------------------------------------- + # --------------------------------------------------------------------------------------------- # Ask to compute the IBS growth rates (this function logs so no need to do it here) ibs_growth_rates = twiss.get_ibs_growth_rates( formalism=formalism, @@ -127,7 +127,7 @@ def _ibs_rates_and_emittance_derivatives( bunch_length=sigma_zeta, # 1 sigma_{zeta,RMS} bunch length **kwargs, ) - # ---------------------------------------------------------------------------------------------- + # --------------------------------------------------------------------------------------------- # Computing the emittance time derivatives analytically. # TODO: ADD A REF TO THE FORMULA HERE LOGGER.debug("Computing emittance time derivatives analytically.") @@ -143,7 +143,7 @@ def _ibs_rates_and_emittance_derivatives( -2 * twiss.damping_constants_s[2] * (gemitt_zeta - twiss.eq_gemitt_zeta) + ibs_growth_rates.Tz * gemitt_zeta ) - # ---------------------------------------------------------------------------------------------- + # --------------------------------------------------------------------------------------------- # And return the results return ( ibs_growth_rates, @@ -270,7 +270,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( - sr_ibs_eq_gemitt_zeta: final longitudinal equilibrium geometric emittance converged to, in [m]. """ # fmt: off - # ---------------------------------------------------------------------------------------------- + # --------------------------------------------------------------------------------------------- # Check for SR equilibrium emittances, damping constants and partition numbers in the TwissTable _required_attrs = ["damping_constants_s", "partition_numbers", "eq_gemitt_x", "eq_gemitt_y", "eq_gemitt_zeta"] if any(getattr(twiss, attr, None) is None for attr in _required_attrs): @@ -279,8 +279,8 @@ def compute_equilibrium_emittances_from_sr_and_ibs( "The TwissTable must contain SR equilibrium emittances and damping constants. " "Did you activate radiation and twiss with `eneloss_and_damping=True?`" ) - # ---------------------------------------------------------------------------------------------- - # Perform checks on required & exclusive parameters, and convert emittances to geometric if needed + # # --------------------------------------------------------------------------------------------- + # # Perform checks on required & exclusive parameters # assert total_beam_intensity is not None, "Must provide 'total_beam_intensity'" # assert any([gemitt_x, nemitt_x]), "Must provide either 'gemitt_x' or 'nemitt_x'" # assert any([gemitt_y, nemitt_y]), "Must provide either 'gemitt_y' or 'nemitt_y'" @@ -289,6 +289,10 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # assert nemitt_x is None, "Cannot provide both 'gemitt_x' and 'nemitt_x'" # if gemitt_y is not None: # assert nemitt_y is None, "Cannot provide both 'gemitt_y' and 'nemitt_y'" + # if gemitt_zeta is not None: + # assert nemitt_zeta is None, "Cannot provide both 'gemitt_zeta' and 'nemitt_zeta'" + # # --------------------------------------------------------------------------------------------- + # # Convert emittances to geometric if needed # if nemitt_x is not None: # assert gemitt_x is None, "Cannot provide both 'gemitt_x' and 'nemitt_x'" # gemitt_x = nemitt_x / (twiss.beta0 * twiss.gamma0) @@ -298,13 +302,13 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # if nemitt_zeta is not None: # assert gemitt_zeta is None, "Cannot provide both 'gemitt_zeta' and 'nemitt_zeta'" # gemitt_zeta = nemitt_zeta / (twiss.beta0 * twiss.gamma0) - # ---------------------------------------------------------------------------------------------- + # --------------------------------------------------------------------------------------------- # Check for valid value of emittance_constraint and warn if constraint provided but factor is 0 if emittance_constraint is not None: assert emittance_constraint.lower() in ("coupling", "excitation"), "Invalid 'emittance_constraint', accepted values are 'coupling' or 'excitation'." if emittance_coupling_factor == 0: LOGGER.warning("As 'emittance_coupling_factor` is zero, providing 'emittance_constraint' has no effect!") - # ---------------------------------------------------------------------------------------------- + # --------------------------------------------------------------------------------------------- # Handle initial transverse emittances and potential effect of coupling / excitation constraints # TODO: I don't like this, would rather force the user to provide gemitt_x, gemitt_y & gemitt_zeta if initial_emittances is None: @@ -327,7 +331,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( else: emittance_x, emittance_y, emittance_z = initial_emittances # fmt: on - # ---------------------------------------------------------------------------------------------- + # --------------------------------------------------------------------------------------------- # Handle initial longitudinal emittance and potential effect of bunch lengthening sigma_zeta = (emittance_z * twiss.bets0) ** 0.5 sigma_delta = (emittance_z / twiss.bets0) ** 0.5 @@ -349,7 +353,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( ) # Since a longitudinal property was overwritten we recompute the emittance_z emittance_z = sigma_zeta * sigma_delta - # ---------------------------------------------------------------------------------------------- + # --------------------------------------------------------------------------------------------- # Initialize values for the iterative process (first time step is revolution period) iterations: float = 0 tolerance: float = np.inf @@ -364,7 +368,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( res_gemitt_zeta: list[float] = [] # Starting emittances (numpy array since we compute the next ones from these) current_emittances: ArrayLike = np.array([emittance_x, emittance_y, emittance_z]) - # ---------------------------------------------------------------------------------------------- + # --------------------------------------------------------------------------------------------- # Start the iterative process until convergence: # - Compute IBS rates and emittance time derivatives # - Compute new emittances using the time derivatives and time step @@ -372,12 +376,14 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # - Store all intermediate results for this time step # - Compute tolerance and check for convergence while tolerance > rtol: - if verbose is True: # Display estimated convergence progress if asked + # -------------------------------------------------------------------------- + # Display estimated convergence progress if asked + if verbose is True: xo.general._print( f"Iteration {iterations} - convergence = {100 * rtol / tolerance:.1f}%", end="\r" ) - - # Compute IBS growth rates and emittance derivatives + # -------------------------------------------------------------------------- + # Compute IBS growth rates and emittance derivatives (and unpack) ibs_growth_rates, emittance_derivatives = _ibs_rates_and_emittance_derivatives( twiss=twiss, formalism=formalism, @@ -388,14 +394,13 @@ def compute_equilibrium_emittances_from_sr_and_ibs( longitudinal_emittance_ratio=longitudinal_emittance_ratio, **kwargs, ) - # Make sure we have them as tuples for below ibs_growth_rates = ibs_growth_rates.as_tuple() emittance_derivatives = emittance_derivatives.as_tuple() - + # -------------------------------------------------------------------------- # Update current emittances - add the time step * emittance time derivatives current_emittances += np.array(emittance_derivatives) * time_step - - # Enforce constraints if specified + # -------------------------------------------------------------------------- + # Enforce transverse constraint if specified if emittance_constraint.lower() == "coupling": forced_emittance_x = (current_emittances[0] + current_emittances[1]) / ( 1 + emittance_coupling_factor @@ -403,12 +408,11 @@ def compute_equilibrium_emittances_from_sr_and_ibs( forced_emittance_y = forced_emittance_x * emittance_coupling_factor current_emittances[0] = forced_emittance_x current_emittances[1] = forced_emittance_y - elif emittance_constraint.lower() == "excitation": forced_emittance_y = current_emittances[0] * emittance_coupling_factor current_emittances[1] = forced_emittance_y - - # Append current values to lists + # -------------------------------------------------------------------------- + # Store the current values for this time step time_deltas.append(time_step) T_x.append(ibs_growth_rates[0]) T_y.append(ibs_growth_rates[1]) @@ -416,19 +420,17 @@ def compute_equilibrium_emittances_from_sr_and_ibs( res_gemitt_x.append(current_emittances[0]) res_gemitt_y.append(current_emittances[1]) res_gemitt_zeta.append(current_emittances[2]) - + # -------------------------------------------------------------------------- # Compute tolerance (but not at first step since there is no previous value) + # and store current emittances for tolerance computation at next iteration if iterations > 0: tolerance = np.max( np.abs((current_emittances - previous_emittances) / previous_emittances) ) - - # Store current emittances for the next iteration previous_emittances = current_emittances.copy() - - # Update time step for the next iteration + # -------------------------------------------------------------------------- + # Update time step for the next iteration and increase counter time_step = 0.01 / np.max((ibs_growth_rates, twiss.damping_constants_s)) - iterations += 1 # ---------------------------------------------------------------------------------------------- # We have exited the loop, we have converged. Construct a Table with the results and return it From 361f6e362d9d31c838a5677354fb1c07edcacf0c Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 16:30:26 +0100 Subject: [PATCH 074/137] avoid attribute access in loop for print --- xfields/ibs/_equilibrium.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 39a1484c..6a22742a 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -13,6 +13,7 @@ import xobjects as xo import xtrack as xt from numpy.typing import ArrayLike +from xobjects.general import _print from xtrack import Table from xfields.ibs._analytical import IBSGrowthRates @@ -379,9 +380,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # -------------------------------------------------------------------------- # Display estimated convergence progress if asked if verbose is True: - xo.general._print( - f"Iteration {iterations} - convergence = {100 * rtol / tolerance:.1f}%", end="\r" - ) + _print(f"Iteration {iterations} - convergence = {100 * rtol / tolerance:.1f}%", end="\r") # -------------------------------------------------------------------------- # Compute IBS growth rates and emittance derivatives (and unpack) ibs_growth_rates, emittance_derivatives = _ibs_rates_and_emittance_derivatives( @@ -434,7 +433,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( iterations += 1 # ---------------------------------------------------------------------------------------------- # We have exited the loop, we have converged. Construct a Table with the results and return it - xo.general._print(f"Reached equilibrium with tolerance={tolerance:.2e} (vs rtol={rtol:.2e})") + _print(f"Reached equilibrium with tolerance={tolerance:.2e} (vs rtol={rtol:.2e})") result_table = Table( data={ "time": np.cumsum(time_deltas), From 83852529c8ef07e620ff4abfc90d2c96c507adf1 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 16:49:15 +0100 Subject: [PATCH 075/137] use logging.warning instead of warning module --- xfields/ibs/_equilibrium.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 6a22742a..b65dbe4b 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -6,7 +6,6 @@ from __future__ import annotations import logging -import warnings from typing import Literal import numpy as np @@ -313,7 +312,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # Handle initial transverse emittances and potential effect of coupling / excitation constraints # TODO: I don't like this, would rather force the user to provide gemitt_x, gemitt_y & gemitt_zeta if initial_emittances is None: - print("Emittances from the Twiss object are being used.") + _print("Emittances from the Twiss object are being used.") emittance_x, emittance_y, emittance_z = ( twiss.eq_gemitt_x, twiss.eq_gemitt_y, @@ -337,14 +336,10 @@ def compute_equilibrium_emittances_from_sr_and_ibs( sigma_zeta = (emittance_z * twiss.bets0) ** 0.5 sigma_delta = (emittance_z / twiss.bets0) ** 0.5 if overwrite_sigma_zeta is not None: - warnings.warn( - "'overwrite_sigma_zeta' is specified, make sure it remains consistent with 'initial_emittances'." - ) + LOGGER.warning("'overwrite_sigma_zeta' is specified, make sure it remains consistent with 'initial_emittances'.") sigma_zeta = overwrite_sigma_zeta elif overwrite_sigma_delta is not None: - warnings.warn( - "'sigma_delta' is specified, make sure it remains consistent with 'initial_emittances'." - ) + LOGGER.warning("'sigma_delta' is specified, make sure it remains consistent with 'initial_emittances'.") sigma_delta = overwrite_sigma_delta longitudinal_emittance_ratio = sigma_zeta / sigma_delta if overwrite_sigma_zeta is not None or overwrite_sigma_delta is not None: @@ -401,9 +396,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # -------------------------------------------------------------------------- # Enforce transverse constraint if specified if emittance_constraint.lower() == "coupling": - forced_emittance_x = (current_emittances[0] + current_emittances[1]) / ( - 1 + emittance_coupling_factor - ) + forced_emittance_x = (current_emittances[0] + current_emittances[1]) / (1 + emittance_coupling_factor) forced_emittance_y = forced_emittance_x * emittance_coupling_factor current_emittances[0] = forced_emittance_x current_emittances[1] = forced_emittance_y From 86a2079f2e35920a9fa7d2b7eee83ea88f9f57d7 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 17:12:09 +0100 Subject: [PATCH 076/137] TYPE CHECKING guard --- xfields/ibs/_equilibrium.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index b65dbe4b..0402b5b9 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -6,16 +6,20 @@ from __future__ import annotations import logging -from typing import Literal +from typing import TYPE_CHECKING import numpy as np import xobjects as xo import xtrack as xt -from numpy.typing import ArrayLike from xobjects.general import _print from xtrack import Table -from xfields.ibs._analytical import IBSGrowthRates +if TYPE_CHECKING: + from typing import Literal + + from numpy.typing import ArrayLike + + from xfields.ibs._analytical import IBSGrowthRates LOGGER = logging.getLogger(__name__) From a98eb8725c3271e34f684b152a73a04359faba09 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 15 Jan 2025 17:20:20 +0100 Subject: [PATCH 077/137] constraint first as it makes the conditionals clearer --- xfields/ibs/_equilibrium.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 0402b5b9..cc179b13 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -324,11 +324,11 @@ def compute_equilibrium_emittances_from_sr_and_ibs( ) # If emittance_coupling_factor is non zero, then natural emittance is modified accordingly (used # convention is valid for arbitrary damping partition numbers and emittance_coupling_factor). - if emittance_coupling_factor != 0 and emittance_constraint.lower() == "coupling": + if emittance_constraint.lower() == "coupling" and emittance_coupling_factor != 0: emittance_y = emittance_x * emittance_coupling_factor / (1 + emittance_coupling_factor * twiss.partition_numbers[1] / twiss.partition_numbers[0]) emittance_x *= 1 / (1 + emittance_coupling_factor * twiss.partition_numbers[1] / twiss.partition_numbers[0]) - if emittance_coupling_factor != 0 and emittance_constraint.lower() == "excitation": + if emittance_constraint.lower() == "excitation" and emittance_coupling_factor != 0: # The convention used only enforce a constraint on the vertical # emittance emittance_y = emittance_x * emittance_coupling_factor From 9dc4390b9a4c5e1d4508ff26de87d3e8a2d3f0cb Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Thu, 16 Jan 2025 11:51:59 +0100 Subject: [PATCH 078/137] also add sigma_zeta and sigma_delta into the result table --- xfields/ibs/_equilibrium.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index cc179b13..e33fcd4c 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -336,9 +336,11 @@ def compute_equilibrium_emittances_from_sr_and_ibs( emittance_x, emittance_y, emittance_z = initial_emittances # fmt: on # --------------------------------------------------------------------------------------------- - # Handle initial longitudinal emittance and potential effect of bunch lengthening + # Handle the potential longitudinal effects (bunch lengthening, microwave instability) + # First compute bunch length and momentum spread from longitudinal emittance (see xsuite twiss doc) sigma_zeta = (emittance_z * twiss.bets0) ** 0.5 sigma_delta = (emittance_z / twiss.bets0) ** 0.5 + # Now handle the scenario where the user wants to overwrite those if overwrite_sigma_zeta is not None: LOGGER.warning("'overwrite_sigma_zeta' is specified, make sure it remains consistent with 'initial_emittances'.") sigma_zeta = overwrite_sigma_zeta @@ -346,6 +348,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( LOGGER.warning("'sigma_delta' is specified, make sure it remains consistent with 'initial_emittances'.") sigma_delta = overwrite_sigma_delta longitudinal_emittance_ratio = sigma_zeta / sigma_delta + # Recompute the longidutinal emittance if either bunch length or momentum spread was overwritten if overwrite_sigma_zeta is not None or overwrite_sigma_delta is not None: assert initial_emittances is not None, ( "Input of 'overwrite_sigma_zeta' or 'overwrite_sigma_delta' provided, but " @@ -437,6 +440,8 @@ def compute_equilibrium_emittances_from_sr_and_ibs( "gemitt_x": np.array(res_gemitt_x), "gemitt_y": np.array(res_gemitt_y), "gemitt_zeta": np.array(res_gemitt_zeta), + "sigma_zeta": np.sqrt(np.array(res_gemitt_zeta) * twiss.bets0) , + "sigma_delta": np.sqrt(np.array(res_gemitt_zeta) / twiss.bets0), "Tx": np.array(T_x), "Ty": np.array(T_y), "Tz": np.array(T_z), From e41b2e4535f98310ad2fc73254e1600e1bc80a31 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Thu, 16 Jan 2025 11:54:18 +0100 Subject: [PATCH 079/137] also add these to docstring --- xfields/ibs/_equilibrium.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index e33fcd4c..033e5aa6 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -260,6 +260,8 @@ def compute_equilibrium_emittances_from_sr_and_ibs( - gemitt_x: horizontal geometric emittance values, in [m]. - gemitt_y: vertical geometric emittance values, in [m]. - gemitt_zeta: longitudinal geometric emittance values, in [m]. + - sigma_zeta: bunch length values, in [m]. + - sigma_delta: momentum spread values, in [-]. - Tx: horizontal IBS growth rate, in [s^-1]. - Ty: vertical IBS growth rate, in [s^-1]. - Tz: longitudinal IBS growth rate, in [s^-1]. @@ -440,7 +442,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( "gemitt_x": np.array(res_gemitt_x), "gemitt_y": np.array(res_gemitt_y), "gemitt_zeta": np.array(res_gemitt_zeta), - "sigma_zeta": np.sqrt(np.array(res_gemitt_zeta) * twiss.bets0) , + "sigma_zeta": np.sqrt(np.array(res_gemitt_zeta) * twiss.bets0), "sigma_delta": np.sqrt(np.array(res_gemitt_zeta) / twiss.bets0), "Tx": np.array(T_x), "Ty": np.array(T_y), From 54124e0e358a94b4f538aac368d672f3c7eec043 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Thu, 16 Jan 2025 15:57:13 +0100 Subject: [PATCH 080/137] typo fix and note admonition about required radiation config --- xfields/ibs/_equilibrium.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 033e5aa6..8aca14b3 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -183,7 +183,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( and Intra-Beam Scattering until convergence to equilibrium values. The equilibrium state is determined by an iterative process which consists in computing the IBS growth rates and the emittance time - derivaties, then computing the emittances at the next time step, + derivatives, then computing the emittances at the next time step, potentially including the effect of transverse constraints, and checking for convergence. The convergence criteria can be chosen by the user. @@ -196,6 +196,15 @@ def compute_equilibrium_emittances_from_sr_and_ibs( or microwave instability) can be accounted for by specifying the RMS bunch length and momentum spread. + Notes + ----- + It is required that radiation has been configured in the line, + and that the `TwissTable` holds information on the equilibrium + state from Synchrotron Radiation. This means calling first + `line.configure_radiation(model="mean")` and then the `.twiss()` + method with `eneloss_and_damping=True`. Please refer to the Twiss + user guide in the `xsuite` documentation for more information. + Parameters ---------- twiss : xtrack.TwissTable From 604f7c4ce02f419aea2c6e5068aa3183e8b9a74d Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Thu, 16 Jan 2025 16:02:01 +0100 Subject: [PATCH 081/137] make it very clear we recompute the emittance --- xfields/ibs/_equilibrium.py | 1 + 1 file changed, 1 insertion(+) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 8aca14b3..2bc7a5e3 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -366,6 +366,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( "not of 'initial_emittances'. Please provide 'initial_emittances'." ) # Since a longitudinal property was overwritten we recompute the emittance_z + LOGGER.warning("At least one longitudinal property overwritten, recomputing longitudinal emittance.") emittance_z = sigma_zeta * sigma_delta # --------------------------------------------------------------------------------------------- # Initialize values for the iterative process (first time step is revolution period) From 40a585ed90bb8f0cd81ead70ba9414b0774d2a76 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Thu, 16 Jan 2025 16:04:07 +0100 Subject: [PATCH 082/137] consistency in units --- xfields/ibs/_equilibrium.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 2bc7a5e3..07422cba 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -275,7 +275,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( - Ty: vertical IBS growth rate, in [s^-1]. - Tz: longitudinal IBS growth rate, in [s^-1]. The table also contains the following global quantities: - - damping_constants_s: radiation damping constants per second used. + - damping_constants_s: radiation damping constants used, in [s^-1]. - partition_numbers: damping partition numbers used. - eq_gemitt_x: horizontal equilibrium geometric emittance from synchrotron radiation used, in [m]. - eq_gemitt_y: vertical equilibrium geometric emittance from synchrotron radiation used, in [m]. From 9ad6de78ba6a95858f758cda8debdf281c454cb9 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Thu, 16 Jan 2025 16:05:39 +0100 Subject: [PATCH 083/137] also speficy in docstring that overwriting args force eps_zeta recompute --- xfields/ibs/_equilibrium.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 07422cba..82ef900d 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -244,10 +244,12 @@ def compute_equilibrium_emittances_from_sr_and_ibs( effect unless a non-zero factor is provided. overwrite_sigma_zeta : float, optional The RMS bunch length. If provided, overwrites the one computed from - the longitudinal emittance. Defaults to `None`. + the longitudinal emittance and forces a recompute of the longitudinal + emittance. Defaults to `None`. overwrite_sigma_delta : float, optional The RMS momentum spread of the bunch. If provided, overwrites the one - computed from the longitudinal emittance. Defaults to `None`. + computed from the longitudinal emittance and forces a recompute of the + longitudinal emittance. Defaults to `None`. rtol : float, optional Relative tolerance to determine when convergence is reached: if the relative difference between the computed emittances and those at the previous step is From aa3ba4df87a77f3ac49a234dfb4a103deacbc599 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Fri, 17 Jan 2025 14:13:02 +0100 Subject: [PATCH 084/137] create checks for important params, shuffle some comments around --- xfields/ibs/_equilibrium.py | 46 +++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 82ef900d..f3acf189 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -164,12 +164,12 @@ def compute_equilibrium_emittances_from_sr_and_ibs( total_beam_intensity: int, # TODO: want to force providing gemitt_x, gemitt_y & gemitt_zeta instead of this initial_emittances (can allow nemitt_x and add checks / conversions) initial_emittances: tuple[float, float, float] = None, - # gemitt_x: float = None, - # nemitt_x: float = None, - # gemitt_y: float = None, - # nemitt_y: float = None, - # gemitt_zeta: float = None, - # nemitt_zeta: float = None, + gemitt_x: float = None, + nemitt_x: float = None, + gemitt_y: float = None, + nemitt_y: float = None, + gemitt_zeta: float = None, + nemitt_zeta: float = None, overwrite_sigma_zeta: float = None, overwrite_sigma_delta: float = None, emittance_coupling_factor: float = 0, @@ -288,6 +288,11 @@ def compute_equilibrium_emittances_from_sr_and_ibs( """ # fmt: off # --------------------------------------------------------------------------------------------- + # Check for the required parameters + assert formalism is not None, "Must provide 'formalism'" # accepted values check in called functions + assert total_beam_intensity is not None, "Must provide 'total_beam_intensity'" + assert rtol is not None, "Must provide 'rtol'" # if the user sets None we would crash in weird ways + # --------------------------------------------------------------------------------------------- # Check for SR equilibrium emittances, damping constants and partition numbers in the TwissTable _required_attrs = ["damping_constants_s", "partition_numbers", "eq_gemitt_x", "eq_gemitt_y", "eq_gemitt_zeta"] if any(getattr(twiss, attr, None) is None for attr in _required_attrs): @@ -296,20 +301,9 @@ def compute_equilibrium_emittances_from_sr_and_ibs( "The TwissTable must contain SR equilibrium emittances and damping constants. " "Did you activate radiation and twiss with `eneloss_and_damping=True?`" ) - # # --------------------------------------------------------------------------------------------- - # # Perform checks on required & exclusive parameters - # assert total_beam_intensity is not None, "Must provide 'total_beam_intensity'" - # assert any([gemitt_x, nemitt_x]), "Must provide either 'gemitt_x' or 'nemitt_x'" - # assert any([gemitt_y, nemitt_y]), "Must provide either 'gemitt_y' or 'nemitt_y'" - # assert any([gemitt_zeta, nemitt_zeta]), "Must provide either 'gemitt_zeta' or 'nemitt_zeta'" - # if gemitt_x is not None: - # assert nemitt_x is None, "Cannot provide both 'gemitt_x' and 'nemitt_x'" - # if gemitt_y is not None: - # assert nemitt_y is None, "Cannot provide both 'gemitt_y' and 'nemitt_y'" - # if gemitt_zeta is not None: - # assert nemitt_zeta is None, "Cannot provide both 'gemitt_zeta' and 'nemitt_zeta'" - # # --------------------------------------------------------------------------------------------- - # # Convert emittances to geometric if needed + # --------------------------------------------------------------------------------------------- + # First: if geometric emittances are provided we will just use those. If instead normalized + # emittances are provided, we convert those to geometric emittances # if nemitt_x is not None: # assert gemitt_x is None, "Cannot provide both 'gemitt_x' and 'nemitt_x'" # gemitt_x = nemitt_x / (twiss.beta0 * twiss.gamma0) @@ -319,6 +313,18 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # if nemitt_zeta is not None: # assert gemitt_zeta is None, "Cannot provide both 'gemitt_zeta' and 'nemitt_zeta'" # gemitt_zeta = nemitt_zeta / (twiss.beta0 * twiss.gamma0) + # --------------------------------------------------------------------------------------------- + # Perform checks on required & exclusive parameters + # assert any([gemitt_x, nemitt_x]), "Must provide either 'gemitt_x' or 'nemitt_x'" + # assert any([gemitt_y, nemitt_y]), "Must provide either 'gemitt_y' or 'nemitt_y'" + # assert any([gemitt_zeta, nemitt_zeta]), "Must provide either 'gemitt_zeta' or 'nemitt_zeta'" + # if gemitt_x is not None: + # assert nemitt_x is None, "Cannot provide both 'gemitt_x' and 'nemitt_x'" + # if gemitt_y is not None: + # assert nemitt_y is None, "Cannot provide both 'gemitt_y' and 'nemitt_y'" + # if gemitt_zeta is not None: + # assert nemitt_zeta is None, "Cannot provide both 'gemitt_zeta' and 'nemitt_zeta'" + # --------------------------------------------------------------------------------------------- # Check for valid value of emittance_constraint and warn if constraint provided but factor is 0 if emittance_constraint is not None: From 8f87d393a9e542c380ee964ce4857d36387eb8f1 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Fri, 17 Jan 2025 14:13:45 +0100 Subject: [PATCH 085/137] start taking from normalized emittances if they are provided, compute and override gemitt --- xfields/ibs/_equilibrium.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index f3acf189..196e9c38 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -304,15 +304,15 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # --------------------------------------------------------------------------------------------- # First: if geometric emittances are provided we will just use those. If instead normalized # emittances are provided, we convert those to geometric emittances - # if nemitt_x is not None: - # assert gemitt_x is None, "Cannot provide both 'gemitt_x' and 'nemitt_x'" - # gemitt_x = nemitt_x / (twiss.beta0 * twiss.gamma0) - # if nemitt_y is not None: - # assert gemitt_y is None, "Cannot provide both 'gemitt_y' and 'nemitt_y'" - # gemitt_y = nemitt_y / (twiss.beta0 * twiss.gamma0) - # if nemitt_zeta is not None: - # assert gemitt_zeta is None, "Cannot provide both 'gemitt_zeta' and 'nemitt_zeta'" - # gemitt_zeta = nemitt_zeta / (twiss.beta0 * twiss.gamma0) + if nemitt_x is not None: + assert gemitt_x is None, "Cannot provide both 'gemitt_x' and 'nemitt_x'" + gemitt_x = nemitt_x / (twiss.beta0 * twiss.gamma0) + if nemitt_y is not None: + assert gemitt_y is None, "Cannot provide both 'gemitt_y' and 'nemitt_y'" + gemitt_y = nemitt_y / (twiss.beta0 * twiss.gamma0) + if nemitt_zeta is not None: + assert gemitt_zeta is None, "Cannot provide both 'gemitt_zeta' and 'nemitt_zeta'" + gemitt_zeta = nemitt_zeta / (twiss.beta0 * twiss.gamma0) # --------------------------------------------------------------------------------------------- # Perform checks on required & exclusive parameters # assert any([gemitt_x, nemitt_x]), "Must provide either 'gemitt_x' or 'nemitt_x'" From c6854550b31602ff96f6a894d5dde7ea2a85208d Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Fri, 17 Jan 2025 14:20:14 +0100 Subject: [PATCH 086/137] shuffle around a bit --- xfields/ibs/_equilibrium.py | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 196e9c38..ddabc5d2 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -302,6 +302,13 @@ def compute_equilibrium_emittances_from_sr_and_ibs( "Did you activate radiation and twiss with `eneloss_and_damping=True?`" ) # --------------------------------------------------------------------------------------------- + # Check for valid value of emittance_constraint and warn if constraint provided but factor is 0 + if emittance_constraint is not None: + _valid_constraints = ("coupling", "excitation") + assert emittance_constraint.lower() in _valid_constraints, "Invalid 'emittance_constraint', accepted values are 'coupling' or 'excitation'." + if emittance_coupling_factor == 0: + LOGGER.warning("As 'emittance_coupling_factor` is zero, providing 'emittance_constraint' has no effect!") + # --------------------------------------------------------------------------------------------- # First: if geometric emittances are provided we will just use those. If instead normalized # emittances are provided, we convert those to geometric emittances if nemitt_x is not None: @@ -314,23 +321,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( assert gemitt_zeta is None, "Cannot provide both 'gemitt_zeta' and 'nemitt_zeta'" gemitt_zeta = nemitt_zeta / (twiss.beta0 * twiss.gamma0) # --------------------------------------------------------------------------------------------- - # Perform checks on required & exclusive parameters - # assert any([gemitt_x, nemitt_x]), "Must provide either 'gemitt_x' or 'nemitt_x'" - # assert any([gemitt_y, nemitt_y]), "Must provide either 'gemitt_y' or 'nemitt_y'" - # assert any([gemitt_zeta, nemitt_zeta]), "Must provide either 'gemitt_zeta' or 'nemitt_zeta'" - # if gemitt_x is not None: - # assert nemitt_x is None, "Cannot provide both 'gemitt_x' and 'nemitt_x'" - # if gemitt_y is not None: - # assert nemitt_y is None, "Cannot provide both 'gemitt_y' and 'nemitt_y'" - # if gemitt_zeta is not None: - # assert nemitt_zeta is None, "Cannot provide both 'gemitt_zeta' and 'nemitt_zeta'" - # --------------------------------------------------------------------------------------------- - # Check for valid value of emittance_constraint and warn if constraint provided but factor is 0 - if emittance_constraint is not None: - assert emittance_constraint.lower() in ("coupling", "excitation"), "Invalid 'emittance_constraint', accepted values are 'coupling' or 'excitation'." - if emittance_coupling_factor == 0: - LOGGER.warning("As 'emittance_coupling_factor` is zero, providing 'emittance_constraint' has no effect!") # --------------------------------------------------------------------------------------------- # Handle initial transverse emittances and potential effect of coupling / excitation constraints # TODO: I don't like this, would rather force the user to provide gemitt_x, gemitt_y & gemitt_zeta From 646ac2bca26943c85952af5bbb802e5d9539de16 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Fri, 17 Jan 2025 14:20:37 +0100 Subject: [PATCH 087/137] if no initial emittance, take the SR eq value, log and set a flag --- xfields/ibs/_equilibrium.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index ddabc5d2..92c9032f 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -321,7 +321,20 @@ def compute_equilibrium_emittances_from_sr_and_ibs( assert gemitt_zeta is None, "Cannot provide both 'gemitt_zeta' and 'nemitt_zeta'" gemitt_zeta = nemitt_zeta / (twiss.beta0 * twiss.gamma0) # --------------------------------------------------------------------------------------------- - + # Take default values from the TwissTable if no initial emittances are provided. In this case we + # need to renormalize with the emittance_constraint so we add a flag to know we need to do that + _renormalize_transverse_emittances = False + if gemitt_x is None and nemitt_x is None: + LOGGER.info("No initial horizontal emittance provided, taking SR equilibrium value from TwissTable.") + gemitt_x = twiss.eq_gemitt_x + _renormalize_transverse_emittances = True + if gemitt_y is None and nemitt_y is None: + LOGGER.info("No initial vertical emittance provided, taking SR equilibrium value from TwissTable.") + gemitt_y = twiss.eq_gemitt_y + _renormalize_transverse_emittances = True + if gemitt_zeta is None and nemitt_zeta is None: + LOGGER.info("No initial longitudinal emittance provided, taking SR equilibrium value from TwissTable.") + gemitt_zeta = twiss.eq_gemitt_zeta # --------------------------------------------------------------------------------------------- # Handle initial transverse emittances and potential effect of coupling / excitation constraints # TODO: I don't like this, would rather force the user to provide gemitt_x, gemitt_y & gemitt_zeta From a84c63f2474235f2ac370dcb5d985ddcda5f8bdb Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Fri, 17 Jan 2025 14:29:03 +0100 Subject: [PATCH 088/137] use starting_gemitt_[x,y,zeta] and different logic for renormalization --- xfields/ibs/_equilibrium.py | 39 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 92c9032f..54f4f2ac 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -336,27 +336,26 @@ def compute_equilibrium_emittances_from_sr_and_ibs( LOGGER.info("No initial longitudinal emittance provided, taking SR equilibrium value from TwissTable.") gemitt_zeta = twiss.eq_gemitt_zeta # --------------------------------------------------------------------------------------------- - # Handle initial transverse emittances and potential effect of coupling / excitation constraints - # TODO: I don't like this, would rather force the user to provide gemitt_x, gemitt_y & gemitt_zeta - if initial_emittances is None: - _print("Emittances from the Twiss object are being used.") - emittance_x, emittance_y, emittance_z = ( - twiss.eq_gemitt_x, - twiss.eq_gemitt_y, - twiss.eq_gemitt_zeta, - ) - # If emittance_coupling_factor is non zero, then natural emittance is modified accordingly (used - # convention is valid for arbitrary damping partition numbers and emittance_coupling_factor). + # By now we should have a value for geometric emittances in each plane. We assign them to new + # variables for clarity, and these might be overwritten below in case we have to apply a constraint + starting_gemitt_x = gemitt_x + starting_gemitt_y = gemitt_y + starting_gemitt_zeta = gemitt_zeta + # --------------------------------------------------------------------------------------------- + # If we need to renormalize the transverse emittances, we so now. If emittance_coupling_factor is + # non-zero, transverse emittances are modified accordingly (used convention is valid for arbitrary + # damping partition numbers and emittance_coupling_factor values). + if _renormalize_transverse_emittances is True: + # If constraint is coupling, both emittances are modified (from factor and partition numbers) if emittance_constraint.lower() == "coupling" and emittance_coupling_factor != 0: - emittance_y = emittance_x * emittance_coupling_factor / (1 + emittance_coupling_factor * twiss.partition_numbers[1] / twiss.partition_numbers[0]) - emittance_x *= 1 / (1 + emittance_coupling_factor * twiss.partition_numbers[1] / twiss.partition_numbers[0]) - - if emittance_constraint.lower() == "excitation" and emittance_coupling_factor != 0: - # The convention used only enforce a constraint on the vertical - # emittance - emittance_y = emittance_x * emittance_coupling_factor - else: - emittance_x, emittance_y, emittance_z = initial_emittances + LOGGER.info("Enforcing 'coupling' constraint on transverse emittances.") + starting_gemitt_y = gemitt_x * emittance_coupling_factor / (1 + emittance_coupling_factor * twiss.partition_numbers[1] / twiss.partition_numbers[0]) + starting_gemitt_x = gemitt_x / (1 + emittance_coupling_factor * twiss.partition_numbers[1] / twiss.partition_numbers[0]) + # If constraint is excitation, only vertical emittance is modified + elif emittance_constraint.lower() == "excitation" and emittance_coupling_factor != 0: + LOGGER.info("Enforcing 'excitation' constraint on transverse emittances.") + starting_gemitt_y = gemitt_x * emittance_coupling_factor + starting_gemitt_x = gemitt_x # fmt: on # --------------------------------------------------------------------------------------------- # Handle the potential longitudinal effects (bunch lengthening, microwave instability) From 90bb4034d79bdcc45822edcb4a53af817ccf1b99 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Fri, 17 Jan 2025 14:30:26 +0100 Subject: [PATCH 089/137] and use these new variables, will need to run tests here --- xfields/ibs/_equilibrium.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 54f4f2ac..36dbb153 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -360,8 +360,8 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # --------------------------------------------------------------------------------------------- # Handle the potential longitudinal effects (bunch lengthening, microwave instability) # First compute bunch length and momentum spread from longitudinal emittance (see xsuite twiss doc) - sigma_zeta = (emittance_z * twiss.bets0) ** 0.5 - sigma_delta = (emittance_z / twiss.bets0) ** 0.5 + sigma_zeta = (starting_gemitt_zeta * twiss.bets0) ** 0.5 + sigma_delta = (starting_gemitt_zeta / twiss.bets0) ** 0.5 # Now handle the scenario where the user wants to overwrite those if overwrite_sigma_zeta is not None: LOGGER.warning("'overwrite_sigma_zeta' is specified, make sure it remains consistent with 'initial_emittances'.") @@ -378,7 +378,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( ) # Since a longitudinal property was overwritten we recompute the emittance_z LOGGER.warning("At least one longitudinal property overwritten, recomputing longitudinal emittance.") - emittance_z = sigma_zeta * sigma_delta + starting_gemitt_zeta = sigma_zeta * sigma_delta # --------------------------------------------------------------------------------------------- # Initialize values for the iterative process (first time step is revolution period) iterations: float = 0 @@ -393,7 +393,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( res_gemitt_y: list[float] = [] res_gemitt_zeta: list[float] = [] # Starting emittances (numpy array since we compute the next ones from these) - current_emittances: ArrayLike = np.array([emittance_x, emittance_y, emittance_z]) + current_emittances: ArrayLike = np.array([starting_gemitt_x, starting_gemitt_y, starting_gemitt_zeta]) # --------------------------------------------------------------------------------------------- # Start the iterative process until convergence: # - Compute IBS rates and emittance time derivatives From 233c6494c17dfd30c7cfeaf7f8716f914edaa640 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Fri, 17 Jan 2025 14:31:08 +0100 Subject: [PATCH 090/137] todo for discussion and review with Seb --- xfields/ibs/_equilibrium.py | 1 + 1 file changed, 1 insertion(+) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 36dbb153..733b3ec8 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -342,6 +342,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( starting_gemitt_y = gemitt_y starting_gemitt_zeta = gemitt_zeta # --------------------------------------------------------------------------------------------- + # TODO: Decide with Seb if this is ok - we renormalize even if just 1 value was taken from TwissTable SR eq # If we need to renormalize the transverse emittances, we so now. If emittance_coupling_factor is # non-zero, transverse emittances are modified accordingly (used convention is valid for arbitrary # damping partition numbers and emittance_coupling_factor values). From f64a8c732f652f0bb1a0b0a88eb68e08fffc45bc Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Fri, 17 Jan 2025 14:37:31 +0100 Subject: [PATCH 091/137] docstring for the starting emittance parameters --- xfields/ibs/_equilibrium.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 733b3ec8..936c52db 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -220,6 +220,30 @@ def compute_equilibrium_emittances_from_sr_and_ibs( vertical and longitudinal planes, in [m]. If not provided, the SR equilibrium emittances from the TwissTable are used. Defaults to `None`. + gemitt_x : float, optional + Starting horizontal geometric emittance, in [m]. If neither this nor + the normalized one is provided, the SR equilibrium value from the + provided `TwissTable` is used. + nemitt_x : float, optional + Starting horizontal normalized emittance, in [m]. If neither this nor + the geometric one is provided, the SR equilibrium value from the + provided `TwissTable` is used. + gemitt_y : float, optional + Starting vertical geometric emittance, in [m]. If neither this nor + the normalized one is provided, the SR equilibrium value from the + provided `TwissTable` is used. + nemitt_y : float, optional + Starting vertical normalized emittance, in [m]. If neither this nor + the geometric one is provided, the SR equilibrium value from the + provided `TwissTable` is used. + gemitt_zeta : float, optional + Starting longitudinal geometric emittance, in [m]. If neither this + nor the normalized one is provided, the SR equilibrium value from the + provided `TwissTable` is used. + nemitt_zeta : float, optional + Starting longitudinal normalized emittance, in [m]. If neither this + nor the geometric one is provided, the SR equilibrium value from the + provided `TwissTable` is used. emittance_coupling_factor : float, optional The ratio of perturbed transverse emittances due to betatron coupling. If a value is provided, it is taken into account for the evolution of From 7bc2f6500da5a79e1d6ea3e90c6bb2830b685ca8 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Fri, 17 Jan 2025 14:37:54 +0100 Subject: [PATCH 092/137] remove initial_emittances from params and docstring --- xfields/ibs/_equilibrium.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 936c52db..67b13524 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -162,8 +162,6 @@ def compute_equilibrium_emittances_from_sr_and_ibs( twiss: xt.TwissTable, formalism: Literal["Nagaitsev", "Bjorken-Mtingwa", "B&M"], total_beam_intensity: int, - # TODO: want to force providing gemitt_x, gemitt_y & gemitt_zeta instead of this initial_emittances (can allow nemitt_x and add checks / conversions) - initial_emittances: tuple[float, float, float] = None, gemitt_x: float = None, nemitt_x: float = None, gemitt_y: float = None, @@ -215,11 +213,6 @@ def compute_equilibrium_emittances_from_sr_and_ibs( case-insensitively. total_beam_intensity : int The bunch intensity, in [particles per bunch]. - initial_emittances : tuple[float, float, float], optional - The bunch's starting geometric emittances in the horizontal, - vertical and longitudinal planes, in [m]. If not provided, the - SR equilibrium emittances from the TwissTable are used. Defaults - to `None`. gemitt_x : float, optional Starting horizontal geometric emittance, in [m]. If neither this nor the normalized one is provided, the SR equilibrium value from the From df3744e5cba9c3eba87c808c9b771d6ead591b4b Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Fri, 17 Jan 2025 14:38:50 +0100 Subject: [PATCH 093/137] start warning admonition with TODO --- xfields/ibs/_equilibrium.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 67b13524..73afeeed 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -203,6 +203,11 @@ def compute_equilibrium_emittances_from_sr_and_ibs( method with `eneloss_and_damping=True`. Please refer to the Twiss user guide in the `xsuite` documentation for more information. + Warning + ------- + TODO: write about providing initial emittances and respecting the + constraint if it is also provided. + Parameters ---------- twiss : xtrack.TwissTable From a5b45121662e1ffb00ade13e4db3886a4a7287c4 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Fri, 17 Jan 2025 14:40:25 +0100 Subject: [PATCH 094/137] no more trace of input_emittances --- xfields/ibs/_equilibrium.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 73afeeed..29065018 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -387,19 +387,15 @@ def compute_equilibrium_emittances_from_sr_and_ibs( sigma_delta = (starting_gemitt_zeta / twiss.bets0) ** 0.5 # Now handle the scenario where the user wants to overwrite those if overwrite_sigma_zeta is not None: - LOGGER.warning("'overwrite_sigma_zeta' is specified, make sure it remains consistent with 'initial_emittances'.") + LOGGER.warning("'overwrite_sigma_zeta' specified, make sure it is consistent with provided longitudinal emittance.") sigma_zeta = overwrite_sigma_zeta elif overwrite_sigma_delta is not None: - LOGGER.warning("'sigma_delta' is specified, make sure it remains consistent with 'initial_emittances'.") + LOGGER.warning("'overwrite_sigma_delta' specified, make sure it is consistent with provided longitudinal emittance.") sigma_delta = overwrite_sigma_delta longitudinal_emittance_ratio = sigma_zeta / sigma_delta # Recompute the longidutinal emittance if either bunch length or momentum spread was overwritten if overwrite_sigma_zeta is not None or overwrite_sigma_delta is not None: - assert initial_emittances is not None, ( - "Input of 'overwrite_sigma_zeta' or 'overwrite_sigma_delta' provided, but " - "not of 'initial_emittances'. Please provide 'initial_emittances'." - ) - # Since a longitudinal property was overwritten we recompute the emittance_z + # Since a longitudinal property was overwritten we recompute the starting longitudinal emittance LOGGER.warning("At least one longitudinal property overwritten, recomputing longitudinal emittance.") starting_gemitt_zeta = sigma_zeta * sigma_delta # --------------------------------------------------------------------------------------------- From 97ed5c532c36a445056e72bed040320598b7b6b3 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Fri, 17 Jan 2025 14:48:53 +0100 Subject: [PATCH 095/137] some formatting --- xfields/ibs/_equilibrium.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 29065018..4bac87a4 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -359,12 +359,12 @@ def compute_equilibrium_emittances_from_sr_and_ibs( gemitt_zeta = twiss.eq_gemitt_zeta # --------------------------------------------------------------------------------------------- # By now we should have a value for geometric emittances in each plane. We assign them to new - # variables for clarity, and these might be overwritten below in case we have to apply a constraint + # variables for clarity. These might be overwritten below in case we have to apply a constraint starting_gemitt_x = gemitt_x starting_gemitt_y = gemitt_y starting_gemitt_zeta = gemitt_zeta # --------------------------------------------------------------------------------------------- - # TODO: Decide with Seb if this is ok - we renormalize even if just 1 value was taken from TwissTable SR eq + # TODO: Decide with Seb if this is ok - we renormalize EVEN if just 1 value was taken from TwissTable SR eq # If we need to renormalize the transverse emittances, we so now. If emittance_coupling_factor is # non-zero, transverse emittances are modified accordingly (used convention is valid for arbitrary # damping partition numbers and emittance_coupling_factor values). @@ -393,9 +393,8 @@ def compute_equilibrium_emittances_from_sr_and_ibs( LOGGER.warning("'overwrite_sigma_delta' specified, make sure it is consistent with provided longitudinal emittance.") sigma_delta = overwrite_sigma_delta longitudinal_emittance_ratio = sigma_zeta / sigma_delta - # Recompute the longidutinal emittance if either bunch length or momentum spread was overwritten + # If either bunch length or momentum spread was overwritten, we recompute the longidutinal emittance if overwrite_sigma_zeta is not None or overwrite_sigma_delta is not None: - # Since a longitudinal property was overwritten we recompute the starting longitudinal emittance LOGGER.warning("At least one longitudinal property overwritten, recomputing longitudinal emittance.") starting_gemitt_zeta = sigma_zeta * sigma_delta # --------------------------------------------------------------------------------------------- @@ -443,7 +442,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # Update current emittances - add the time step * emittance time derivatives current_emittances += np.array(emittance_derivatives) * time_step # -------------------------------------------------------------------------- - # Enforce transverse constraint if specified + # Enforce transverse constraint if specified (valid with a factor value of 0) if emittance_constraint.lower() == "coupling": forced_emittance_x = (current_emittances[0] + current_emittances[1]) / (1 + emittance_coupling_factor) forced_emittance_y = forced_emittance_x * emittance_coupling_factor @@ -465,9 +464,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # Compute tolerance (but not at first step since there is no previous value) # and store current emittances for tolerance computation at next iteration if iterations > 0: - tolerance = np.max( - np.abs((current_emittances - previous_emittances) / previous_emittances) - ) + tolerance = np.max(np.abs((current_emittances - previous_emittances) / previous_emittances)) previous_emittances = current_emittances.copy() # -------------------------------------------------------------------------- # Update time step for the next iteration and increase counter From cfe428de55e9ebd5e486f7f852efddce1cbfeed1 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Fri, 17 Jan 2025 14:57:42 +0100 Subject: [PATCH 096/137] simpler message --- xfields/ibs/_equilibrium.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 4bac87a4..a5f1d6bb 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -347,15 +347,15 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # need to renormalize with the emittance_constraint so we add a flag to know we need to do that _renormalize_transverse_emittances = False if gemitt_x is None and nemitt_x is None: - LOGGER.info("No initial horizontal emittance provided, taking SR equilibrium value from TwissTable.") + LOGGER.info("No horizontal emittance provided, taking SR equilibrium value from TwissTable.") gemitt_x = twiss.eq_gemitt_x _renormalize_transverse_emittances = True if gemitt_y is None and nemitt_y is None: - LOGGER.info("No initial vertical emittance provided, taking SR equilibrium value from TwissTable.") + LOGGER.info("No vertical emittance provided, taking SR equilibrium value from TwissTable.") gemitt_y = twiss.eq_gemitt_y _renormalize_transverse_emittances = True if gemitt_zeta is None and nemitt_zeta is None: - LOGGER.info("No initial longitudinal emittance provided, taking SR equilibrium value from TwissTable.") + LOGGER.info("No longitudinal emittance provided, taking SR equilibrium value from TwissTable.") gemitt_zeta = twiss.eq_gemitt_zeta # --------------------------------------------------------------------------------------------- # By now we should have a value for geometric emittances in each plane. We assign them to new From 4c5c10cc244555c2fde9ed366d8a064b2fb9707d Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Fri, 17 Jan 2025 15:34:26 +0100 Subject: [PATCH 097/137] also check for provided values, should be renormalized if given as tw.eq_gemitt_etc --- xfields/ibs/_equilibrium.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index a5f1d6bb..d76828ab 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -357,6 +357,9 @@ def compute_equilibrium_emittances_from_sr_and_ibs( if gemitt_zeta is None and nemitt_zeta is None: LOGGER.info("No longitudinal emittance provided, taking SR equilibrium value from TwissTable.") gemitt_zeta = twiss.eq_gemitt_zeta + # We need to also check for the case where use provided emittance and it is the twiss SR eq one + if gemitt_x == twiss.eq_gemitt_x or gemitt_y == twiss.eq_gemitt_y or gemitt_zeta == twiss.eq_gemitt_zeta: + _renormalize_transverse_emittances = True # --------------------------------------------------------------------------------------------- # By now we should have a value for geometric emittances in each plane. We assign them to new # variables for clarity. These might be overwritten below in case we have to apply a constraint @@ -472,7 +475,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( iterations += 1 # ---------------------------------------------------------------------------------------------- # We have exited the loop, we have converged. Construct a Table with the results and return it - _print(f"Reached equilibrium with tolerance={tolerance:.2e} (vs rtol={rtol:.2e})") + _print(f"Reached equilibrium in {iterations} iterations, with tolerance={tolerance:.2e} (vs rtol={rtol:.2e})") result_table = Table( data={ "time": np.cumsum(time_deltas), From 248ba6ec564fec44e88c98db163fe9771ea68aa9 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Fri, 17 Jan 2025 15:40:00 +0100 Subject: [PATCH 098/137] docstring warning about providing values and constraint --- xfields/ibs/_equilibrium.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index d76828ab..5d159617 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -205,8 +205,15 @@ def compute_equilibrium_emittances_from_sr_and_ibs( Warning ------- - TODO: write about providing initial emittances and respecting the - constraint if it is also provided. + If the user does not provide a starting emittance, the program + defaults to using the SR equilibrium value from the `TwissTable`, + which is a reasonable defaults for light sources. If an constraint + is provided via `emittance_constraint` the starting emittances are + re-computed to respect that constraint (this is logged to the user). + + If the user does provide starting emittances **and** a constraint, it + is up to the user to make sure these provided values are consistent + with the provided constraint! Parameters ---------- From 8cbbddc1a437200fdd759e6a50f745a0a1bc40a1 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Fri, 17 Jan 2025 15:50:10 +0100 Subject: [PATCH 099/137] one more todo, suggestion for behaviour --- xfields/ibs/_equilibrium.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 5d159617..16827578 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -350,6 +350,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( assert gemitt_zeta is None, "Cannot provide both 'gemitt_zeta' and 'nemitt_zeta'" gemitt_zeta = nemitt_zeta / (twiss.beta0 * twiss.gamma0) # --------------------------------------------------------------------------------------------- + # TODO: We can just check if ONE (or more) is None and say we take all 3 from TwissTable in this case -> any(emit is None for emit in (gemitt_x, gemitt_y, gemitt_zeta)) # Take default values from the TwissTable if no initial emittances are provided. In this case we # need to renormalize with the emittance_constraint so we add a flag to know we need to do that _renormalize_transverse_emittances = False @@ -374,7 +375,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( starting_gemitt_y = gemitt_y starting_gemitt_zeta = gemitt_zeta # --------------------------------------------------------------------------------------------- - # TODO: Decide with Seb if this is ok - we renormalize EVEN if just 1 value was taken from TwissTable SR eq + # TODO: Decide with Seb if this is ok - we renormalize EVEN if just 1 value was taken from TwissTable SR eq -> not case if we do the TODO from above # If we need to renormalize the transverse emittances, we so now. If emittance_coupling_factor is # non-zero, transverse emittances are modified accordingly (used convention is valid for arbitrary # damping partition numbers and emittance_coupling_factor values). From 4dd19e918afb92c784e555a969aed30925482a26 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 21 Jan 2025 10:34:06 +0100 Subject: [PATCH 100/137] tstep parameter commented out everywhere, for later feature if we want it --- xfields/ibs/_equilibrium.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 16827578..9bd49694 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -6,8 +6,9 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional +from matplotlib.pyplot import step import numpy as np import xobjects as xo import xtrack as xt @@ -173,9 +174,15 @@ def compute_equilibrium_emittances_from_sr_and_ibs( emittance_coupling_factor: float = 0, emittance_constraint: Literal["coupling", "excitation"] = "coupling", rtol: float = 1e-6, + # tstep: float = None, # TODO: feature for later? verbose: bool = True, **kwargs, ) -> Table: + # TODO: tstep docstring, feature for later? + # tstep : float, Optional + # Time step to use for each iteration, in [s]. If not provided, an + # adaptive time step is computed based on the IBS growth rates and + # the damping constants. Defaults to `None`. """ Compute the evolution of emittances due to Synchrotron Radiation and Intra-Beam Scattering until convergence to equilibrium values. @@ -367,6 +374,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( gemitt_zeta = twiss.eq_gemitt_zeta # We need to also check for the case where use provided emittance and it is the twiss SR eq one if gemitt_x == twiss.eq_gemitt_x or gemitt_y == twiss.eq_gemitt_y or gemitt_zeta == twiss.eq_gemitt_zeta: + # TODO: log here _renormalize_transverse_emittances = True # --------------------------------------------------------------------------------------------- # By now we should have a value for geometric emittances in each plane. We assign them to new @@ -381,6 +389,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # damping partition numbers and emittance_coupling_factor values). if _renormalize_transverse_emittances is True: # If constraint is coupling, both emittances are modified (from factor and partition numbers) + # TODO: renormalize using starting_... variables if emittance_constraint.lower() == "coupling" and emittance_coupling_factor != 0: LOGGER.info("Enforcing 'coupling' constraint on transverse emittances.") starting_gemitt_y = gemitt_x * emittance_coupling_factor / (1 + emittance_coupling_factor * twiss.partition_numbers[1] / twiss.partition_numbers[0]) @@ -479,6 +488,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( previous_emittances = current_emittances.copy() # -------------------------------------------------------------------------- # Update time step for the next iteration and increase counter + # time_step = tstep if tstep is not None else 0.01 / np.max((ibs_growth_rates, twiss.damping_constants_s)) # TODO: feature for later? time_step = 0.01 / np.max((ibs_growth_rates, twiss.damping_constants_s)) iterations += 1 # ---------------------------------------------------------------------------------------------- From 79fd22d1d48757b906b2f247678e032ae2d27622 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 21 Jan 2025 10:37:03 +0100 Subject: [PATCH 101/137] todo for later on sigma_zeta/delta --- xfields/ibs/_equilibrium.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 9bd49694..de24835c 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -500,8 +500,9 @@ def compute_equilibrium_emittances_from_sr_and_ibs( "gemitt_x": np.array(res_gemitt_x), "gemitt_y": np.array(res_gemitt_y), "gemitt_zeta": np.array(res_gemitt_zeta), - "sigma_zeta": np.sqrt(np.array(res_gemitt_zeta) * twiss.bets0), - "sigma_delta": np.sqrt(np.array(res_gemitt_zeta) / twiss.bets0), + # TODO: check the below, might need to input the longitudinal_ratio in this calculation (if we have bunch lengthening etc) + # "sigma_zeta": np.sqrt(np.array(res_gemitt_zeta) * twiss.bets0), + # "sigma_delta": np.sqrt(np.array(res_gemitt_zeta) / twiss.bets0), "Tx": np.array(T_x), "Ty": np.array(T_y), "Tz": np.array(T_z), From f19f08e2ba001d4020fd6d052127cc81d9529a4a Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 21 Jan 2025 10:53:43 +0100 Subject: [PATCH 102/137] start name with eq_ for stored results --- xfields/ibs/_equilibrium.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index de24835c..f5dc7716 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -517,9 +517,9 @@ def compute_equilibrium_emittances_from_sr_and_ibs( "eq_gemitt_x": twiss.eq_gemitt_x, "eq_gemitt_y": twiss.eq_gemitt_y, "eq_gemitt_zeta": twiss.eq_gemitt_zeta, - "sr_ibs_eq_gemitt_x": res_gemitt_x[-1], - "sr_ibs_eq_gemitt_y": res_gemitt_y[-1], - "sr_ibs_eq_gemitt_zeta": res_gemitt_zeta[-1], + "eq_sr_ibs_gemitt_x": res_gemitt_x[-1], + "eq_sr_ibs_gemitt_y": res_gemitt_y[-1], + "eq_sr_ibs_gemitt_zeta": res_gemitt_zeta[-1], } ) return result_table From 0b74c9c99b62b491d7ec95dc07698e6a70fa9065 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 21 Jan 2025 10:54:06 +0100 Subject: [PATCH 103/137] also in the docstring --- xfields/ibs/_equilibrium.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index f5dc7716..003af6b1 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -318,9 +318,9 @@ def compute_equilibrium_emittances_from_sr_and_ibs( - eq_gemitt_x: horizontal equilibrium geometric emittance from synchrotron radiation used, in [m]. - eq_gemitt_y: vertical equilibrium geometric emittance from synchrotron radiation used, in [m]. - eq_gemitt_zeta: longitudinal equilibrium geometric emittance from synchrotron radiation used, in [m]. - - sr_ibs_eq_gemitt_x: final horizontal equilibrium geometric emittance converged to, in [m]. - - sr_ibs_eq_gemitt_y: final vertical equilibrium geometric emittance converged to, in [m]. - - sr_ibs_eq_gemitt_zeta: final longitudinal equilibrium geometric emittance converged to, in [m]. + - eq_sr_ibs_gemitt_x: final horizontal equilibrium geometric emittance converged to, in [m]. + - eq_sr_ibs_gemitt_y: final vertical equilibrium geometric emittance converged to, in [m]. + - eq_sr_ibs_gemitt_zeta: final longitudinal equilibrium geometric emittance converged to, in [m]. """ # fmt: off # --------------------------------------------------------------------------------------------- From e666d05ecec34809e3d511c647ff01ede31ec1fc Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 21 Jan 2025 10:56:31 +0100 Subject: [PATCH 104/137] remove unused imports --- xfields/ibs/_equilibrium.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 003af6b1..b9af022c 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -6,9 +6,8 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING -from matplotlib.pyplot import step import numpy as np import xobjects as xo import xtrack as xt From e8f7237623a946ddb7aee5d2f5c4a3b9e5309248 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 21 Jan 2025 11:25:57 +0100 Subject: [PATCH 105/137] ref for derivatives computation --- xfields/ibs/_equilibrium.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index b9af022c..da52159c 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -73,8 +73,9 @@ def _ibs_rates_and_emittance_derivatives( ) -> tuple[IBSGrowthRates, EmittanceTimeDerivatives]: """ Compute the IBS growth rates and emittance time derivatives from - the effect of both IBS and SR. TODO: Include a ref here to the - analytical formula used. + the effect of both IBS and SR. Time derivatives are computed as + expressed in 'A Code for Calculating Intrabeam Scattering and + Beam Lifetime', C.H.Kim, LBNL-40296, 1997. Parameters ---------- @@ -132,8 +133,7 @@ def _ibs_rates_and_emittance_derivatives( **kwargs, ) # --------------------------------------------------------------------------------------------- - # Computing the emittance time derivatives analytically. - # TODO: ADD A REF TO THE FORMULA HERE + # Computing the emittance time derivatives analytically. These are Eq (1-3) of the reference LOGGER.debug("Computing emittance time derivatives analytically.") depsilon_x_dt = ( -2 * twiss.damping_constants_s[0] * (gemitt_x - twiss.eq_gemitt_x) From 62dca8b72e0a17b1b6dc52d33465d94271bf608a Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 21 Jan 2025 11:27:42 +0100 Subject: [PATCH 106/137] log here that we would renormalize --- xfields/ibs/_equilibrium.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index da52159c..fe05c2ce 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -356,7 +356,6 @@ def compute_equilibrium_emittances_from_sr_and_ibs( assert gemitt_zeta is None, "Cannot provide both 'gemitt_zeta' and 'nemitt_zeta'" gemitt_zeta = nemitt_zeta / (twiss.beta0 * twiss.gamma0) # --------------------------------------------------------------------------------------------- - # TODO: We can just check if ONE (or more) is None and say we take all 3 from TwissTable in this case -> any(emit is None for emit in (gemitt_x, gemitt_y, gemitt_zeta)) # Take default values from the TwissTable if no initial emittances are provided. In this case we # need to renormalize with the emittance_constraint so we add a flag to know we need to do that _renormalize_transverse_emittances = False @@ -372,8 +371,8 @@ def compute_equilibrium_emittances_from_sr_and_ibs( LOGGER.info("No longitudinal emittance provided, taking SR equilibrium value from TwissTable.") gemitt_zeta = twiss.eq_gemitt_zeta # We need to also check for the case where use provided emittance and it is the twiss SR eq one - if gemitt_x == twiss.eq_gemitt_x or gemitt_y == twiss.eq_gemitt_y or gemitt_zeta == twiss.eq_gemitt_zeta: - # TODO: log here + if gemitt_x == twiss.eq_gemitt_x or gemitt_y == twiss.eq_gemitt_y: + LOGGER.debug("At least one provided transverse emittance is the SR equilibrium value, renormalizing transverse emittances if constraint provided.") _renormalize_transverse_emittances = True # --------------------------------------------------------------------------------------------- # By now we should have a value for geometric emittances in each plane. We assign them to new @@ -382,7 +381,6 @@ def compute_equilibrium_emittances_from_sr_and_ibs( starting_gemitt_y = gemitt_y starting_gemitt_zeta = gemitt_zeta # --------------------------------------------------------------------------------------------- - # TODO: Decide with Seb if this is ok - we renormalize EVEN if just 1 value was taken from TwissTable SR eq -> not case if we do the TODO from above # If we need to renormalize the transverse emittances, we so now. If emittance_coupling_factor is # non-zero, transverse emittances are modified accordingly (used convention is valid for arbitrary # damping partition numbers and emittance_coupling_factor values). From 13656499cd7c5d1bd87593e8d48367d7a55fb568 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Tue, 21 Jan 2025 11:28:36 +0100 Subject: [PATCH 107/137] use starting_gemitt here as discussed --- xfields/ibs/_equilibrium.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index fe05c2ce..868db9e6 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -386,16 +386,15 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # damping partition numbers and emittance_coupling_factor values). if _renormalize_transverse_emittances is True: # If constraint is coupling, both emittances are modified (from factor and partition numbers) - # TODO: renormalize using starting_... variables if emittance_constraint.lower() == "coupling" and emittance_coupling_factor != 0: LOGGER.info("Enforcing 'coupling' constraint on transverse emittances.") - starting_gemitt_y = gemitt_x * emittance_coupling_factor / (1 + emittance_coupling_factor * twiss.partition_numbers[1] / twiss.partition_numbers[0]) - starting_gemitt_x = gemitt_x / (1 + emittance_coupling_factor * twiss.partition_numbers[1] / twiss.partition_numbers[0]) + starting_gemitt_y = starting_gemitt_x * emittance_coupling_factor / (1 + emittance_coupling_factor * twiss.partition_numbers[1] / twiss.partition_numbers[0]) + starting_gemitt_x = starting_gemitt_x / (1 + emittance_coupling_factor * twiss.partition_numbers[1] / twiss.partition_numbers[0]) # If constraint is excitation, only vertical emittance is modified elif emittance_constraint.lower() == "excitation" and emittance_coupling_factor != 0: LOGGER.info("Enforcing 'excitation' constraint on transverse emittances.") - starting_gemitt_y = gemitt_x * emittance_coupling_factor - starting_gemitt_x = gemitt_x + starting_gemitt_y = starting_gemitt_x * emittance_coupling_factor + starting_gemitt_x = starting_gemitt_x # fmt: on # --------------------------------------------------------------------------------------------- # Handle the potential longitudinal effects (bunch lengthening, microwave instability) From 3fd39b0fb8ffd6949b21b7ddc8958f379f8996e6 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 27 Jan 2025 11:10:42 +0100 Subject: [PATCH 108/137] tstep parameter, with docstring, and some comments --- xfields/ibs/_equilibrium.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 868db9e6..906df037 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -173,15 +173,10 @@ def compute_equilibrium_emittances_from_sr_and_ibs( emittance_coupling_factor: float = 0, emittance_constraint: Literal["coupling", "excitation"] = "coupling", rtol: float = 1e-6, - # tstep: float = None, # TODO: feature for later? + tstep: float = None, verbose: bool = True, **kwargs, ) -> Table: - # TODO: tstep docstring, feature for later? - # tstep : float, Optional - # Time step to use for each iteration, in [s]. If not provided, an - # adaptive time step is computed based on the IBS growth rates and - # the damping constants. Defaults to `None`. """ Compute the evolution of emittances due to Synchrotron Radiation and Intra-Beam Scattering until convergence to equilibrium values. @@ -289,6 +284,10 @@ def compute_equilibrium_emittances_from_sr_and_ibs( Relative tolerance to determine when convergence is reached: if the relative difference between the computed emittances and those at the previous step is below `rtol`, then convergence is considered achieved. Defaults to 1e-6. + tstep : float, optional + Time step to use for each iteration, in [s]. If not provided, an + adaptive time step is computed based on the IBS growth rates and + the damping constants. Defaults to `None`. verbose : bool, optional Whether to print out information on the current iteration step and estimated convergence progress. Defaults to `True`. @@ -371,6 +370,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( LOGGER.info("No longitudinal emittance provided, taking SR equilibrium value from TwissTable.") gemitt_zeta = twiss.eq_gemitt_zeta # We need to also check for the case where use provided emittance and it is the twiss SR eq one + # which would be our default if not provided and then would trigger renormalization above if gemitt_x == twiss.eq_gemitt_x or gemitt_y == twiss.eq_gemitt_y: LOGGER.debug("At least one provided transverse emittance is the SR equilibrium value, renormalizing transverse emittances if constraint provided.") _renormalize_transverse_emittances = True @@ -484,8 +484,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( previous_emittances = current_emittances.copy() # -------------------------------------------------------------------------- # Update time step for the next iteration and increase counter - # time_step = tstep if tstep is not None else 0.01 / np.max((ibs_growth_rates, twiss.damping_constants_s)) # TODO: feature for later? - time_step = 0.01 / np.max((ibs_growth_rates, twiss.damping_constants_s)) + time_step = tstep if tstep is not None else 0.01 / np.max((ibs_growth_rates, twiss.damping_constants_s)) iterations += 1 # ---------------------------------------------------------------------------------------------- # We have exited the loop, we have converged. Construct a Table with the results and return it From 93b4313bfeb857eda11cb00e16cb0c464ceba817 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 27 Jan 2025 11:15:05 +0100 Subject: [PATCH 109/137] some ibs conftest tweaks --- tests/ibs_conftest.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/ibs_conftest.py b/tests/ibs_conftest.py index 75937073..559efda6 100644 --- a/tests/ibs_conftest.py +++ b/tests/ibs_conftest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from pathlib import Path from typing import Tuple @@ -7,6 +9,7 @@ # /!\ This assumes xtrack repo is sitting next to xfields repo XTRACK_TEST_DATA = Path(__file__).parent.parent.parent / "xtrack" / "test_data/" + def set_madx_beam_parameters( madx: Madx, total_beam_intensity: int, @@ -80,7 +83,7 @@ def set_madx_beam_parameters( madx.sequence[seq_name].beam.bunched = bunched # set if the beam is bunched -def get_madx_ibs_growth_rates(madx: Madx) -> Tuple[float, float, float]: +def get_madx_ibs_growth_rates(madx: Madx) -> tuple[float, float, float]: """ Calls the IBS module then return horizontal, vertical and longitudinal growth rates. A Twiss call is done to make sure it is centered. @@ -128,7 +131,7 @@ def get_ref_particle_from_madx_beam(madx: Madx) -> xt.Particles: return xt.Particles(q0=q0, mass0=mass0, gamma0=gamma0) -def get_parameters_from_madx_beam(madx: Madx) -> Tuple[float, float, float, float, float]: +def get_parameters_from_madx_beam(madx: Madx) -> tuple[float, float, float, float, float]: """ Get beam intensity, transverse emittances, momentum spread and bunch length from the MAD-X's beam parameters. A Twiss call is From 837e4e3bb99ef4ce5df3584c2359a72c6e19d49e Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 27 Jan 2025 11:15:39 +0100 Subject: [PATCH 110/137] file rename --- ...test_ibs_equilibrium_emittances.py => test_ibs_equilibrium.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{test_ibs_equilibrium_emittances.py => test_ibs_equilibrium.py} (100%) diff --git a/tests/test_ibs_equilibrium_emittances.py b/tests/test_ibs_equilibrium.py similarity index 100% rename from tests/test_ibs_equilibrium_emittances.py rename to tests/test_ibs_equilibrium.py From 8b718eb34f140bd7bd7ebc17a4b303c2fe40cb0b Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 27 Jan 2025 11:16:59 +0100 Subject: [PATCH 111/137] quick formatting --- tests/test_ibs_equilibrium.py | 177 +++++++++++++++++++--------------- 1 file changed, 98 insertions(+), 79 deletions(-) diff --git a/tests/test_ibs_equilibrium.py b/tests/test_ibs_equilibrium.py index d8831858..f2960588 100644 --- a/tests/test_ibs_equilibrium.py +++ b/tests/test_ibs_equilibrium.py @@ -1,22 +1,24 @@ import pytest -import xfields as xf import xobjects as xo import xtrack as xt from ibs_conftest import XTRACK_TEST_DATA from numpy.testing import assert_allclose -bunch_intensity = 6.2e9 # 1C bunch intensity +import xfields as xf + +bunch_intensity = 6.2e9 # 1C bunch intensity bessy3_dir = XTRACK_TEST_DATA / "bessy3" line = xt.Line.from_json(str(bessy3_dir / "line.json")) line.build_tracker(_context=xo.ContextCpu()) line.matrix_stability_tol = 1e-2 -line.configure_radiation(model='mean') +line.configure_radiation(model="mean") line.compensate_radiation_energy_loss() twiss = line.twiss(eneloss_and_damping=True) -@pytest.mark.parametrize('emittance_constraint', ['coupling', 'excitation']) + +@pytest.mark.parametrize("emittance_constraint", ["coupling", "excitation"]) def test_ibs_emittance_constraints(emittance_constraint): ####################################### @@ -25,130 +27,147 @@ def test_ibs_emittance_constraints(emittance_constraint): emittance_coupling_factor = 0.02 - time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( - twiss, bunch_intensity, - emittance_coupling_factor=emittance_coupling_factor, - emittance_constraint=emittance_constraint, + time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = ( + xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( + twiss, + bunch_intensity, + emittance_coupling_factor=emittance_coupling_factor, + emittance_constraint=emittance_constraint, + ) ) - if emittance_constraint == 'coupling': + if emittance_constraint == "coupling": # Check equilibrium emittance assert_allclose( - emittances_x_list[-1], - twiss.eq_gemitt_x / (1 + emittance_coupling_factor) / (1 - T_x[-1] / 2 / twiss.damping_constants_s[0]), - rtol=5e-2 - ) + emittances_x_list[-1], + twiss.eq_gemitt_x + / (1 + emittance_coupling_factor) + / (1 - T_x[-1] / 2 / twiss.damping_constants_s[0]), + rtol=5e-2, + ) # Check equilibrium emittance assert_allclose( - emittances_z_list[-1], - twiss.eq_gemitt_zeta / (1 - T_z[-1] / 2 / twiss.damping_constants_s[2]), - rtol=2e-2 - ) + emittances_z_list[-1], + twiss.eq_gemitt_zeta / (1 - T_z[-1] / 2 / twiss.damping_constants_s[2]), + rtol=2e-2, + ) # Check emittance coupling constraint assert_allclose( - emittances_y_list[-1] / emittances_x_list[-1], - emittance_coupling_factor, - rtol=2e-2 - ) + emittances_y_list[-1] / emittances_x_list[-1], emittance_coupling_factor, rtol=2e-2 + ) else: # Check equilibrium emittance assert_allclose( - emittances_x_list[-1], - twiss.eq_gemitt_x / (1 - T_x[-1] / 2 / twiss.damping_constants_s[0]), - rtol=5e-2 - ) + emittances_x_list[-1], + twiss.eq_gemitt_x / (1 - T_x[-1] / 2 / twiss.damping_constants_s[0]), + rtol=5e-2, + ) # Check equilibrium emittance assert_allclose( - emittances_z_list[-1], - twiss.eq_gemitt_zeta / (1 - T_z[-1] / 2 / twiss.damping_constants_s[2]), - rtol=2e-2 - ) + emittances_z_list[-1], + twiss.eq_gemitt_zeta / (1 - T_z[-1] / 2 / twiss.damping_constants_s[2]), + rtol=2e-2, + ) # Check emittance coupling constraint assert_allclose( - emittances_y_list[-1] / emittances_x_list[-1], - emittance_coupling_factor, - ) - -@pytest.mark.parametrize('emittance_coupling_factor', [0.02, 0.1, 0.2]) + emittances_y_list[-1] / emittances_x_list[-1], + emittance_coupling_factor, + ) + + +@pytest.mark.parametrize("emittance_coupling_factor", [0.02, 0.1, 0.2]) def test_ibs_emittance_coupling_factor(emittance_coupling_factor): """ As the emittance coupling factor increases, the equilibrium emittance - cannot be compared anymore to the solution of the differential equation - describing the emittance evolution in presence of IBS and SR if a + cannot be compared anymore to the solution of the differential equation + describing the emittance evolution in presence of IBS and SR if a constraint on the emittance is enforced. """ ####################################### # Equilibrium emittances calculations # ####################################### - time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( - twiss, bunch_intensity, - emittance_coupling_factor=emittance_coupling_factor, + time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = ( + xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( + twiss, + bunch_intensity, + emittance_coupling_factor=emittance_coupling_factor, + ) ) # Check equilibrium emittance assert_allclose( - emittances_x_list[-1], - twiss.eq_gemitt_x / (1 + emittance_coupling_factor) / (1 - T_x[-1] / 2 / twiss.damping_constants_s[0]), - rtol=1e-1 - ) + emittances_x_list[-1], + twiss.eq_gemitt_x + / (1 + emittance_coupling_factor) + / (1 - T_x[-1] / 2 / twiss.damping_constants_s[0]), + rtol=1e-1, + ) # Check equilibrium emittance assert_allclose( - emittances_z_list[-1], - twiss.eq_gemitt_zeta / (1 - T_z[-1] / 2 / twiss.damping_constants_s[2]), - rtol=2e-2 - ) + emittances_z_list[-1], + twiss.eq_gemitt_zeta / (1 - T_z[-1] / 2 / twiss.damping_constants_s[2]), + rtol=2e-2, + ) # Check emittance coupling constraint assert_allclose( - emittances_y_list[-1] / emittances_x_list[-1], - emittance_coupling_factor, - ) - -@pytest.mark.parametrize('emittance_coupling_factor', [0.02, 0.1, 1.]) + emittances_y_list[-1] / emittances_x_list[-1], + emittance_coupling_factor, + ) + + +@pytest.mark.parametrize("emittance_coupling_factor", [0.02, 0.1, 1.0]) def test_ibs_emittance_no_constraint(emittance_coupling_factor): """ Without any emittance constraint, the equilibrium emittance becomes almost identical to the solution of the differential equation describing the emittance evolution in presence of IBS and SR. """ - initial_emittances=( - twiss.eq_gemitt_x, emittance_coupling_factor*twiss.eq_gemitt_x, - twiss.eq_gemitt_zeta - ) - emittance_constraint="" - natural_emittances=( - twiss.eq_gemitt_x, emittance_coupling_factor*twiss.eq_gemitt_x, - twiss.eq_gemitt_zeta - ) + initial_emittances = ( + twiss.eq_gemitt_x, + emittance_coupling_factor * twiss.eq_gemitt_x, + twiss.eq_gemitt_zeta, + ) + emittance_constraint = "" + natural_emittances = ( + twiss.eq_gemitt_x, + emittance_coupling_factor * twiss.eq_gemitt_x, + twiss.eq_gemitt_zeta, + ) ####################################### # Equilibrium emittances calculations # ####################################### - time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( - twiss, bunch_intensity, - initial_emittances=initial_emittances, - emittance_coupling_factor=emittance_coupling_factor, - emittance_constraint=emittance_constraint, - natural_emittances=natural_emittances, + time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = ( + xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( + twiss, + bunch_intensity, + initial_emittances=initial_emittances, + emittance_coupling_factor=emittance_coupling_factor, + emittance_constraint=emittance_constraint, + natural_emittances=natural_emittances, + ) ) # Check equilibrium emittance assert_allclose( - emittances_x_list[-1], - twiss.eq_gemitt_x / (1 - T_x[-1] / 2 / twiss.damping_constants_s[0]), - rtol=2e-2 - ) + emittances_x_list[-1], + twiss.eq_gemitt_x / (1 - T_x[-1] / 2 / twiss.damping_constants_s[0]), + rtol=2e-2, + ) # Check equilibrium emittance assert_allclose( - emittances_y_list[-1], - emittance_coupling_factor*twiss.eq_gemitt_x / (1 - T_y[-1] / 2 / twiss.damping_constants_s[1]), - rtol=2e-2 - ) + emittances_y_list[-1], + emittance_coupling_factor + * twiss.eq_gemitt_x + / (1 - T_y[-1] / 2 / twiss.damping_constants_s[1]), + rtol=2e-2, + ) # Check equilibrium emittance assert_allclose( - emittances_z_list[-1], - twiss.eq_gemitt_zeta / (1 - T_z[-1] / 2 / twiss.damping_constants_s[2]), - rtol=2e-2 - ) \ No newline at end of file + emittances_z_list[-1], + twiss.eq_gemitt_zeta / (1 - T_z[-1] / 2 / twiss.damping_constants_s[2]), + rtol=2e-2, + ) From da230dfe74775839dc7131f7bfa85a8beb2240c0 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 27 Jan 2025 11:36:03 +0100 Subject: [PATCH 112/137] unused import --- tests/ibs_conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/ibs_conftest.py b/tests/ibs_conftest.py index 559efda6..7e33f4f9 100644 --- a/tests/ibs_conftest.py +++ b/tests/ibs_conftest.py @@ -1,7 +1,6 @@ from __future__ import annotations from pathlib import Path -from typing import Tuple import xtrack as xt from cpymad.madx import Madx From 98b9afa62a31ce6e4eb447730067c31af41eb42a Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 27 Jan 2025 11:36:22 +0100 Subject: [PATCH 113/137] fixture for BESSY3 line --- tests/test_ibs_equilibrium.py | 46 ++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/tests/test_ibs_equilibrium.py b/tests/test_ibs_equilibrium.py index f2960588..fd432040 100644 --- a/tests/test_ibs_equilibrium.py +++ b/tests/test_ibs_equilibrium.py @@ -8,19 +8,37 @@ bunch_intensity = 6.2e9 # 1C bunch intensity -bessy3_dir = XTRACK_TEST_DATA / "bessy3" -line = xt.Line.from_json(str(bessy3_dir / "line.json")) -line.build_tracker(_context=xo.ContextCpu()) -line.matrix_stability_tol = 1e-2 -line.configure_radiation(model="mean") -line.compensate_radiation_energy_loss() +# ----- Fixture for the (configured) BESSY III line ----- # -twiss = line.twiss(eneloss_and_damping=True) +@pytest.fixture(scope="module") +def bessy3_line_with_radiation() -> xt.Line: + """ + Loads the BESSY III lattice as a Line and + configures radiation before returning it. + """ + # ------------------------------------------- + # Load the line with a particle_ref + bess3_dir = XTRACK_TEST_DATA / "bessy3" + linefile = bess3_dir / "line.json" + line = xt.Line.from_json(linefile) + # ------------------------------------------- + # Build tracker and configure radiation + line.build_tracker() + line.matrix_stability_tol = 1e-2 + line.configure_radiation(model="mean") + line.compensate_radiation_energy_loss() + # Run twiss in fixture to compile kernels once + line.twiss(eneloss_and_damping=True) + return line -@pytest.mark.parametrize("emittance_constraint", ["coupling", "excitation"]) -def test_ibs_emittance_constraints(emittance_constraint): +# ----- Test Functions ----- # + + +@pytest.mark.parametrize("emittance_constraint", ["coupling", "excitation"]) +def test_ibs_emittance_constraints(emittance_constraint, bessy3_line_with_radiation: xt.Line): + twiss = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) ####################################### # Equilibrium emittances calculations # ####################################### @@ -77,13 +95,16 @@ def test_ibs_emittance_constraints(emittance_constraint): @pytest.mark.parametrize("emittance_coupling_factor", [0.02, 0.1, 0.2]) -def test_ibs_emittance_coupling_factor(emittance_coupling_factor): +def test_ibs_emittance_coupling_factor( + emittance_coupling_factor, bessy3_line_with_radiation: xt.Line +): """ As the emittance coupling factor increases, the equilibrium emittance cannot be compared anymore to the solution of the differential equation describing the emittance evolution in presence of IBS and SR if a constraint on the emittance is enforced. """ + twiss = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) ####################################### # Equilibrium emittances calculations # ####################################### @@ -118,12 +139,15 @@ def test_ibs_emittance_coupling_factor(emittance_coupling_factor): @pytest.mark.parametrize("emittance_coupling_factor", [0.02, 0.1, 1.0]) -def test_ibs_emittance_no_constraint(emittance_coupling_factor): +def test_ibs_emittance_no_constraint( + emittance_coupling_factor, bessy3_line_with_radiation: xt.Line +): """ Without any emittance constraint, the equilibrium emittance becomes almost identical to the solution of the differential equation describing the emittance evolution in presence of IBS and SR. """ + twiss = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) initial_emittances = ( twiss.eq_gemitt_x, emittance_coupling_factor * twiss.eq_gemitt_x, From 39119090e134df99c74b29b709326d072f3ec19f Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 27 Jan 2025 11:37:38 +0100 Subject: [PATCH 114/137] comments --- tests/test_ibs_equilibrium.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_ibs_equilibrium.py b/tests/test_ibs_equilibrium.py index fd432040..10495323 100644 --- a/tests/test_ibs_equilibrium.py +++ b/tests/test_ibs_equilibrium.py @@ -33,7 +33,7 @@ def bessy3_line_with_radiation() -> xt.Line: return line -# ----- Test Functions ----- # +# ----- Test Functions vs Analytical Formulae ----- # @pytest.mark.parametrize("emittance_constraint", ["coupling", "excitation"]) From 9c230c57c195ed50b118c38f1538d9e1bd2a5de1 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 27 Jan 2025 11:38:28 +0100 Subject: [PATCH 115/137] use set bunch intensity --- tests/test_ibs_equilibrium.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_ibs_equilibrium.py b/tests/test_ibs_equilibrium.py index 10495323..cac3b195 100644 --- a/tests/test_ibs_equilibrium.py +++ b/tests/test_ibs_equilibrium.py @@ -6,7 +6,7 @@ import xfields as xf -bunch_intensity = 6.2e9 # 1C bunch intensity +BUNCH_INTENSITY: float = 6.2e9 # 1C bunch intensity # ----- Fixture for the (configured) BESSY III line ----- # @@ -48,7 +48,7 @@ def test_ibs_emittance_constraints(emittance_constraint, bessy3_line_with_radiat time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = ( xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( twiss, - bunch_intensity, + BUNCH_INTENSITY, emittance_coupling_factor=emittance_coupling_factor, emittance_constraint=emittance_constraint, ) @@ -112,7 +112,7 @@ def test_ibs_emittance_coupling_factor( time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = ( xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( twiss, - bunch_intensity, + BUNCH_INTENSITY, emittance_coupling_factor=emittance_coupling_factor, ) ) @@ -167,7 +167,7 @@ def test_ibs_emittance_no_constraint( time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = ( xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( twiss, - bunch_intensity, + BUNCH_INTENSITY, initial_emittances=initial_emittances, emittance_coupling_factor=emittance_coupling_factor, emittance_constraint=emittance_constraint, From e372849dba09f580d622f5c6b009a689b7787df3 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 27 Jan 2025 11:40:29 +0100 Subject: [PATCH 116/137] gianni prefers tw to twiss --- tests/test_ibs_equilibrium.py | 50 +++++++++++++++++------------------ 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/tests/test_ibs_equilibrium.py b/tests/test_ibs_equilibrium.py index cac3b195..a325c5bc 100644 --- a/tests/test_ibs_equilibrium.py +++ b/tests/test_ibs_equilibrium.py @@ -23,7 +23,7 @@ def bessy3_line_with_radiation() -> xt.Line: linefile = bess3_dir / "line.json" line = xt.Line.from_json(linefile) # ------------------------------------------- - # Build tracker and configure radiation + # Build tracker and configure radiation line.build_tracker() line.matrix_stability_tol = 1e-2 line.configure_radiation(model="mean") @@ -38,7 +38,7 @@ def bessy3_line_with_radiation() -> xt.Line: @pytest.mark.parametrize("emittance_constraint", ["coupling", "excitation"]) def test_ibs_emittance_constraints(emittance_constraint, bessy3_line_with_radiation: xt.Line): - twiss = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) + tw = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) ####################################### # Equilibrium emittances calculations # ####################################### @@ -47,7 +47,7 @@ def test_ibs_emittance_constraints(emittance_constraint, bessy3_line_with_radiat time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = ( xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( - twiss, + tw, BUNCH_INTENSITY, emittance_coupling_factor=emittance_coupling_factor, emittance_constraint=emittance_constraint, @@ -58,15 +58,15 @@ def test_ibs_emittance_constraints(emittance_constraint, bessy3_line_with_radiat # Check equilibrium emittance assert_allclose( emittances_x_list[-1], - twiss.eq_gemitt_x + tw.eq_gemitt_x / (1 + emittance_coupling_factor) - / (1 - T_x[-1] / 2 / twiss.damping_constants_s[0]), + / (1 - T_x[-1] / 2 / tw.damping_constants_s[0]), rtol=5e-2, ) # Check equilibrium emittance assert_allclose( emittances_z_list[-1], - twiss.eq_gemitt_zeta / (1 - T_z[-1] / 2 / twiss.damping_constants_s[2]), + tw.eq_gemitt_zeta / (1 - T_z[-1] / 2 / tw.damping_constants_s[2]), rtol=2e-2, ) # Check emittance coupling constraint @@ -78,13 +78,13 @@ def test_ibs_emittance_constraints(emittance_constraint, bessy3_line_with_radiat # Check equilibrium emittance assert_allclose( emittances_x_list[-1], - twiss.eq_gemitt_x / (1 - T_x[-1] / 2 / twiss.damping_constants_s[0]), + tw.eq_gemitt_x / (1 - T_x[-1] / 2 / tw.damping_constants_s[0]), rtol=5e-2, ) # Check equilibrium emittance assert_allclose( emittances_z_list[-1], - twiss.eq_gemitt_zeta / (1 - T_z[-1] / 2 / twiss.damping_constants_s[2]), + tw.eq_gemitt_zeta / (1 - T_z[-1] / 2 / tw.damping_constants_s[2]), rtol=2e-2, ) # Check emittance coupling constraint @@ -104,14 +104,14 @@ def test_ibs_emittance_coupling_factor( describing the emittance evolution in presence of IBS and SR if a constraint on the emittance is enforced. """ - twiss = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) + tw = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) ####################################### # Equilibrium emittances calculations # ####################################### time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = ( xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( - twiss, + tw, BUNCH_INTENSITY, emittance_coupling_factor=emittance_coupling_factor, ) @@ -120,15 +120,15 @@ def test_ibs_emittance_coupling_factor( # Check equilibrium emittance assert_allclose( emittances_x_list[-1], - twiss.eq_gemitt_x + tw.eq_gemitt_x / (1 + emittance_coupling_factor) - / (1 - T_x[-1] / 2 / twiss.damping_constants_s[0]), + / (1 - T_x[-1] / 2 / tw.damping_constants_s[0]), rtol=1e-1, ) # Check equilibrium emittance assert_allclose( emittances_z_list[-1], - twiss.eq_gemitt_zeta / (1 - T_z[-1] / 2 / twiss.damping_constants_s[2]), + tw.eq_gemitt_zeta / (1 - T_z[-1] / 2 / tw.damping_constants_s[2]), rtol=2e-2, ) # Check emittance coupling constraint @@ -147,17 +147,17 @@ def test_ibs_emittance_no_constraint( almost identical to the solution of the differential equation describing the emittance evolution in presence of IBS and SR. """ - twiss = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) + tw = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) initial_emittances = ( - twiss.eq_gemitt_x, - emittance_coupling_factor * twiss.eq_gemitt_x, - twiss.eq_gemitt_zeta, + tw.eq_gemitt_x, + emittance_coupling_factor * tw.eq_gemitt_x, + tw.eq_gemitt_zeta, ) emittance_constraint = "" natural_emittances = ( - twiss.eq_gemitt_x, - emittance_coupling_factor * twiss.eq_gemitt_x, - twiss.eq_gemitt_zeta, + tw.eq_gemitt_x, + emittance_coupling_factor * tw.eq_gemitt_x, + tw.eq_gemitt_zeta, ) ####################################### @@ -166,7 +166,7 @@ def test_ibs_emittance_no_constraint( time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = ( xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( - twiss, + tw, BUNCH_INTENSITY, initial_emittances=initial_emittances, emittance_coupling_factor=emittance_coupling_factor, @@ -178,20 +178,18 @@ def test_ibs_emittance_no_constraint( # Check equilibrium emittance assert_allclose( emittances_x_list[-1], - twiss.eq_gemitt_x / (1 - T_x[-1] / 2 / twiss.damping_constants_s[0]), + tw.eq_gemitt_x / (1 - T_x[-1] / 2 / tw.damping_constants_s[0]), rtol=2e-2, ) # Check equilibrium emittance assert_allclose( emittances_y_list[-1], - emittance_coupling_factor - * twiss.eq_gemitt_x - / (1 - T_y[-1] / 2 / twiss.damping_constants_s[1]), + emittance_coupling_factor * tw.eq_gemitt_x / (1 - T_y[-1] / 2 / tw.damping_constants_s[1]), rtol=2e-2, ) # Check equilibrium emittance assert_allclose( emittances_z_list[-1], - twiss.eq_gemitt_zeta / (1 - T_z[-1] / 2 / twiss.damping_constants_s[2]), + tw.eq_gemitt_zeta / (1 - T_z[-1] / 2 / tw.damping_constants_s[2]), rtol=2e-2, ) From 400dcc88ef80626b8a84070b2be4437943f12bd6 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 27 Jan 2025 11:42:07 +0100 Subject: [PATCH 117/137] use xo.assert_allclose --- tests/test_ibs_equilibrium.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/tests/test_ibs_equilibrium.py b/tests/test_ibs_equilibrium.py index a325c5bc..f06a482c 100644 --- a/tests/test_ibs_equilibrium.py +++ b/tests/test_ibs_equilibrium.py @@ -2,7 +2,6 @@ import xobjects as xo import xtrack as xt from ibs_conftest import XTRACK_TEST_DATA -from numpy.testing import assert_allclose import xfields as xf @@ -56,7 +55,7 @@ def test_ibs_emittance_constraints(emittance_constraint, bessy3_line_with_radiat if emittance_constraint == "coupling": # Check equilibrium emittance - assert_allclose( + xo.assert_allclose( emittances_x_list[-1], tw.eq_gemitt_x / (1 + emittance_coupling_factor) @@ -64,31 +63,31 @@ def test_ibs_emittance_constraints(emittance_constraint, bessy3_line_with_radiat rtol=5e-2, ) # Check equilibrium emittance - assert_allclose( + xo.assert_allclose( emittances_z_list[-1], tw.eq_gemitt_zeta / (1 - T_z[-1] / 2 / tw.damping_constants_s[2]), rtol=2e-2, ) # Check emittance coupling constraint - assert_allclose( + xo.assert_allclose( emittances_y_list[-1] / emittances_x_list[-1], emittance_coupling_factor, rtol=2e-2 ) else: # Check equilibrium emittance - assert_allclose( + xo.assert_allclose( emittances_x_list[-1], tw.eq_gemitt_x / (1 - T_x[-1] / 2 / tw.damping_constants_s[0]), rtol=5e-2, ) # Check equilibrium emittance - assert_allclose( + xo.assert_allclose( emittances_z_list[-1], tw.eq_gemitt_zeta / (1 - T_z[-1] / 2 / tw.damping_constants_s[2]), rtol=2e-2, ) # Check emittance coupling constraint - assert_allclose( + xo.assert_allclose( emittances_y_list[-1] / emittances_x_list[-1], emittance_coupling_factor, ) @@ -118,7 +117,7 @@ def test_ibs_emittance_coupling_factor( ) # Check equilibrium emittance - assert_allclose( + xo.assert_allclose( emittances_x_list[-1], tw.eq_gemitt_x / (1 + emittance_coupling_factor) @@ -126,13 +125,13 @@ def test_ibs_emittance_coupling_factor( rtol=1e-1, ) # Check equilibrium emittance - assert_allclose( + xo.assert_allclose( emittances_z_list[-1], tw.eq_gemitt_zeta / (1 - T_z[-1] / 2 / tw.damping_constants_s[2]), rtol=2e-2, ) # Check emittance coupling constraint - assert_allclose( + xo.assert_allclose( emittances_y_list[-1] / emittances_x_list[-1], emittance_coupling_factor, ) @@ -176,19 +175,19 @@ def test_ibs_emittance_no_constraint( ) # Check equilibrium emittance - assert_allclose( + xo.assert_allclose( emittances_x_list[-1], tw.eq_gemitt_x / (1 - T_x[-1] / 2 / tw.damping_constants_s[0]), rtol=2e-2, ) # Check equilibrium emittance - assert_allclose( + xo.assert_allclose( emittances_y_list[-1], emittance_coupling_factor * tw.eq_gemitt_x / (1 - T_y[-1] / 2 / tw.damping_constants_s[1]), rtol=2e-2, ) # Check equilibrium emittance - assert_allclose( + xo.assert_allclose( emittances_z_list[-1], tw.eq_gemitt_zeta / (1 - T_z[-1] / 2 / tw.damping_constants_s[2]), rtol=2e-2, From 7d60a987fb42b719c4366cd46b0e076e0b06aac5 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 27 Jan 2025 14:16:48 +0100 Subject: [PATCH 118/137] adding debug logs and clearing name --- xfields/ibs/_equilibrium.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index 906df037..e5329c13 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -414,6 +414,9 @@ def compute_equilibrium_emittances_from_sr_and_ibs( LOGGER.warning("At least one longitudinal property overwritten, recomputing longitudinal emittance.") starting_gemitt_zeta = sigma_zeta * sigma_delta # --------------------------------------------------------------------------------------------- + # Log the starting emittances and the computed longitudinal properties - useful for debugging + LOGGER.debug(f"Starting emittances are: {starting_gemitt_x:.2e}, {starting_gemitt_y:.2e}, {starting_gemitt_zeta:.2e}") + # --------------------------------------------------------------------------------------------- # Initialize values for the iterative process (first time step is revolution period) iterations: float = 0 tolerance: float = np.inf @@ -436,6 +439,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # - Store all intermediate results for this time step # - Compute tolerance and check for convergence while tolerance > rtol: + LOGGER.debug(f"Current emittances (x, y, zeta): {current_emittances}") # -------------------------------------------------------------------------- # Display estimated convergence progress if asked if verbose is True: @@ -460,13 +464,13 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # -------------------------------------------------------------------------- # Enforce transverse constraint if specified (valid with a factor value of 0) if emittance_constraint.lower() == "coupling": - forced_emittance_x = (current_emittances[0] + current_emittances[1]) / (1 + emittance_coupling_factor) - forced_emittance_y = forced_emittance_x * emittance_coupling_factor - current_emittances[0] = forced_emittance_x - current_emittances[1] = forced_emittance_y + forced_gemitt_x = (current_emittances[0] + current_emittances[1]) / (1 + emittance_coupling_factor) + forced_gemitt_y = forced_gemitt_x * emittance_coupling_factor + current_emittances[0] = forced_gemitt_x + current_emittances[1] = forced_gemitt_y elif emittance_constraint.lower() == "excitation": - forced_emittance_y = current_emittances[0] * emittance_coupling_factor - current_emittances[1] = forced_emittance_y + forced_gemitt_y = current_emittances[0] * emittance_coupling_factor + current_emittances[1] = forced_gemitt_y # -------------------------------------------------------------------------- # Store the current values for this time step time_deltas.append(time_step) @@ -485,6 +489,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # -------------------------------------------------------------------------- # Update time step for the next iteration and increase counter time_step = tstep if tstep is not None else 0.01 / np.max((ibs_growth_rates, twiss.damping_constants_s)) + LOGGER.debug(f"Time step for next iteration: {time_step:.2e}") iterations += 1 # ---------------------------------------------------------------------------------------------- # We have exited the loop, we have converged. Construct a Table with the results and return it From 78705045a5aef9c89810e7d078b1b5a4c4c0aebc Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 27 Jan 2025 14:18:40 +0100 Subject: [PATCH 119/137] more debug --- xfields/ibs/_equilibrium.py | 1 + 1 file changed, 1 insertion(+) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index e5329c13..ccd03b55 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -471,6 +471,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( elif emittance_constraint.lower() == "excitation": forced_gemitt_y = current_emittances[0] * emittance_coupling_factor current_emittances[1] = forced_gemitt_y + LOGGER.debug(f"Stored emittances after constraint: {current_emittances}") # -------------------------------------------------------------------------- # Store the current values for this time step time_deltas.append(time_step) From cbfc7b6d3f3658fbe1e2d7893b442d5d70d60fb5 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Mon, 27 Jan 2025 14:19:50 +0100 Subject: [PATCH 120/137] replace in progress --- tests/test_ibs_equilibrium.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/tests/test_ibs_equilibrium.py b/tests/test_ibs_equilibrium.py index f06a482c..ecd2fc48 100644 --- a/tests/test_ibs_equilibrium.py +++ b/tests/test_ibs_equilibrium.py @@ -37,21 +37,27 @@ def bessy3_line_with_radiation() -> xt.Line: @pytest.mark.parametrize("emittance_constraint", ["coupling", "excitation"]) def test_ibs_emittance_constraints(emittance_constraint, bessy3_line_with_radiation: xt.Line): - tw = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) - ####################################### - # Equilibrium emittances calculations # - ####################################### - + """TODO: Succinct description of what is tested.""" + # ------------------------------------------- + # Get the twiss with SR effects from the configured line emittance_coupling_factor = 0.02 - - time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = ( - xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( - tw, - BUNCH_INTENSITY, - emittance_coupling_factor=emittance_coupling_factor, - emittance_constraint=emittance_constraint, - ) + tw = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) + # ------------------------------------------- + # Compute the equilibrium emittances + result = tw.compute_equilibrium_emittances_from_sr_and_ibs( + formalism="Nagaitsev", # No Dy in the line, faster + total_beam_intensity=BUNCH_INTENSITY, + emittance_coupling_factor=emittance_coupling_factor, + emittance_constraint=emittance_constraint, ) +# time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = ( +# xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( +# tw, +# BUNCH_INTENSITY, +# emittance_coupling_factor=emittance_coupling_factor, +# emittance_constraint=emittance_constraint, +# ) +# ) if emittance_constraint == "coupling": # Check equilibrium emittance From 30ed006ec1daa597021598574bcaf4f4b5620c5c Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 29 Jan 2025 12:00:04 +0100 Subject: [PATCH 121/137] some comments here --- xfields/ibs/_equilibrium.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index ccd03b55..ea8ea6a6 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -488,7 +488,9 @@ def compute_equilibrium_emittances_from_sr_and_ibs( tolerance = np.max(np.abs((current_emittances - previous_emittances) / previous_emittances)) previous_emittances = current_emittances.copy() # -------------------------------------------------------------------------- - # Update time step for the next iteration and increase counter + # Update time step for the next iteration and increase counter. If the user + # provided a time step value we use it, otherwise it is adaptive and set to + # 1% of the max damping time between IBS and SR across all planes time_step = tstep if tstep is not None else 0.01 / np.max((ibs_growth_rates, twiss.damping_constants_s)) LOGGER.debug(f"Time step for next iteration: {time_step:.2e}") iterations += 1 From 7d3cd9eda0fa54ca646d4c9fa502a7b42b111bfb Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 29 Jan 2025 12:00:41 +0100 Subject: [PATCH 122/137] first test adapted to new API, parametrized over values of coupling constraint and only coupling mode. Will do one for excitation now --- tests/test_ibs_equilibrium.py | 93 ++++++++++++++++------------------- 1 file changed, 43 insertions(+), 50 deletions(-) diff --git a/tests/test_ibs_equilibrium.py b/tests/test_ibs_equilibrium.py index ecd2fc48..5aa35472 100644 --- a/tests/test_ibs_equilibrium.py +++ b/tests/test_ibs_equilibrium.py @@ -1,7 +1,10 @@ +from turtle import rt + import pytest import xobjects as xo import xtrack as xt from ibs_conftest import XTRACK_TEST_DATA +from matplotlib.image import resample import xfields as xf @@ -35,12 +38,13 @@ def bessy3_line_with_radiation() -> xt.Line: # ----- Test Functions vs Analytical Formulae ----- # -@pytest.mark.parametrize("emittance_constraint", ["coupling", "excitation"]) -def test_ibs_emittance_constraints(emittance_constraint, bessy3_line_with_radiation: xt.Line): +@pytest.mark.parametrize("emittance_coupling_factor", [0.02, 0.1, 1]) +def test_equilibrium_coupling_constraint_vs_analytical( + emittance_coupling_factor, bessy3_line_with_radiation: xt.Line +): """TODO: Succinct description of what is tested.""" # ------------------------------------------- # Get the twiss with SR effects from the configured line - emittance_coupling_factor = 0.02 tw = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) # ------------------------------------------- # Compute the equilibrium emittances @@ -48,55 +52,44 @@ def test_ibs_emittance_constraints(emittance_constraint, bessy3_line_with_radiat formalism="Nagaitsev", # No Dy in the line, faster total_beam_intensity=BUNCH_INTENSITY, emittance_coupling_factor=emittance_coupling_factor, - emittance_constraint=emittance_constraint, + emittance_constraint="coupling", + ) + # ------------------------------------------- + # Check results vs analytical estimations + factor = 1 + emittance_coupling_factor * (tw.partition_numbers[1] / tw.partition_numbers[0]) + # Check the horizontal equilibrium emittance + xo.assert_allclose( + result.eq_sr_ibs_gemitt_x, + result.gemitt_x[0] / (1 - result.Tx[-1] / 2 / (tw.damping_constants_s[0] * factor)), + rtol=1e-2, + ) + # Check the emittance coupling constraint (also checks vert. eq. emittance) + xo.assert_allclose(result.gemitt_y / result.gemitt_x, emittance_coupling_factor, rtol=1e-2) + # Check the longitudinal equilibrium emittance + xo.assert_allclose( + result.eq_sr_ibs_gemitt_zeta, # this is result.gemitt_zeta[-1] + result.gemitt_zeta[0] / (1 - result.Tz[-1] / 2 / (tw.damping_constants_s[2])), + rtol=1e-2, ) -# time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = ( -# xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( -# tw, -# BUNCH_INTENSITY, -# emittance_coupling_factor=emittance_coupling_factor, -# emittance_constraint=emittance_constraint, -# ) -# ) - - if emittance_constraint == "coupling": - # Check equilibrium emittance - xo.assert_allclose( - emittances_x_list[-1], - tw.eq_gemitt_x - / (1 + emittance_coupling_factor) - / (1 - T_x[-1] / 2 / tw.damping_constants_s[0]), - rtol=5e-2, - ) - # Check equilibrium emittance - xo.assert_allclose( - emittances_z_list[-1], - tw.eq_gemitt_zeta / (1 - T_z[-1] / 2 / tw.damping_constants_s[2]), - rtol=2e-2, - ) - # Check emittance coupling constraint - xo.assert_allclose( - emittances_y_list[-1] / emittances_x_list[-1], emittance_coupling_factor, rtol=2e-2 - ) - else: - # Check equilibrium emittance - xo.assert_allclose( - emittances_x_list[-1], - tw.eq_gemitt_x / (1 - T_x[-1] / 2 / tw.damping_constants_s[0]), - rtol=5e-2, - ) - # Check equilibrium emittance - xo.assert_allclose( - emittances_z_list[-1], - tw.eq_gemitt_zeta / (1 - T_z[-1] / 2 / tw.damping_constants_s[2]), - rtol=2e-2, - ) - # Check emittance coupling constraint - xo.assert_allclose( - emittances_y_list[-1] / emittances_x_list[-1], - emittance_coupling_factor, - ) + # else: + # Check equilibrium emittance + # xo.assert_allclose( + # emittances_x_list[-1], + # tw.eq_gemitt_x / (1 - T_x[-1] / 2 / tw.damping_constants_s[0]), + # rtol=5e-2, + # ) + # # Check equilibrium emittance + # xo.assert_allclose( + # emittances_z_list[-1], + # tw.eq_gemitt_zeta / (1 - T_z[-1] / 2 / tw.damping_constants_s[2]), + # rtol=2e-2, + # ) + # # Check emittance coupling constraint + # xo.assert_allclose( + # emittances_y_list[-1] / emittances_x_list[-1], + # emittance_coupling_factor, + # ) @pytest.mark.parametrize("emittance_coupling_factor", [0.02, 0.1, 0.2]) From 6a786ee6f2d264367013630ed3acde2f59326faf Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 29 Jan 2025 12:01:36 +0100 Subject: [PATCH 123/137] a better name --- tests/test_ibs_equilibrium.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_ibs_equilibrium.py b/tests/test_ibs_equilibrium.py index 5aa35472..81b525de 100644 --- a/tests/test_ibs_equilibrium.py +++ b/tests/test_ibs_equilibrium.py @@ -39,7 +39,7 @@ def bessy3_line_with_radiation() -> xt.Line: @pytest.mark.parametrize("emittance_coupling_factor", [0.02, 0.1, 1]) -def test_equilibrium_coupling_constraint_vs_analytical( +def test_equilibrium_vs_analytical_constraint_coupling( emittance_coupling_factor, bessy3_line_with_radiation: xt.Line ): """TODO: Succinct description of what is tested.""" From c8b7683e56c69c4ea0d5aec3b7893c1fd3a062e7 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 29 Jan 2025 12:07:19 +0100 Subject: [PATCH 124/137] comments fixes --- tests/test_ibs_equilibrium.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_ibs_equilibrium.py b/tests/test_ibs_equilibrium.py index 81b525de..1d1fefe5 100644 --- a/tests/test_ibs_equilibrium.py +++ b/tests/test_ibs_equilibrium.py @@ -47,7 +47,7 @@ def test_equilibrium_vs_analytical_constraint_coupling( # Get the twiss with SR effects from the configured line tw = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) # ------------------------------------------- - # Compute the equilibrium emittances + # Compute the equilibrium emittances - coupling constraint result = tw.compute_equilibrium_emittances_from_sr_and_ibs( formalism="Nagaitsev", # No Dy in the line, faster total_beam_intensity=BUNCH_INTENSITY, @@ -67,7 +67,7 @@ def test_equilibrium_vs_analytical_constraint_coupling( xo.assert_allclose(result.gemitt_y / result.gemitt_x, emittance_coupling_factor, rtol=1e-2) # Check the longitudinal equilibrium emittance xo.assert_allclose( - result.eq_sr_ibs_gemitt_zeta, # this is result.gemitt_zeta[-1] + result.eq_sr_ibs_gemitt_zeta, result.gemitt_zeta[0] / (1 - result.Tz[-1] / 2 / (tw.damping_constants_s[2])), rtol=1e-2, ) From 5d7c7d801125ce770fba0018000e3d59d4b925c7 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 29 Jan 2025 12:09:49 +0100 Subject: [PATCH 125/137] same parametrized test for excitation constraint --- tests/test_ibs_equilibrium.py | 233 ++++++++++++++++++---------------- 1 file changed, 124 insertions(+), 109 deletions(-) diff --git a/tests/test_ibs_equilibrium.py b/tests/test_ibs_equilibrium.py index 1d1fefe5..efa6e1bc 100644 --- a/tests/test_ibs_equilibrium.py +++ b/tests/test_ibs_equilibrium.py @@ -72,122 +72,137 @@ def test_equilibrium_vs_analytical_constraint_coupling( rtol=1e-2, ) - # else: - # Check equilibrium emittance - # xo.assert_allclose( - # emittances_x_list[-1], - # tw.eq_gemitt_x / (1 - T_x[-1] / 2 / tw.damping_constants_s[0]), - # rtol=5e-2, - # ) - # # Check equilibrium emittance - # xo.assert_allclose( - # emittances_z_list[-1], - # tw.eq_gemitt_zeta / (1 - T_z[-1] / 2 / tw.damping_constants_s[2]), - # rtol=2e-2, - # ) - # # Check emittance coupling constraint - # xo.assert_allclose( - # emittances_y_list[-1] / emittances_x_list[-1], - # emittance_coupling_factor, - # ) - - -@pytest.mark.parametrize("emittance_coupling_factor", [0.02, 0.1, 0.2]) -def test_ibs_emittance_coupling_factor( + +@pytest.mark.parametrize("emittance_coupling_factor", [0.02, 0.1, 1]) +def test_equilibrium_vs_analytical_constraint_excitation( emittance_coupling_factor, bessy3_line_with_radiation: xt.Line ): - """ - As the emittance coupling factor increases, the equilibrium emittance - cannot be compared anymore to the solution of the differential equation - describing the emittance evolution in presence of IBS and SR if a - constraint on the emittance is enforced. - """ + """TODO: Succinct description of what is tested.""" + # ------------------------------------------- + # Get the twiss with SR effects from the configured line tw = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) - ####################################### - # Equilibrium emittances calculations # - ####################################### - - time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = ( - xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( - tw, - BUNCH_INTENSITY, - emittance_coupling_factor=emittance_coupling_factor, - ) - ) - - # Check equilibrium emittance - xo.assert_allclose( - emittances_x_list[-1], - tw.eq_gemitt_x - / (1 + emittance_coupling_factor) - / (1 - T_x[-1] / 2 / tw.damping_constants_s[0]), - rtol=1e-1, + # ------------------------------------------- + # Compute the equilibrium emittances - excitation constraint + result = tw.compute_equilibrium_emittances_from_sr_and_ibs( + formalism="Nagaitsev", # No Dy in the line, faster + total_beam_intensity=BUNCH_INTENSITY, + emittance_coupling_factor=emittance_coupling_factor, + emittance_constraint="excitation", ) - # Check equilibrium emittance + # ------------------------------------------- + # Check results vs analytical estimations + # Check the horizontal equilibrium emittance xo.assert_allclose( - emittances_z_list[-1], - tw.eq_gemitt_zeta / (1 - T_z[-1] / 2 / tw.damping_constants_s[2]), - rtol=2e-2, + result.eq_sr_ibs_gemitt_x, + result.gemitt_x[0] / (1 - result.Tx[-1] / 2 / tw.damping_constants_s[0]), + rtol=1e-2, ) - # Check emittance coupling constraint + # Check the emittance coupling constraint (also checks vert. eq. emittance) + xo.assert_allclose(result.gemitt_y / result.gemitt_x, emittance_coupling_factor, rtol=1e-2) + # Check the longitudinal equilibrium emittance xo.assert_allclose( - emittances_y_list[-1] / emittances_x_list[-1], - emittance_coupling_factor, - ) - - -@pytest.mark.parametrize("emittance_coupling_factor", [0.02, 0.1, 1.0]) -def test_ibs_emittance_no_constraint( - emittance_coupling_factor, bessy3_line_with_radiation: xt.Line -): - """ - Without any emittance constraint, the equilibrium emittance becomes - almost identical to the solution of the differential equation describing - the emittance evolution in presence of IBS and SR. - """ - tw = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) - initial_emittances = ( - tw.eq_gemitt_x, - emittance_coupling_factor * tw.eq_gemitt_x, - tw.eq_gemitt_zeta, - ) - emittance_constraint = "" - natural_emittances = ( - tw.eq_gemitt_x, - emittance_coupling_factor * tw.eq_gemitt_x, - tw.eq_gemitt_zeta, + result.eq_sr_ibs_gemitt_zeta, + result.gemitt_zeta[0] / (1 - result.Tz[-1] / 2 / (tw.damping_constants_s[2])), + rtol=1e-2, ) - ####################################### - # Equilibrium emittances calculations # - ####################################### - - time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = ( - xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( - tw, - BUNCH_INTENSITY, - initial_emittances=initial_emittances, - emittance_coupling_factor=emittance_coupling_factor, - emittance_constraint=emittance_constraint, - natural_emittances=natural_emittances, - ) - ) - # Check equilibrium emittance - xo.assert_allclose( - emittances_x_list[-1], - tw.eq_gemitt_x / (1 - T_x[-1] / 2 / tw.damping_constants_s[0]), - rtol=2e-2, - ) - # Check equilibrium emittance - xo.assert_allclose( - emittances_y_list[-1], - emittance_coupling_factor * tw.eq_gemitt_x / (1 - T_y[-1] / 2 / tw.damping_constants_s[1]), - rtol=2e-2, - ) - # Check equilibrium emittance - xo.assert_allclose( - emittances_z_list[-1], - tw.eq_gemitt_zeta / (1 - T_z[-1] / 2 / tw.damping_constants_s[2]), - rtol=2e-2, - ) +# @pytest.mark.parametrize("emittance_coupling_factor", [0.02, 0.1, 0.2]) +# def test_ibs_emittance_coupling_factor( +# emittance_coupling_factor, bessy3_line_with_radiation: xt.Line +# ): +# """ +# As the emittance coupling factor increases, the equilibrium emittance +# cannot be compared anymore to the solution of the differential equation +# describing the emittance evolution in presence of IBS and SR if a +# constraint on the emittance is enforced. +# """ +# tw = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) +# ####################################### +# # Equilibrium emittances calculations # +# ####################################### + +# time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = ( +# xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( +# tw, +# BUNCH_INTENSITY, +# emittance_coupling_factor=emittance_coupling_factor, +# ) +# ) + +# # Check equilibrium emittance +# xo.assert_allclose( +# emittances_x_list[-1], +# tw.eq_gemitt_x +# / (1 + emittance_coupling_factor) +# / (1 - T_x[-1] / 2 / tw.damping_constants_s[0]), +# rtol=1e-1, +# ) +# # Check equilibrium emittance +# xo.assert_allclose( +# emittances_z_list[-1], +# tw.eq_gemitt_zeta / (1 - T_z[-1] / 2 / tw.damping_constants_s[2]), +# rtol=2e-2, +# ) +# # Check emittance coupling constraint +# xo.assert_allclose( +# emittances_y_list[-1] / emittances_x_list[-1], +# emittance_coupling_factor, +# ) + + +# @pytest.mark.parametrize("emittance_coupling_factor", [0.02, 0.1, 1.0]) +# def test_ibs_emittance_no_constraint( +# emittance_coupling_factor, bessy3_line_with_radiation: xt.Line +# ): +# """ +# Without any emittance constraint, the equilibrium emittance becomes +# almost identical to the solution of the differential equation describing +# the emittance evolution in presence of IBS and SR. +# """ +# tw = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) +# initial_emittances = ( +# tw.eq_gemitt_x, +# emittance_coupling_factor * tw.eq_gemitt_x, +# tw.eq_gemitt_zeta, +# ) +# emittance_constraint = "" +# natural_emittances = ( +# tw.eq_gemitt_x, +# emittance_coupling_factor * tw.eq_gemitt_x, +# tw.eq_gemitt_zeta, +# ) + +# ####################################### +# # Equilibrium emittances calculations # +# ####################################### + +# time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = ( +# xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( +# tw, +# BUNCH_INTENSITY, +# initial_emittances=initial_emittances, +# emittance_coupling_factor=emittance_coupling_factor, +# emittance_constraint=emittance_constraint, +# natural_emittances=natural_emittances, +# ) +# ) + +# # Check equilibrium emittance +# xo.assert_allclose( +# emittances_x_list[-1], +# tw.eq_gemitt_x / (1 - T_x[-1] / 2 / tw.damping_constants_s[0]), +# rtol=2e-2, +# ) +# # Check equilibrium emittance +# xo.assert_allclose( +# emittances_y_list[-1], +# emittance_coupling_factor * tw.eq_gemitt_x / (1 - T_y[-1] / 2 / tw.damping_constants_s[1]), +# rtol=2e-2, +# ) +# # Check equilibrium emittance +# xo.assert_allclose( +# emittances_z_list[-1], +# tw.eq_gemitt_zeta / (1 - T_z[-1] / 2 / tw.damping_constants_s[2]), +# rtol=2e-2, +# ) From 9d87d062fb7acdec7f957e9a8d8d34a3d484e18e Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 29 Jan 2025 12:13:06 +0100 Subject: [PATCH 126/137] some docstrings in the tests --- tests/test_ibs_equilibrium.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/test_ibs_equilibrium.py b/tests/test_ibs_equilibrium.py index efa6e1bc..da4cfd77 100644 --- a/tests/test_ibs_equilibrium.py +++ b/tests/test_ibs_equilibrium.py @@ -42,7 +42,13 @@ def bessy3_line_with_radiation() -> xt.Line: def test_equilibrium_vs_analytical_constraint_coupling( emittance_coupling_factor, bessy3_line_with_radiation: xt.Line ): - """TODO: Succinct description of what is tested.""" + """ + Load the BESSY III line and compute ierations until we reach + an equilibrium with SR and IBS, in the case where we enforce + a betatron coupling constraint on the transverse planes. The + resulting values are tested against an analytical estimate. + TODO: ref for the analytical formula + """ # ------------------------------------------- # Get the twiss with SR effects from the configured line tw = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) @@ -77,7 +83,13 @@ def test_equilibrium_vs_analytical_constraint_coupling( def test_equilibrium_vs_analytical_constraint_excitation( emittance_coupling_factor, bessy3_line_with_radiation: xt.Line ): - """TODO: Succinct description of what is tested.""" + """ + Load the BESSY III line and compute ierations until we reach + an equilibrium with SR and IBS, in the case where we enforce + an excitation type constraint on the transverse planes. The + resulting values are tested against an analytical estimate. + TODO: ref for the analytical formula + """ # ------------------------------------------- # Get the twiss with SR effects from the configured line tw = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) From 6495ad40fa8937e7f9e99ec18d1b2dc72fd81873 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 29 Jan 2025 12:13:23 +0100 Subject: [PATCH 127/137] remove this test as it's the first one --- tests/test_ibs_equilibrium.py | 44 ----------------------------------- 1 file changed, 44 deletions(-) diff --git a/tests/test_ibs_equilibrium.py b/tests/test_ibs_equilibrium.py index da4cfd77..5ed9f9e4 100644 --- a/tests/test_ibs_equilibrium.py +++ b/tests/test_ibs_equilibrium.py @@ -119,50 +119,6 @@ def test_equilibrium_vs_analytical_constraint_excitation( ) -# @pytest.mark.parametrize("emittance_coupling_factor", [0.02, 0.1, 0.2]) -# def test_ibs_emittance_coupling_factor( -# emittance_coupling_factor, bessy3_line_with_radiation: xt.Line -# ): -# """ -# As the emittance coupling factor increases, the equilibrium emittance -# cannot be compared anymore to the solution of the differential equation -# describing the emittance evolution in presence of IBS and SR if a -# constraint on the emittance is enforced. -# """ -# tw = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) -# ####################################### -# # Equilibrium emittances calculations # -# ####################################### - -# time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = ( -# xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( -# tw, -# BUNCH_INTENSITY, -# emittance_coupling_factor=emittance_coupling_factor, -# ) -# ) - -# # Check equilibrium emittance -# xo.assert_allclose( -# emittances_x_list[-1], -# tw.eq_gemitt_x -# / (1 + emittance_coupling_factor) -# / (1 - T_x[-1] / 2 / tw.damping_constants_s[0]), -# rtol=1e-1, -# ) -# # Check equilibrium emittance -# xo.assert_allclose( -# emittances_z_list[-1], -# tw.eq_gemitt_zeta / (1 - T_z[-1] / 2 / tw.damping_constants_s[2]), -# rtol=2e-2, -# ) -# # Check emittance coupling constraint -# xo.assert_allclose( -# emittances_y_list[-1] / emittances_x_list[-1], -# emittance_coupling_factor, -# ) - - # @pytest.mark.parametrize("emittance_coupling_factor", [0.02, 0.1, 1.0]) # def test_ibs_emittance_no_constraint( # emittance_coupling_factor, bessy3_line_with_radiation: xt.Line From 649f5d2cce6df04f74c7ea632124686b11a2b1e6 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 29 Jan 2025 12:36:49 +0100 Subject: [PATCH 128/137] test for no constraint but Y does not pass --- tests/test_ibs_equilibrium.py | 109 ++++++++++++++++------------------ xfields/ibs/_equilibrium.py | 21 +++---- 2 files changed, 63 insertions(+), 67 deletions(-) diff --git a/tests/test_ibs_equilibrium.py b/tests/test_ibs_equilibrium.py index 5ed9f9e4..976aba31 100644 --- a/tests/test_ibs_equilibrium.py +++ b/tests/test_ibs_equilibrium.py @@ -47,7 +47,7 @@ def test_equilibrium_vs_analytical_constraint_coupling( an equilibrium with SR and IBS, in the case where we enforce a betatron coupling constraint on the transverse planes. The resulting values are tested against an analytical estimate. - TODO: ref for the analytical formula + TODO: ref for the analytical formula? """ # ------------------------------------------- # Get the twiss with SR effects from the configured line @@ -88,7 +88,7 @@ def test_equilibrium_vs_analytical_constraint_excitation( an equilibrium with SR and IBS, in the case where we enforce an excitation type constraint on the transverse planes. The resulting values are tested against an analytical estimate. - TODO: ref for the analytical formula + TODO: ref for the analytical formula? """ # ------------------------------------------- # Get the twiss with SR effects from the configured line @@ -119,58 +119,53 @@ def test_equilibrium_vs_analytical_constraint_excitation( ) -# @pytest.mark.parametrize("emittance_coupling_factor", [0.02, 0.1, 1.0]) -# def test_ibs_emittance_no_constraint( -# emittance_coupling_factor, bessy3_line_with_radiation: xt.Line -# ): -# """ -# Without any emittance constraint, the equilibrium emittance becomes -# almost identical to the solution of the differential equation describing -# the emittance evolution in presence of IBS and SR. -# """ -# tw = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) -# initial_emittances = ( -# tw.eq_gemitt_x, -# emittance_coupling_factor * tw.eq_gemitt_x, -# tw.eq_gemitt_zeta, -# ) -# emittance_constraint = "" -# natural_emittances = ( -# tw.eq_gemitt_x, -# emittance_coupling_factor * tw.eq_gemitt_x, -# tw.eq_gemitt_zeta, -# ) - -# ####################################### -# # Equilibrium emittances calculations # -# ####################################### - -# time, emittances_x_list, emittances_y_list, emittances_z_list, T_x, T_y, T_z = ( -# xf.ibs.compute_equilibrium_emittances_from_sr_and_ibs( -# tw, -# BUNCH_INTENSITY, -# initial_emittances=initial_emittances, -# emittance_coupling_factor=emittance_coupling_factor, -# emittance_constraint=emittance_constraint, -# natural_emittances=natural_emittances, -# ) -# ) - -# # Check equilibrium emittance -# xo.assert_allclose( -# emittances_x_list[-1], -# tw.eq_gemitt_x / (1 - T_x[-1] / 2 / tw.damping_constants_s[0]), -# rtol=2e-2, -# ) -# # Check equilibrium emittance -# xo.assert_allclose( -# emittances_y_list[-1], -# emittance_coupling_factor * tw.eq_gemitt_x / (1 - T_y[-1] / 2 / tw.damping_constants_s[1]), -# rtol=2e-2, -# ) -# # Check equilibrium emittance -# xo.assert_allclose( -# emittances_z_list[-1], -# tw.eq_gemitt_zeta / (1 - T_z[-1] / 2 / tw.damping_constants_s[2]), -# rtol=2e-2, -# ) +@pytest.mark.parametrize("initial_factor", [0.02, 0.1, 1]) +def test_equilibrium_vs_analytical_no_constraint( + initial_factor, bessy3_line_with_radiation: xt.Line +): + """ + Load the BESSY III line and compute ierations until we reach + an equilibrium with SR and IBS, whithout any constraint on + the transverse planes. In that case, the equilibrium emittance + becomes almost identical to the solution of the differential + equation describing the emittance evolution in presence of IBS + and SR. + TODO: ref for the analytical formula? + """ + # ------------------------------------------- + # Get the twiss with SR effects from the configured line + tw = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) + # Determine initial emittances based on a ratio + init_gemitt_x = tw.eq_gemitt_x + init_gemitt_y = init_gemitt_x * initial_factor + init_gemitt_zeta = tw.eq_gemitt_zeta + # ------------------------------------------- + # Compute the equilibrium emittances - no constraint + result = tw.compute_equilibrium_emittances_from_sr_and_ibs( + formalism="Nagaitsev", # No Dy in the line, faster + total_beam_intensity=BUNCH_INTENSITY, + gemitt_x=init_gemitt_x, + gemitt_y=init_gemitt_y, + gemitt_zeta=init_gemitt_zeta, + emittance_constraint=None, + ) + # ------------------------------------------- + # Check results vs analytical estimations + # Check the horizontal equilibrium emittance + xo.assert_allclose( + result.eq_sr_ibs_gemitt_x, + result.gemitt_x[0] / (1 - result.Tx[-1] / 2 / tw.damping_constants_s[0]), + rtol=1e-2, + ) + # Check the vertical equilibrium emittance + xo.assert_allclose( + result.eq_sr_ibs_gemitt_y, + result.gemitt_y[0] / (1 - result.Ty[-1] / 2 / tw.damping_constants_s[1]), + rtol=1e-2 + ) + # Check the longitudinal equilibrium emittance + xo.assert_allclose( + result.eq_sr_ibs_gemitt_zeta, + result.gemitt_zeta[0] / (1 - result.Tz[-1] / 2 / (tw.damping_constants_s[2])), + rtol=1e-2, + ) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index ea8ea6a6..af1fe290 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -384,7 +384,7 @@ def compute_equilibrium_emittances_from_sr_and_ibs( # If we need to renormalize the transverse emittances, we so now. If emittance_coupling_factor is # non-zero, transverse emittances are modified accordingly (used convention is valid for arbitrary # damping partition numbers and emittance_coupling_factor values). - if _renormalize_transverse_emittances is True: + if _renormalize_transverse_emittances is True and emittance_constraint is not None: # If constraint is coupling, both emittances are modified (from factor and partition numbers) if emittance_constraint.lower() == "coupling" and emittance_coupling_factor != 0: LOGGER.info("Enforcing 'coupling' constraint on transverse emittances.") @@ -463,15 +463,16 @@ def compute_equilibrium_emittances_from_sr_and_ibs( current_emittances += np.array(emittance_derivatives) * time_step # -------------------------------------------------------------------------- # Enforce transverse constraint if specified (valid with a factor value of 0) - if emittance_constraint.lower() == "coupling": - forced_gemitt_x = (current_emittances[0] + current_emittances[1]) / (1 + emittance_coupling_factor) - forced_gemitt_y = forced_gemitt_x * emittance_coupling_factor - current_emittances[0] = forced_gemitt_x - current_emittances[1] = forced_gemitt_y - elif emittance_constraint.lower() == "excitation": - forced_gemitt_y = current_emittances[0] * emittance_coupling_factor - current_emittances[1] = forced_gemitt_y - LOGGER.debug(f"Stored emittances after constraint: {current_emittances}") + if emittance_constraint is not None: + if emittance_constraint.lower() == "coupling": + forced_gemitt_x = (current_emittances[0] + current_emittances[1]) / (1 + emittance_coupling_factor) + forced_gemitt_y = forced_gemitt_x * emittance_coupling_factor + current_emittances[0] = forced_gemitt_x + current_emittances[1] = forced_gemitt_y + elif emittance_constraint.lower() == "excitation": + forced_gemitt_y = current_emittances[0] * emittance_coupling_factor + current_emittances[1] = forced_gemitt_y + LOGGER.debug(f"Stored emittances after time step: {current_emittances}") # -------------------------------------------------------------------------- # Store the current values for this time step time_deltas.append(time_step) From 92080bbe76a59eef2e55a1806d0db2f82d76890a Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 29 Jan 2025 14:00:57 +0100 Subject: [PATCH 129/137] test is good --- tests/test_ibs_equilibrium.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_ibs_equilibrium.py b/tests/test_ibs_equilibrium.py index 976aba31..396f9d4c 100644 --- a/tests/test_ibs_equilibrium.py +++ b/tests/test_ibs_equilibrium.py @@ -119,7 +119,9 @@ def test_equilibrium_vs_analytical_constraint_excitation( ) -@pytest.mark.parametrize("initial_factor", [0.02, 0.1, 1]) +# This factor should not be too high as otherwise the starting +# vertical emittance will be way out of realistic values +@pytest.mark.parametrize("initial_factor", [0.01, 0.02, 0.05]) def test_equilibrium_vs_analytical_no_constraint( initial_factor, bessy3_line_with_radiation: xt.Line ): @@ -135,18 +137,16 @@ def test_equilibrium_vs_analytical_no_constraint( # ------------------------------------------- # Get the twiss with SR effects from the configured line tw = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) - # Determine initial emittances based on a ratio - init_gemitt_x = tw.eq_gemitt_x - init_gemitt_y = init_gemitt_x * initial_factor - init_gemitt_zeta = tw.eq_gemitt_zeta # ------------------------------------------- # Compute the equilibrium emittances - no constraint + # No constraint so no renormalization of transverse emittances + # and SR eq in vertical is 0 so we change it to avoid exact 0 result = tw.compute_equilibrium_emittances_from_sr_and_ibs( formalism="Nagaitsev", # No Dy in the line, faster total_beam_intensity=BUNCH_INTENSITY, - gemitt_x=init_gemitt_x, - gemitt_y=init_gemitt_y, - gemitt_zeta=init_gemitt_zeta, + gemitt_x=tw.eq_gemitt_x, + gemitt_y=tw.eq_gemitt_x * initial_factor, + gemitt_zeta=tw.eq_gemitt_zeta, emittance_constraint=None, ) # ------------------------------------------- From f21d52e3a761595121981bc2ddf758e3fe4c51aa Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Thu, 30 Jan 2025 14:37:37 +0100 Subject: [PATCH 130/137] remove auto added garbage --- tests/test_ibs_equilibrium.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/test_ibs_equilibrium.py b/tests/test_ibs_equilibrium.py index 396f9d4c..986ee02f 100644 --- a/tests/test_ibs_equilibrium.py +++ b/tests/test_ibs_equilibrium.py @@ -1,15 +1,12 @@ -from turtle import rt - import pytest import xobjects as xo import xtrack as xt from ibs_conftest import XTRACK_TEST_DATA -from matplotlib.image import resample -import xfields as xf BUNCH_INTENSITY: float = 6.2e9 # 1C bunch intensity + # ----- Fixture for the (configured) BESSY III line ----- # @@ -161,7 +158,7 @@ def test_equilibrium_vs_analytical_no_constraint( xo.assert_allclose( result.eq_sr_ibs_gemitt_y, result.gemitt_y[0] / (1 - result.Ty[-1] / 2 / tw.damping_constants_s[1]), - rtol=1e-2 + rtol=1e-2, ) # Check the longitudinal equilibrium emittance xo.assert_allclose( From 03b0a9cf6954f9f89f5b5805785f6a6addf06397 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Thu, 30 Jan 2025 15:41:17 +0100 Subject: [PATCH 131/137] use xo function --- tests/test_ibs_growth_rates.py | 48 +++++++++++++++++----------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/test_ibs_growth_rates.py b/tests/test_ibs_growth_rates.py index f405bef6..34bef6b4 100644 --- a/tests/test_ibs_growth_rates.py +++ b/tests/test_ibs_growth_rates.py @@ -1,4 +1,5 @@ import pytest +import xobjects as xo import xtrack as xt from cpymad.madx import Madx from ibs_conftest import ( @@ -8,7 +9,6 @@ get_ref_particle_from_madx_beam, set_madx_beam_parameters, ) -from numpy.testing import assert_allclose # ------------------------------------------------------------------------ # We compare our values to the ones of MAD-X, hence in the numpy function @@ -78,13 +78,13 @@ def test_clic_dr_growth_rates(bunched): # Compare the results - Nagaitsev if bunched is True: # in Nagaitsev coasting makes big assumptions # Computed with different formalism than MAD-X so 11% isn't crazy - assert_allclose(nag_rates.Tx, mad_Tx, atol=1e-8, rtol=11.5e-2) - assert_allclose(nag_rates.Ty, mad_Ty, atol=1e-8, rtol=5e-2) - assert_allclose(nag_rates.Tz, mad_Tz, atol=1e-8, rtol=5e-2) + xo.assert_allclose(nag_rates.Tx, mad_Tx, atol=1e-8, rtol=11.5e-2) + xo.assert_allclose(nag_rates.Ty, mad_Ty, atol=1e-8, rtol=5e-2) + xo.assert_allclose(nag_rates.Tz, mad_Tz, atol=1e-8, rtol=5e-2) # Compare the results - Bjorken-Mtingwa - assert_allclose(bm_rates.Tx, mad_Tx, atol=1e-8, rtol=11.5e-2) - assert_allclose(bm_rates.Ty, mad_Ty, atol=1e-8, rtol=5e-2) - assert_allclose(bm_rates.Tz, mad_Tz, atol=1e-8, rtol=5e-2) + xo.assert_allclose(bm_rates.Tx, mad_Tx, atol=1e-8, rtol=11.5e-2) + xo.assert_allclose(bm_rates.Ty, mad_Ty, atol=1e-8, rtol=5e-2) + xo.assert_allclose(bm_rates.Tz, mad_Tz, atol=1e-8, rtol=5e-2) # ----- Test with positive charge particle ----- # @@ -135,13 +135,13 @@ def test_sps_injection_protons_growth_rates(bunched): # Compare the results - Nagaitsev if bunched is True: # in Nagaitsev coasting makes big assumptions # Computed with different formalism than MAD-X so 10% isn't crazy - assert_allclose(nag_rates.Tx, mad_Tx, atol=1e-8, rtol=2.5e-2) - assert_allclose(nag_rates.Ty, mad_Ty, atol=1e-8, rtol=10e-2) - assert_allclose(nag_rates.Tz, mad_Tz, atol=1e-8, rtol=2.5e-2) + xo.assert_allclose(nag_rates.Tx, mad_Tx, atol=1e-8, rtol=2.5e-2) + xo.assert_allclose(nag_rates.Ty, mad_Ty, atol=1e-8, rtol=10e-2) + xo.assert_allclose(nag_rates.Tz, mad_Tz, atol=1e-8, rtol=2.5e-2) # Compare the results - Bjorken-Mtingwa - assert_allclose(bm_rates.Tx, mad_Tx, atol=1e-8, rtol=2.5e-2) - assert_allclose(bm_rates.Ty, mad_Ty, atol=1e-8, rtol=2.5e-2) - assert_allclose(bm_rates.Tz, mad_Tz, atol=1e-8, rtol=2.5e-2) + xo.assert_allclose(bm_rates.Tx, mad_Tx, atol=1e-8, rtol=2.5e-2) + xo.assert_allclose(bm_rates.Ty, mad_Ty, atol=1e-8, rtol=2.5e-2) + xo.assert_allclose(bm_rates.Tz, mad_Tz, atol=1e-8, rtol=2.5e-2) # ----- Test with ion particles ----- # @@ -206,13 +206,13 @@ def test_sps_ions_growth_rates(bunched): # Compare the results - Nagaitsev if bunched is True: # in Nagaitsev coasting makes big assumptions # Computed with different formalism than MAD-X so 10% isn't crazy - assert_allclose(nag_rates.Tx, mad_Tx, atol=1e-8, rtol=2.5e-2) - assert_allclose(nag_rates.Ty, mad_Ty, atol=1e-8, rtol=2.5e-2) - assert_allclose(nag_rates.Tz, mad_Tz, atol=1e-8, rtol=2.5e-2) + xo.assert_allclose(nag_rates.Tx, mad_Tx, atol=1e-8, rtol=2.5e-2) + xo.assert_allclose(nag_rates.Ty, mad_Ty, atol=1e-8, rtol=2.5e-2) + xo.assert_allclose(nag_rates.Tz, mad_Tz, atol=1e-8, rtol=2.5e-2) # Compare the results - Bjorken-Mtingwa - assert_allclose(bm_rates.Tx, mad_Tx, atol=1e-8, rtol=2.5e-2) - assert_allclose(bm_rates.Ty, mad_Ty, atol=1e-8, rtol=2.5e-2) - assert_allclose(bm_rates.Tz, mad_Tz, atol=1e-8, rtol=2.5e-2) + xo.assert_allclose(bm_rates.Tx, mad_Tx, atol=1e-8, rtol=2.5e-2) + xo.assert_allclose(bm_rates.Ty, mad_Ty, atol=1e-8, rtol=2.5e-2) + xo.assert_allclose(bm_rates.Tz, mad_Tz, atol=1e-8, rtol=2.5e-2) # ----- Test with vertical dispersion ----- # @@ -268,9 +268,9 @@ def test_hllhc14_growth_rates(bunched): # Compare the results - Nagaitsev (don't compare vertical # as lattice has Dy and formalism is wrong in this case) if bunched is True: # in Nagaitsev coasting makes big assumptions - assert_allclose(nag_rates.Tx, mad_Tx, atol=1e-8, rtol=4e-2) - assert_allclose(nag_rates.Tz, mad_Tz, atol=1e-8, rtol=2.5e-2) + xo.assert_allclose(nag_rates.Tx, mad_Tx, atol=1e-8, rtol=4e-2) + xo.assert_allclose(nag_rates.Tz, mad_Tz, atol=1e-8, rtol=2.5e-2) # Compare the results - Bjorken-Mtingwa - assert_allclose(bm_rates.Tx, mad_Tx, atol=1e-8, rtol=4e-2) - assert_allclose(bm_rates.Ty, mad_Ty, atol=1e-8, rtol=2.5e-2) - assert_allclose(bm_rates.Tz, mad_Tz, atol=1e-8, rtol=2.5e-2) + xo.assert_allclose(bm_rates.Tx, mad_Tx, atol=1e-8, rtol=4e-2) + xo.assert_allclose(bm_rates.Ty, mad_Ty, atol=1e-8, rtol=2.5e-2) + xo.assert_allclose(bm_rates.Tz, mad_Tz, atol=1e-8, rtol=2.5e-2) From d380f32362f226a881c24cd2d88abafc9128d136 Mon Sep 17 00:00:00 2001 From: SebastienJoly Date: Thu, 30 Jan 2025 17:11:41 +0100 Subject: [PATCH 132/137] Remove obsolete logger message --- xfields/ibs/_equilibrium.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index af1fe290..83ce86a7 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -403,10 +403,10 @@ def compute_equilibrium_emittances_from_sr_and_ibs( sigma_delta = (starting_gemitt_zeta / twiss.bets0) ** 0.5 # Now handle the scenario where the user wants to overwrite those if overwrite_sigma_zeta is not None: - LOGGER.warning("'overwrite_sigma_zeta' specified, make sure it is consistent with provided longitudinal emittance.") + LOGGER.warning("'overwrite_sigma_zeta' specified.") sigma_zeta = overwrite_sigma_zeta elif overwrite_sigma_delta is not None: - LOGGER.warning("'overwrite_sigma_delta' specified, make sure it is consistent with provided longitudinal emittance.") + LOGGER.warning("'overwrite_sigma_delta' specified.") sigma_delta = overwrite_sigma_delta longitudinal_emittance_ratio = sigma_zeta / sigma_delta # If either bunch length or momentum spread was overwritten, we recompute the longidutinal emittance From 8b67852521681093c14aff94872acc354721165b Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Fri, 31 Jan 2025 10:24:41 +0100 Subject: [PATCH 133/137] behaviour tests for missing elements --- tests/test_ibs_equilibrium.py | 54 +++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/test_ibs_equilibrium.py b/tests/test_ibs_equilibrium.py index 986ee02f..e11adecf 100644 --- a/tests/test_ibs_equilibrium.py +++ b/tests/test_ibs_equilibrium.py @@ -1,3 +1,4 @@ +from math import e import pytest import xobjects as xo import xtrack as xt @@ -166,3 +167,56 @@ def test_equilibrium_vs_analytical_no_constraint( result.gemitt_zeta[0] / (1 - result.Tz[-1] / 2 / (tw.damping_constants_s[2])), rtol=1e-2, ) + + +# ----- Test Functions for Behaviour ----- # + + +def test_missing_required_twiss_attributes_raises(bessy3_line_with_radiation: xt.Line): + """Check we raise if Twiss has no SR equilibrium values.""" + tw = bessy3_line_with_radiation.twiss(eneloss_and_damping=False) # no tw.eq_* + # This should tell us we're missing something in the config + with pytest.raises( + AttributeError, + match="TwissTable must contain SR equilibrium emittances and damping constants.", + ): + tw.compute_equilibrium_emittances_from_sr_and_ibs( + formalism="Nagaitsev", + total_beam_intensity=BUNCH_INTENSITY, + emittance_coupling_factor=1, + emittance_constraint="coupling", + ) + + +def test_missing_params_raises(bessy3_line_with_radiation: xt.Line): + """Check that not provided required params raises.""" + tw = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) + # These should tell us we're necessary missing arguments + with pytest.raises(AssertionError, match="Must provide 'formalism'"): + tw.compute_equilibrium_emittances_from_sr_and_ibs( + formalism=None, + total_beam_intensity=BUNCH_INTENSITY, + ) + # Not providing formalism just passes to IBS rates computation which raises + with pytest.raises(TypeError): + tw.compute_equilibrium_emittances_from_sr_and_ibs( + # formalism="Nagaitsev", + total_beam_intensity=BUNCH_INTENSITY, + ) + with pytest.raises(AssertionError, match="Must provide 'total_beam_intensity'"): + tw.compute_equilibrium_emittances_from_sr_and_ibs( + formalism="Nagaitsev", + total_beam_intensity=None, + ) + # Not providing formalism just passes to IBS rates computation which raises + with pytest.raises(TypeError): + tw.compute_equilibrium_emittances_from_sr_and_ibs( + formalism="Nagaitsev", + # total_beam_intensity=BUNCH_INTENSITY, + ) + with pytest.raises(AssertionError, match="Must provide 'rtol'"): + tw.compute_equilibrium_emittances_from_sr_and_ibs( + formalism="Nagaitsev", + total_beam_intensity=BUNCH_INTENSITY, + rtol=None, + ) From 221f66848990af79aba84d3eccb20f0f95698cd1 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Fri, 31 Jan 2025 10:28:44 +0100 Subject: [PATCH 134/137] test invalid constraint --- tests/test_ibs_equilibrium.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_ibs_equilibrium.py b/tests/test_ibs_equilibrium.py index e11adecf..2df76318 100644 --- a/tests/test_ibs_equilibrium.py +++ b/tests/test_ibs_equilibrium.py @@ -220,3 +220,17 @@ def test_missing_params_raises(bessy3_line_with_radiation: xt.Line): total_beam_intensity=BUNCH_INTENSITY, rtol=None, ) + + +@pytest.mark.parametrize("emittance_constraint", ["WRONG", "invalid"]) +def test_invalid_constraint_raises(emittance_constraint, bessy3_line_with_radiation: xt.Line): + """Check we raise if the emittance coupling constraint is invalid.""" + tw = bessy3_line_with_radiation.twiss(eneloss_and_damping=True) + # This should tell us we're missing something in the config + with pytest.raises(AssertionError, match="Invalid 'emittance_constraint'"): + tw.compute_equilibrium_emittances_from_sr_and_ibs( + formalism="Nagaitsev", + total_beam_intensity=BUNCH_INTENSITY, + emittance_coupling_factor=1, + emittance_constraint=emittance_constraint, + ) From 6c3112a236da54b43a38713ed4438f820c26b24c Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Fri, 31 Jan 2025 10:37:27 +0100 Subject: [PATCH 135/137] some --- tests/test_ibs_equilibrium.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_ibs_equilibrium.py b/tests/test_ibs_equilibrium.py index 2df76318..22a3af74 100644 --- a/tests/test_ibs_equilibrium.py +++ b/tests/test_ibs_equilibrium.py @@ -1,4 +1,3 @@ -from math import e import pytest import xobjects as xo import xtrack as xt @@ -169,7 +168,7 @@ def test_equilibrium_vs_analytical_no_constraint( ) -# ----- Test Functions for Behaviour ----- # +# ----- Some Test Functions for Behaviour ----- # def test_missing_required_twiss_attributes_raises(bessy3_line_with_radiation: xt.Line): From 1c0521addc93d5fa1d14e3c7b49b95ee96002a92 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 5 Feb 2025 14:22:59 +0100 Subject: [PATCH 136/137] also include correct sigma_zeta and sigma_delta using longitudinal ratio --- xfields/ibs/_equilibrium.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/xfields/ibs/_equilibrium.py b/xfields/ibs/_equilibrium.py index af1fe290..6340f446 100644 --- a/xfields/ibs/_equilibrium.py +++ b/xfields/ibs/_equilibrium.py @@ -504,9 +504,8 @@ def compute_equilibrium_emittances_from_sr_and_ibs( "gemitt_x": np.array(res_gemitt_x), "gemitt_y": np.array(res_gemitt_y), "gemitt_zeta": np.array(res_gemitt_zeta), - # TODO: check the below, might need to input the longitudinal_ratio in this calculation (if we have bunch lengthening etc) - # "sigma_zeta": np.sqrt(np.array(res_gemitt_zeta) * twiss.bets0), - # "sigma_delta": np.sqrt(np.array(res_gemitt_zeta) / twiss.bets0), + "sigma_zeta": np.sqrt(np.array(res_gemitt_zeta) * longitudinal_emittance_ratio), + "sigma_delta": np.sqrt(np.array(res_gemitt_zeta) / longitudinal_emittance_ratio), "Tx": np.array(T_x), "Ty": np.array(T_y), "Tz": np.array(T_z), From 3b50667b79aa680d4a4511aae8dab7e5aecc9a85 Mon Sep 17 00:00:00 2001 From: Felix Soubelet Date: Wed, 5 Feb 2025 14:23:16 +0100 Subject: [PATCH 137/137] todo for later --- tests/test_ibs_equilibrium.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_ibs_equilibrium.py b/tests/test_ibs_equilibrium.py index 22a3af74..2cec3f8f 100644 --- a/tests/test_ibs_equilibrium.py +++ b/tests/test_ibs_equilibrium.py @@ -166,6 +166,10 @@ def test_equilibrium_vs_analytical_no_constraint( result.gemitt_zeta[0] / (1 - result.Tz[-1] / 2 / (tw.damping_constants_s[2])), rtol=1e-2, ) + # ------------------------------------------- + # Check against ELEGANT results for this scenario + # These are hardcoded (from ELEGANT version with + # corrected partition numbers use) # ----- Some Test Functions for Behaviour ----- #