diff --git a/examples/005_ibs/000_growth_rates_from_parameters_no_vdisp.py b/examples/005_ibs/000_growth_rates_from_parameters_no_vdisp.py index dea5ff53..3936d34b 100644 --- a/examples/005_ibs/000_growth_rates_from_parameters_no_vdisp.py +++ b/examples/005_ibs/000_growth_rates_from_parameters_no_vdisp.py @@ -4,8 +4,6 @@ # ########################################### # import xtrack as xt -from xfields.ibs import get_intrabeam_scattering_growth_rates - ########################## # Load xt.Line from file # ########################## @@ -63,6 +61,11 @@ print(f"Nagaitsev: {nag_growth_rates}") print(f"Bjorken-Mtingwa: {bm_growth_rates}") +# Computed from normalized emittances: +# ------------------------------------ +# Nagaitsev: IBSGrowthRates(Tx=0.00104, Ty=0.011, Tz=0.00803) +# Bjorken-Mtingwa: IBSGrowthRates(Tx=0.00111, Ty=0.0109, Tz=0.008) + ##################### # Define parameters # ##################### @@ -108,3 +111,8 @@ print("------------------------------------------------------") print(f"Nagaitsev: {nag_growth_rates2}") print(f"Bjorken-Mtingwa: {bm_growth_rates2}") + +# Computed from geometric emittances (rough equivalent): +# ------------------------------------------------------ +# Nagaitsev: IBSGrowthRates(Tx=0.00103, Ty=0.0111, Tz=0.00803) +# Bjorken-Mtingwa: IBSGrowthRates(Tx=0.00111, Ty=0.0109, Tz=0.008) \ No newline at end of file diff --git a/examples/005_ibs/001_growth_rates_from_parameters_with_vdisp.py b/examples/005_ibs/001_growth_rates_from_parameters_with_vdisp.py index 1cbb5531..a9fbe456 100644 --- a/examples/005_ibs/001_growth_rates_from_parameters_with_vdisp.py +++ b/examples/005_ibs/001_growth_rates_from_parameters_with_vdisp.py @@ -4,7 +4,6 @@ # ########################################### # import json -import numpy as np import xtrack as xt ########################## @@ -60,7 +59,6 @@ bunched=True, ) - ########################################################## # Compare: we expect Nagaitsev to be wrong in horizontal # ########################################################## @@ -71,47 +69,7 @@ print(f"Nagaitsev: {nag_growth_rates}") print(f"Bjorken-Mtingwa: {bm_growth_rates}") -##################### -# Define parameters # -##################### - -gemitt_x: float = 2.598e-10 -gemitt_y: float = 2.598e-10 - -################################### -# Get growth rates with Nagaitsev # -################################### - -nag_growth_rates2 = tw.get_ibs_growth_rates( - formalism="nagaitsev", - total_beam_intensity=bunch_intensity, - gemitt_x=gemitt_x, - gemitt_y=gemitt_y, - sigma_delta=sigma_delta, - bunch_length=bunch_length, - bunched=True, -) - -######################################### -# Get growth rates with Bjorken-Mtingwa # -######################################### - -bm_growth_rates2 = tw.get_ibs_growth_rates( - formalism="bjorken-mtingwa", # also accepts "b&m" - total_beam_intensity=bunch_intensity, - gemitt_x=gemitt_x, - gemitt_y=gemitt_y, - sigma_delta=sigma_delta, - bunch_length=bunch_length, - bunched=True, -) - -########################################################## -# Compare: we expect Nagaitsev to be wrong in horizontal # -########################################################## - -print() -print("Computed from geometric emittances (rough equivalent):") -print("------------------------------------------------------") -print(f"Nagaitsev: {nag_growth_rates2}") -print(f"Bjorken-Mtingwa: {bm_growth_rates2}") +# Computed from normalized emittances: +# ------------------------------------ +# Nagaitsev: IBSGrowthRates(Tx=6.24e-05, Ty=-2.27e-09, Tz=0.00031) +# Bjorken-Mtingwa: IBSGrowthRates(Tx=6.21e-05, Ty=1.1e-06, Tz=0.00031) diff --git a/examples/005_ibs/002_growth_rates_from_particles.py b/examples/005_ibs/002_growth_rates_from_particles.py index b453ec8a..d5965c25 100644 --- a/examples/005_ibs/002_growth_rates_from_particles.py +++ b/examples/005_ibs/002_growth_rates_from_particles.py @@ -69,6 +69,11 @@ print(f"Nagaitsev: {nag_growth_rates}") print(f"Bjorken-Mtingwa: {bm_growth_rates}") +# Computed from particles object: +# ------------------------------- +# Nagaitsev: IBSGrowthRates(Tx=1.54e-06, Ty=-1.46e-07, Tz=1.65e-06) +# Bjorken-Mtingwa: IBSGrowthRates(Tx=1.54e-06, Ty=-1.48e-07, Tz=1.65e-06) + ################################### # Get growth rates with Nagaitsev # ################################### @@ -106,3 +111,8 @@ print("-------------------------------------------------------") print(f"Nagaitsev: {nag_growth_rates}") print(f"Bjorken-Mtingwa: {bm_growth_rates}") + +# Computed from normalized emittances (rough equivalent): +# ------------------------------------------------------- +# Nagaitsev: IBSGrowthRates(Tx=1.54e-06, Ty=-1.46e-07, Tz=1.65e-06) +# Bjorken-Mtingwa: IBSGrowthRates(Tx=1.54e-06, Ty=-1.48e-07, Tz=1.65e-06) diff --git a/examples/005_ibs/003_tracking_with_kicks.py b/examples/005_ibs/003_tracking_with_kicks.py index 89c72cc6..628af380 100644 --- a/examples/005_ibs/003_tracking_with_kicks.py +++ b/examples/005_ibs/003_tracking_with_kicks.py @@ -17,7 +17,26 @@ fname_line_particles = "../../../xtrack/test_data/sps_w_spacecharge/"\ "line_no_spacecharge_and_particle.json" line: xt.Line = xt.Line.from_json(fname_line_particles) -line.build_tracker() +line.build_tracker(_context=context) + +####################################### +# Create and Install IBS Kick Element # +####################################### + +# For the analytical kick formalism: kicks are computed based +# on the analytical growth rates (so it needs a formalism) +# ibs_kick = xf.IBSAnalyticalKick(formalism="nagaitsev", num_slices=50) + +# For the kinetic formalism: kicks are computed based on the +# friction and diffusion terms of the kinetic theory of gases +ibs_kick = xf.IBSKineticKick(num_slices=50) + +# By default the element is off until configuration. Let's install +# the kick at the end of the line and configure it. This internally +# provides the necessary information to the element +line.configure_intrabeam_scattering( + element=ibs_kick, name="ibskick", index=-1, update_every=50 +) ############################################ # Define parameters and Generate Particles # @@ -40,25 +59,6 @@ _context=context, ) -####################################### -# Create and Install IBS Kick Element # -####################################### - -# For the analytical kick formalism: kicks are computed based -# on the analytical growth rates (so it needs a formalism) -# ibs_kick = xf.IBSAnalyticalKick(formalism="nagaitsev", num_slices=50) - -# For the kinetic formalism: kicks are computed based on the -# friction and diffusion terms of the kinetic theory of gases -ibs_kick = xf.IBSKineticKick(num_slices=50) - -# By default the element is off until configuration. Let's install -# the kick at the end of the line and configure it. This internally -# provides the necessary information to the element -line.configure_intrabeam_scattering( - element=ibs_kick, name="ibskick", index=-1, update_every=50 -) - ############################################## # Track now applies an IBS kick at each turn # ############################################## diff --git a/examples/005_ibs/004_tracking_with_kicks_tbt_emittances.py b/examples/005_ibs/004_tracking_with_kicks_tbt_emittances.py new file mode 100644 index 00000000..97a9a452 --- /dev/null +++ b/examples/005_ibs/004_tracking_with_kicks_tbt_emittances.py @@ -0,0 +1,94 @@ +# copyright ################################# # +# This file is part of the Xfields Package. # +# Copyright (c) CERN, 2021. # +# ########################################### # +import xfields as xf +import xobjects as xo +import xpart as xp +import xtrack as xt +from xfields.ibs._formulary import _gemitt_x, _gemitt_y, _sigma_delta, _bunch_length +import numpy as np +import matplotlib.pyplot as plt + +# context = xo.ContextCupy() +context = xo.ContextCpu(omp_num_threads="auto") + +########################## +# Load xt.Line from file # +########################## + +fname_line_particles = "../../../xtrack/test_data/clic_dr/line.json" +line: xt.Line = xt.Line.from_json(fname_line_particles) +line.build_tracker(_context=context) +cavities = [element for element in line.elements if isinstance(element, xt.Cavity)] +for cavity in cavities: + cavity.lag = 180 +tw = line.twiss(method="4d") + +####################################### +# Create and Install IBS Kick Element # +####################################### + +# ibs_kick = xf.IBSKineticKick(num_slices=50) +ibs_kick = xf.IBSAnalyticalKick(formalism="nagaitsev", num_slices=50) +line.configure_intrabeam_scattering( + element=ibs_kick, name="ibskick", index=-1, update_every=50 +) + +################################ +# Generate Particles and Track # +################################ + +nturns: int = 2000 +epsx, epsy, sigd, bl = [], [], [], [] + +particles = xp.generate_matched_gaussian_bunch( + num_particles=2500, + total_intensity_particles=int(4.5e9), + nemitt_x=5.66e-7, + nemitt_y=3.7e-9, + sigma_z=1.58e-3, + line=line, + _context=context, +) + +for turn in range(nturns): + line.track(particles, num_turns=1) + epsx.append(_gemitt_x(particles, tw.betx[0], tw.dx[0])) + epsy.append(_gemitt_y(particles, tw.bety[0], tw.dy[0])) + sigd.append(_sigma_delta(particles)) + bl.append(_bunch_length(particles)) + +############################# +# Plot turn-by-turn results # +############################# + +turns = np.arange(nturns) + 1 # start from 1 +epsx = np.array(epsx) +epsy = np.array(epsy) +sigd = np.array(sigd) +bl = np.array(bl) + +fig, axx = plt.subplots() +axx.plot(turns, 1e10 * epsx, c="C0", label=r"$\varepsilon_x$") +axx.set_xlabel("Turn") +axx.set_ylabel(r"$\varepsilon_x$ [$10^{-10}$m]") +axy = axx.twinx() +axy.plot(turns, 1e13 * epsy, c="C1", label=r"$\varepsilon_y$") +axy.yaxis.set_label_position("right") +axy.set_ylabel(r"$\varepsilon_y$ [$10^{-13}$m]") +fig.legend(loc="upper center", bbox_to_anchor=(0.5, 0.95), ncols=2) +fig.tight_layout() +fig.show() + +fig2, axd = plt.subplots() +axd.plot(turns, 1e3 * sigd, c="C2", label=r"$\sigma_{\delta}$") +axd.set_xlabel("Turn") +axd.set_ylabel(r"$\sigma_{\delta}$ [$10^{-3}$]") +axb = axd.twinx() +axb.plot(turns, 1e3 * bl, c="C3", label=r"$\sigma_z$") +axb.yaxis.set_label_position("right") +axb.set_ylabel(r"$\sigma_z$ [$10^{-3}$m]") +fig2.legend(loc="upper center", bbox_to_anchor=(0.5, 0.95), ncols=2) +fig2.tight_layout() +fig2.show() diff --git a/tests/test_ibs_kicks.py b/tests/test_ibs_kicks.py index efa9d475..193cba06 100644 --- a/tests/test_ibs_kicks.py +++ b/tests/test_ibs_kicks.py @@ -129,7 +129,7 @@ def test_configuration_raises_on_below_transition_analytical_kick(): # ----- Test coefficients computation ----- # -@for_all_test_contexts(excluding=("ContextPyopencl", "ContextCupy")) +@for_all_test_contexts(excluding=("ContextPyopencl")) @pytest.mark.parametrize("formalism", ["b&m", "nagaitsev"]) def test_kick_coefficients(test_context, formalism): """ @@ -144,6 +144,7 @@ def test_kick_coefficients(test_context, formalism): sps_dir = XTRACK_TEST_DATA / "sps_w_spacecharge" linefile = sps_dir / "line_no_spacecharge_and_particle.json" line = xt.Line.from_json(linefile) + line.build_tracker(_context=test_context) ibskick = IBSAnalyticalKick(formalism=formalism, num_slices=50) # ----------------------------------------------------- # Configure in line and generate particles distribution @@ -168,7 +169,7 @@ def test_kick_coefficients(test_context, formalism): assert_allclose(coeffs.Kz, refs.Kz, rtol=1.5e-2) -@for_all_test_contexts(excluding=("ContextPyopencl", "ContextCupy")) +@for_all_test_contexts(excluding=("ContextPyopencl")) def test_kinetic_coefficients(test_context): """ We get a line and generate a large particle distribution @@ -182,6 +183,7 @@ def test_kinetic_coefficients(test_context): sps_dir = XTRACK_TEST_DATA / "sps_w_spacecharge" linefile = sps_dir / "line_no_spacecharge_and_particle.json" line = xt.Line.from_json(linefile) + line.build_tracker(_context=test_context) ibskick = IBSKineticKick(num_slices=50) # ----------------------------------------------------- # Configure in line and generate particles distribution @@ -213,8 +215,8 @@ def test_kinetic_coefficients(test_context): # ----- Tests with tracking ----- # -@for_all_test_contexts(excluding=["ContextPyopencl", "ContextCupy"]) -@retry() +@for_all_test_contexts(excluding=["ContextPyopencl"]) +@retry() # emittances fluctuate around a given trend def test_track_analytical_kick(test_context): """ Track a particle distribution in CLIC DR with exagerated @@ -234,10 +236,10 @@ def test_track_analytical_kick(test_context): cavities = [element for element in line.elements if isinstance(element, xt.Cavity)] for cavity in cavities: cavity.lag = 180 - line.configure_intrabeam_scattering(element=ibskick, name="ibskick", index=-1, update_every=50) + line.configure_intrabeam_scattering(element=ibskick, name="ibskick", index=-1, update_every=100) tw = line.twiss(method="4d") particles = xp.generate_matched_gaussian_bunch( - num_particles=1000, + num_particles=2000, nemitt_x=5.66e-7, nemitt_y=3.7e-9, sigma_z=1.58e-3, @@ -248,24 +250,25 @@ def test_track_analytical_kick(test_context): # ----------------------------------------------------- # Perform a few checks before tracking with flaky_assertions(): - assert_allclose(_gemitt_x(particles, tw.betx[0], tw.dx[0]), 1e-10, rtol=10e-2) - assert_allclose(_gemitt_y(particles, tw.bety[0], tw.dy[0]), 6.65e-13, rtol=10e-2) - assert_allclose(_sigma_delta(particles), 1.78e-3, rtol=10e-2) - assert_allclose(_bunch_length(particles), 1.58e-3, rtol=10e-2) + assert_allclose(_gemitt_x(particles, tw.betx[0], tw.dx[0]), 1e-10, rtol=5e-2) + assert_allclose(_gemitt_y(particles, tw.bety[0], tw.dy[0]), 6.65e-13, rtol=5e-2) + assert_allclose(_sigma_delta(particles), 1.78e-3, rtol=5e-2) + assert_allclose(_bunch_length(particles), 1.58e-3, rtol=5e-2) # ----------------------------------------------------- # Track for 1000 turns, and make final checks on emittances. Random # numbers are involved so we can't expect big accuracy. Without the # IBS though emittances would stay constant, so these are enough. - line.track(particles, num_turns=1000) + line.track(particles, num_turns=2000) with flaky_assertions(): - assert _gemitt_x(particles, tw.betx[0], tw.dx[0]) >= 1.4e-10 # most of the effect in x - assert _gemitt_y(particles, tw.bety[0], tw.dy[0]) >= 6.85e-13 # smaller growth in y - assert _sigma_delta(particles) >= 1.8e-3 # little growth - assert _bunch_length(particles) >= 1.625e-3 # little growth + assert _gemitt_x(particles, tw.betx[0], tw.dx[0]) >= 1.7e-10 # most of the effect in x + assert _gemitt_y(particles, tw.bety[0], tw.dy[0]) >= 7.3e-13 # smaller growth in y + # These two grow just a tad and oscillate a bit, check is loose + assert _sigma_delta(particles) >= 1.825e-3 # very little growth here + assert _bunch_length(particles) >= 1.66e-3 # very little growth here -@for_all_test_contexts(excluding=["ContextPyopencl", "ContextCupy"]) -@retry() +@for_all_test_contexts(excluding=["ContextPyopencl"]) +@retry() # emittances fluctuate around a given trend def test_track_kinetic_kick(test_context): """ Track a particle distribution in CLIC DR with exagerated @@ -285,10 +288,10 @@ def test_track_kinetic_kick(test_context): cavities = [element for element in line.elements if isinstance(element, xt.Cavity)] for cavity in cavities: cavity.lag = 180 - line.configure_intrabeam_scattering(element=ibskick, name="ibskick", index=-1, update_every=50) + line.configure_intrabeam_scattering(element=ibskick, name="ibskick", index=-1, update_every=100) tw = line.twiss(method="4d") particles = xp.generate_matched_gaussian_bunch( - num_particles=1000, + num_particles=2000, nemitt_x=5.66e-7, nemitt_y=3.7e-9, sigma_z=1.58e-3, @@ -299,17 +302,18 @@ def test_track_kinetic_kick(test_context): # ----------------------------------------------------- # Perform a few checks before tracking with flaky_assertions(): - assert_allclose(_gemitt_x(particles, tw.betx[0], tw.dx[0]), 1e-10, rtol=10e-2) - assert_allclose(_gemitt_y(particles, tw.bety[0], tw.dy[0]), 6.65e-13, rtol=10e-2) - assert_allclose(_sigma_delta(particles), 1.78e-3, rtol=10e-2) - assert_allclose(_bunch_length(particles), 1.58e-3, rtol=10e-2) + assert_allclose(_gemitt_x(particles, tw.betx[0], tw.dx[0]), 1e-10, rtol=5e-2) + assert_allclose(_gemitt_y(particles, tw.bety[0], tw.dy[0]), 6.65e-13, rtol=5e-2) + assert_allclose(_sigma_delta(particles), 1.78e-3, rtol=5e-2) + assert_allclose(_bunch_length(particles), 1.58e-3, rtol=5e-2) # ----------------------------------------------------- # Track for 1000 turns, and make final checks on emittances. Random # numbers are involved so we can't expect big accuracy. Without the # IBS though emittances would stay constant, so these are enough. - line.track(particles, num_turns=1000) + line.track(particles, num_turns=2000) with flaky_assertions(): - assert _gemitt_x(particles, tw.betx[0], tw.dx[0]) >= 1.4e-10 # most of the effect in x - assert _gemitt_y(particles, tw.bety[0], tw.dy[0]) >= 6.85e-13 # smaller growth in y - assert _sigma_delta(particles) >= 1.8e-3 # little growth - assert _bunch_length(particles) >= 1.625e-3 # little growth + assert _gemitt_x(particles, tw.betx[0], tw.dx[0]) >= 1.7e-10 # most of the effect in x + assert _gemitt_y(particles, tw.bety[0], tw.dy[0]) >= 7.3e-13 # smaller growth in y + # These two grow just a tad and oscillate a bit, check is loose + assert _sigma_delta(particles) >= 1.825e-3 # very little growth here + assert _bunch_length(particles) >= 1.66e-3 # very little growth here diff --git a/xfields/_version.py b/xfields/_version.py index 1317d755..e9fa21e9 100644 --- a/xfields/_version.py +++ b/xfields/_version.py @@ -1 +1 @@ -__version__ = "0.18.0" +__version__ = "0.18.1" diff --git a/xfields/ibs/_formulary.py b/xfields/ibs/_formulary.py index ef19baa1..84d2713a 100644 --- a/xfields/ibs/_formulary.py +++ b/xfields/ibs/_formulary.py @@ -149,6 +149,3 @@ def _assert_accepted_context(ctx: xo.context.XContext): assert not isinstance(ctx, xo.ContextPyopencl), ( "PyOpenCL context is not supported for IBS. " "Please use either the CPU or CuPy context." ) - - assert not isinstance(ctx, xo.ContextCupy), ( - "Temporarily disabled CuPy support for IBS. ") diff --git a/xfields/ibs/_kicks.py b/xfields/ibs/_kicks.py index b6f300f2..1837ba86 100644 --- a/xfields/ibs/_kicks.py +++ b/xfields/ibs/_kicks.py @@ -323,9 +323,13 @@ def compute_kick_coefficients(self, particles: xt.Particles, **kwargs) -> IBSKic An ``IBSKickCoefficients`` object with the computed kick coefficients. """ # ---------------------------------------------------------------------------------------------- - # Get the nplike_lib from the particles' context, to compute on the context device + # This full computation (apart from getting properties from the particles object) is done + # on the CPU, as the growth rates are computed on CPU. The rest of the computing is made of + # small operations, better to do these on CPU than transfer to GPU and finish there. The full + # computing is dominated by the growth rates computation in any case. + # ---------------------------------------------------------------------------------------------- + # Make sure the particles' context will not cause issues _assert_accepted_context(particles._context) - nplike = particles._context.nplike_lib # ---------------------------------------------------------------------------------------------- # Compute the momentum spread, bunch length and (geometric) emittances from the Particles object LOGGER.debug("Computing emittances, momentum spread and bunch length from particles") @@ -339,13 +343,11 @@ def compute_kick_coefficients(self, particles: xt.Particles, **kwargs) -> IBSKic # Normalized: for momentum we have to multiply with 1/sqrt(gamma) = sqrt(beta) / sqrt(1 + alpha^2), and the # sqrt(beta) is included in the std of p[xy]. If bunch is rotated, the std takes from the "other plane" so # we take the normalized momenta to compensate. - sigma_px_normalized: float = _sigma_px(particles) / nplike.sqrt(1 + self._twiss["alfx", self._name] ** 2) - sigma_py_normalized: float = _sigma_py(particles) / nplike.sqrt(1 + self._twiss["alfy", self._name] ** 2) + sigma_px_normalized: float = _sigma_px(particles) / np.sqrt(1 + self._twiss["alfx", self._name] ** 2) + sigma_py_normalized: float = _sigma_py(particles) / np.sqrt(1 + self._twiss["alfy", self._name] ** 2) # ---------------------------------------------------------------------------------------------- # Determine the "scaling factor", corresponding to 2 * sigma_t * sqrt(pi) in Eq (8) of reference - zeta: ArrayLike = particles.zeta[particles.state > 0] # careful to only consider active particles - bunch_length_rms: float = nplike.std(zeta) # rms bunch length in [m] - scaling_factor: float = float(2 * nplike.sqrt(np.pi) * bunch_length_rms) + scaling_factor: float = float(2 * np.sqrt(np.pi) * bunch_length) # ---------------------------------------------------------------------------------------------- # Computing the analytical IBS growth rates through the instance's set TwissTable # TODO (Gianni): how do we deal with coasting beams? Can pass to Coulog but how to detect? @@ -361,9 +363,9 @@ def compute_kick_coefficients(self, particles: xt.Particles, **kwargs) -> IBSKic Tx, Ty, Tz = growth_rates.as_tuple() # each is a float # ---------------------------------------------------------------------------------------------- # Making sure we do not have negative growth rates (see class docstring warning for detail) - Tx = 0.0 if Tx < 0 else float(Tx) - Ty = 0.0 if Ty < 0 else float(Ty) - Tz = 0.0 if Tz < 0 else float(Tz) + Tx: float = 0.0 if Tx < 0 else float(Tx) + Ty: float = 0.0 if Ty < 0 else float(Ty) + Tz: float = 0.0 if Tz < 0 else float(Tz) if any(rate == 0 for rate in (Tx, Ty, Tz)): LOGGER.debug("At least one IBS growth rate was negative, and was set to 0") # ---------------------------------------------------------------------------------------------- @@ -373,9 +375,9 @@ def compute_kick_coefficients(self, particles: xt.Particles, **kwargs) -> IBSKic LOGGER.debug("Computing simple kick coefficients") beta0 = self._twiss.beta0 revolution_frequency: float = 1 / self._twiss.T_rev0 - Kx = float(sigma_px_normalized * nplike.sqrt(2 * scaling_factor * Tx / revolution_frequency)) - Ky = float(sigma_py_normalized * nplike.sqrt(2 * scaling_factor * Ty / revolution_frequency)) - Kz = float(sigma_delta * nplike.sqrt(2 * scaling_factor * Tz / revolution_frequency) * beta0**2) + Kx = float(sigma_px_normalized * np.sqrt(2 * scaling_factor * Tx / revolution_frequency)) + Ky = float(sigma_py_normalized * np.sqrt(2 * scaling_factor * Ty / revolution_frequency)) + Kz = float(sigma_delta * np.sqrt(2 * scaling_factor * Tz / revolution_frequency) * beta0**2) result = IBSKickCoefficients(Kx, Ky, Kz) # ---------------------------------------------------------------------------------------------- # Self-update the instance's attributes and then return the results