From 70c221751bbfc59a85107d2d40acf353422d4cc6 Mon Sep 17 00:00:00 2001 From: Alexis GAMELIN <alexis.gamelin@synchrotron-soleil.fr> Date: Wed, 4 Dec 2024 15:25:10 +0100 Subject: [PATCH] Add a test suite and fix some bugs. Code modifications: * [Fix] adts in TransverseMapSector * ComplexData.name_and_coefficients_table is now a staticmethod. * Extend WakeField to "mixed" componenets (xydip, yxdip, ...). * Small fix/improvements for rf.py (CavityResonator for n_bin=1) * [Fix] WakePotential.check_sampling method. * [Fix] Sweep.plot method. * Add Element.track_bunch_if_non_empty decorator. * Modify Bunch.binning to add a return_full_length optional parameter. * Modify Mpi.share_distributions for n_bin=1. * Modify TransverseSpaceCharge to work with track_alive = True. * Modify WakePotential for n_bin=1 and use track_bunch_if_non_empty decorator. * [Fix] IntrabeamScattering for nonuniform fills with Beam.mpi_switch == False * Modify PhysicalModel to return errors if needed. * Update BeamIonElement Project management: * Update pyproject.toml and poetry.lock with pytest-mock * Add assert_attr_changed test function. --- mbtrack2/impedance/wakefield.py | 19 +- mbtrack2/tracking/__init__.py | 14 +- mbtrack2/tracking/beam_ion_effects.py | 105 +- mbtrack2/tracking/element.py | 41 +- mbtrack2/tracking/excite.py | 4 +- mbtrack2/tracking/ibs.py | 13 +- mbtrack2/tracking/parallel.py | 28 +- mbtrack2/tracking/particles.py | 126 +- mbtrack2/tracking/rf.py | 42 +- mbtrack2/tracking/spacecharge.py | 22 +- mbtrack2/tracking/wakepotential.py | 67 +- mbtrack2/utilities/optics.py | 18 + mbtrack2/utilities/spectrum.py | 7 +- poetry.lock | 1114 +++++++++-------- pyproject.toml | 1 + tests/conftest.py | 109 +- .../{test_ibs.py => physics/test_ibs_phys.py} | 77 +- tests/test_bunch.py | 105 -- tests/test_optics.py | 17 - tests/test_synchrotron.py | 63 - tests/unit/impedance/test_impedance_model.py | 266 ++++ tests/unit/impedance/test_wakefield.py | 321 +++++ tests/unit/tracking/test_aperture.py | 181 +++ tests/unit/tracking/test_beam_ion_effects.py | 333 +++++ tests/unit/tracking/test_element.py | 360 ++++++ tests/unit/tracking/test_emfields.py | 102 ++ tests/unit/tracking/test_excite.py | 72 ++ tests/unit/tracking/test_ibs.py | 73 ++ tests/unit/tracking/test_parallel.py | 98 ++ tests/unit/tracking/test_particle.py | 306 +++++ tests/unit/tracking/test_rf.py | 420 +++++++ tests/unit/tracking/test_spacecharge.py | 37 + tests/unit/tracking/test_synchrotron.py | 120 ++ tests/unit/tracking/test_wakepotential.py | 316 +++++ tests/unit/utilities/test_optics.py | 295 +++++ tests/utility_test_functions.py | 63 + 36 files changed, 4369 insertions(+), 986 deletions(-) rename tests/{test_ibs.py => physics/test_ibs_phys.py} (67%) delete mode 100644 tests/test_bunch.py delete mode 100644 tests/test_optics.py delete mode 100644 tests/test_synchrotron.py create mode 100644 tests/unit/impedance/test_impedance_model.py create mode 100644 tests/unit/impedance/test_wakefield.py create mode 100644 tests/unit/tracking/test_aperture.py create mode 100644 tests/unit/tracking/test_beam_ion_effects.py create mode 100644 tests/unit/tracking/test_element.py create mode 100644 tests/unit/tracking/test_emfields.py create mode 100644 tests/unit/tracking/test_excite.py create mode 100644 tests/unit/tracking/test_ibs.py create mode 100644 tests/unit/tracking/test_parallel.py create mode 100644 tests/unit/tracking/test_particle.py create mode 100644 tests/unit/tracking/test_rf.py create mode 100644 tests/unit/tracking/test_spacecharge.py create mode 100644 tests/unit/tracking/test_synchrotron.py create mode 100644 tests/unit/tracking/test_wakepotential.py create mode 100644 tests/unit/utilities/test_optics.py create mode 100644 tests/utility_test_functions.py diff --git a/mbtrack2/impedance/wakefield.py b/mbtrack2/impedance/wakefield.py index ead052e..56c7e39 100644 --- a/mbtrack2/impedance/wakefield.py +++ b/mbtrack2/impedance/wakefield.py @@ -223,7 +223,8 @@ class ComplexData: self.d = component_coefficients["d"] self.plane = component_coefficients["plane"] - def name_and_coefficients_table(self): + @staticmethod + def name_and_coefficients_table(): """ Return a table associating the human readbale names of an impedance component and its associated coefficients and plane. @@ -895,7 +896,8 @@ class WakeField: Return an array of the impedance component names for the element. """ valid = [ - "Zlong", "Zxdip", "Zydip", "Zxquad", "Zyquad", "Zxcst", "Zycst" + "Zlong", "Zxdip", "Zydip", "Zxquad", "Zyquad", "Zxcst", "Zycst", + "Zxydip", "Zyxdip", "Zxyquad", "Zyxquad" ] return np.array([comp for comp in dir(self) if comp in valid]) @@ -905,7 +907,8 @@ class WakeField: Return an array of the wake function component names for the element. """ valid = [ - "Wlong", "Wxdip", "Wydip", "Wxquad", "Wyquad", "Wxcst", "Wycst" + "Wlong", "Wxdip", "Wydip", "Wxquad", "Wyquad", "Wxcst", "Wycst", + "Wxydip", "Wyxdip", "Wxyquad", "Wyxquad" ] return np.array([comp for comp in dir(self) if comp in valid]) @@ -916,7 +919,9 @@ class WakeField: """ valid = [ "Wlong", "Wxdip", "Wydip", "Wxquad", "Wyquad", "Wxcst", "Wycst", - "Zlong", "Zxdip", "Zydip", "Zxquad", "Zyquad", "Zxcst", "Zycst" + "Wxydip", "Wyxdip", "Wxyquad", "Wyxquad", "Zlong", "Zxdip", + "Zydip", "Zxquad", "Zyquad", "Zxcst", "Zycst", "Zxydip", "Zyxdip", + "Zxyquad", "Zyxquad" ] return np.array([comp for comp in dir(self) if comp in valid]) @@ -1073,12 +1078,12 @@ class WakeField: if len(wakefields) == 1: return wakefields[0] elif len(wakefields) > 1: - wake_sum = WakeField.add_wakefields(wakefields[0], beta[:, 0], - wakefields[1], beta[:, 1]) + wake_sum = WakeField.add_wakefields(wakefields[0], beta[0, :], + wakefields[1], beta[1, :]) for i in range(len(wakefields) - 2): wake_sum = WakeField.add_wakefields(wake_sum, [1, 1], wakefields[i + 2], - beta[:, i + 2]) + beta[i + 2, :]) return wake_sum else: raise ValueError("Error in input.") diff --git a/mbtrack2/tracking/__init__.py b/mbtrack2/tracking/__init__.py index 2b4a60e..c18034b 100644 --- a/mbtrack2/tracking/__init__.py +++ b/mbtrack2/tracking/__init__.py @@ -20,12 +20,24 @@ from mbtrack2.tracking.element import ( TransverseMapSector, transverse_map_sector_generator, ) +from mbtrack2.tracking.emfields import ( + add_sigma_check, + efieldn_gauss_round, + get_displaced_efield, +) from mbtrack2.tracking.excite import Sweep from mbtrack2.tracking.feedback import ExponentialDamper, FIRDamper from mbtrack2.tracking.ibs import IntrabeamScattering from mbtrack2.tracking.monitors import * from mbtrack2.tracking.parallel import Mpi -from mbtrack2.tracking.particles import Beam, Bunch, Electron, Particle, Proton +from mbtrack2.tracking.particles import ( + Beam, + Bunch, + Electron, + Ion, + Particle, + Proton, +) from mbtrack2.tracking.rf import ( CavityResonator, DirectFeedback, diff --git a/mbtrack2/tracking/beam_ion_effects.py b/mbtrack2/tracking/beam_ion_effects.py index da354ba..a041fee 100644 --- a/mbtrack2/tracking/beam_ion_effects.py +++ b/mbtrack2/tracking/beam_ion_effects.py @@ -189,7 +189,7 @@ class IonAperture(ElipticalAperture): """ alive = (bunch.particles["x"]**2 / self.X_radius_squared + bunch.particles["y"]**2 / self.Y_radius_squared <= 1) - for stat in ['x', 'xp', 'y', 'yp', 'tau', 'delta']: + for stat in bunch: bunch.particles[stat] = bunch.particles[stat][alive] bunch.mp_number = len(bunch.particles['x']) bunch.alive = np.ones((bunch.mp_number, )) @@ -226,7 +226,8 @@ class IonParticles(Bunch): track_alive=False, alive=True): self.ring = ring - self._mp_number = int(mp_number) + mp_number = int(mp_number) + self._mp_number = mp_number self.alive = np.ones((self.mp_number, ), dtype=bool) if not alive: self.alive = np.zeros((self.mp_number, ), dtype=bool) @@ -262,6 +263,9 @@ class IonParticles(Bunch): electron_bunch : Bunch An instance of the Bunch class representing the electron bunch. """ + if electron_bunch.is_empty: + raise ValueError("Electron bunch is empty.") + self["x"], self["y"] = ( normal( loc=electron_bunch["x"].mean(), @@ -295,6 +299,9 @@ class IonParticles(Bunch): electron_bunch : Bunch An instance of the Bunch class representing the electron bunch. """ + if electron_bunch.is_empty: + raise ValueError("Electron bunch is empty.") + self["x"], self["y"] = ( choice(electron_bunch["x"], size=self.mp_number), choice(electron_bunch["y"], size=self.mp_number), @@ -312,7 +319,7 @@ class IonParticles(Bunch): def __add__(self, new_particles): self.mp_number += new_particles.mp_number - for t in ["x", "xp", "y", "yp", "tau", "delta"]: + for t in new_particles: self.particles[t] = np.append(self.particles[t], new_particles.particles[t]) self.alive = np.append( @@ -341,8 +348,6 @@ class BeamIonElement(Element): For 'PIC' the PyPIC package is required. electron_field_model : str The electron field model, the options are 'weak', 'strong', 'PIC'. - bunch_spacing : float - The bunch spacing, the distance between bunches in meters. Used to propagate the ions in between bunches or in the gaps between the bunches. ion_element_length : float The length of the beam-ion interaction region. For example, if only a single interaction point is used this should be equal to ring.L. x_radius : float @@ -360,22 +365,22 @@ class BeamIonElement(Element): Whether to use the ion phase space monitor. generate_method : str, optional The method to generate the ion macroparticles, the options are 'distribution', 'samples'. Defaults to 'distribution'. - 'Distribution' generates a distribution statistically equivalent to the distribution of electrons. - 'Samples' generates ions from random samples of electron positions. + 'distribution' generates a distribution statistically equivalent to the distribution of electrons. + 'samples' generates ions from random samples of electron positions. Methods ------- - __init__(self, ion_mass, ion_charge, ionization_cross_section, residual_gas_density, ring, ion_field_model, electron_field_model, bunch_spacing, ion_element_length, n_steps, x_radius, y_radius, ion_beam_monitor_name=None, use_ion_phase_space_monitor=False, n_ion_macroparticles_per_bunch=30, generate_method='distribution') + __init__(ion_mass, ion_charge, ionization_cross_section, residual_gas_density, ring, ion_field_model, electron_field_model, ion_element_length, n_steps, x_radius, y_radius, ion_beam_monitor_name=None, use_ion_phase_space_monitor=False, n_ion_macroparticles_per_bunch=30, generate_method='distribution') Initializes the BeamIonElement object. parallel(track) Defines the decorator @parallel to handle tracking of Beam() objects. - clear_ions(self) + clear_ions() Clear the ion particles in the ion beam. - track_ions_in_a_drift(self, drift_length) + track_ions_in_a_drift(drift_length) Tracks the ions in a drift. - generate_new_ions(self, electron_bunch) + generate_new_ions(electron_bunch) Generate new ions based on the given electron bunch. - track(self, electron_bunch) + track(electron_bunch) Beam-ion interaction kicks. Raises @@ -394,7 +399,6 @@ class BeamIonElement(Element): ring, ion_field_model, electron_field_model, - bunch_spacing, ion_element_length, n_steps, x_radius, @@ -407,7 +411,7 @@ class BeamIonElement(Element): raise NotImplementedError( "Ion phase space monitor is not implemented.") self.ring = ring - self.bunch_spacing = bunch_spacing + self.bunch_spacing = ring.L / ring.h self.ion_mass = ion_mass self.ionization_cross_section = ionization_cross_section self.residual_gas_density = residual_gas_density @@ -416,7 +420,9 @@ class BeamIonElement(Element): self.ion_field_model = ion_field_model self.ion_element_length = ion_element_length self.generate_method = generate_method - self.n_ion_macroparticles_per_bunch = 30 + if not self.generate_method in ["distribution", "samples"]: + raise ValueError("Wrong generate_method.") + self.n_ion_macroparticles_per_bunch = n_ion_macroparticles_per_bunch self.ion_beam_monitor_name = ion_beam_monitor_name self.ion_beam = IonParticles( mp_number=1, @@ -470,11 +476,10 @@ class BeamIonElement(Element): self = args[0] beam = args[1] if beam.mpi_switch: - warnings.warn( - 'Tracking through beam-ion element is performed sequentially. Bunches are not parallelized.', - UserWarning, - stacklevel=2) - for bunch in beam.bunch_list: + raise ValueError( + "Tracking through beam-ion element is performed sequentially." + ) + for bunch in beam: track(self, bunch, *args[2:], **kwargs) else: self = args[0] @@ -488,7 +493,9 @@ class BeamIonElement(Element): Clear the ion particles in the ion beam. """ self.ion_beam.particles = IonParticles( - mp_number=1, ion_element_length=self.ion_element_length) + mp_number=1, + ion_element_length=self.ion_element_length, + ring=self.ring) def track_ions_in_a_drift(self, drift_length): """ @@ -525,12 +532,10 @@ class BeamIonElement(Element): en_y : numpy.ndarray The y component of the electric field. """ - assert field_model in [ - "weak", - "strong", - "PIC", - ], "The implementation for required beam-ion interaction model {:} is not implemented".format( - self.interaction_model) + if not field_model in ["weak", "strong", "PIC"]: + raise ValueError( + f"The implementation for required beam-ion interaction model {field_model} is not implemented" + ) sb_mx, sb_stdx = ( second_beam["x"].mean(), second_beam["x"].std(), @@ -666,29 +671,37 @@ class BeamIonElement(Element): An electron bunch to be interacted with. """ - self.generate_new_ions(electron_bunch=electron_bunch) + if electron_bunch.is_empty: + empty_bucket = True + else: + empty_bucket = False + + if not empty_bucket: + self.generate_new_ions(electron_bunch=electron_bunch) self.aperture.track(self.ion_beam) if self.ion_beam_monitor_name is not None: self.beam_monitor.track(self.ion_beam) - prefactor_to_ion_field = -self.ion_beam.charge / (self.ring.E0) - prefactor_to_electron_field = -electron_bunch.charge * ( - e / (self.ion_mass * c**2)) - new_xp_ions, new_yp_ions = self._get_new_beam_momentum( - self.ion_beam, - electron_bunch, - prefactor_to_electron_field, - field_model=self.electron_field_model, - ) - new_xp_electrons, new_yp_electrons = self._get_new_beam_momentum( - electron_bunch, - self.ion_beam, - prefactor_to_ion_field, - field_model=self.ion_field_model, - ) - self._update_beam_momentum(self.ion_beam, new_xp_ions, new_yp_ions) - self._update_beam_momentum(electron_bunch, new_xp_electrons, - new_yp_electrons) + if not empty_bucket: + prefactor_to_ion_field = -self.ion_beam.charge / (self.ring.E0) + prefactor_to_electron_field = -electron_bunch.charge * ( + e / (self.ion_mass * c**2)) + new_xp_ions, new_yp_ions = self._get_new_beam_momentum( + self.ion_beam, + electron_bunch, + prefactor_to_electron_field, + field_model=self.electron_field_model, + ) + new_xp_electrons, new_yp_electrons = self._get_new_beam_momentum( + electron_bunch, + self.ion_beam, + prefactor_to_ion_field, + field_model=self.ion_field_model, + ) + self._update_beam_momentum(self.ion_beam, new_xp_ions, new_yp_ions) + self._update_beam_momentum(electron_bunch, new_xp_electrons, + new_yp_electrons) + self.track_ions_in_a_drift(drift_length=self.bunch_spacing) diff --git a/mbtrack2/tracking/element.py b/mbtrack2/tracking/element.py index 049f700..4c6d3dc 100644 --- a/mbtrack2/tracking/element.py +++ b/mbtrack2/tracking/element.py @@ -36,7 +36,7 @@ class Element(metaclass=ABCMeta): @staticmethod def parallel(track): """ - Defines the decorator @parallel which handle the embarrassingly + Defines the decorator @parallel which handles the embarrassingly parallel case which happens when there is no bunch to bunch interaction in the tracking routine. @@ -73,6 +73,39 @@ class Element(metaclass=ABCMeta): return track_wrapper + @staticmethod + def track_bunch_if_non_empty(track): + """ + Defines the decorator @track_bunch_if_non_empty which handles the case + where a track method should not be called if the bunch is empty. + + Should be added only the track method defined for Bunch elements. + + Parameters + ---------- + track : function, method of an Element subclass + track method of an Element subclass which takes a Bunch object as + input + + Returns + ------- + track_wrapper: function, method of an Element subclass + track method of an Element subclass which takes a Bunch object as + input + + """ + + @wraps(track) + def track_wrapper(*args): + #self = args[0] + bunch = args[1] + if bunch.is_empty: + pass + else: + track(*args) + + return track_wrapper + class LongitudinalMap(Element): """ @@ -272,7 +305,7 @@ class TransverseMapSector(Element): self.chro_diff[5] / 6 * bunch["delta"]**3 + self.chro_diff[7] / 24 * bunch["delta"]**4) else: - coefs = np.array([1 / factorial(i + 1) for i in range(order + 1)]) + coefs = np.array([1 / factorial(i) for i in range(order + 1)]) coefs[0] = 0 self.chro_diff = np.concatenate(([0, 0], self.chro_diff)) tune_advance_x = np.polynomial.polynomial.Polynomial( @@ -328,13 +361,13 @@ class TransverseMapSector(Element): if self.adts_poly is not None: Jx = ((self.gamma0[0] * bunch["x"]**2) + - (2 * self.alpha0[0] * bunch["x"] * self["xp"]) + + (2 * self.alpha0[0] * bunch["x"] * bunch["xp"]) + (self.beta0[0] * bunch["xp"]**2)) Jy = ((self.gamma0[1] * bunch["y"]**2) + (2 * self.alpha0[1] * bunch["y"] * bunch["yp"]) + (self.beta0[1] * bunch["yp"]**2)) tune_advance_x += (self.adts_poly[0](Jx) + self.adts_poly[2](Jy)) - tune_advance_x += (self.adts_poly[0](Jx) + self.adts_poly[2](Jy)) + tune_advance_y += (self.adts_poly[1](Jx) + self.adts_poly[3](Jy)) bunch['x'], bunch['xp'] = self._compute_new_coords( bunch, tune_advance_x, 'x') diff --git a/mbtrack2/tracking/excite.py b/mbtrack2/tracking/excite.py index 818fceb..7afa3f3 100644 --- a/mbtrack2/tracking/excite.py +++ b/mbtrack2/tracking/excite.py @@ -113,6 +113,6 @@ class Sweep(Element): """Plot the sweep voltage applied.""" fig, ax = plt.subplots() ax.plot(self.t, self.sweep) - ax.xlabel("Time [s]") - ax.ylabel("Sweep voltage [V]") + ax.set_xlabel("Time [s]") + ax.set_ylabel("Sweep voltage [V]") return fig diff --git a/mbtrack2/tracking/ibs.py b/mbtrack2/tracking/ibs.py index 13e7b74..a02efd0 100644 --- a/mbtrack2/tracking/ibs.py +++ b/mbtrack2/tracking/ibs.py @@ -93,9 +93,8 @@ class IntrabeamScattering(Element): """ - def __init__(self, ring, bunch, model, n_points=1000, n_bin=100): + def __init__(self, ring, model, n_points=1000, n_bin=100): self.ring = ring - self.bunch = bunch self.n_points = int(n_points) self.n_bin = int(n_bin) self.s = np.linspace(0, self.ring.L, self.n_points) @@ -134,7 +133,7 @@ class IntrabeamScattering(Element): bunch: Bunch or Beam object Bunch or Beam object which will be tracked. """ - self.N = self.bunch.current * self.ring.T0 / elementary_charge + self.N = bunch.current * self.ring.T0 / elementary_charge self.d = 4 * np.std(bunch['y']) self.sigma_s = np.std(bunch['tau']) self.sigma_p = np.std(bunch['delta']) @@ -412,21 +411,23 @@ class IntrabeamScattering(Element): else: Rho = 1.0 + N_mp = len(bunch) Delta_pz = self.sigma_p * np.sqrt( np.sqrt(2) * T_p * self.ring.T0 * - Rho) * np.random.normal(size=bunch.mp_number) + Rho) * np.random.normal(size=N_mp) Delta_px = self.sigma_px * np.sqrt( np.sqrt(2) * T_x * self.ring.T0 * - Rho) * np.random.normal(size=bunch.mp_number) + Rho) * np.random.normal(size=N_mp) Delta_py = self.sigma_py * np.sqrt( np.sqrt(2) * T_y * self.ring.T0 * - Rho) * np.random.normal(size=bunch.mp_number) + Rho) * np.random.normal(size=N_mp) bunch['xp'] += Delta_px bunch['yp'] += Delta_py bunch['delta'] += Delta_pz @Element.parallel + @Element.track_bunch_if_non_empty def track(self, bunch): """ Tracking method of IntrabeamScattering takes T_(x,y,p) and apply diff --git a/mbtrack2/tracking/parallel.py b/mbtrack2/tracking/parallel.py index 6ef1846..a441f2d 100644 --- a/mbtrack2/tracking/parallel.py +++ b/mbtrack2/tracking/parallel.py @@ -64,9 +64,9 @@ class Mpi: self.MPI = MPI self.comm = MPI.COMM_WORLD self.rank = self.comm.Get_rank() - if self.rank != 0: - sys.stdout = open('/dev/null', 'w') - sys.stderr = open('/dev/null', 'w') + # if self.rank != 0: + # sys.stdout = open('/dev/null', 'w') + # sys.stderr = open('/dev/null', 'w') self.size = self.comm.Get_size() self.write_table(filling_pattern) @@ -180,31 +180,39 @@ class Mpi: dim = dimensions[i] n = n_bin[i] - if len(bunch) != 0: + if not bunch.is_empty: bins, sorted_index, profile, center = bunch.binning( dimension=dim, n_bin=n) + bin_length = bins[1] - bins[0] else: sorted_index = None - profile = np.zeros((n - 1, ), dtype=np.int64) - center = np.zeros((n - 1, ), dtype=np.float64) + profile = np.zeros((n, ), dtype=np.int64) + center = np.zeros((n, ), dtype=np.float64) + bin_length = np.zeros((1, ), dtype=np.float64) if beam.filling_pattern[self.bunch_num] is True: beam.update_filling_pattern() beam.update_distance_between_bunches() self.__setattr__(dim + "_center", - np.empty((self.size, n - 1), dtype=np.float64)) + np.empty((self.size, n), dtype=np.float64)) self.comm.Allgather( [center, self.MPI.DOUBLE], [self.__getattribute__(dim + "_center"), self.MPI.DOUBLE]) self.__setattr__(dim + "_profile", - np.empty((self.size, n - 1), dtype=np.int64)) + np.empty((self.size, n), dtype=np.int64)) self.comm.Allgather( [profile, self.MPI.INT64_T], [self.__getattribute__(dim + "_profile"), self.MPI.INT64_T]) self.__setattr__(dim + "_sorted_index", sorted_index) + self.__setattr__(dim + "_bin_length", + np.empty((self.size, 1), dtype=np.float64)) + self.comm.Allgather( + [bin_length, self.MPI.DOUBLE], + [self.__getattribute__(dim + "_bin_length"), self.MPI.DOUBLE]) + def share_means(self, beam): """ Compute the bunch means and share it between the different bunches. @@ -224,7 +232,7 @@ class Mpi: self.charge_all = charge_all self.mean_all = np.empty((self.size, 6), dtype=np.float64) - if len(bunch) != 0: + if not bunch.is_empty: mean = bunch.mean else: mean = np.zeros((6, ), dtype=np.float64) @@ -250,7 +258,7 @@ class Mpi: self.charge_all = charge_all self.std_all = np.empty((self.size, 6), dtype=np.float64) - if len(bunch) != 0: + if not bunch.is_empty: std = bunch.std else: std = np.zeros((6, ), dtype=np.float64) diff --git a/mbtrack2/tracking/particles.py b/mbtrack2/tracking/particles.py index 2b3edbb..0deea66 100644 --- a/mbtrack2/tracking/particles.py +++ b/mbtrack2/tracking/particles.py @@ -144,6 +144,13 @@ class Bunch: if not alive: mp_number = 1 current = 0 + track_alive = True + + if current == 0 and track_alive == False: + raise ValueError( + "Can not initialize empty bunch with track_alive=False, use the empty=True keyword." + ) + self._mp_number = int(mp_number) self.particles = { @@ -165,7 +172,10 @@ class Bunch: def __len__(self): """Return the number of alive particles""" - return self.alive.sum() + if self.track_alive is True: + return self.alive.sum() + else: + return self.mp_number def __getitem__(self, label): """Return the columns label for alive particles""" @@ -240,7 +250,10 @@ class Bunch: @property def is_empty(self): """Return True if the bunch is empty.""" - return ~np.any(self.alive) + if self.track_alive is True: + return ~np.any(self.alive) + else: + return False @property def mean(self): @@ -282,38 +295,60 @@ class Bunch: """ Return the bunch emittance for each plane. """ - - cov_x = np.cov(self['x'], self['xp']) - cov_y = np.cov(self['y'], self['yp']) - cov_z = np.cov(self['tau'], self['delta']) - - if (np.array(self.ring.optics.local_dispersion) - != np.array([0, 0, 0, 0])).any(): - cov_xdelta = np.cov(self['x'], self['delta']) - cov_xpdelta = np.cov(self['xp'], self['delta']) - cov_ydelta = np.cov(self['y'], self['delta']) - cov_ypdelta = np.cov(self['yp'], self['delta']) - - sig11 = cov_x[ - 0, 0] - cov_xdelta[0, 1] * cov_xdelta[0, 1] / cov_z[1, 1] - sig12 = cov_x[ - 0, 1] - cov_xdelta[0, 1] * cov_xpdelta[0, 1] / cov_z[1, 1] - sig22 = cov_x[ - 1, 1] - cov_xpdelta[0, 1] * cov_xpdelta[0, 1] / cov_z[1, 1] - emitX = np.sqrt(sig11*sig22 - sig12*sig12) - - sig11 = cov_y[ - 0, 0] - cov_ydelta[0, 1] * cov_ydelta[0, 1] / cov_z[1, 1] - sig12 = cov_y[ - 0, 1] - cov_ydelta[0, 1] * cov_ypdelta[0, 1] / cov_z[1, 1] - sig22 = cov_y[ - 1, 1] - cov_ypdelta[0, 1] * cov_ypdelta[0, 1] / cov_z[1, 1] - emitY = np.sqrt(sig11*sig22 - sig12*sig12) + if len(self) > 1: + cov_x = np.cov(self['x'], self['xp']) + cov_y = np.cov(self['y'], self['yp']) + cov_z = np.cov(self['tau'], self['delta']) + + if (np.array(self.ring.optics.local_dispersion) + != np.array([0, 0, 0, 0])).any(): + cov_xdelta = np.cov(self['x'], self['delta']) + cov_xpdelta = np.cov(self['xp'], self['delta']) + cov_ydelta = np.cov(self['y'], self['delta']) + cov_ypdelta = np.cov(self['yp'], self['delta']) + + sig11 = cov_x[ + 0, 0] - cov_xdelta[0, 1] * cov_xdelta[0, 1] / cov_z[1, 1] + sig12 = cov_x[ + 0, 1] - cov_xdelta[0, 1] * cov_xpdelta[0, 1] / cov_z[1, 1] + sig22 = cov_x[ + 1, 1] - cov_xpdelta[0, 1] * cov_xpdelta[0, 1] / cov_z[1, 1] + with np.errstate(invalid='raise'): + try: + emitX = np.sqrt(sig11*sig22 - sig12*sig12) + except FloatingPointError: + emitX = 0 + + sig11 = cov_y[ + 0, 0] - cov_ydelta[0, 1] * cov_ydelta[0, 1] / cov_z[1, 1] + sig12 = cov_y[ + 0, 1] - cov_ydelta[0, 1] * cov_ypdelta[0, 1] / cov_z[1, 1] + sig22 = cov_y[ + 1, 1] - cov_ypdelta[0, 1] * cov_ypdelta[0, 1] / cov_z[1, 1] + with np.errstate(invalid='raise'): + try: + emitY = np.sqrt(sig11*sig22 - sig12*sig12) + except FloatingPointError: + emitY = 0 + else: + with np.errstate(invalid='raise'): + try: + emitX = np.sqrt(np.linalg.det(cov_x)) + except FloatingPointError: + emitX = 0 + try: + emitY = np.sqrt(np.linalg.det(cov_y)) + except FloatingPointError: + emitY = 0 + + with np.errstate(invalid='raise'): + try: + emitS = np.sqrt(np.linalg.det(cov_z)) + except FloatingPointError: + emitS = 0 else: - emitX = np.sqrt(np.linalg.det(cov_x)) - emitY = np.sqrt(np.linalg.det(cov_y)) + emitX, emitY, emitS = 0, 0, 0 - emitS = np.sqrt(np.linalg.det(cov_z)) return np.array([emitX, emitY, emitS]) @property @@ -412,7 +447,7 @@ class Bunch: self.particles["tau"] = values[:, 4] self.particles["delta"] = values[:, 5] - def binning(self, dimension="tau", n_bin=75): + def binning(self, dimension="tau", n_bin=75, return_full_length=False): """ Bin macro-particles. @@ -422,16 +457,21 @@ class Bunch: Dimension in which the binning is done. The default is "tau". n_bin : int, optional Number of bins. The default is 75. + return_full_length : bool, optional + If True (and self.track_alive), also assign bins to untracked + macro-particles. + The returned sorted_index has shape (self.mp_number,). + Default is False. Returns ------- - bins : array of shape (n_bin,) + bins : array of shape (n_bin + 1,) Bins where the particles are sorted. - sorted_index : array of shape (self.mp_number,) + sorted_index : array of shape (len(self),) or (self.mp_number,) Bin number of each macro-particles. - profile : array of shape (n_bin - 1,) + profile : array of shape (n_bin,) Number of marco-particles in each bin. - center : array of shape (n_bin - 1,) + center : array of shape (n_bin,) Center of each bin. """ @@ -440,11 +480,17 @@ class Bunch: bin_max = self[dimension].max() bin_max = max(bin_max * 0.99, bin_max * 1.01) - bins = np.linspace(bin_min, bin_max, n_bin) + bins = np.linspace(bin_min, bin_max, n_bin + 1) center = (bins[1:] + bins[:-1]) / 2 sorted_index = np.searchsorted(bins, self[dimension], side="left") sorted_index -= 1 - profile = np.bincount(sorted_index, minlength=n_bin - 1) + profile = np.bincount(sorted_index, minlength=n_bin) + + if self.track_alive and return_full_length: + sorted_index = np.searchsorted(bins, + self.particles[dimension], + side="left") + sorted_index -= 1 return (bins, sorted_index, profile, center) @@ -946,7 +992,7 @@ class Beam: "bunch_mean", "bunch_std", "bunch_emit"} Variable to be plotted. option : str, optional - If var is "bunch_mean", "bunch_std", or "bunch_emit, option needs + If var is "bunch_mean", "bunch_std", or "bunch_emit", option needs to be specified. For "bunch_mean" and "bunch_std", option = {"x","xp","y","yp","tau","delta"}. diff --git a/mbtrack2/tracking/rf.py b/mbtrack2/tracking/rf.py index 62a2798..3abc5dc 100644 --- a/mbtrack2/tracking/rf.py +++ b/mbtrack2/tracking/rf.py @@ -231,7 +231,7 @@ class CavityResonator(): self.detune = detune self.Vc = Vc self.theta = theta - self.beam_phasor = np.zeros(1, dtype=complex) + self.beam_phasor = 0 + 0j self.beam_phasor_record = np.zeros((self.ring.h), dtype=complex) self.generator_phasor_record = np.zeros((self.ring.h), dtype=complex) self.tracking = False @@ -288,16 +288,16 @@ class CavityResonator(): # mpi -> get shared bunch profile for current bunch center = beam.mpi.tau_center[rank] profile = beam.mpi.tau_profile[rank] - bin_length = center[1] - center[0] - charge_per_mp = beam.mpi.charge_per_mp_all[rank] + bin_length = float(beam.mpi.tau_bin_length[rank][0]) + charge_per_mp = float(beam.mpi.charge_per_mp_all[rank]) if index == self.bunch_index: sorted_index = beam.mpi.tau_sorted_index else: # no mpi -> get bunch profile for current bunch - if len(bunch) != 0: + if not bunch.is_empty: (bins, sorted_index, profile, center) = bunch.binning(n_bin=self.n_bin) - bin_length = center[1] - center[0] + bin_length = bins[1] - bins[0] charge_per_mp = bunch.charge_per_mp self.bunch_index = index else: @@ -323,7 +323,7 @@ class CavityResonator(): else: # modify beam phasor for i, center0 in enumerate(center): - mp_per_bin = profile[i] + mp_per_bin = int(profile[i]) if mp_per_bin == 0: self.phasor_decay(bin_length, ref_frame="beam") @@ -389,7 +389,7 @@ class CavityResonator(): # get shared bunch profile for current bunch center = beam.mpi.tau_center[j] profile = beam.mpi.tau_profile[j] - bin_length = center[1] - center[0] + bin_length = beam.mpi.tau_bin_length[j] charge_per_mp = beam.mpi.charge_per_mp_all[j] else: if i == 0: @@ -411,7 +411,7 @@ class CavityResonator(): profile = self.profile_save[j, :] center = self.center_save[j, :] - bin_length = center[1] - center[0] + bin_length = bins[1] - bins[0] charge_per_mp = bunch.charge_per_mp self.phasor_decay(center[0] - bin_length/2, ref_frame="rf") @@ -511,13 +511,14 @@ class CavityResonator(): if self.tracking is False: self.init_tracking(beam) - N = self.n_bin - 1 + N = self.n_bin delta = (self.wr - self.m * self.ring.omega1) n_turn = int(self.filling_time / self.ring.T0 * 10) T = np.ones(self.ring.h) * self.ring.T1 bin_length = np.zeros(self.ring.h) charge_per_mp = np.zeros(self.ring.h) + bins = np.zeros((N + 1, self.ring.h)) profile = np.zeros((N, self.ring.h)) center = np.zeros((N, self.ring.h)) @@ -528,12 +529,12 @@ class CavityResonator(): beam.mpi.share_distributions(beam, n_bin=self.n_bin) center[:, index] = beam.mpi.tau_center[j] profile[:, index] = beam.mpi.tau_profile[j] - bin_length[index] = center[1, index] - center[0, index] + bin_length[index] = beam.mpi.bin_length[j] charge_per_mp[index] = beam.mpi.charge_per_mp_all[j] else: - (bins, sorted_index, profile[:, index], + (bins[:, index], sorted_index, profile[:, index], center[:, index]) = bunch.binning(n_bin=self.n_bin) - bin_length[index] = center[1, index] - center[0, index] + bin_length[index] = bins[1, index] - bins[0, index] charge_per_mp[index] = bunch.charge_per_mp T[index] -= (center[-1, index] + bin_length[index] / 2) if index != 0: @@ -1221,22 +1222,29 @@ class TunerLoop(): gain : float Proportional gain of the tuner loop. If not specified, 0.01 is used. - avering_period: + avering_period : int, optional Period during which the phase difference is monitored and averaged. Then the feedback correction is applied every avering_period turn. Unit is turn number. A value longer than one synchrotron period (1/fs) is recommended. - If not specified, 2-synchrotron period (2/fs) is used, although it is + If None, 2-synchrotron period (2/fs) is used, although it is too fast compared to the actual situation. - offset : float + Default is None. + offset : float, optional Tuning offset in [rad]. + Default is 0. """ - def __init__(self, ring, cav_res, gain=0.01, avering_period=0, offset=0): + def __init__(self, + ring, + cav_res, + gain=0.01, + avering_period=None, + offset=0): self.ring = ring self.cav_res = cav_res - if avering_period == 0: + if avering_period is None: fs = self.ring.synchrotron_tune( self.cav_res.Vc) * self.ring.f1 / self.ring.h avering_period = 2 / fs / self.ring.T0 diff --git a/mbtrack2/tracking/spacecharge.py b/mbtrack2/tracking/spacecharge.py index 661bc10..b421c6a 100644 --- a/mbtrack2/tracking/spacecharge.py +++ b/mbtrack2/tracking/spacecharge.py @@ -60,6 +60,7 @@ class TransverseSpaceCharge(Element): self.efieldn = _efieldn_mit @Element.parallel + @Element.track_bunch_if_non_empty def track(self, bunch): """ Perform the tracking of the bunch through the space charge element. @@ -73,26 +74,29 @@ class TransverseSpaceCharge(Element): prefactor = self.interaction_length / (self.ring.E0 * self.ring.gamma**2) (bins, sorted_index, profile, - center) = bunch.binning(n_bin=self.n_bins) + center) = bunch.binning(n_bin=self.n_bins, return_full_length=True) dz = (bins[1] - bins[0]) * c charge_density = bunch.charge_per_mp * profile / dz for bin_index in range(self.n_bins - 1): particle_ids = (bin_index == sorted_index) + if bunch.track_alive: + particle_ids = particle_ids & bunch.alive if len(particle_ids) == 0: continue - x = bunch['x'][particle_ids] - y = bunch['y'][particle_ids] + x = bunch.particles['x'][particle_ids] + y = bunch.particles['y'][particle_ids] if len(x) != 0 and len(y) != 0: mean_x, std_x = x.mean(), x.std() mean_y, std_y = y.mean(), y.std() - en_x, en_y = get_displaced_efield(self.efieldn, - bunch['x'][particle_ids], - bunch['y'][particle_ids], - std_x, std_y, mean_x, mean_y) + en_x, en_y = get_displaced_efield( + self.efieldn, bunch.particles['x'][particle_ids], + bunch.particles['y'][particle_ids], std_x, std_y, mean_x, + mean_y) kicks_x = prefactor * en_x * charge_density[bin_index] kicks_y = prefactor * en_y * charge_density[bin_index] - bunch['xp'][particle_ids] += kicks_x - bunch['yp'][particle_ids] += kicks_y + + bunch.particles['xp'][particle_ids] += kicks_x + bunch.particles['yp'][particle_ids] += kicks_y diff --git a/mbtrack2/tracking/wakepotential.py b/mbtrack2/tracking/wakepotential.py index 3fdb19b..47581c4 100644 --- a/mbtrack2/tracking/wakepotential.py +++ b/mbtrack2/tracking/wakepotential.py @@ -83,6 +83,8 @@ class WakePotential(Element): self.n_types = len(self.wakefield.wake_components) self.ring = ring self.n_bin = n_bin + if self.n_bin < 2: + raise ValueError("n_bin must be >= 2.") self.check_sampling() self.interp_on_postion = interp_on_postion @@ -149,8 +151,8 @@ class WakePotential(Element): Dipole moment of the bunch. """ - dipole = np.empty((self.n_bin - 1, )) - for i in range(self.n_bin - 1): + dipole = np.empty((self.n_bin, )) + for i in range(self.n_bin): dipole[i] = bunch[plane][self.sorted_index == i].sum() dipole = dipole / self.profile dipole[np.isnan(dipole)] = 0 @@ -287,6 +289,7 @@ class WakePotential(Element): return tau0, Wp @Element.parallel + @Element.track_bunch_if_non_empty def track(self, bunch): """ Tracking method for the element. @@ -298,34 +301,32 @@ class WakePotential(Element): bunch : Bunch or Beam object. """ - - if len(bunch) != 0: - self.charge_density(bunch) - for wake_type in self.types: - tau0, Wp = self.get_wakepotential(bunch, wake_type) - if self.interp_on_postion: - Wp_interp = np.interp(bunch["tau"], tau0 + self.tau_mean, - Wp, 0, 0) - else: - Wp_interp = np.interp(self.center, tau0 + self.tau_mean, - Wp, 0, 0) - Wp_interp = Wp_interp[self.sorted_index] - if wake_type == "Wlong": - bunch["delta"] += Wp_interp * bunch.charge / self.ring.E0 - elif wake_type == "Wxdip": - bunch["xp"] += Wp_interp * bunch.charge / self.ring.E0 - elif wake_type == "Wydip": - bunch["yp"] += Wp_interp * bunch.charge / self.ring.E0 - elif wake_type == "Wxquad": - bunch["xp"] += (bunch["x"] * Wp_interp * bunch.charge / - self.ring.E0) - elif wake_type == "Wyquad": - bunch["yp"] += (bunch["y"] * Wp_interp * bunch.charge / - self.ring.E0) - elif wake_type == "Wxcst": - bunch["xp"] += Wp_interp * bunch.charge / self.ring.E0 - elif wake_type == "Wycst": - bunch["yp"] += Wp_interp * bunch.charge / self.ring.E0 + self.charge_density(bunch) + for wake_type in self.types: + tau0, Wp = self.get_wakepotential(bunch, wake_type) + if self.interp_on_postion: + Wp_interp = np.interp(bunch["tau"], tau0 + self.tau_mean, Wp, + 0, 0) + else: + Wp_interp = np.interp(self.center, tau0 + self.tau_mean, Wp, 0, + 0) + Wp_interp = Wp_interp[self.sorted_index] + if wake_type == "Wlong": + bunch["delta"] += Wp_interp * bunch.charge / self.ring.E0 + elif wake_type == "Wxdip": + bunch["xp"] += Wp_interp * bunch.charge / self.ring.E0 + elif wake_type == "Wydip": + bunch["yp"] += Wp_interp * bunch.charge / self.ring.E0 + elif wake_type == "Wxquad": + bunch["xp"] += (bunch["x"] * Wp_interp * bunch.charge / + self.ring.E0) + elif wake_type == "Wyquad": + bunch["yp"] += (bunch["y"] * Wp_interp * bunch.charge / + self.ring.E0) + elif wake_type == "Wxcst": + bunch["xp"] += Wp_interp * bunch.charge / self.ring.E0 + elif wake_type == "Wycst": + bunch["yp"] += Wp_interp * bunch.charge / self.ring.E0 def plot_last_wake(self, wake_type, @@ -435,7 +436,9 @@ class WakePotential(Element): profile0 = gaussian_bunch(tau0, sigma) dipole0 = np.ones_like(profile0) * dipole - if wake_type == "Wlong" or wake_type == "Wxquad" or wake_type == "Wyquad": + if (wake_type == "Wlong" or wake_type == "Wxquad" + or wake_type == "Wyquad" or wake_type == "Wxcst" + or wake_type == "Wycst"): Wp = signal.convolve(profile0, W0 * -1, mode='same') * dtau0 elif wake_type == "Wxdip": Wp = signal.convolve(profile0 * dipole0, W0, mode='same') * dtau0 @@ -581,7 +584,7 @@ class WakePotential(Element): idx = getattr(self.wakefield, wake_type).data.index diff = idx[1:] - idx[:-1] result = np.all(np.isclose(diff, diff[0], atol=1e-15)) - if result is False: + if result == False: raise ValueError( "The wake function must be uniformly sampled.") diff --git a/mbtrack2/utilities/optics.py b/mbtrack2/utilities/optics.py index 4e2f58b..2aba395 100644 --- a/mbtrack2/utilities/optics.py +++ b/mbtrack2/utilities/optics.py @@ -657,6 +657,15 @@ class PhysicalModel: sym : bool, optional If True, right/left and top/bottum symmetry is applied. """ + if start_position < self.position[0]: + raise ValueError(f"Wrong start_position value: {start_position}") + if end_position > self.position[-1]: + raise ValueError(f"Wrong end_position value: {end_position}") + if start_position > end_position: + raise ValueError( + f"start_position > end_position: {start_position} > {end_position}" + ) + ind = (self.position > start_position) & (self.position < end_position) if x_right is not None: self.x_right[ind] = x_right @@ -723,6 +732,15 @@ class PhysicalModel: sym : bool, optional If True, right/left and top/bottum symmetry is applied. """ + if start_position < self.position[0]: + raise ValueError(f"Wrong start_position value: {start_position}") + if end_position > self.position[-1]: + raise ValueError(f"Wrong end_position value: {end_position}") + if start_position > end_position: + raise ValueError( + f"start_position > end_position: {start_position} > {end_position}" + ) + ind = (self.position > start_position) & (self.position < end_position) if (x_right_start is not None) and (x_right_end is not None): self.x_right[ind] = np.linspace(x_right_start, x_right_end, diff --git a/mbtrack2/utilities/spectrum.py b/mbtrack2/utilities/spectrum.py index 7f8fb88..449cbde 100644 --- a/mbtrack2/utilities/spectrum.py +++ b/mbtrack2/utilities/spectrum.py @@ -3,6 +3,8 @@ Module where bunch and beam spectrums and profile are defined. """ +from math import factorial + import numpy as np from scipy.special import jv, spherical_jn @@ -41,9 +43,8 @@ def spectral_density(frequency, sigma, m=1, k=0, mode="Hermite"): """ if mode == "Hermite": - return 1 / (np.math.factorial(m) * - 2**m) * (2 * np.pi * frequency * sigma)**( - 2 * m) * np.exp(-(2 * np.pi * frequency * sigma)**2) + return 1 / (factorial(m) * 2**m) * (2 * np.pi * frequency * sigma)**( + 2 * m) * np.exp(-(2 * np.pi * frequency * sigma)**2) elif mode == "Chebyshev": tau_l = 4 * sigma return (jv(m, 2 * np.pi * frequency * tau_l))**2 diff --git a/poetry.lock b/poetry.lock index 584ca37..f137972 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,60 +2,65 @@ [[package]] name = "accelerator-toolbox" -version = "0.5.0" +version = "0.6.1" description = "Accelerator Toolbox" optional = false python-versions = ">=3.7" files = [ - {file = "accelerator-toolbox-0.5.0.tar.gz", hash = "sha256:519de56f87a71513d7eac93a5670fd493ec12799c019c213bc5db686e6413637"}, - {file = "accelerator_toolbox-0.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6a4696192782db3b419b1dd39f6f84fb10bfbf05e74dc8746d521ee7d43f0577"}, - {file = "accelerator_toolbox-0.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:06eca2ade40c0ec940d145ac2a57ca6e38ad9c7b3a418b71d705475ef64b5302"}, - {file = "accelerator_toolbox-0.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59fa4dc6d3cfddc6a11fae1600dae93c0206b8be0ca26bdd3a507972cf259b5d"}, - {file = "accelerator_toolbox-0.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811d0f107bdd1d652aa4c75861351224ff2df5aebbbe335e987c9df170e0d144"}, - {file = "accelerator_toolbox-0.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f008b09c414158c7dfa1fcb725eeb3ec6a7a7ffc20d94c041ea65ac71b4a8de6"}, - {file = "accelerator_toolbox-0.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:77f01fadd8620c2977e8034125fb5d672a7752ae8f07150f5d9cefffce22d6b3"}, - {file = "accelerator_toolbox-0.5.0-cp310-cp310-win32.whl", hash = "sha256:74868c75e3c503a90d16d0f218cf4fab07744a49a854d3dea303b84ced681ed5"}, - {file = "accelerator_toolbox-0.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6105e6f5421f3514216ed649cfcd11ab8fb0d808816f0983ba516e4148e4c6c"}, - {file = "accelerator_toolbox-0.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2def21702adb6bc6841681f060aadb24ed8e708ee84afb25a7226af4a4bb955c"}, - {file = "accelerator_toolbox-0.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:388119d32c948f74f2f8eb12ba28b82bb744b895eb64c3d57f1e8ea629aea5ab"}, - {file = "accelerator_toolbox-0.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9607528289ea8d5aa74ef4a8ddf8b1dd2dfd81ffda32d011d66567349b6445b6"}, - {file = "accelerator_toolbox-0.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:520721a348d69c59d766e28b7c482505b175dc6448696f49f0bc55386a3bb1c8"}, - {file = "accelerator_toolbox-0.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:78b1fa8e4a141fb5655977fd03bd54ecced34c12c5b676dae2763e59bec65159"}, - {file = "accelerator_toolbox-0.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b4e230f434e4839ccf56e13b285685f2d5163d089ea4dba488c4e58dd0d4bc2"}, - {file = "accelerator_toolbox-0.5.0-cp311-cp311-win32.whl", hash = "sha256:4168f12e1c007c89df5967518d8343179ae66c0890e1817f6702096d4182659d"}, - {file = "accelerator_toolbox-0.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:8fd0802b8728373aad936a48f599dbe886b18c060b6283da1e548676ffcabf67"}, - {file = "accelerator_toolbox-0.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dddf954cade3c60451be8ff3fbee4daa2e27a41d06608a7c425c8dad894b8830"}, - {file = "accelerator_toolbox-0.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:144110ede61a6b5947e99f54e10d2205a165946290817fdc37a6ffa57e19c7a2"}, - {file = "accelerator_toolbox-0.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f43002154b9b14576bd746a2f3c4684926dc1d65191e6923e01263a76a6595a5"}, - {file = "accelerator_toolbox-0.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1fcbafb2e85556933461335e1df035dee20e6aaf93fc9b5d516ce08b57ea2d82"}, - {file = "accelerator_toolbox-0.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4da4c1ffa3b063447d1ee9ed4a4e2579af958c04a9aa2eecdb37b0984094f5f7"}, - {file = "accelerator_toolbox-0.5.0-cp37-cp37m-win32.whl", hash = "sha256:4d46f7c518cbc6d2fbfaf6af157cea1a6437b4503b5fb0db5cee851768c48e27"}, - {file = "accelerator_toolbox-0.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1b065c09879e7f5d0e7978fe564462bd2da8b9a3574a36069b7ca86f618adca5"}, - {file = "accelerator_toolbox-0.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:14f06d9606ec55f689ac2ab38b43e3c8e6403f4f253e6019adb8f512a7210dfd"}, - {file = "accelerator_toolbox-0.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2bce394ab8715b6186fdad9a6fb273fb08a492d4d194a8ab44903450fce77c64"}, - {file = "accelerator_toolbox-0.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ec8a7c1a1ecca3223cfeba408ef20a9b8ca68b1cbefde6f854c84703fde272"}, - {file = "accelerator_toolbox-0.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e8136ca8f46fbcdbb7ae2aacc84473d7db07c155f2afcb4c7d289164bea38a"}, - {file = "accelerator_toolbox-0.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cef92eb3726a4084520f80f7a291461b800ab79dc40b261cd8cb25887a1ec5a2"}, - {file = "accelerator_toolbox-0.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c4b02cf8628cc352099d0819a474d4d4e4f2997dd04c8854f231040e73253b00"}, - {file = "accelerator_toolbox-0.5.0-cp38-cp38-win32.whl", hash = "sha256:3c628105026ef7949e00c5e71d056e28253381fc447c31188587c75f0b899d9b"}, - {file = "accelerator_toolbox-0.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:3992890702d11c3fb74a7c5f098f2f81daf5664eed13f58e43b791fdc2e354f6"}, - {file = "accelerator_toolbox-0.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801c467d4f514a2d07aef83d1570f29cc856526d1c1d1a661ee7aa2fbca8e110"}, - {file = "accelerator_toolbox-0.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb88778a9d6a379f41c79a49e0379a95a2172af613fbd4fadde736e811bb9bf1"}, - {file = "accelerator_toolbox-0.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8623e6086e3253232bf666562500e992f4f6ee20bfd3e645f03c08ed741c3b7"}, - {file = "accelerator_toolbox-0.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fddbcbef404cc7f0e9e0994a54d20273600b3db540359c2b0d38edbe17a7ba12"}, - {file = "accelerator_toolbox-0.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:49de8d3f8b5d3e27ac7899edf45c65fe98f302f42f2bb5d2fa16db278b97ae90"}, - {file = "accelerator_toolbox-0.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fcd3329b7e52839a6a7d5edba3b2cb0f0326390297776162b634e0ca88d81ce7"}, - {file = "accelerator_toolbox-0.5.0-cp39-cp39-win32.whl", hash = "sha256:de14d5205905278cdd91c5c3900174240f2e15566e9c777197d27e1addbe1eb2"}, - {file = "accelerator_toolbox-0.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:c6f2a65bb5e7bd9d63b9780fbc1b1a5b0648bf143f1e8f721d4fc7bb77cd3396"}, + {file = "accelerator_toolbox-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3ac5152cb0a120f35e1578e2881ce10d276d650c06be895bbde65967b9905b40"}, + {file = "accelerator_toolbox-0.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d76bf18eb905d12dd11586e5955ac8ec32763a520176d666656cc0d51818dd55"}, + {file = "accelerator_toolbox-0.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:672d86d95c6a8c3c4e2ac07b5a606070e53fc94d476a0652ec470c0c7a2ac72e"}, + {file = "accelerator_toolbox-0.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:340bcf315d1b86bd34255ac04b44158a23433cb2b972f23856cd6013c5eaf262"}, + {file = "accelerator_toolbox-0.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0fef4748a472a6e2e58d24678e04179386d5fafbec6a193fcebffea88b8b704c"}, + {file = "accelerator_toolbox-0.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:89ffd9c45b8dd0d4e1518a74beb31aa6040511aa6f096dd0123010e8d65dec85"}, + {file = "accelerator_toolbox-0.6.1-cp310-cp310-win32.whl", hash = "sha256:06e6a18fa5c7cb082f14be666d83811911aa2ec9124fbd56b8b4a8086218be7c"}, + {file = "accelerator_toolbox-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:86e5ee9449c1dbc034916cfed174d525b115252b779d1379c1ed35152f40fbc3"}, + {file = "accelerator_toolbox-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79dc93e26395266725dc652bbda46f9f45eca1e869dcfe5bda169c2823cea998"}, + {file = "accelerator_toolbox-0.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bb4cc9958cf082b338900cf51e4805d0111f4b4952b3527ae8c667847fa594d1"}, + {file = "accelerator_toolbox-0.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9caea973e753d97ce86315f7d2c08234a1d9081b66791a85639eeb03b5f59a7"}, + {file = "accelerator_toolbox-0.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4dffd19a623a9c83374920ba969ccb20d82c4bef85e26c699fca2356277b1236"}, + {file = "accelerator_toolbox-0.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:24761db8fd74d9feb4de25b3034012779c3da2cb8880fce3ff64c4bfa8ae23fb"}, + {file = "accelerator_toolbox-0.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b60e95ce65a818ca3c3e987995f764039d47bd81abf181b6d300e6d5f7cee146"}, + {file = "accelerator_toolbox-0.6.1-cp311-cp311-win32.whl", hash = "sha256:63d97e642b44fdc1df4dbfa5d12b853e9e04252708a4ee9580ddb2a998251896"}, + {file = "accelerator_toolbox-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:154004613a8e3ab0d32a53956519e686c98449b22fdef43860f874948eaa9f63"}, + {file = "accelerator_toolbox-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a1cfbce362b707dfe1278c817e0caee9e953ca071fc4c2fb9c2e64d670d2d0f"}, + {file = "accelerator_toolbox-0.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:245ddd37377b1f27380bee7dfe5a333249a258670ad1a2d76c0a04d3452c214b"}, + {file = "accelerator_toolbox-0.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:089ae3abd03d9d8a385bf1c402e03f4e74d8c08f8200951ae78c177afe020a00"}, + {file = "accelerator_toolbox-0.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e53895f4eaacb9cc7eeecb60f0ae7c27435794c7d3874eaca4b66499257034f9"}, + {file = "accelerator_toolbox-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:da4527f7607d11ac4fc6b844dedc21cf1dc666d30cd852e80eb68bb45530aaeb"}, + {file = "accelerator_toolbox-0.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:03339a7cfdce54709e3e2946f1d073d9461d4826151384d6e7b99219b81a840b"}, + {file = "accelerator_toolbox-0.6.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcb79ec9ea3403c8b863cd32fa9a7628f1a5a274ceecea17a4ec70257de11c74"}, + {file = "accelerator_toolbox-0.6.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e9c2506a122f23fe0e8432c3f78d81217151c9fd4b925f41c2414983ba617cf"}, + {file = "accelerator_toolbox-0.6.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:c9f5340617f7dfb602e2591039b4ac841a8aa7e854aae2b9ab030582caaa4d4a"}, + {file = "accelerator_toolbox-0.6.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:ff3e7a5dee31e866ad8d44300c1311148e1190b2f930b971fe2724ceb398a452"}, + {file = "accelerator_toolbox-0.6.1-cp37-cp37m-win32.whl", hash = "sha256:96fbd1db4955d5709e7e84e9b00ab410e6381cb6118f2aabe2ab01c49dbd83df"}, + {file = "accelerator_toolbox-0.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4c3d3ab862f908641ace70b523d657d51d38acaf316dde2734dfb54fda623b3f"}, + {file = "accelerator_toolbox-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:415397692eac5f463cc34befe6bf620c5526b2e1c19ff91b6db80cfc721c2cd1"}, + {file = "accelerator_toolbox-0.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:692d46673cbcba35667f3dcbf2b55d849b3905baafd45a4481d4bbca0f339edd"}, + {file = "accelerator_toolbox-0.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d48226cfa0253412f1a57a360087a9c810f64491eb4696fa0501b5563032845"}, + {file = "accelerator_toolbox-0.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed296d7d31595b9e68987b3be702e0c19227f3c649e5940c54d6f040c1c46fc5"}, + {file = "accelerator_toolbox-0.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:3a5d17534d0d979f1aa3e258405c0f001148dd74938ae60f9311834d89e710bf"}, + {file = "accelerator_toolbox-0.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd9ced17e05112bfb8ed24377fbffefdc38e2ad4fc65495e48558a663085b45b"}, + {file = "accelerator_toolbox-0.6.1-cp38-cp38-win32.whl", hash = "sha256:c82c5640142593d9251bcd89bb7dddd76dd9b408c7ae489ade3acb2a769975c3"}, + {file = "accelerator_toolbox-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:986d1752e193681fb5082747c7cfe32c91cbe2588aa1d94957a8cf2b2aba1c8b"}, + {file = "accelerator_toolbox-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ab176e692f3b37805f11a9366677fbb5e5eac40f3ad959170b5485234916ae6"}, + {file = "accelerator_toolbox-0.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:13a5772298b73c591a8f84029ea79f9255ae8ae4983d33728d0cf5bfdabafe82"}, + {file = "accelerator_toolbox-0.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6b9e6fa1becaa831dc88c786537559324ae551f162968ed512a04a22c56e771"}, + {file = "accelerator_toolbox-0.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3e01953df2e2f38c480a9b7d4f0ff737d71dce6e9b5f40888c878b5bce1113cc"}, + {file = "accelerator_toolbox-0.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:43f072fe45a922b23800acb16f2624739670b93d62efe586f588ad71c2ef219a"}, + {file = "accelerator_toolbox-0.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9e0ebedf6a3b0941b8b77f6f800cc9aa1bfaa226973bc6de355edceaa735c209"}, + {file = "accelerator_toolbox-0.6.1-cp39-cp39-win32.whl", hash = "sha256:eac7beac945ad1451f749c9eb96a1bc45b270dc894691ad5465fd19f7b0b752a"}, + {file = "accelerator_toolbox-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:12dee22b6f7ea04e224afee519d82e32a2c0c8c28ee9317ddba76c9555093416"}, + {file = "accelerator_toolbox-0.6.1.tar.gz", hash = "sha256:9f4c67216634d20b07f8105ead471b7440c80151dfe81ad27538ea4adefdaa89"}, ] [package.dependencies] -numpy = ">=1.16.6" +numpy = {version = ">=1.23.5", markers = "python_version >= \"3.9\""} scipy = ">=1.4.0" [package.extras] -dev = ["flake8", "pytest (>=2.9)", "pytest-cov", "pytest-lazy-fixture"] -doc = ["Sphinx (>=5.3,<6.0)", "myst-parser", "pydata-sphinx-theme (>=0.11.0,<0.12.0)", "sphinx-copybutton"] +dev = ["flake8", "pytest (>=2.9)", "pytest-cov"] +doc = ["Sphinx (>=7.2,<8.0)", "myst-nb", "myst-parser", "pydata-sphinx-theme (>=0.14,<1.0)", "sphinx-copybutton", "sphinx_design"] mpi = ["mpi4py"] plot = ["matplotlib"] @@ -72,66 +77,87 @@ files = [ [[package]] name = "contourpy" -version = "1.2.0" +version = "1.3.0" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false python-versions = ">=3.9" files = [ - {file = "contourpy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0274c1cb63625972c0c007ab14dd9ba9e199c36ae1a231ce45d725cbcbfd10a8"}, - {file = "contourpy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab459a1cbbf18e8698399c595a01f6dcc5c138220ca3ea9e7e6126232d102bb4"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fdd887f17c2f4572ce548461e4f96396681212d858cae7bd52ba3310bc6f00f"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d16edfc3fc09968e09ddffada434b3bf989bf4911535e04eada58469873e28e"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c203f617abc0dde5792beb586f827021069fb6d403d7f4d5c2b543d87edceb9"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b69303ceb2e4d4f146bf82fda78891ef7bcd80c41bf16bfca3d0d7eb545448aa"}, - {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:884c3f9d42d7218304bc74a8a7693d172685c84bd7ab2bab1ee567b769696df9"}, - {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4a1b1208102be6e851f20066bf0e7a96b7d48a07c9b0cfe6d0d4545c2f6cadab"}, - {file = "contourpy-1.2.0-cp310-cp310-win32.whl", hash = "sha256:34b9071c040d6fe45d9826cbbe3727d20d83f1b6110d219b83eb0e2a01d79488"}, - {file = "contourpy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:bd2f1ae63998da104f16a8b788f685e55d65760cd1929518fd94cd682bf03e41"}, - {file = "contourpy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd10c26b4eadae44783c45ad6655220426f971c61d9b239e6f7b16d5cdaaa727"}, - {file = "contourpy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c6b28956b7b232ae801406e529ad7b350d3f09a4fde958dfdf3c0520cdde0dd"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebeac59e9e1eb4b84940d076d9f9a6cec0064e241818bcb6e32124cc5c3e377a"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139d8d2e1c1dd52d78682f505e980f592ba53c9f73bd6be102233e358b401063"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e9dc350fb4c58adc64df3e0703ab076f60aac06e67d48b3848c23647ae4310e"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18fc2b4ed8e4a8fe849d18dce4bd3c7ea637758c6343a1f2bae1e9bd4c9f4686"}, - {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:16a7380e943a6d52472096cb7ad5264ecee36ed60888e2a3d3814991a0107286"}, - {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d8faf05be5ec8e02a4d86f616fc2a0322ff4a4ce26c0f09d9f7fb5330a35c95"}, - {file = "contourpy-1.2.0-cp311-cp311-win32.whl", hash = "sha256:67b7f17679fa62ec82b7e3e611c43a016b887bd64fb933b3ae8638583006c6d6"}, - {file = "contourpy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:99ad97258985328b4f207a5e777c1b44a83bfe7cf1f87b99f9c11d4ee477c4de"}, - {file = "contourpy-1.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:575bcaf957a25d1194903a10bc9f316c136c19f24e0985a2b9b5608bdf5dbfe0"}, - {file = "contourpy-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e6c93b5b2dbcedad20a2f18ec22cae47da0d705d454308063421a3b290d9ea4"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:464b423bc2a009088f19bdf1f232299e8b6917963e2b7e1d277da5041f33a779"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68ce4788b7d93e47f84edd3f1f95acdcd142ae60bc0e5493bfd120683d2d4316"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d7d1f8871998cdff5d2ff6a087e5e1780139abe2838e85b0b46b7ae6cc25399"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e739530c662a8d6d42c37c2ed52a6f0932c2d4a3e8c1f90692ad0ce1274abe0"}, - {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:247b9d16535acaa766d03037d8e8fb20866d054d3c7fbf6fd1f993f11fc60ca0"}, - {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:461e3ae84cd90b30f8d533f07d87c00379644205b1d33a5ea03381edc4b69431"}, - {file = "contourpy-1.2.0-cp312-cp312-win32.whl", hash = "sha256:1c2559d6cffc94890b0529ea7eeecc20d6fadc1539273aa27faf503eb4656d8f"}, - {file = "contourpy-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:491b1917afdd8638a05b611a56d46587d5a632cabead889a5440f7c638bc6ed9"}, - {file = "contourpy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5fd1810973a375ca0e097dee059c407913ba35723b111df75671a1976efa04bc"}, - {file = "contourpy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:999c71939aad2780f003979b25ac5b8f2df651dac7b38fb8ce6c46ba5abe6ae9"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7caf9b241464c404613512d5594a6e2ff0cc9cb5615c9475cc1d9b514218ae8"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:266270c6f6608340f6c9836a0fb9b367be61dde0c9a9a18d5ece97774105ff3e"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbd50d0a0539ae2e96e537553aff6d02c10ed165ef40c65b0e27e744a0f10af8"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11f8d2554e52f459918f7b8e6aa20ec2a3bce35ce95c1f0ef4ba36fbda306df5"}, - {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ce96dd400486e80ac7d195b2d800b03e3e6a787e2a522bfb83755938465a819e"}, - {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d3364b999c62f539cd403f8123ae426da946e142312a514162adb2addd8d808"}, - {file = "contourpy-1.2.0-cp39-cp39-win32.whl", hash = "sha256:1c88dfb9e0c77612febebb6ac69d44a8d81e3dc60f993215425b62c1161353f4"}, - {file = "contourpy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:78e6ad33cf2e2e80c5dfaaa0beec3d61face0fb650557100ee36db808bfa6843"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be16975d94c320432657ad2402f6760990cb640c161ae6da1363051805fa8108"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b95a225d4948b26a28c08307a60ac00fb8671b14f2047fc5476613252a129776"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d7e03c0f9a4f90dc18d4e77e9ef4ec7b7bbb437f7f675be8e530d65ae6ef956"}, - {file = "contourpy-1.2.0.tar.gz", hash = "sha256:171f311cb758de7da13fc53af221ae47a5877be5a0843a9fe150818c51ed276a"}, + {file = "contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7"}, + {file = "contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41"}, + {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d"}, + {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223"}, + {file = "contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f"}, + {file = "contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b"}, + {file = "contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad"}, + {file = "contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d"}, + {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c"}, + {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb"}, + {file = "contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c"}, + {file = "contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67"}, + {file = "contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f"}, + {file = "contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09"}, + {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd"}, + {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35"}, + {file = "contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb"}, + {file = "contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b"}, + {file = "contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3"}, + {file = "contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da"}, + {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14"}, + {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8"}, + {file = "contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294"}, + {file = "contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087"}, + {file = "contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8"}, + {file = "contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6"}, + {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2"}, + {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927"}, + {file = "contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8"}, + {file = "contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2"}, + {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e"}, + {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800"}, + {file = "contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5"}, + {file = "contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb"}, + {file = "contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4"}, ] [package.dependencies] -numpy = ">=1.20,<2.0" +numpy = ">=1.23" [package.extras] bokeh = ["bokeh", "selenium"] docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] -mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.6.1)", "types-Pillow"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.11.1)", "types-Pillow"] test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] -test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] +test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] [[package]] name = "cycler" @@ -150,13 +176,13 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] @@ -164,53 +190,59 @@ test = ["pytest (>=6)"] [[package]] name = "fonttools" -version = "4.50.0" +version = "4.54.1" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.50.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effd303fb422f8ce06543a36ca69148471144c534cc25f30e5be752bc4f46736"}, - {file = "fonttools-4.50.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7913992ab836f621d06aabac118fc258b9947a775a607e1a737eb3a91c360335"}, - {file = "fonttools-4.50.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e0a1c5bd2f63da4043b63888534b52c5a1fd7ae187c8ffc64cbb7ae475b9dab"}, - {file = "fonttools-4.50.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d40fc98540fa5360e7ecf2c56ddf3c6e7dd04929543618fd7b5cc76e66390562"}, - {file = "fonttools-4.50.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fff65fbb7afe137bac3113827855e0204482727bddd00a806034ab0d3951d0d"}, - {file = "fonttools-4.50.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1aeae3dd2ee719074a9372c89ad94f7c581903306d76befdaca2a559f802472"}, - {file = "fonttools-4.50.0-cp310-cp310-win32.whl", hash = "sha256:e9623afa319405da33b43c85cceb0585a6f5d3a1d7c604daf4f7e1dd55c03d1f"}, - {file = "fonttools-4.50.0-cp310-cp310-win_amd64.whl", hash = "sha256:778c5f43e7e654ef7fe0605e80894930bc3a7772e2f496238e57218610140f54"}, - {file = "fonttools-4.50.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3dfb102e7f63b78c832e4539969167ffcc0375b013080e6472350965a5fe8048"}, - {file = "fonttools-4.50.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e58fe34cb379ba3d01d5d319d67dd3ce7ca9a47ad044ea2b22635cd2d1247fc"}, - {file = "fonttools-4.50.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c673ab40d15a442a4e6eb09bf007c1dda47c84ac1e2eecbdf359adacb799c24"}, - {file = "fonttools-4.50.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b3ac35cdcd1a4c90c23a5200212c1bb74fa05833cc7c14291d7043a52ca2aaa"}, - {file = "fonttools-4.50.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8844e7a2c5f7ecf977e82eb6b3014f025c8b454e046d941ece05b768be5847ae"}, - {file = "fonttools-4.50.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f849bd3c5c2249b49c98eca5aaebb920d2bfd92b3c69e84ca9bddf133e9f83f0"}, - {file = "fonttools-4.50.0-cp311-cp311-win32.whl", hash = "sha256:39293ff231b36b035575e81c14626dfc14407a20de5262f9596c2cbb199c3625"}, - {file = "fonttools-4.50.0-cp311-cp311-win_amd64.whl", hash = "sha256:c33d5023523b44d3481624f840c8646656a1def7630ca562f222eb3ead16c438"}, - {file = "fonttools-4.50.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b4a886a6dbe60100ba1cd24de962f8cd18139bd32808da80de1fa9f9f27bf1dc"}, - {file = "fonttools-4.50.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b2ca1837bfbe5eafa11313dbc7edada79052709a1fffa10cea691210af4aa1fa"}, - {file = "fonttools-4.50.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0493dd97ac8977e48ffc1476b932b37c847cbb87fd68673dee5182004906828"}, - {file = "fonttools-4.50.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77844e2f1b0889120b6c222fc49b2b75c3d88b930615e98893b899b9352a27ea"}, - {file = "fonttools-4.50.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3566bfb8c55ed9100afe1ba6f0f12265cd63a1387b9661eb6031a1578a28bad1"}, - {file = "fonttools-4.50.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:35e10ddbc129cf61775d58a14f2d44121178d89874d32cae1eac722e687d9019"}, - {file = "fonttools-4.50.0-cp312-cp312-win32.whl", hash = "sha256:cc8140baf9fa8f9b903f2b393a6c413a220fa990264b215bf48484f3d0bf8710"}, - {file = "fonttools-4.50.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ccc85fd96373ab73c59833b824d7a73846670a0cb1f3afbaee2b2c426a8f931"}, - {file = "fonttools-4.50.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e270a406219af37581d96c810172001ec536e29e5593aa40d4c01cca3e145aa6"}, - {file = "fonttools-4.50.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac2463de667233372e9e1c7e9de3d914b708437ef52a3199fdbf5a60184f190c"}, - {file = "fonttools-4.50.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47abd6669195abe87c22750dbcd366dc3a0648f1b7c93c2baa97429c4dc1506e"}, - {file = "fonttools-4.50.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:074841375e2e3d559aecc86e1224caf78e8b8417bb391e7d2506412538f21adc"}, - {file = "fonttools-4.50.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0743fd2191ad7ab43d78cd747215b12033ddee24fa1e088605a3efe80d6984de"}, - {file = "fonttools-4.50.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3d7080cce7be5ed65bee3496f09f79a82865a514863197ff4d4d177389e981b0"}, - {file = "fonttools-4.50.0-cp38-cp38-win32.whl", hash = "sha256:a467ba4e2eadc1d5cc1a11d355abb945f680473fbe30d15617e104c81f483045"}, - {file = "fonttools-4.50.0-cp38-cp38-win_amd64.whl", hash = "sha256:f77e048f805e00870659d6318fd89ef28ca4ee16a22b4c5e1905b735495fc422"}, - {file = "fonttools-4.50.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b6245eafd553c4e9a0708e93be51392bd2288c773523892fbd616d33fd2fda59"}, - {file = "fonttools-4.50.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a4062cc7e8de26f1603323ef3ae2171c9d29c8a9f5e067d555a2813cd5c7a7e0"}, - {file = "fonttools-4.50.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34692850dfd64ba06af61e5791a441f664cb7d21e7b544e8f385718430e8f8e4"}, - {file = "fonttools-4.50.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678dd95f26a67e02c50dcb5bf250f95231d455642afbc65a3b0bcdacd4e4dd38"}, - {file = "fonttools-4.50.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4f2ce7b0b295fe64ac0a85aef46a0f2614995774bd7bc643b85679c0283287f9"}, - {file = "fonttools-4.50.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d346f4dc2221bfb7ab652d1e37d327578434ce559baf7113b0f55768437fe6a0"}, - {file = "fonttools-4.50.0-cp39-cp39-win32.whl", hash = "sha256:a51eeaf52ba3afd70bf489be20e52fdfafe6c03d652b02477c6ce23c995222f4"}, - {file = "fonttools-4.50.0-cp39-cp39-win_amd64.whl", hash = "sha256:8639be40d583e5d9da67795aa3eeeda0488fb577a1d42ae11a5036f18fb16d93"}, - {file = "fonttools-4.50.0-py3-none-any.whl", hash = "sha256:48fa36da06247aa8282766cfd63efff1bb24e55f020f29a335939ed3844d20d3"}, - {file = "fonttools-4.50.0.tar.gz", hash = "sha256:fa5cf61058c7dbb104c2ac4e782bf1b2016a8cf2f69de6e4dd6a865d2c969bb5"}, + {file = "fonttools-4.54.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ed7ee041ff7b34cc62f07545e55e1468808691dddfd315d51dd82a6b37ddef2"}, + {file = "fonttools-4.54.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41bb0b250c8132b2fcac148e2e9198e62ff06f3cc472065dff839327945c5882"}, + {file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7965af9b67dd546e52afcf2e38641b5be956d68c425bef2158e95af11d229f10"}, + {file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278913a168f90d53378c20c23b80f4e599dca62fbffae4cc620c8eed476b723e"}, + {file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0e88e3018ac809b9662615072dcd6b84dca4c2d991c6d66e1970a112503bba7e"}, + {file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4aa4817f0031206e637d1e685251ac61be64d1adef111060df84fdcbc6ab6c44"}, + {file = "fonttools-4.54.1-cp310-cp310-win32.whl", hash = "sha256:7e3b7d44e18c085fd8c16dcc6f1ad6c61b71ff463636fcb13df7b1b818bd0c02"}, + {file = "fonttools-4.54.1-cp310-cp310-win_amd64.whl", hash = "sha256:dd9cc95b8d6e27d01e1e1f1fae8559ef3c02c76317da650a19047f249acd519d"}, + {file = "fonttools-4.54.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5419771b64248484299fa77689d4f3aeed643ea6630b2ea750eeab219588ba20"}, + {file = "fonttools-4.54.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:301540e89cf4ce89d462eb23a89464fef50915255ece765d10eee8b2bf9d75b2"}, + {file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ae5091547e74e7efecc3cbf8e75200bc92daaeb88e5433c5e3e95ea8ce5aa7"}, + {file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82834962b3d7c5ca98cb56001c33cf20eb110ecf442725dc5fdf36d16ed1ab07"}, + {file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d26732ae002cc3d2ecab04897bb02ae3f11f06dd7575d1df46acd2f7c012a8d8"}, + {file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58974b4987b2a71ee08ade1e7f47f410c367cdfc5a94fabd599c88165f56213a"}, + {file = "fonttools-4.54.1-cp311-cp311-win32.whl", hash = "sha256:ab774fa225238986218a463f3fe151e04d8c25d7de09df7f0f5fce27b1243dbc"}, + {file = "fonttools-4.54.1-cp311-cp311-win_amd64.whl", hash = "sha256:07e005dc454eee1cc60105d6a29593459a06321c21897f769a281ff2d08939f6"}, + {file = "fonttools-4.54.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:54471032f7cb5fca694b5f1a0aaeba4af6e10ae989df408e0216f7fd6cdc405d"}, + {file = "fonttools-4.54.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fa92cb248e573daab8d032919623cc309c005086d743afb014c836636166f08"}, + {file = "fonttools-4.54.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a911591200114969befa7f2cb74ac148bce5a91df5645443371aba6d222e263"}, + {file = "fonttools-4.54.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93d458c8a6a354dc8b48fc78d66d2a8a90b941f7fec30e94c7ad9982b1fa6bab"}, + {file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5eb2474a7c5be8a5331146758debb2669bf5635c021aee00fd7c353558fc659d"}, + {file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9c563351ddc230725c4bdf7d9e1e92cbe6ae8553942bd1fb2b2ff0884e8b714"}, + {file = "fonttools-4.54.1-cp312-cp312-win32.whl", hash = "sha256:fdb062893fd6d47b527d39346e0c5578b7957dcea6d6a3b6794569370013d9ac"}, + {file = "fonttools-4.54.1-cp312-cp312-win_amd64.whl", hash = "sha256:e4564cf40cebcb53f3dc825e85910bf54835e8a8b6880d59e5159f0f325e637e"}, + {file = "fonttools-4.54.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6e37561751b017cf5c40fce0d90fd9e8274716de327ec4ffb0df957160be3bff"}, + {file = "fonttools-4.54.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:357cacb988a18aace66e5e55fe1247f2ee706e01debc4b1a20d77400354cddeb"}, + {file = "fonttools-4.54.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e953cc0bddc2beaf3a3c3b5dd9ab7554677da72dfaf46951e193c9653e515a"}, + {file = "fonttools-4.54.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58d29b9a294573d8319f16f2f79e42428ba9b6480442fa1836e4eb89c4d9d61c"}, + {file = "fonttools-4.54.1-cp313-cp313-win32.whl", hash = "sha256:9ef1b167e22709b46bf8168368b7b5d3efeaaa746c6d39661c1b4405b6352e58"}, + {file = "fonttools-4.54.1-cp313-cp313-win_amd64.whl", hash = "sha256:262705b1663f18c04250bd1242b0515d3bbae177bee7752be67c979b7d47f43d"}, + {file = "fonttools-4.54.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ed2f80ca07025551636c555dec2b755dd005e2ea8fbeb99fc5cdff319b70b23b"}, + {file = "fonttools-4.54.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9dc080e5a1c3b2656caff2ac2633d009b3a9ff7b5e93d0452f40cd76d3da3b3c"}, + {file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d152d1be65652fc65e695e5619e0aa0982295a95a9b29b52b85775243c06556"}, + {file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8583e563df41fdecef31b793b4dd3af8a9caa03397be648945ad32717a92885b"}, + {file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0d1d353ef198c422515a3e974a1e8d5b304cd54a4c2eebcae708e37cd9eeffb1"}, + {file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fda582236fee135d4daeca056c8c88ec5f6f6d88a004a79b84a02547c8f57386"}, + {file = "fonttools-4.54.1-cp38-cp38-win32.whl", hash = "sha256:e7d82b9e56716ed32574ee106cabca80992e6bbdcf25a88d97d21f73a0aae664"}, + {file = "fonttools-4.54.1-cp38-cp38-win_amd64.whl", hash = "sha256:ada215fd079e23e060157aab12eba0d66704316547f334eee9ff26f8c0d7b8ab"}, + {file = "fonttools-4.54.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5b8a096e649768c2f4233f947cf9737f8dbf8728b90e2771e2497c6e3d21d13"}, + {file = "fonttools-4.54.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e10d2e0a12e18f4e2dd031e1bf7c3d7017be5c8dbe524d07706179f355c5dac"}, + {file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31c32d7d4b0958600eac75eaf524b7b7cb68d3a8c196635252b7a2c30d80e986"}, + {file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c39287f5c8f4a0c5a55daf9eaf9ccd223ea59eed3f6d467133cc727d7b943a55"}, + {file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a7a310c6e0471602fe3bf8efaf193d396ea561486aeaa7adc1f132e02d30c4b9"}, + {file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d3b659d1029946f4ff9b6183984578041b520ce0f8fb7078bb37ec7445806b33"}, + {file = "fonttools-4.54.1-cp39-cp39-win32.whl", hash = "sha256:e96bc94c8cda58f577277d4a71f51c8e2129b8b36fd05adece6320dd3d57de8a"}, + {file = "fonttools-4.54.1-cp39-cp39-win_amd64.whl", hash = "sha256:e8a4b261c1ef91e7188a30571be6ad98d1c6d9fa2427244c545e2fa0a2494dd7"}, + {file = "fonttools-4.54.1-py3-none-any.whl", hash = "sha256:37cddd62d83dc4f72f7c3f3c2bcf2697e89a30efb152079896544a93907733bd"}, + {file = "fonttools-4.54.1.tar.gz", hash = "sha256:957f669d4922f92c171ba01bef7f29410668db09f6c02111e22b2bce446f3285"}, ] [package.extras] @@ -229,77 +261,86 @@ woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] [[package]] name = "h5py" -version = "3.10.0" +version = "3.12.1" description = "Read and write HDF5 files from Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "h5py-3.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b963fb772964fc1d1563c57e4e2e874022ce11f75ddc6df1a626f42bd49ab99f"}, - {file = "h5py-3.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:012ab448590e3c4f5a8dd0f3533255bc57f80629bf7c5054cf4c87b30085063c"}, - {file = "h5py-3.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:781a24263c1270a62cd67be59f293e62b76acfcc207afa6384961762bb88ea03"}, - {file = "h5py-3.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f42e6c30698b520f0295d70157c4e202a9e402406f50dc08f5a7bc416b24e52d"}, - {file = "h5py-3.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:93dd840bd675787fc0b016f7a05fc6efe37312a08849d9dd4053fd0377b1357f"}, - {file = "h5py-3.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2381e98af081b6df7f6db300cd88f88e740649d77736e4b53db522d8874bf2dc"}, - {file = "h5py-3.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:667fe23ab33d5a8a6b77970b229e14ae3bb84e4ea3382cc08567a02e1499eedd"}, - {file = "h5py-3.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90286b79abd085e4e65e07c1bd7ee65a0f15818ea107f44b175d2dfe1a4674b7"}, - {file = "h5py-3.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c013d2e79c00f28ffd0cc24e68665ea03ae9069e167087b2adb5727d2736a52"}, - {file = "h5py-3.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:92273ce69ae4983dadb898fd4d3bea5eb90820df953b401282ee69ad648df684"}, - {file = "h5py-3.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c97d03f87f215e7759a354460fb4b0d0f27001450b18b23e556e7856a0b21c3"}, - {file = "h5py-3.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86df4c2de68257b8539a18646ceccdcf2c1ce6b1768ada16c8dcfb489eafae20"}, - {file = "h5py-3.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba9ab36be991119a3ff32d0c7cbe5faf9b8d2375b5278b2aea64effbeba66039"}, - {file = "h5py-3.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:2c8e4fda19eb769e9a678592e67eaec3a2f069f7570c82d2da909c077aa94339"}, - {file = "h5py-3.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:492305a074327e8d2513011fa9fffeb54ecb28a04ca4c4227d7e1e9616d35641"}, - {file = "h5py-3.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9450464b458cca2c86252b624279115dcaa7260a40d3cb1594bf2b410a2bd1a3"}, - {file = "h5py-3.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd6f6d1384a9f491732cee233b99cd4bfd6e838a8815cc86722f9d2ee64032af"}, - {file = "h5py-3.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3074ec45d3dc6e178c6f96834cf8108bf4a60ccb5ab044e16909580352010a97"}, - {file = "h5py-3.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:212bb997a91e6a895ce5e2f365ba764debeaef5d2dca5c6fb7098d66607adf99"}, - {file = "h5py-3.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5dfc65ac21fa2f630323c92453cadbe8d4f504726ec42f6a56cf80c2f90d6c52"}, - {file = "h5py-3.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d4682b94fd36ab217352be438abd44c8f357c5449b8995e63886b431d260f3d3"}, - {file = "h5py-3.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aece0e2e1ed2aab076c41802e50a0c3e5ef8816d60ece39107d68717d4559824"}, - {file = "h5py-3.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43a61b2c2ad65b1fabc28802d133eed34debcc2c8b420cb213d3d4ef4d3e2229"}, - {file = "h5py-3.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:ae2f0201c950059676455daf92700eeb57dcf5caaf71b9e1328e6e6593601770"}, - {file = "h5py-3.10.0.tar.gz", hash = "sha256:d93adc48ceeb33347eb24a634fb787efc7ae4644e6ea4ba733d099605045c049"}, + {file = "h5py-3.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f0f1a382cbf494679c07b4371f90c70391dedb027d517ac94fa2c05299dacda"}, + {file = "h5py-3.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cb65f619dfbdd15e662423e8d257780f9a66677eae5b4b3fc9dca70b5fd2d2a3"}, + {file = "h5py-3.12.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b15d8dbd912c97541312c0e07438864d27dbca857c5ad634de68110c6beb1c2"}, + {file = "h5py-3.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59685fe40d8c1fbbee088c88cd4da415a2f8bee5c270337dc5a1c4aa634e3307"}, + {file = "h5py-3.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:577d618d6b6dea3da07d13cc903ef9634cde5596b13e832476dd861aaf651f3e"}, + {file = "h5py-3.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ccd9006d92232727d23f784795191bfd02294a4f2ba68708825cb1da39511a93"}, + {file = "h5py-3.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad8a76557880aed5234cfe7279805f4ab5ce16b17954606cca90d578d3e713ef"}, + {file = "h5py-3.12.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1473348139b885393125126258ae2d70753ef7e9cec8e7848434f385ae72069e"}, + {file = "h5py-3.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:018a4597f35092ae3fb28ee851fdc756d2b88c96336b8480e124ce1ac6fb9166"}, + {file = "h5py-3.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:3fdf95092d60e8130ba6ae0ef7a9bd4ade8edbe3569c13ebbaf39baefffc5ba4"}, + {file = "h5py-3.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:06a903a4e4e9e3ebbc8b548959c3c2552ca2d70dac14fcfa650d9261c66939ed"}, + {file = "h5py-3.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b3b8f3b48717e46c6a790e3128d39c61ab595ae0a7237f06dfad6a3b51d5351"}, + {file = "h5py-3.12.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:050a4f2c9126054515169c49cb900949814987f0c7ae74c341b0c9f9b5056834"}, + {file = "h5py-3.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c4b41d1019322a5afc5082864dfd6359f8935ecd37c11ac0029be78c5d112c9"}, + {file = "h5py-3.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:e4d51919110a030913201422fb07987db4338eba5ec8c5a15d6fab8e03d443fc"}, + {file = "h5py-3.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:513171e90ed92236fc2ca363ce7a2fc6f2827375efcbb0cc7fbdd7fe11fecafc"}, + {file = "h5py-3.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:59400f88343b79655a242068a9c900001a34b63e3afb040bd7cdf717e440f653"}, + {file = "h5py-3.12.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3e465aee0ec353949f0f46bf6c6f9790a2006af896cee7c178a8c3e5090aa32"}, + {file = "h5py-3.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba51c0c5e029bb5420a343586ff79d56e7455d496d18a30309616fdbeed1068f"}, + {file = "h5py-3.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:52ab036c6c97055b85b2a242cb540ff9590bacfda0c03dd0cf0661b311f522f8"}, + {file = "h5py-3.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d2b8dd64f127d8b324f5d2cd1c0fd6f68af69084e9e47d27efeb9e28e685af3e"}, + {file = "h5py-3.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4532c7e97fbef3d029735db8b6f5bf01222d9ece41e309b20d63cfaae2fb5c4d"}, + {file = "h5py-3.12.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fdf6d7936fa824acfa27305fe2d9f39968e539d831c5bae0e0d83ed521ad1ac"}, + {file = "h5py-3.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84342bffd1f82d4f036433e7039e241a243531a1d3acd7341b35ae58cdab05bf"}, + {file = "h5py-3.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:62be1fc0ef195891949b2c627ec06bc8e837ff62d5b911b6e42e38e0f20a897d"}, + {file = "h5py-3.12.1.tar.gz", hash = "sha256:326d70b53d31baa61f00b8aa5f95c2fcb9621a3ee8365d770c551a13dbbcbfdf"}, ] [package.dependencies] -numpy = ">=1.17.3" +numpy = ">=1.19.3" [[package]] name = "importlib-metadata" -version = "7.1.0" +version = "8.5.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, - {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, + {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, + {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, ] [package.dependencies] -zipp = ">=0.5" +zipp = ">=3.20" [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] [[package]] name = "importlib-resources" -version = "6.4.0" +version = "6.4.5" description = "Read resources from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_resources-6.4.0-py3-none-any.whl", hash = "sha256:50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c"}, - {file = "importlib_resources-6.4.0.tar.gz", hash = "sha256:cdb2b453b8046ca4e3798eb1d84f3cce1446a0e8e7b5ef4efb600f19fc398145"}, + {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, + {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, ] [package.dependencies] zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] +type = ["pytest-mypy"] [[package]] name = "iniconfig" @@ -328,152 +369,174 @@ colors = ["colorama (>=0.4.6)"] [[package]] name = "kiwisolver" -version = "1.4.5" +version = "1.4.7" description = "A fast implementation of the Cassowary constraint solver" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"}, - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"}, - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"}, - {file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"}, - {file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"}, - {file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"}, - {file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"}, - {file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"}, - {file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"}, - {file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"}, - {file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"}, - {file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"}, - {file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"}, - {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"}, + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6"}, + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17"}, + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5d5abf8f8ec1f4e22882273c423e16cae834c36856cac348cfbfa68e01c40f3a"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aeb3531b196ef6f11776c21674dba836aeea9d5bd1cf630f869e3d90b16cfade"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7d755065e4e866a8086c9bdada157133ff466476a2ad7861828e17b6026e22c"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08471d4d86cbaec61f86b217dd938a83d85e03785f51121e791a6e6689a3be95"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bbfcb7165ce3d54a3dfbe731e470f65739c4c1f85bb1018ee912bae139e263b"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d34eb8494bea691a1a450141ebb5385e4b69d38bb8403b5146ad279f4b30fa3"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9242795d174daa40105c1d86aba618e8eab7bf96ba8c3ee614da8302a9f95503"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a0f64a48bb81af7450e641e3fe0b0394d7381e342805479178b3d335d60ca7cf"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8e045731a5416357638d1700927529e2b8ab304811671f665b225f8bf8d8f933"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4322872d5772cae7369f8351da1edf255a604ea7087fe295411397d0cfd9655e"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e1631290ee9271dffe3062d2634c3ecac02c83890ada077d225e081aca8aab89"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:edcfc407e4eb17e037bca59be0e85a2031a2ac87e4fed26d3e9df88b4165f92d"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4d05d81ecb47d11e7f8932bd8b61b720bf0b41199358f3f5e36d38e28f0532c5"}, + {file = "kiwisolver-1.4.7-cp38-cp38-win32.whl", hash = "sha256:b38ac83d5f04b15e515fd86f312479d950d05ce2368d5413d46c088dda7de90a"}, + {file = "kiwisolver-1.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:d83db7cde68459fc803052a55ace60bea2bae361fc3b7a6d5da07e11954e4b09"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9362ecfca44c863569d3d3c033dbe8ba452ff8eed6f6b5806382741a1334bd"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8df2eb9b2bac43ef8b082e06f750350fbbaf2887534a5be97f6cf07b19d9583"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f32d6edbc638cde7652bd690c3e728b25332acbadd7cad670cc4a02558d9c417"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2e6c39bd7b9372b0be21456caab138e8e69cc0fc1190a9dfa92bd45a1e6e904"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dda56c24d869b1193fcc763f1284b9126550eaf84b88bbc7256e15028f19188a"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79849239c39b5e1fd906556c474d9b0439ea6792b637511f3fe3a41158d89ca8"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e3bc157fed2a4c02ec468de4ecd12a6e22818d4f09cde2c31ee3226ffbefab2"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3da53da805b71e41053dc670f9a820d1157aae77b6b944e08024d17bcd51ef88"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8705f17dfeb43139a692298cb6637ee2e59c0194538153e83e9ee0c75c2eddde"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82a5c2f4b87c26bb1a0ef3d16b5c4753434633b83d365cc0ddf2770c93829e3c"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce8be0466f4c0d585cdb6c1e2ed07232221df101a4c6f28821d2aa754ca2d9e2"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:409afdfe1e2e90e6ee7fc896f3df9a7fec8e793e58bfa0d052c8a82f99c37abb"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b9c3f4ee0b9a439d2415012bd1b1cc2df59e4d6a9939f4d669241d30b414327"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bfa1acfa0c54932d5607e19a2c24646fb4c1ae2694437789129cf099789a3b00"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:eee3ea935c3d227d49b4eb85660ff631556841f6e567f0f7bda972df6c2c9935"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f3160309af4396e0ed04db259c3ccbfdc3621b5559b5453075e5de555e1f3a1b"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a17f6a29cf8935e587cc8a4dbfc8368c55edc645283db0ce9801016f83526c2d"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10849fb2c1ecbfae45a693c070e0320a91b35dd4bcf58172c023b994283a124d"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ac542bf38a8a4be2dc6b15248d36315ccc65f0743f7b1a76688ffb6b5129a5c2"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b01aac285f91ca889c800042c35ad3b239e704b150cfd3382adfc9dcc780e39"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48be928f59a1f5c8207154f935334d374e79f2b5d212826307d072595ad76a2e"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f37cfe618a117e50d8c240555331160d73d0411422b59b5ee217843d7b693608"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599b5c873c63a1f6ed7eead644a8a380cfbdf5db91dcb6f85707aaab213b1674"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801fa7802e5cfabe3ab0c81a34c323a319b097dfb5004be950482d882f3d7225"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0"}, + {file = "kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60"}, ] [[package]] name = "matplotlib" -version = "3.8.3" +version = "3.9.2" description = "Python plotting package" optional = false python-versions = ">=3.9" files = [ - {file = "matplotlib-3.8.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cf60138ccc8004f117ab2a2bad513cc4d122e55864b4fe7adf4db20ca68a078f"}, - {file = "matplotlib-3.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f557156f7116be3340cdeef7f128fa99b0d5d287d5f41a16e169819dcf22357"}, - {file = "matplotlib-3.8.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f386cf162b059809ecfac3bcc491a9ea17da69fa35c8ded8ad154cd4b933d5ec"}, - {file = "matplotlib-3.8.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3c5f96f57b0369c288bf6f9b5274ba45787f7e0589a34d24bdbaf6d3344632f"}, - {file = "matplotlib-3.8.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:83e0f72e2c116ca7e571c57aa29b0fe697d4c6425c4e87c6e994159e0c008635"}, - {file = "matplotlib-3.8.3-cp310-cp310-win_amd64.whl", hash = "sha256:1c5c8290074ba31a41db1dc332dc2b62def469ff33766cbe325d32a3ee291aea"}, - {file = "matplotlib-3.8.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5184e07c7e1d6d1481862ee361905b7059f7fe065fc837f7c3dc11eeb3f2f900"}, - {file = "matplotlib-3.8.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d7e7e0993d0758933b1a241a432b42c2db22dfa37d4108342ab4afb9557cbe3e"}, - {file = "matplotlib-3.8.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04b36ad07eac9740fc76c2aa16edf94e50b297d6eb4c081e3add863de4bb19a7"}, - {file = "matplotlib-3.8.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c42dae72a62f14982f1474f7e5c9959fc4bc70c9de11cc5244c6e766200ba65"}, - {file = "matplotlib-3.8.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf5932eee0d428192c40b7eac1399d608f5d995f975cdb9d1e6b48539a5ad8d0"}, - {file = "matplotlib-3.8.3-cp311-cp311-win_amd64.whl", hash = "sha256:40321634e3a05ed02abf7c7b47a50be50b53ef3eaa3a573847431a545585b407"}, - {file = "matplotlib-3.8.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:09074f8057917d17ab52c242fdf4916f30e99959c1908958b1fc6032e2d0f6d4"}, - {file = "matplotlib-3.8.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5745f6d0fb5acfabbb2790318db03809a253096e98c91b9a31969df28ee604aa"}, - {file = "matplotlib-3.8.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97653d869a71721b639714b42d87cda4cfee0ee74b47c569e4874c7590c55c5"}, - {file = "matplotlib-3.8.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:242489efdb75b690c9c2e70bb5c6550727058c8a614e4c7716f363c27e10bba1"}, - {file = "matplotlib-3.8.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:83c0653c64b73926730bd9ea14aa0f50f202ba187c307a881673bad4985967b7"}, - {file = "matplotlib-3.8.3-cp312-cp312-win_amd64.whl", hash = "sha256:ef6c1025a570354297d6c15f7d0f296d95f88bd3850066b7f1e7b4f2f4c13a39"}, - {file = "matplotlib-3.8.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c4af3f7317f8a1009bbb2d0bf23dfaba859eb7dd4ccbd604eba146dccaaaf0a4"}, - {file = "matplotlib-3.8.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4c6e00a65d017d26009bac6808f637b75ceade3e1ff91a138576f6b3065eeeba"}, - {file = "matplotlib-3.8.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7b49ab49a3bea17802df6872f8d44f664ba8f9be0632a60c99b20b6db2165b7"}, - {file = "matplotlib-3.8.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6728dde0a3997396b053602dbd907a9bd64ec7d5cf99e728b404083698d3ca01"}, - {file = "matplotlib-3.8.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:813925d08fb86aba139f2d31864928d67511f64e5945ca909ad5bc09a96189bb"}, - {file = "matplotlib-3.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:cd3a0c2be76f4e7be03d34a14d49ded6acf22ef61f88da600a18a5cd8b3c5f3c"}, - {file = "matplotlib-3.8.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:fa93695d5c08544f4a0dfd0965f378e7afc410d8672816aff1e81be1f45dbf2e"}, - {file = "matplotlib-3.8.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9764df0e8778f06414b9d281a75235c1e85071f64bb5d71564b97c1306a2afc"}, - {file = "matplotlib-3.8.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5e431a09e6fab4012b01fc155db0ce6dccacdbabe8198197f523a4ef4805eb26"}, - {file = "matplotlib-3.8.3.tar.gz", hash = "sha256:7b416239e9ae38be54b028abbf9048aff5054a9aba5416bef0bd17f9162ce161"}, + {file = "matplotlib-3.9.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9d78bbc0cbc891ad55b4f39a48c22182e9bdaea7fc0e5dbd364f49f729ca1bbb"}, + {file = "matplotlib-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c375cc72229614632c87355366bdf2570c2dac01ac66b8ad048d2dabadf2d0d4"}, + {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d94ff717eb2bd0b58fe66380bd8b14ac35f48a98e7c6765117fe67fb7684e64"}, + {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab68d50c06938ef28681073327795c5db99bb4666214d2d5f880ed11aeaded66"}, + {file = "matplotlib-3.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:65aacf95b62272d568044531e41de26285d54aec8cb859031f511f84bd8b495a"}, + {file = "matplotlib-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:3fd595f34aa8a55b7fc8bf9ebea8aa665a84c82d275190a61118d33fbc82ccae"}, + {file = "matplotlib-3.9.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8dd059447824eec055e829258ab092b56bb0579fc3164fa09c64f3acd478772"}, + {file = "matplotlib-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c797dac8bb9c7a3fd3382b16fe8f215b4cf0f22adccea36f1545a6d7be310b41"}, + {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d719465db13267bcef19ea8954a971db03b9f48b4647e3860e4bc8e6ed86610f"}, + {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8912ef7c2362f7193b5819d17dae8629b34a95c58603d781329712ada83f9447"}, + {file = "matplotlib-3.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7741f26a58a240f43bee74965c4882b6c93df3e7eb3de160126d8c8f53a6ae6e"}, + {file = "matplotlib-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:ae82a14dab96fbfad7965403c643cafe6515e386de723e498cf3eeb1e0b70cc7"}, + {file = "matplotlib-3.9.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ac43031375a65c3196bee99f6001e7fa5bdfb00ddf43379d3c0609bdca042df9"}, + {file = "matplotlib-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be0fc24a5e4531ae4d8e858a1a548c1fe33b176bb13eff7f9d0d38ce5112a27d"}, + {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf81de2926c2db243c9b2cbc3917619a0fc85796c6ba4e58f541df814bbf83c7"}, + {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ee45bc4245533111ced13f1f2cace1e7f89d1c793390392a80c139d6cf0e6c"}, + {file = "matplotlib-3.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:306c8dfc73239f0e72ac50e5a9cf19cc4e8e331dd0c54f5e69ca8758550f1e1e"}, + {file = "matplotlib-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:5413401594cfaff0052f9d8b1aafc6d305b4bd7c4331dccd18f561ff7e1d3bd3"}, + {file = "matplotlib-3.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:18128cc08f0d3cfff10b76baa2f296fc28c4607368a8402de61bb3f2eb33c7d9"}, + {file = "matplotlib-3.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4876d7d40219e8ae8bb70f9263bcbe5714415acfdf781086601211335e24f8aa"}, + {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d9f07a80deab4bb0b82858a9e9ad53d1382fd122be8cde11080f4e7dfedb38b"}, + {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7c0410f181a531ec4e93bbc27692f2c71a15c2da16766f5ba9761e7ae518413"}, + {file = "matplotlib-3.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:909645cce2dc28b735674ce0931a4ac94e12f5b13f6bb0b5a5e65e7cea2c192b"}, + {file = "matplotlib-3.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:f32c7410c7f246838a77d6d1eff0c0f87f3cb0e7c4247aebea71a6d5a68cab49"}, + {file = "matplotlib-3.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:37e51dd1c2db16ede9cfd7b5cabdfc818b2c6397c83f8b10e0e797501c963a03"}, + {file = "matplotlib-3.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b82c5045cebcecd8496a4d694d43f9cc84aeeb49fe2133e036b207abe73f4d30"}, + {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f053c40f94bc51bc03832a41b4f153d83f2062d88c72b5e79997072594e97e51"}, + {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbe196377a8248972f5cede786d4c5508ed5f5ca4a1e09b44bda889958b33f8c"}, + {file = "matplotlib-3.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5816b1e1fe8c192cbc013f8f3e3368ac56fbecf02fb41b8f8559303f24c5015e"}, + {file = "matplotlib-3.9.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:cef2a73d06601437be399908cf13aee74e86932a5ccc6ccdf173408ebc5f6bb2"}, + {file = "matplotlib-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0830e188029c14e891fadd99702fd90d317df294c3298aad682739c5533721a"}, + {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ba9c1299c920964e8d3857ba27173b4dbb51ca4bab47ffc2c2ba0eb5e2cbc5"}, + {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cd93b91ab47a3616b4d3c42b52f8363b88ca021e340804c6ab2536344fad9ca"}, + {file = "matplotlib-3.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6d1ce5ed2aefcdce11904fc5bbea7d9c21fff3d5f543841edf3dea84451a09ea"}, + {file = "matplotlib-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:b2696efdc08648536efd4e1601b5fd491fd47f4db97a5fbfd175549a7365c1b2"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d52a3b618cb1cbb769ce2ee1dcdb333c3ab6e823944e9a2d36e37253815f9556"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:039082812cacd6c6bec8e17a9c1e6baca230d4116d522e81e1f63a74d01d2e21"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6758baae2ed64f2331d4fd19be38b7b4eae3ecec210049a26b6a4f3ae1c85dcc"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:050598c2b29e0b9832cde72bcf97627bf00262adbc4a54e2b856426bb2ef0697"}, + {file = "matplotlib-3.9.2.tar.gz", hash = "sha256:96ab43906269ca64a6366934106fa01534454a69e471b7bf3d79083981aaab92"}, ] [package.dependencies] @@ -482,38 +545,41 @@ cycler = ">=0.10" fonttools = ">=4.22.0" importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} kiwisolver = ">=1.3.1" -numpy = ">=1.21,<2" +numpy = ">=1.23" packaging = ">=20.0" pillow = ">=8" pyparsing = ">=2.3.1" python-dateutil = ">=2.7" +[package.extras] +dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6)", "setuptools (>=64)", "setuptools_scm (>=7)"] + [[package]] name = "mpi4py" -version = "3.1.5" +version = "3.1.6" description = "Python bindings for MPI" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "mpi4py-3.1.5-cp27-cp27m-win32.whl", hash = "sha256:dbc47296a560d2a5aa50d716cb9bf90552349687ee068a493d646e3f6c20a9a4"}, - {file = "mpi4py-3.1.5-cp27-cp27m-win_amd64.whl", hash = "sha256:b3e06c9ef1d0a2da3d5c7431bbf4af50a01ac9915fb4ab33409a21cd9e67be9c"}, - {file = "mpi4py-3.1.5-cp310-cp310-win32.whl", hash = "sha256:f39df0d985cb6fb342ee6c6902cadf21b2d828d7df00b182573da0242646b715"}, - {file = "mpi4py-3.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:aec0e6238ed76c930c07df7dcea19f3be5ca958fb76353e668b19511ed4c86d7"}, - {file = "mpi4py-3.1.5-cp311-cp311-win32.whl", hash = "sha256:f73686e3ff8f76bacb9ecacba0515f84392ad4c561b76603f9680f0fe64ef0ed"}, - {file = "mpi4py-3.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:d854dae2e62042a0355fa24ef7bea50b5380414806319240a57e654be1e59d9c"}, - {file = "mpi4py-3.1.5-cp312-cp312-win32.whl", hash = "sha256:17cc793bf2fe3921f2c3cda59a2a708d2e0c68ce07c8b9d2b6ee1a9adc28fe3d"}, - {file = "mpi4py-3.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:8b38ad45a843bcbd7d11e23fd901016bb8069f35a9d4500666090465a2f734f2"}, - {file = "mpi4py-3.1.5-cp35-cp35m-win32.whl", hash = "sha256:61d6c5df1803002cf6c61523417d48f9ecf64b55808e3d9d47815c174d7125dd"}, - {file = "mpi4py-3.1.5-cp35-cp35m-win_amd64.whl", hash = "sha256:de6291eb7587e09637bc56f0e00d94863a1253f9d06b2ee97937bb9d49b53615"}, - {file = "mpi4py-3.1.5-cp36-cp36m-win32.whl", hash = "sha256:dd7ebe2d9c52330670e2424ba3a535df999e57bdaf3a93a8967fede1d2d5927d"}, - {file = "mpi4py-3.1.5-cp36-cp36m-win_amd64.whl", hash = "sha256:0de523428e15b453539da14208489a19f9fc7570cc8f9d1a3365175e441bba8e"}, - {file = "mpi4py-3.1.5-cp37-cp37m-win32.whl", hash = "sha256:9cc87b70ce7164fbc521c45bbc4f5fb9acce8ea70d3503da0768ef67ba52186a"}, - {file = "mpi4py-3.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:8574fe09d34d89531be6e640d18b54f7b7a046514de069f18c181bafeff51108"}, - {file = "mpi4py-3.1.5-cp38-cp38-win32.whl", hash = "sha256:265f4c0a9cfdd606701fa36e0b373afae5930bedbf03c8360fd62f8c38639bf6"}, - {file = "mpi4py-3.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:edf2224fa9c4416f891ac87f8e5b8a754ab45bc31dcfc4fbc8e29d6c643084c9"}, - {file = "mpi4py-3.1.5-cp39-cp39-win32.whl", hash = "sha256:c4e6e776e183dbf3aa0945679303120716bb3f0826faeeb740dc5a055fcff3a8"}, - {file = "mpi4py-3.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:3a2853b7b41899a17c3f1e6ad27e48a30e98d49d12830b345672fef3a6dcc8d6"}, - {file = "mpi4py-3.1.5.tar.gz", hash = "sha256:a706e76db9255135c2fb5d1ef54cb4f7b0e4ad9e33cbada7de27626205f2a153"}, +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "mpi4py-3.1.6-cp27-cp27m-win32.whl", hash = "sha256:95f27e00f3951f9c1533cd99ffeae2f384f7ba53cc3870ee06c3c88f9e5bd6c3"}, + {file = "mpi4py-3.1.6-cp27-cp27m-win_amd64.whl", hash = "sha256:ea8a65f74707e1be5a0125ae3f4f6c0c475c3d845d623b9f5686a919c1119439"}, + {file = "mpi4py-3.1.6-cp310-cp310-win32.whl", hash = "sha256:57cb8cc5e4f5730ca2116b4918fddb158bdac3852b57b0fb799278bcd2f9df03"}, + {file = "mpi4py-3.1.6-cp310-cp310-win_amd64.whl", hash = "sha256:242459fcfd18c50b2ad6cd15f433efbaad1f12f4a343bdff5fa1720f4797317d"}, + {file = "mpi4py-3.1.6-cp311-cp311-win32.whl", hash = "sha256:33d16f85d827417fd451ae61c304a26f5a1c3e881d9555c58fc8d5c7ac171034"}, + {file = "mpi4py-3.1.6-cp311-cp311-win_amd64.whl", hash = "sha256:542fa0b3caa69fbb8f5e8ea60fea3414eef1444d93af28b4ee6486a3f8f27640"}, + {file = "mpi4py-3.1.6-cp312-cp312-win32.whl", hash = "sha256:6662ffa622ee21041dcfd95bffd4b81906349e22d993239cc0abd17ebca6bed0"}, + {file = "mpi4py-3.1.6-cp312-cp312-win_amd64.whl", hash = "sha256:606264e22c315de6250745fa84267c18a7370a16bd6c51acf226cbb977d0a10b"}, + {file = "mpi4py-3.1.6-cp35-cp35m-win32.whl", hash = "sha256:1de7f6bd22ea6c9b1d6cb42e9d8092217552ffc8267f81df884b61f46aef557c"}, + {file = "mpi4py-3.1.6-cp35-cp35m-win_amd64.whl", hash = "sha256:8dfadb2d7b50bda0f5c8538f8b5af5b2f830babc7c43a267102407a29616c38f"}, + {file = "mpi4py-3.1.6-cp36-cp36m-win32.whl", hash = "sha256:757e324084c41f84b257b6d03850e73803bafea1b5e94dd6f9ebb3a149764299"}, + {file = "mpi4py-3.1.6-cp36-cp36m-win_amd64.whl", hash = "sha256:76ecc87605193ef906597cadef571cabc8731ed1848a8c92320385c10ed41168"}, + {file = "mpi4py-3.1.6-cp37-cp37m-win32.whl", hash = "sha256:67d28b5e5102a9d44eac6bc2765c2e28966b6b79cd54e0ea432403e054c70946"}, + {file = "mpi4py-3.1.6-cp37-cp37m-win_amd64.whl", hash = "sha256:c0dfd9dbbcfa2fe61031eaba714f1f814e59439188b0a5ac063b2edc42daa234"}, + {file = "mpi4py-3.1.6-cp38-cp38-win32.whl", hash = "sha256:8f9810decd319110b7dcf7a210a76d7db6a39d4c2b33c750ac4dc4638d7ce012"}, + {file = "mpi4py-3.1.6-cp38-cp38-win_amd64.whl", hash = "sha256:ac671aa8c512fff432e0c1670c94eabd5571f5085c61579fee534b5b9e41bcf4"}, + {file = "mpi4py-3.1.6-cp39-cp39-win32.whl", hash = "sha256:e8bd3fd0056580b1aaa4966ab9e54566bf6d6b35ff94c0ee7ceba83d55d039ac"}, + {file = "mpi4py-3.1.6-cp39-cp39-win_amd64.whl", hash = "sha256:f9a35e23deadf7de9063523f19863957f379d0f13afc2b9787eafc9d570ab868"}, + {file = "mpi4py-3.1.6.tar.gz", hash = "sha256:c8fa625e0f92b082ef955bfb52f19fa6691d29273d7d71135d295aa143dee6cb"}, ] [[package]] @@ -580,58 +646,71 @@ files = [ [[package]] name = "packaging" -version = "24.0" +version = "24.2" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] [[package]] name = "pandas" -version = "2.2.1" +version = "2.2.3" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" files = [ - {file = "pandas-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8df8612be9cd1c7797c93e1c5df861b2ddda0b48b08f2c3eaa0702cf88fb5f88"}, - {file = "pandas-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0f573ab277252ed9aaf38240f3b54cfc90fff8e5cab70411ee1d03f5d51f3944"}, - {file = "pandas-2.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f02a3a6c83df4026e55b63c1f06476c9aa3ed6af3d89b4f04ea656ccdaaaa359"}, - {file = "pandas-2.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c38ce92cb22a4bea4e3929429aa1067a454dcc9c335799af93ba9be21b6beb51"}, - {file = "pandas-2.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c2ce852e1cf2509a69e98358e8458775f89599566ac3775e70419b98615f4b06"}, - {file = "pandas-2.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:53680dc9b2519cbf609c62db3ed7c0b499077c7fefda564e330286e619ff0dd9"}, - {file = "pandas-2.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:94e714a1cca63e4f5939cdce5f29ba8d415d85166be3441165edd427dc9f6bc0"}, - {file = "pandas-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f821213d48f4ab353d20ebc24e4faf94ba40d76680642fb7ce2ea31a3ad94f9b"}, - {file = "pandas-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c70e00c2d894cb230e5c15e4b1e1e6b2b478e09cf27cc593a11ef955b9ecc81a"}, - {file = "pandas-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e97fbb5387c69209f134893abc788a6486dbf2f9e511070ca05eed4b930b1b02"}, - {file = "pandas-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101d0eb9c5361aa0146f500773395a03839a5e6ecde4d4b6ced88b7e5a1a6403"}, - {file = "pandas-2.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7d2ed41c319c9fb4fd454fe25372028dfa417aacb9790f68171b2e3f06eae8cd"}, - {file = "pandas-2.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:af5d3c00557d657c8773ef9ee702c61dd13b9d7426794c9dfeb1dc4a0bf0ebc7"}, - {file = "pandas-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:06cf591dbaefb6da9de8472535b185cba556d0ce2e6ed28e21d919704fef1a9e"}, - {file = "pandas-2.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:88ecb5c01bb9ca927ebc4098136038519aa5d66b44671861ffab754cae75102c"}, - {file = "pandas-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:04f6ec3baec203c13e3f8b139fb0f9f86cd8c0b94603ae3ae8ce9a422e9f5bee"}, - {file = "pandas-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a935a90a76c44fe170d01e90a3594beef9e9a6220021acfb26053d01426f7dc2"}, - {file = "pandas-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c391f594aae2fd9f679d419e9a4d5ba4bce5bb13f6a989195656e7dc4b95c8f0"}, - {file = "pandas-2.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9d1265545f579edf3f8f0cb6f89f234f5e44ba725a34d86535b1a1d38decbccc"}, - {file = "pandas-2.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:11940e9e3056576ac3244baef2fedade891977bcc1cb7e5cc8f8cc7d603edc89"}, - {file = "pandas-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:4acf681325ee1c7f950d058b05a820441075b0dd9a2adf5c4835b9bc056bf4fb"}, - {file = "pandas-2.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9bd8a40f47080825af4317d0340c656744f2bfdb6819f818e6ba3cd24c0e1397"}, - {file = "pandas-2.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:df0c37ebd19e11d089ceba66eba59a168242fc6b7155cba4ffffa6eccdfb8f16"}, - {file = "pandas-2.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:739cc70eaf17d57608639e74d63387b0d8594ce02f69e7a0b046f117974b3019"}, - {file = "pandas-2.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9d3558d263073ed95e46f4650becff0c5e1ffe0fc3a015de3c79283dfbdb3df"}, - {file = "pandas-2.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4aa1d8707812a658debf03824016bf5ea0d516afdea29b7dc14cf687bc4d4ec6"}, - {file = "pandas-2.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:76f27a809cda87e07f192f001d11adc2b930e93a2b0c4a236fde5429527423be"}, - {file = "pandas-2.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:1ba21b1d5c0e43416218db63037dbe1a01fc101dc6e6024bcad08123e48004ab"}, - {file = "pandas-2.2.1.tar.gz", hash = "sha256:0ab90f87093c13f3e8fa45b48ba9f39181046e8f3317d3aadb2fffbb1b978572"}, + {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, + {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"}, + {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"}, + {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"}, + {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"}, + {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"}, + {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"}, + {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"}, ] [package.dependencies] numpy = [ - {version = ">=1.22.4,<2", markers = "python_version < \"3.11\""}, - {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""}, - {version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""}, + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -664,83 +743,90 @@ xml = ["lxml (>=4.9.2)"] [[package]] name = "pillow" -version = "10.2.0" +version = "11.0.0" description = "Python Imaging Library (Fork)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"}, - {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"}, - {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"}, - {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"}, - {file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"}, - {file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"}, - {file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"}, - {file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"}, - {file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"}, - {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"}, - {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"}, - {file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"}, - {file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"}, - {file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"}, - {file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"}, - {file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"}, - {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"}, - {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"}, - {file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"}, - {file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"}, - {file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"}, - {file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"}, - {file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"}, - {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"}, - {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"}, - {file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"}, - {file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"}, - {file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"}, - {file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"}, - {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"}, - {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"}, - {file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"}, - {file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"}, - {file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"}, - {file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"}, - {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"}, + {file = "pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947"}, + {file = "pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f"}, + {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb"}, + {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97"}, + {file = "pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50"}, + {file = "pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c"}, + {file = "pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1"}, + {file = "pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc"}, + {file = "pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa"}, + {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306"}, + {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9"}, + {file = "pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5"}, + {file = "pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291"}, + {file = "pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9"}, + {file = "pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923"}, + {file = "pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7"}, + {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6"}, + {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc"}, + {file = "pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6"}, + {file = "pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47"}, + {file = "pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25"}, + {file = "pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699"}, + {file = "pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa"}, + {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f"}, + {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb"}, + {file = "pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798"}, + {file = "pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de"}, + {file = "pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84"}, + {file = "pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b"}, + {file = "pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003"}, + {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2"}, + {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a"}, + {file = "pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8"}, + {file = "pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8"}, + {file = "pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904"}, + {file = "pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3"}, + {file = "pillow-11.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2e46773dc9f35a1dd28bd6981332fd7f27bec001a918a72a79b4133cf5291dba"}, + {file = "pillow-11.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2679d2258b7f1192b378e2893a8a0a0ca472234d4c2c0e6bdd3380e8dfa21b6a"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda2616eb2313cbb3eebbe51f19362eb434b18e3bb599466a1ffa76a033fb916"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ec184af98a121fb2da42642dea8a29ec80fc3efbaefb86d8fdd2606619045d"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8594f42df584e5b4bb9281799698403f7af489fba84c34d53d1c4bfb71b7c4e7"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c12b5ae868897c7338519c03049a806af85b9b8c237b7d675b8c5e089e4a618e"}, + {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:70fbbdacd1d271b77b7721fe3cdd2d537bbbd75d29e6300c672ec6bb38d9672f"}, + {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5178952973e588b3f1360868847334e9e3bf49d19e169bbbdfaf8398002419ae"}, + {file = "pillow-11.0.0-cp39-cp39-win32.whl", hash = "sha256:8c676b587da5673d3c75bd67dd2a8cdfeb282ca38a30f37950511766b26858c4"}, + {file = "pillow-11.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:94f3e1780abb45062287b4614a5bc0874519c86a777d4a7ad34978e86428b8dd"}, + {file = "pillow-11.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:290f2cc809f9da7d6d622550bbf4c1e57518212da51b6a30fe8e0a270a5b78bd"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bd2d3bdb846d757055910f0a59792d33b555800813c3b39ada1829c372ccb06"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:375b8dd15a1f5d2feafff536d47e22f69625c1aa92f12b339ec0b2ca40263273"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:daffdf51ee5db69a82dd127eabecce20729e21f7a3680cf7cbb23f0829189790"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7326a1787e3c7b0429659e0a944725e1b03eeaa10edd945a86dead1913383944"}, + {file = "pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739"}, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] @@ -749,28 +835,29 @@ xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.3.6" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -779,13 +866,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pyparsing" -version = "3.1.2" +version = "3.2.0" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false -python-versions = ">=3.6.8" +python-versions = ">=3.9" files = [ - {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, - {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, + {file = "pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84"}, + {file = "pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c"}, ] [package.extras] @@ -813,6 +900,23 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-mock" +version = "3.14.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -829,56 +933,56 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2024.1" +version = "2024.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, - {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, + {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, + {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, ] [[package]] name = "scipy" -version = "1.12.0" +version = "1.13.1" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "scipy-1.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:78e4402e140879387187f7f25d91cc592b3501a2e51dfb320f48dfb73565f10b"}, - {file = "scipy-1.12.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5f00ebaf8de24d14b8449981a2842d404152774c1a1d880c901bf454cb8e2a1"}, - {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e53958531a7c695ff66c2e7bb7b79560ffdc562e2051644c5576c39ff8efb563"}, - {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e32847e08da8d895ce09d108a494d9eb78974cf6de23063f93306a3e419960c"}, - {file = "scipy-1.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4c1020cad92772bf44b8e4cdabc1df5d87376cb219742549ef69fc9fd86282dd"}, - {file = "scipy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:75ea2a144096b5e39402e2ff53a36fecfd3b960d786b7efd3c180e29c39e53f2"}, - {file = "scipy-1.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:408c68423f9de16cb9e602528be4ce0d6312b05001f3de61fe9ec8b1263cad08"}, - {file = "scipy-1.12.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5adfad5dbf0163397beb4aca679187d24aec085343755fcdbdeb32b3679f254c"}, - {file = "scipy-1.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3003652496f6e7c387b1cf63f4bb720951cfa18907e998ea551e6de51a04467"}, - {file = "scipy-1.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b8066bce124ee5531d12a74b617d9ac0ea59245246410e19bca549656d9a40a"}, - {file = "scipy-1.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8bee4993817e204d761dba10dbab0774ba5a8612e57e81319ea04d84945375ba"}, - {file = "scipy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a24024d45ce9a675c1fb8494e8e5244efea1c7a09c60beb1eeb80373d0fecc70"}, - {file = "scipy-1.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e7e76cc48638228212c747ada851ef355c2bb5e7f939e10952bc504c11f4e372"}, - {file = "scipy-1.12.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f7ce148dffcd64ade37b2df9315541f9adad6efcaa86866ee7dd5db0c8f041c3"}, - {file = "scipy-1.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c39f92041f490422924dfdb782527a4abddf4707616e07b021de33467f917bc"}, - {file = "scipy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7ebda398f86e56178c2fa94cad15bf457a218a54a35c2a7b4490b9f9cb2676c"}, - {file = "scipy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:95e5c750d55cf518c398a8240571b0e0782c2d5a703250872f36eaf737751338"}, - {file = "scipy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e646d8571804a304e1da01040d21577685ce8e2db08ac58e543eaca063453e1c"}, - {file = "scipy-1.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:913d6e7956c3a671de3b05ccb66b11bc293f56bfdef040583a7221d9e22a2e35"}, - {file = "scipy-1.12.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba1b0c7256ad75401c73e4b3cf09d1f176e9bd4248f0d3112170fb2ec4db067"}, - {file = "scipy-1.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:730badef9b827b368f351eacae2e82da414e13cf8bd5051b4bdfd720271a5371"}, - {file = "scipy-1.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6546dc2c11a9df6926afcbdd8a3edec28566e4e785b915e849348c6dd9f3f490"}, - {file = "scipy-1.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:196ebad3a4882081f62a5bf4aeb7326aa34b110e533aab23e4374fcccb0890dc"}, - {file = "scipy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:b360f1b6b2f742781299514e99ff560d1fe9bd1bff2712894b52abe528d1fd1e"}, - {file = "scipy-1.12.0.tar.gz", hash = "sha256:4bf5abab8a36d20193c698b0f1fc282c1d083c94723902c447e5d2f1780936a3"}, + {file = "scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca"}, + {file = "scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f"}, + {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989"}, + {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f"}, + {file = "scipy-1.13.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94"}, + {file = "scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54"}, + {file = "scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9"}, + {file = "scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326"}, + {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299"}, + {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa"}, + {file = "scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59"}, + {file = "scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b"}, + {file = "scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1"}, + {file = "scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d"}, + {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627"}, + {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884"}, + {file = "scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16"}, + {file = "scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949"}, + {file = "scipy-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5"}, + {file = "scipy-1.13.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24"}, + {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004"}, + {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d"}, + {file = "scipy-1.13.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c"}, + {file = "scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2"}, + {file = "scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c"}, ] [package.dependencies] -numpy = ">=1.22.4,<1.29.0" +numpy = ">=1.22.4,<2.3" [package.extras] -dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] -doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] -test = ["asv", "gmpy2", "hypothesis", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.12.0)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] +test = ["array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "seaborn" @@ -914,24 +1018,24 @@ files = [ [[package]] name = "tomli" -version = "2.0.1" +version = "2.0.2" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, ] [[package]] name = "tzdata" -version = "2024.1" +version = "2024.2" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" files = [ - {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, - {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, + {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, + {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, ] [[package]] @@ -952,20 +1056,24 @@ tomli = ">=2.0.1" [[package]] name = "zipp" -version = "3.18.1" +version = "3.21.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"}, - {file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"}, + {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, + {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.9" -content-hash = "e3e024139bcf83ce86307d978adf2be8fdd3965a1df096691c3c2da0c935053c" +content-hash = "7a8f30aaac9e77dfcd3cd051472f779e9a66d28ac08e094038ac28ae58a1a804" diff --git a/pyproject.toml b/pyproject.toml index 5e2b884..f4f25eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ seaborn = "^0.12" yapf = ">= 0.29" isort = "5.*" pytest = "^7.1" +pytest-mock = ">= 3.14.0" [build-system] requires = ["poetry-core"] diff --git a/tests/conftest.py b/tests/conftest.py index cf83d88..6c1a25e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,101 +1,8 @@ -import numpy as np -import pytest - -from mbtrack2 import Bunch, Electron, Optics, Synchrotron - - -@pytest.fixture -def model_ring(): - h = 416 # Harmonic number of the accelerator. - L = 353.97 # Ring circumference in [m]. - E0 = 2.75e9 # Nominal (total) energy of the ring in [eV]. - particle = Electron() # Particle considered. - ac = 1.0695e-5 #1.0695e-4 - U0 = 452.6e3 # Energy loss per turn in [eV]. - tau = np.array([ - 7.68e-3, 14.14e-3, 12.18e-3 - ]) #horizontal, vertical and longitudinal damping times in [s]. - tune = np.array([54.2, 18.3]) - emit = np.array([84.4e-12, 84.4e-13]) - sigma_0 = 9e-12 - sigma_delta = 9.07649e-4 - chro = np.array([1.8, 1.3]) - beta = np.array([3.288, 4.003]) - alpha = np.array([0.004, 0.02]) - dispersion = np.array([0.008, 0.01, 0.003, 0.001]) - optics = Optics(local_beta=beta, - local_alpha=alpha, - local_dispersion=dispersion) - ring = Synchrotron(h=h, - optics=optics, - particle=particle, - L=L, - E0=E0, - ac=ac, - U0=U0, - tau=tau, - emit=emit, - tune=tune, - sigma_delta=sigma_delta, - sigma_0=sigma_0, - chro=chro) - return ring - - -@pytest.fixture -def local_optics(): - beta = np.array([1, 1]) - alpha = np.array([0, 0]) - dispersion = np.array([0, 0, 0, 0]) - local_optics = Optics(local_beta=beta, - local_alpha=alpha, - local_dispersion=dispersion) - return local_optics - - -@pytest.fixture -def demo_ring(local_optics): - - h = 20 - L = 100 - E0 = 1e9 - particle = Electron() - ac = 1e-3 - U0 = 250e3 - tau = np.array([10e-3, 10e-3, 5e-3]) - tune = np.array([18.2, 10.3]) - emit = np.array([50e-9, 50e-9 * 0.01]) - sigma_0 = 30e-12 - sigma_delta = 1e-3 - chro = [1.0, 1.0] - - ring = Synchrotron(h, - local_optics, - particle, - L=L, - E0=E0, - ac=ac, - U0=U0, - tau=tau, - emit=emit, - tune=tune, - sigma_delta=sigma_delta, - sigma_0=sigma_0, - chro=chro) - - return ring - - -@pytest.fixture -def mybunch(demo_ring): - mp_number = 10 - mybunch = Bunch(demo_ring, mp_number=mp_number, track_alive=True) - return mybunch - - -@pytest.fixture -def large_bunch(demo_ring): - mp_number = 1e5 - large_bunch = Bunch(demo_ring, mp_number=mp_number, track_alive=True) - large_bunch.init_gaussian() - return large_bunch +# Add modules here to access fixtures from other files +pytest_plugins = [ + "tests.unit.utilities.test_optics", + "tests.unit.tracking.test_synchrotron", + "tests.unit.tracking.test_parallel", + "tests.unit.tracking.test_particle", + "tests.unit.impedance.test_wakefield" +] diff --git a/tests/test_ibs.py b/tests/physics/test_ibs_phys.py similarity index 67% rename from tests/test_ibs.py rename to tests/physics/test_ibs_phys.py index edc93ca..a8a6689 100644 --- a/tests/test_ibs.py +++ b/tests/physics/test_ibs_phys.py @@ -12,6 +12,43 @@ from mbtrack2.tracking import ( ) from mbtrack2.utilities import Optics +@pytest.fixture +def model_ring(): + h = 416 # Harmonic number of the accelerator. + L = 353.97 # Ring circumference in [m]. + E0 = 2.75e9 # Nominal (total) energy of the ring in [eV]. + particle = Electron() # Particle considered. + ac = 1.0695e-5 #1.0695e-4 + U0 = 452.6e3 # Energy loss per turn in [eV]. + tau = np.array([ + 7.68e-3, 14.14e-3, 12.18e-3 + ]) #horizontal, vertical and longitudinal damping times in [s]. + tune = np.array([54.2, 18.3]) + emit = np.array([84.4e-12, 84.4e-13]) + sigma_0 = 9e-12 + sigma_delta = 9.07649e-4 + chro = np.array([1.8, 1.3]) + beta = np.array([3.288, 4.003]) + alpha = np.array([0.004, 0.02]) + dispersion = np.array([0.008, 0.01, 0.003, 0.001]) + optics = Optics(local_beta=beta, + local_alpha=alpha, + local_dispersion=dispersion) + ring = Synchrotron(h=h, + optics=optics, + particle=particle, + L=L, + E0=E0, + ac=ac, + U0=U0, + tau=tau, + emit=emit, + tune=tune, + sigma_delta=sigma_delta, + sigma_0=sigma_0, + chro=chro) + return ring + def test_ibs_PS(model_ring): modelname = "PS" @@ -29,11 +66,11 @@ def test_ibs_PS(model_ring): Vc=1.8e6, theta=np.arccos(model_ring.U0 / 1.8e6)) sr = SynchrotronRadiation(model_ring, switch=[1, 1, 1]) - ibs = IntrabeamScattering(model_ring, - bunch, - model=modelname, - n_points=1000, - n_bin=1) + with pytest.warns(UserWarning): + ibs = IntrabeamScattering(model_ring, + model=modelname, + n_points=1000, + n_bin=1) long_map.track(bunch) trans_map.track(bunch) rf.track(bunch) @@ -66,11 +103,11 @@ def test_ibs_PM(model_ring): Vc=1.8e6, theta=np.arccos(model_ring.U0 / 1.8e6)) sr = SynchrotronRadiation(model_ring, switch=[1, 1, 1]) - ibs = IntrabeamScattering(model_ring, - bunch, - model=modelname, - n_points=1000, - n_bin=1) + with pytest.warns(UserWarning): + ibs = IntrabeamScattering(model_ring, + model=modelname, + n_points=1000, + n_bin=1) long_map.track(bunch) trans_map.track(bunch) rf.track(bunch) @@ -103,11 +140,11 @@ def test_ibs_Bane(model_ring): Vc=1.8e6, theta=np.arccos(model_ring.U0 / 1.8e6)) sr = SynchrotronRadiation(model_ring, switch=[1, 1, 1]) - ibs = IntrabeamScattering(model_ring, - bunch, - model=modelname, - n_points=1000, - n_bin=1) + with pytest.warns(UserWarning): + ibs = IntrabeamScattering(model_ring, + model=modelname, + n_points=1000, + n_bin=1) long_map.track(bunch) trans_map.track(bunch) rf.track(bunch) @@ -138,11 +175,11 @@ def test_ibs_CIMP(model_ring): Vc=1.8e6, theta=np.arccos(model_ring.U0 / 1.8e6)) sr = SynchrotronRadiation(model_ring, switch=[1, 1, 1]) - ibs = IntrabeamScattering(model_ring, - bunch, - model=modelname, - n_points=1000, - n_bin=1) + with pytest.warns(UserWarning): + ibs = IntrabeamScattering(model_ring, + model=modelname, + n_points=1000, + n_bin=1) long_map.track(bunch) trans_map.track(bunch) rf.track(bunch) diff --git a/tests/test_bunch.py b/tests/test_bunch.py deleted file mode 100644 index 25189d3..0000000 --- a/tests/test_bunch.py +++ /dev/null @@ -1,105 +0,0 @@ -import numpy as np -import pytest -from scipy.constants import e - -from mbtrack2 import Bunch - - -def test_bunch_values(demo_ring): - mp_number = 10 - current = 20e-3 - mybunch = Bunch(demo_ring, mp_number=mp_number, current=current, - track_alive=True) - - assert mybunch.mp_number == mp_number - assert pytest.approx(mybunch.current) == current - assert len(mybunch) == mp_number - np.testing.assert_allclose(mybunch.alive, np.ones((mp_number,), dtype=bool)) - assert pytest.approx(mybunch.charge) == current * demo_ring.T0 - assert pytest.approx(mybunch.charge_per_mp) == current * demo_ring.T0 / mp_number - assert pytest.approx(mybunch.particle_number) == current * demo_ring.T0 / e - assert mybunch.is_empty == False - -def test_bunch_magic(mybunch): - for label in mybunch: - np.testing.assert_allclose(mybunch[label], np.zeros(len(mybunch))) - mybunch[label] = np.ones(len(mybunch)) - np.testing.assert_allclose(mybunch[label], np.ones(len(mybunch))) - -def test_bunch_losses(mybunch): - charge_init = mybunch.charge - mybunch.alive[0] = False - assert len(mybunch) == mybunch.mp_number - 1 - assert pytest.approx(mybunch.charge) == charge_init * len(mybunch) / mybunch.mp_number - -def test_bunch_init_gauss(large_bunch): - large_bunch.init_gaussian(mean=np.ones((6,))) - np.testing.assert_allclose(large_bunch.mean, np.ones((6,)), rtol=1e-2) - -def test_bunch_save_load(mybunch, demo_ring, tmp_path): - mybunch["x"] += 1 - mybunch.save(str(tmp_path / "test")) - - mybunch2 = Bunch(demo_ring, mp_number=1, current=1e-5) - mybunch2.load(str(tmp_path / "test.hdf5")) - - assert mybunch.mp_number == mybunch2.mp_number - assert pytest.approx(mybunch.charge) == mybunch2.charge - for label in mybunch: - np.testing.assert_allclose(mybunch[label], mybunch2[label]) - -def test_bunch_stats(demo_ring, large_bunch): - large_bunch.init_gaussian() - np.testing.assert_array_almost_equal(large_bunch.mean, np.zeros((6,)), decimal=5) - sig = np.concatenate((demo_ring.sigma(), [demo_ring.sigma_0, demo_ring.sigma_delta])) - np.testing.assert_allclose(large_bunch.std, sig, rtol=1e-2) - np.testing.assert_allclose(large_bunch.emit[:2], demo_ring.emit, rtol=1e-2) - np.testing.assert_allclose(large_bunch.cs_invariant[:2], demo_ring.emit*2, rtol=1e-2) - -def test_bunch_binning(mybunch): - mybunch.init_gaussian() - (bins, sorted_index, profile, center) = mybunch.binning() - profile0 = np.zeros((len(bins)-1,)) - for i, val in enumerate(sorted_index): - assert bins[val] <= mybunch["tau"][i] <= bins[val+1] - profile0[val] += 1 - np.testing.assert_allclose(profile0, profile) - -def test_bunch_plots(mybunch): - mybunch.init_gaussian() - mybunch.plot_phasespace() - mybunch.plot_profile() - assert True -def test_bunch_emittance(demo_ring): - mp_number = 1_000_000 - current = 1.2e-3 - mybunch = Bunch(demo_ring, mp_number=mp_number, current=current, - track_alive=False) - mybunch.init_gaussian() - np.testing.assert_allclose(mybunch.emit[0], demo_ring.emit[0], rtol=1e-2, atol=0, - err_msg=f'Emittances do not match. {demo_ring.emit[0]} initialised, {mybunch.emit[0]:} calculated') - np.testing.assert_allclose(mybunch.emit[1], demo_ring.emit[1], rtol=1e-2, atol=0, - err_msg=f'Emittances do not match. {demo_ring.emit[1]} initialised, {mybunch.emit[1]:} calculated') - - np.testing.assert_allclose(mybunch.emit[0], mybunch.cs_invariant[0]/2, rtol=1e-2, atol=0, - err_msg=f'Emittances do not match. {mybunch.cs_invariant[0]/2} calculated with optics functions, {mybunch.emit[0]:} calculated with coordinates only') - np.testing.assert_allclose(mybunch.emit[1], mybunch.cs_invariant[1]/2, rtol=1e-2, atol=0, - err_msg=f'Emittances do not match. {mybunch.cs_invariant[1]/2} calculated with optics functions, {mybunch.emit[1]:} calculated with coordinates only') - - -def test_bunch_emittance_with_dispersion(demo_ring): - mp_number = 1_000_000 - current = 1.2e-3 - demo_ring.optics.local_dispersion = np.array([1e-2, 1e-3, 1e-2, 1e-3]) - mybunch = Bunch(demo_ring, mp_number=mp_number, current=current, - track_alive=False) - mybunch.init_gaussian() - np.testing.assert_allclose(mybunch.emit[0], demo_ring.emit[0], rtol=1e-2, atol=0, - err_msg=f'Emittances do not match. {demo_ring.emit[0]} initialised, {mybunch.emit[0]:} calculated') - np.testing.assert_allclose(mybunch.emit[1], demo_ring.emit[1], rtol=1e-2, atol=0, - err_msg=f'Emittances do not match. {demo_ring.emit[1]} initialised, {mybunch.emit[1]:} calculated') - - np.testing.assert_allclose(mybunch.emit[0], mybunch.cs_invariant[0]/2, rtol=1e-2, atol=0, - err_msg=f'Emittances do not match. {mybunch.cs_invariant[0]/2} calculated with optics functions, {mybunch.emit[0]:} calculated with coordinates only') - np.testing.assert_allclose(mybunch.emit[1], mybunch.cs_invariant[1]/2, rtol=1e-2, atol=0, - err_msg=f'Emittances do not match. {mybunch.cs_invariant[1]/2} calculated with optics functions, {mybunch.emit[1]:} calculated with coordinates only') diff --git a/tests/test_optics.py b/tests/test_optics.py deleted file mode 100644 index ceabcba..0000000 --- a/tests/test_optics.py +++ /dev/null @@ -1,17 +0,0 @@ -import numpy as np -import pytest - -from mbtrack2 import Optics - - -def test_local_optics(): - beta = np.array([1, 1]) - alpha = np.array([0, 0]) - dispersion = np.array([0, 0, 0, 0]) - gamma = (1 + alpha**2) / beta - optics = Optics(local_beta=beta, local_alpha=alpha, - local_dispersion=dispersion) - np.testing.assert_allclose(optics.local_beta, beta) - np.testing.assert_allclose(optics.local_alpha, alpha) - np.testing.assert_allclose(optics.local_dispersion, dispersion) - np.testing.assert_allclose(optics.local_gamma, gamma) \ No newline at end of file diff --git a/tests/test_synchrotron.py b/tests/test_synchrotron.py deleted file mode 100644 index ad152a3..0000000 --- a/tests/test_synchrotron.py +++ /dev/null @@ -1,63 +0,0 @@ -import numpy as np -import pytest -from scipy.constants import c, e - -from mbtrack2 import Electron, Synchrotron - - -def test_synchrotron_values(local_optics): - h = 20 - L = 100 - E0 = 1e9 - particle = Electron() - ac = 1e-3 - U0 = 250e3 - tau = np.array([10e-3, 10e-3, 5e-3]) - tune = np.array([18.2, 10.3]) - emit = np.array([50e-9, 50e-9*0.01]) - sigma_0 = 30e-12 - sigma_delta = 1e-3 - chro = [1.0,1.0] - - ring = Synchrotron(h, local_optics, particle, L=L, E0=E0, ac=ac, U0=U0, tau=tau, - emit=emit, tune=tune, sigma_delta=sigma_delta, - sigma_0=sigma_0, chro=chro) - assert pytest.approx(ring.h) == h - assert pytest.approx(ring.L) == L - assert pytest.approx(ring.E0) == E0 - assert pytest.approx(ring.U0) == U0 - assert pytest.approx(ring.ac) == ac - np.testing.assert_allclose(ring.tau, tau) - np.testing.assert_allclose(ring.tune, tune) - np.testing.assert_allclose(ring.emit, emit) - assert pytest.approx(ring.sigma_0) == sigma_0 - assert pytest.approx(ring.sigma_delta) == sigma_delta - np.testing.assert_allclose(ring.chro, chro) - assert pytest.approx(ring.T0) == L/c - assert pytest.approx(ring.T1) == L/c/h - assert pytest.approx(ring.f0) == c/L - assert pytest.approx(ring.f1) == 1/(L/c/h) - assert pytest.approx(ring.omega0) == 2 * np.pi * c/L - assert pytest.approx(ring.omega1) == 2 * np.pi * 1/(L/c/h) - assert pytest.approx(ring.k1) == 2 * np.pi * 1/(L/c/h) / c - assert pytest.approx(ring.gamma) == E0 / (particle.mass * c**2 / e) - assert pytest.approx(ring.beta) == np.sqrt(1 - (E0 / (particle.mass * c**2 / e))**-2) - -def test_synchrotron_mcf(demo_ring): - demo_ring.mcf_order = [5e-4, 1e-4, 1e-3] - assert pytest.approx(demo_ring.mcf(0.5)) == 5e-4*(0.5**2) + 1e-4*0.5 + 1e-3 - assert pytest.approx(demo_ring.eta(0.5)) == demo_ring.mcf(0.5) - 1 / (demo_ring.gamma**2) - -def test_synchrotron_tune(demo_ring): - tuneS = demo_ring.synchrotron_tune(1e6) - assert pytest.approx(tuneS, rel=1e-4) == 0.0017553 - -def test_synchrotron_long_twiss(demo_ring): - tuneS, long_alpha, long_beta, long_gamma = demo_ring.get_longitudinal_twiss(1e6, add=False) - assert pytest.approx(tuneS, rel=1e-4) == demo_ring.synchrotron_tune(1e6) - assert pytest.approx(long_alpha, rel=1e-4) == -0.0055146 - assert pytest.approx(long_beta, rel=1e-4) == 3.0236e-08 - assert pytest.approx(long_gamma, rel=1e-4) == 3.30736e7 - -def test_synchrotron_sigma(demo_ring): - np.testing.assert_allclose(demo_ring.sigma(), np.array([2.23606798e-04, 2.23606798e-04, 2.23606798e-05, 2.23606798e-05])) \ No newline at end of file diff --git a/tests/unit/impedance/test_impedance_model.py b/tests/unit/impedance/test_impedance_model.py new file mode 100644 index 0000000..02e07cf --- /dev/null +++ b/tests/unit/impedance/test_impedance_model.py @@ -0,0 +1,266 @@ + +from mbtrack2 import ImpedanceModel, ComplexData, WakeField +import pandas as pd +import numpy as np + +component_list = ComplexData.name_and_coefficients_table().columns.to_list() + + +import pytest + +class TestImpedanceModel: + + # Adding WakeField objects with unique names should succeed without errors + @pytest.mark.parametrize('pos1,pos2', [([0],[1]), (0,1), (0.5,1.5)]) + def test_add_wakefield(self, + ring_with_at_lattice, + generate_wakefield, + pos1, + pos2): + model = ImpedanceModel(ring_with_at_lattice) + wakefield1 = generate_wakefield(name="wake1") + wakefield2 = generate_wakefield(name="wake2") + model.add(wakefield1, pos1) + model.add(wakefield2, pos2) + assert "wake1" in model.names + assert "wake2" in model.names + assert wakefield1 in model.wakefields + assert wakefield2 in model.wakefields + assert pos1 in model.positions + assert pos2 in model.positions + + # Adding global WakeField objects should correctly append them to the globals list + def test_add_global_wakefield(self, ring_with_at_lattice, generate_wakefield): + model = ImpedanceModel(ring_with_at_lattice) + global_wakefield = generate_wakefield(name = "global_wake") + model.add_global(global_wakefield) + assert "global_wake" in model.globals_names + assert global_wakefield in model.globals + + # Test beta weighting summation + @pytest.mark.parametrize('comp,expected', + [('long', np.array([3, 6])), + ('xcst', np.array([(2**0.5 + 4**0.5 + 6**0.5)/2, (2**0.5 + 4**0.5 + 6**0.5)/2*2])), + ('ycst', np.array([(1**0.5 + 2**0.5 + 3**0.5)/1, (1**0.5 + 2**0.5 + 3**0.5)/1*2])), + ('xdip', np.array([6, 12])), + ('ydip', np.array([6, 12])), + ('xquad', np.array([6, 12])), + ('yquad', np.array([6, 12]))]) + def test_sum_beta(self, demo_ring, generate_wakefield, comp, expected): + + wake_in = generate_wakefield( + wake_function_params=[{'component_type': comp, + 'function': np.array([1, 2])}], + impedance_params=[{'component_type': comp, + 'function': np.array([1, 2])}]) + + demo_ring.optics.local_beta = np.array([2,1]) + model = ImpedanceModel(demo_ring) + model.add(wake_in, 0, name="test") + beta = np.array([[2,4,6],[1,2,3]]) + result_wake = model.sum_beta(model.wakefields[0], beta) + wf = getattr(result_wake, f'W{comp}') + z = getattr(result_wake, f'Z{comp}') + assert np.allclose(wf.data["real"], expected) + assert np.allclose(z.data["real"], expected) + + def test_compute_sum_names(self, ring_with_at_lattice, generate_wakefield): + wake1 = generate_wakefield(name="wake1") + wake2 = generate_wakefield(name="wake2") + model = ImpedanceModel(ring_with_at_lattice) + model.add(wake1, [1,2,3]) + model.add(wake2, [5,6,7]) + model.compute_sum_names() + assert hasattr(model, 'sum_wake1') + assert hasattr(model, 'sum_wake2') + assert 'sum_wake1' in model.sum_names + assert 'sum_wake2' in model.sum_names + + # Computing the sum of WakeFields should correctly aggregate all components + def test_compute_sum(self, ring_with_at_lattice, wakefield_param): + model = ImpedanceModel(ring_with_at_lattice) + model.add(wakefield_param, [0], "wake1") + model.add(wakefield_param, [1], "wake2") + model.compute_sum() + assert hasattr(model, 'sum') + for comp in wakefield_param.components: + assert comp in model.sum.components + + # Saving the ImpedanceModel to a file should serialize all relevant attributes + def test_save_impedance_model(self, tmp_path, ring_with_at_lattice, generate_wakefield): + model = ImpedanceModel(ring_with_at_lattice) + wakefield = generate_wakefield(name="wake") + model.add(wakefield, [0]) + model.compute_sum() + file_path = tmp_path / "impedance_model.pkl" + model.save(file_path) + assert file_path.exists() + + # Loading the ImpedanceModel from a file should restore all attributes accurately + def test_load_impedance_model(self, tmp_path, ring_with_at_lattice, generate_wakefield): + model = ImpedanceModel(ring_with_at_lattice) + wakefield = generate_wakefield(name="wake") + global_wake = generate_wakefield(name="global_wake") + model.add(wakefield, [0]) + model.add_global(global_wake) + model.compute_sum() + file_path = tmp_path / "impedance_model.pkl" + model.save(file_path) + + new_model = ImpedanceModel(ring_with_at_lattice) + new_model.load(file_path) + + assert "wake" in new_model.names + assert isinstance(new_model.wakefields[0], WakeField) + assert [0] in new_model.positions + assert "global_wake" in new_model.globals_names + assert isinstance(new_model.globals[0], WakeField) + + # Plotting the contributions of WakeFields should generate a valid plot + @pytest.mark.parametrize('comp',[('long'),('xdip'),('ydip'),('xquad'),('yquad')]) + def test_plot_area(self, ring_with_at_lattice, generate_wakefield, comp): + model = ImpedanceModel(ring_with_at_lattice) + wake = generate_wakefield( + impedance_params=[{'component_type': comp}], + name="wake" + ) + model.add(wake, [0]) + model.compute_sum() + fig = model.plot_area(Z_type=f"Z{comp}", sigma=30e-12, zoom=True) + assert fig is not None + + # Adding a WakeField with a duplicate name should raise a ValueError + def test_add_duplicate_name_raises_valueerror(self, ring_with_at_lattice, generate_wakefield): + model = ImpedanceModel(ring_with_at_lattice) + wakefield = generate_wakefield() + wakefield.name = "duplicate" + + model.add(wakefield, [0], "duplicate") + + with pytest.raises(ValueError): + model.add(wakefield, [1], "duplicate") + + # Adding a global WakeField with a duplicate name should raise a ValueError + def test_add_global_duplicate_name_raises_valueerror(self, ring_with_at_lattice, generate_wakefield): + model = ImpedanceModel(ring_with_at_lattice) + global_wakefield = generate_wakefield() + global_wakefield.name = "global_duplicate" + + model.add_global(global_wakefield, "global_duplicate") + + with pytest.raises(ValueError): + model.add_global(global_wakefield, "global_duplicate") + + # Renaming an attribute should update the internal dictionary correctly + def test_rename_attribute(self, ring_with_at_lattice): + model = ImpedanceModel(ring_with_at_lattice) + + model.some_attribute = 123 + + model.rename_attribute("some_attribute", "new_attribute_name") + + assert hasattr(model, "new_attribute_name") + assert not hasattr(model, "some_attribute") + + # Grouping attributes should correctly combine specified properties + def test_group_attributes(self, demo_ring, generate_wakefield): + # Setup + model = ImpedanceModel(demo_ring) + + # Create WakeFields with different properties + wake1 = generate_wakefield(impedance_params=[{'component_type': 'long'}, + {'component_type': 'xdip'}]) + wake2 = generate_wakefield(impedance_params=[{'component_type': 'long'}, + {'component_type': 'xdip'}]) + # Add WakeFields to the model + model.add(wake1, positions=[0.0], name='wake1') + model.add(wake2, positions=[1.0], name='wake2') + + # Compute sum to initialize sum_names + model.compute_sum() + + # Group attributes with a common property + model.group_attributes('wake', property_list=['Zlong', 'Zxdip']) + + # Assertions + assert 'wake' in model.sum_names + assert not hasattr(model, 'sum_wake1') + assert not hasattr(model, 'sum_wake2') + + # Check if the grouped attribute has combined properties + grouped_wake = getattr(model, 'wake') + assert hasattr(grouped_wake, 'Zlong') + assert hasattr(grouped_wake, 'Zxdip') + + # Grouping attributes should correctly combine specified properties + # def test_group_attributes_combines_properties(self, demo_ring, generate_wakefield): + # # Setup + # model = ImpedanceModel(demo_ring) + + # # Create WakeFields with different properties + # wake1 = generate_wakefield(impedance_params=[{'component_type': 'long'}, + # {'component_type': 'xdip'}]) + # wake2 = generate_wakefield(impedance_params=[{'component_type': 'long'}]) + # # Add WakeFields to the model + # model.add(wake1, positions=[0.0], name='wake1') + # model.add(wake2, positions=[1.0], name='wake2') + + # # Compute sum to initialize sum_names + # model.compute_sum() + + # # Group attributes with a common property + # model.group_attributes('wake', property_list=['Zlong', 'Zxdip']) + + # # Assertions + # assert 'wake' in model.sum_names + # assert not hasattr(model, 'sum_wake1') + # assert not hasattr(model, 'sum_wake2') + + # # Check if the grouped attribute has combined properties + # grouped_wake = getattr(model, 'wake') + # assert hasattr(grouped_wake, 'Zlong') + # assert hasattr(grouped_wake, 'Zxdip') + + def test_power_loss_spectrum(self, demo_ring, generate_wakefield): + # Setup + wakefield = generate_wakefield(impedance_params=[{'component_type': 'long'}]) + model = ImpedanceModel(demo_ring) + model.add(wakefield, positions=[0], name='test_wakefield') + model.compute_sum() + + # Parameters + M = 100 + bunch_spacing = 1e-9 + I = 0.5 + sigma = 1e-9 + + # Test without max_overlap + pf0, power_loss = model.power_loss_spectrum(M, bunch_spacing, I, sigma=sigma) + + # Assertions + assert len(pf0) == len(power_loss) + assert np.all(power_loss > 0) + + # The energy_loss method should compute correctly with minimal input data + def test_energy_loss(self, demo_ring, generate_wakefield): + # Setup + wakefield = generate_wakefield( + impedance_params=[{'component_type': 'long'}] + ) + model = ImpedanceModel(demo_ring) + model.add(wakefield, positions=[0], name='test_wakefield') + model.compute_sum() + + # Parameters for energy_loss + M = 1 # Minimal number of bunches + bunch_spacing = 1.0 # Arbitrary non-zero spacing + I = 0.1 # Minimal non-zero current + sigma = 1e-12 # Minimal non-zero sigma + + # Execute + summary = model.energy_loss(M, bunch_spacing, I, sigma=sigma) + + # Verify + assert not summary.empty, "The summary should not be empty" + assert "loss factor (beam) [V/pC]" in summary.columns, "Expected column missing" + assert "loss factor (bunch) [V/pC]" in summary.columns, "Expected column missing" \ No newline at end of file diff --git a/tests/unit/impedance/test_wakefield.py b/tests/unit/impedance/test_wakefield.py new file mode 100644 index 0000000..3e22aa0 --- /dev/null +++ b/tests/unit/impedance/test_wakefield.py @@ -0,0 +1,321 @@ +import numpy as np +import pytest +from mbtrack2 import ComplexData, WakeFunction, Impedance, WakeField + +component_list = ComplexData.name_and_coefficients_table().columns.to_list() + +class TestComplexData: + + @pytest.fixture + def complex_data(self): + complex_data = ComplexData(variable=np.array([0, 1]), function=np.array([1+1j, 2+2j])) + return complex_data + + # Add two ComplexData objects using 'zero' method and check resulting DataFrame + def test_add_zero_method(self, complex_data): + complex_data2 = ComplexData(variable=np.array([0, 1]), function=np.array([3+3j, 4+4j])) + result = complex_data + complex_data2 + expected_real = np.array([4, 6]) + expected_imag = np.array([4, 6]) + assert np.allclose(result.data['real'], expected_real) + assert np.allclose(result.data['imag'], expected_imag) + + # Multiply ComplexData by a scalar and verify the resulting DataFrame + def test_multiply_by_scalar(self, complex_data): + result = complex_data * 2 + expected_real = np.array([2, 4]) + expected_imag = np.array([2, 4]) + assert np.allclose(result.data['real'], expected_real) + assert np.allclose(result.data['imag'], expected_imag) + + # Interpolate ComplexData using __call__ and verify the interpolated values + def test_interpolation_call(self): + complex_data = ComplexData(variable=np.array([0, 0.25, 0.75, 1]), + function=np.array([1+1j, 1.25+1.25j, 1.75+1.75j, 2+2j])) + interpolated_values = complex_data(np.array([0.5])) + expected_values = np.array([1.5 + 1.5j]) + assert np.allclose(interpolated_values, expected_values) + + # Set component_type and verify coefficients and plane attributes + def test_set_component_type(self): + complex_data = ComplexData() + complex_data.component_type = 'xdip' + assert complex_data.a == 1 + assert complex_data.b == 0 + assert complex_data.c == 0 + assert complex_data.d == 0 + assert complex_data.plane == 'x' + + # Multiply ComplexData by a non-numeric type and expect a warning + def test_multiply_by_non_numeric_warning(self): + complex_data = ComplexData() + with pytest.warns(UserWarning): + result = complex_data.multiply('non-numeric') + assert result is complex_data + + # Interpolate ComplexData with values outside the index range + def test_interpolation_outside_range(self, complex_data): + with pytest.raises(ValueError): + complex_data(np.array([-0.5, 1.5])) + + # Interpolate ComplexData with values outside the index range + def test_wrong_component_type(self, complex_data): + with pytest.raises(KeyError): + complex_data.component_type = "wrong" + +@pytest.fixture(params=component_list) +def wf_param(request): + wf = WakeFunction(variable=np.array([0, 1]), function=np.array([1, 2]), component_type=request.param) + return wf + +@pytest.fixture +def generate_wf(): + def generate(variable=np.array([0, 1]), + function=np.array([1, 2]), + component_type='long', + ): + wf = WakeFunction(variable=variable, + function=function, + component_type=component_type) + return wf + return generate + +class TestWakeFunction: + + # Add two WakeFunction objects with the same component type and verify result + def test_add_same_component_type(self, generate_wf): + wf1 = generate_wf() + wf2 = generate_wf(function=np.array([3, 4])) + result = wf1 + wf2 + assert np.allclose(result.data['real'], [4, 6]) + assert result.component_type == 'long' + + # Multiply WakeFunction by a scalar and verify the result + def test_multiply_by_scalar(self, generate_wf): + wf = generate_wf() + result = wf * 2 + assert np.allclose(result.data['real'], [2, 4]) + assert result.component_type == 'long' + + # Add WakeFunction objects with different component types and check for warnings + def test_add_different_component_types_warning(self, generate_wf): + wf1 = generate_wf(component_type='long') + wf2 = generate_wf(component_type='xdip') + with pytest.warns(UserWarning, match="do not have the same coordinates or plane or type"): + result = wf1 + wf2 + assert result.data.equals(wf1.data) + + # Convert WakeFunction to Impedance and verify the transformation + def test_convert_to_impedance(self, generate_wf): + for component_type in ["long","xdip","ydip"]: + wf = generate_wf(component_type=component_type) + imp = wf.to_impedance(freq_lim=10) + assert imp.component_type == component_type + assert isinstance(imp, Impedance) + + # Test deconvolution + def test_deconvolution(self, generate_wf): + for component_type in ["long","xdip","ydip"]: + wf = generate_wf(component_type=component_type) + deconv_wf = wf.deconvolution(freq_lim=1e9, sigma=0.01, mu=0) + assert isinstance(deconv_wf, WakeFunction) + assert deconv_wf.component_type == component_type + + # Plot the WakeFunction data and verify the plot output + def test_plot_output(self, wf_param): + wf_param.plot() + assert True + + # Plot the WakeFunction.loss_factor + def test_loss_factor(self, wf_param): + assert wf_param.loss_factor(1) > 0 + +@pytest.fixture(params=component_list) +def imp_param(request): + imp = Impedance(variable=np.array([1, 2]), function=np.array([3+4j, 5+6j]), component_type=request.param) + return imp + +@pytest.fixture +def generate_imp(): + def generate(variable=np.array([1, 2]), + function=np.array([3+4j, 5+6j]), + component_type='long', + ): + imp = Impedance(variable=variable, + function=function, + component_type=component_type) + return imp + return generate + +class TestImpedance: + + # Impedance objects can be added using the add method with matching component types + def test_add_matching_component_types(self, generate_imp): + imp1 = generate_imp() + imp2 = generate_imp(function=np.array([1+1j, 1+1j])) + result = imp1 + imp2 + expected_real = np.array([4, 6]) + expected_imag = np.array([5, 7]) + assert np.allclose(result.data['real'], expected_real) + assert np.allclose(result.data['imag'], expected_imag) + assert result.component_type == 'long' + + # Impedance objects can be multiplied by a scalar using the multiply method + def test_multiply_by_scalar(self, generate_imp): + imp = generate_imp() + result = imp * 2 + expected_real = np.array([6, 10]) + expected_imag = np.array([8, 12]) + assert np.allclose(result.data['real'], expected_real) + assert np.allclose(result.data['imag'], expected_imag) + assert result.component_type == 'long' + + # The loss_factor method computes the correct loss factor for a given sigma + def test_loss_factor_computation(self, generate_imp): + for component_type in ["long","xdip","ydip","xquad","yquad"]: + imp = generate_imp(component_type=component_type) + assert imp.loss_factor(1) > 0 + + # The to_wakefunction method correctly transforms impedance data to a WakeFunction object + def test_to_wakefunction_transformation(self, generate_imp): + for component_type in ["long","xdip","ydip"]: + imp = generate_imp(component_type=component_type) + wf = imp.to_wakefunction() + assert isinstance(wf, WakeFunction) + assert wf.component_type == component_type + + # The plot method generates a plot of the impedance data with appropriate labels + def test_plot_generation(self, imp_param): + imp_param.plot() + assert True + +@pytest.fixture +def generate_wakefield(generate_imp, generate_wf): + def generate(impedance_params=None, wake_function_params=None, name=None): + structure_list = [] + if impedance_params: + for params in impedance_params: + structure_list.append(generate_imp(**params)) + if wake_function_params: + for params in wake_function_params: + structure_list.append(generate_wf(**params)) + return WakeField(structure_list=structure_list, name=name) + return generate + +@pytest.fixture(params=component_list) +def wakefield_param(generate_wakefield, request): + wake = generate_wakefield( + impedance_params=[{'component_type': request.param}], + wake_function_params=[{'component_type': request.param}] + ) + return wake + +class TestWakeField: + + def test_initialize_with_impedance_wakefunction_list(self, generate_wakefield): + for component_type in component_list: + wakefield = generate_wakefield( + impedance_params=[{'component_type': component_type}], + wake_function_params=[{'component_type': component_type}] + ) + assert f'Z{component_type}' in wakefield.components + assert f'W{component_type}' in wakefield.components + + def test_append_to_model(self, generate_imp, generate_wakefield, generate_wf): + wakefield = generate_wakefield() + for component_type in component_list: + imp = generate_imp(component_type=component_type) + wf = generate_wf(component_type=component_type) + wakefield.append_to_model(imp) + assert f'Z{component_type}' in wakefield.components + wakefield.append_to_model(wf) + assert f'W{component_type}' in wakefield.components + + def test_save_and_load_wakefield(self, generate_wakefield, tmp_path): + wakefield = generate_wakefield( + impedance_params=[{'component_type': 'long'}] + ) + file_path = tmp_path / "wakefield.pkl" + wakefield.save(file_path) + loaded_wakefield = WakeField.load(file_path) + assert 'Zlong' in loaded_wakefield.components + + @pytest.mark.parametrize('comp,expected', + [('long', np.array([4, 6])), + ('xcst', np.array([3 + np.sqrt(2), 4 + 2*np.sqrt(2)])), + ('ycst', np.array([3 + np.sqrt(2), 4 + 2*np.sqrt(2)])), + ('xdip', np.array([5, 8])), + ('ydip', np.array([5, 8])), + ('xquad', np.array([5, 8])), + ('yquad', np.array([5, 8]))] + ) + def test_add_wakefields_with_beta_functions(self, + generate_wakefield, + comp, + expected): + wake1 = generate_wakefield( + wake_function_params=[{'component_type': comp, + 'function': np.array([1, 2])}] + ) + wake2 = generate_wakefield( + wake_function_params=[{'component_type': comp, + 'function': np.array([3, 4])}] + ) + beta1 = [2, 2] + beta2 = [1, 1] + + result_wake = WakeField.add_wakefields(wake1, beta1, wake2, beta2) + wf = getattr(result_wake, f'W{comp}') + assert np.allclose(wf.data["real"], expected) + + result_wake = WakeField.add_several_wakefields([wake1, wake2], np.array([beta1, beta2])) + wf = getattr(result_wake, f'W{comp}') + assert np.allclose(wf.data["real"], expected) + + def test_add_duplicate_components_raises_error(self, generate_wakefield): + wakefield = generate_wakefield( + impedance_params=[{'component_type': 'long'}] + ) + with pytest.raises(ValueError): + wakefield.append_to_model(wakefield.Zlong) + + def test_drop_non_existent_component_raises_error(self, generate_wakefield): + wakefield = generate_wakefield() + with pytest.raises(AttributeError): + wakefield.drop('Znonexistent') + + def test_append_to_model_with_invalid_component(self, generate_wakefield): + wakefield = generate_wakefield() + invalid_component = "InvalidComponent" + with pytest.raises(ValueError, match="is not an Impedance nor a WakeFunction."): + wakefield.append_to_model(invalid_component) + + def test_drop_method_input_types(self, generate_wakefield): + wakefield = generate_wakefield( + impedance_params=[{'component_type': 'long'}], + wake_function_params=[{'component_type': 'xdip'}] + ) + + wakefield.drop('Zlong') + assert 'Zlong' not in wakefield.components + + wakefield.drop(['Wxdip']) + assert 'Wxdip' not in wakefield.components + + wakefield = generate_wakefield( + impedance_params=[{'component_type': 'long'}], + wake_function_params=[{'component_type': 'xdip'}] + ) + wakefield.drop('Z') + assert 'Zlong' not in wakefield.components + + wakefield = generate_wakefield( + impedance_params=[{'component_type': 'long'}], + wake_function_params=[{'component_type': 'xdip'}] + ) + wakefield.drop('W') + assert 'Wxdip' not in wakefield.components + + with pytest.raises(TypeError): + wakefield.drop(123) + \ No newline at end of file diff --git a/tests/unit/tracking/test_aperture.py b/tests/unit/tracking/test_aperture.py new file mode 100644 index 0000000..df2a54c --- /dev/null +++ b/tests/unit/tracking/test_aperture.py @@ -0,0 +1,181 @@ +import pytest +from mbtrack2 import (CircularAperture, + ElipticalAperture, + RectangularAperture, + LongitudinalAperture) + +class TestCircularAperture: + + # Initialize CircularAperture with a positive radius and track a bunch with particles inside the radius + def test_track_particles_inside_radius(self, small_bunch): + aperture = CircularAperture(radius=1.0) + small_bunch["x"] = 0.5 + small_bunch["y"] = -0.5 + aperture.track(small_bunch) + assert all(small_bunch.alive) + + # Track a bunch with particles exactly on the radius boundary + @pytest.mark.parametrize("x, y", [(1.0, 0.0), + (0.0, 1.0), + (-1.0, 0.0), + (0.0, -1.0)]) + def test_track_particles_on_boundary(self, small_bunch, x, y): + aperture = CircularAperture(radius=1.0) + small_bunch["x"] = x + small_bunch["y"] = y + aperture.track(small_bunch) + assert all(small_bunch.alive) + + # Track a bunch with all particles outside the radius + @pytest.mark.parametrize("x, y", [(0.75, 0.75), + (0.75, -0.75), + (1.1, 0.0), + (0.0, 1.1)]) + def test_track_all_particles_outside_radius(self, small_bunch, x, y): + aperture = CircularAperture(radius=1.0) + small_bunch["x"] = x + small_bunch["y"] = y + aperture.track(small_bunch) + assert not any(small_bunch.alive) + + # Track a bunch with no particles + def test_track_no_particles(self, small_bunch): + aperture = CircularAperture(radius=1.0) + small_bunch.alive[:] = False + aperture.track(small_bunch) + assert len(small_bunch) == 0 + +class TestElipticalAperture: + + # Track a bunch where all particles are within the elliptical aperture + def test_track_particles_within_aperture(self, small_bunch): + aperture = ElipticalAperture(X_radius=1.0, Y_radius=2.0) + small_bunch["x"] = 0.5 + small_bunch["y"] = 1.0 + aperture.track(small_bunch) + assert all(small_bunch.alive) + + # Track a bunch where some particles are outside the elliptical aperture + @pytest.mark.parametrize("x, y", [(0.5, 1.75), + (0.5, -1.75), + (1.1, 0.0), + (0.0, 2.1)]) + def test_track_particles_outside_aperture(self, small_bunch, x, y): + aperture = ElipticalAperture(X_radius=1.0, Y_radius=2.0) + small_bunch["x"] = x + small_bunch["y"] = y + aperture.track(small_bunch) + assert not all(small_bunch.alive) + + # Track a bunch with no particles + def test_track_no_particles(self, small_bunch): + aperture = ElipticalAperture(X_radius=1.0, Y_radius=2.0) + small_bunch.alive[:] = False + aperture.track(small_bunch) + assert len(small_bunch) == 0 + + # Track a bunch with particles exactly on the radius boundary + @pytest.mark.parametrize("x, y", [(1.0, 0.0), + (0.0, 2.0), + (-1.0, 0.0), + (0.0, -2.0)]) + def test_track_particles_on_boundary(self, small_bunch, x, y): + aperture = ElipticalAperture(X_radius=1.0, Y_radius=2.0) + small_bunch["x"] = x + small_bunch["y"] = y + aperture.track(small_bunch) + assert all(small_bunch.alive) + +class TestRectangularAperture: + + # Track method correctly identifies particles within the rectangular aperture + def test_track_particles_within_aperture(self, small_bunch): + aperture = RectangularAperture(X_right=1.0, Y_top=1.0) + small_bunch["x"] = 0.5 + small_bunch["y"] = 0.5 + aperture.track(small_bunch) + assert all(small_bunch.alive) + + # Particles outside the defined apertures are marked as 'lost' + @pytest.mark.parametrize("x, y", [(1.1, 1.1), + (-1.1, -1.1), + (1.1, 0.0), + (0.0, 1.1)]) + def test_particles_outside_aperture_lost(self, small_bunch, x, y): + aperture = RectangularAperture(X_right=1.0, Y_top=1.0) + small_bunch["x"] = x + small_bunch["y"] = y + aperture.track(small_bunch) + assert not any(small_bunch.alive) + + # Track a bunch with no particles + def test_track_no_particles(self, small_bunch): + aperture = RectangularAperture(X_right=1.0, Y_top=1.0) + small_bunch.alive[:] = False + aperture.track(small_bunch) + assert len(small_bunch) == 0 + + # Manages particles exactly on the aperture boundaries + @pytest.mark.parametrize("x, y", [(1.0, 0.0), + (0.0, 1.0), + (-1.0, 0.0), + (0.0, -1.0), + (1.0, 1.0)]) + def test_particles_on_boundary(self, small_bunch, x, y): + aperture = RectangularAperture(X_right=1.0, Y_top=1.0) + small_bunch.particles["x"] = x + small_bunch.particles["y"] = y + aperture.track(small_bunch) + assert all(small_bunch.alive) + + @pytest.mark.parametrize("x, y", [(1.1, 1.1), + (-1.1, -1.1), + (0.0, 0.0), + (0.5, -0.6)]) + def test_non_default_rect_loss(self, small_bunch, x, y): + aperture = RectangularAperture(X_right=1.0, Y_top=1.0, X_left=0.1, Y_bottom=-0.5) + small_bunch["x"] = x + small_bunch["y"] = y + aperture.track(small_bunch) + assert not any(small_bunch.alive) + + @pytest.mark.parametrize("x, y", [(0.5, 0.0), + (0.2, -0.3)]) + def test_non_default_rect(self, small_bunch, x, y): + aperture = RectangularAperture(X_right=1.0, Y_top=1.0, X_left=0.1, Y_bottom=-0.5) + small_bunch["x"] = x + small_bunch["y"] = y + aperture.track(small_bunch) + assert all(small_bunch.alive) + +class TestLongitudinalAperture: + + # Track a Bunch object with particles within the longitudinal bounds + def test_track_bunch_within_bounds(self, small_bunch): + aperture = LongitudinalAperture(tau_up=1.0) + small_bunch["tau"] = -0.5 + aperture.track(small_bunch) + assert all(small_bunch.alive) + + # Track a Bunch object with particles exactly on the boundary values + @pytest.mark.parametrize("tau",[(-1.0),(1.0)]) + def test_track_bunch_on_boundary(self, small_bunch, tau): + aperture = LongitudinalAperture(tau_up=1.0) + small_bunch["tau"] = tau + aperture.track(small_bunch) + assert all(small_bunch.alive) + + # Track a bunch with no particles + def test_track_no_particles(self, small_bunch): + aperture = LongitudinalAperture(tau_up=1.0) + small_bunch.alive[:] = False + aperture.track(small_bunch) + assert len(small_bunch) == 0 + + # Track a Bunch object with all particles outside the longitudinal bounds + @pytest.mark.parametrize("tau", [(1.1),(-0.6)]) + def test_track_bunch_outside_bounds(self, small_bunch, tau): + aperture = LongitudinalAperture(tau_up=1.0, tau_low=-0.5) + small_bunch["tau"] = tau + aperture.track(small_bunch) + assert not any(small_bunch.alive) \ No newline at end of file diff --git a/tests/unit/tracking/test_beam_ion_effects.py b/tests/unit/tracking/test_beam_ion_effects.py new file mode 100644 index 0000000..3a23e04 --- /dev/null +++ b/tests/unit/tracking/test_beam_ion_effects.py @@ -0,0 +1,333 @@ +from mbtrack2 import IonMonitor, IonParticles, IonAperture, BeamIonElement +from utility_test_functions import assert_attr_changed +import os +import h5py as hp +import numpy as np +import pytest + +@pytest.fixture +def generate_ion_particles(demo_ring): + def generate(mp_number=100, + ion_element_length=1.0, + ring=demo_ring, + track_alive=False, + alive=True): + ions = IonParticles(mp_number=mp_number, + ion_element_length=ion_element_length, + ring=ring, + track_alive=track_alive, + alive=alive) + return ions + return generate + +class TestIonParticles: + + # Generate particle distribution using electron bunch parameters via generate_as_a_distribution() + def test_generate_as_distribution(self, generate_ion_particles, large_bunch): + ions = generate_ion_particles(mp_number=1e5) + ions.generate_as_a_distribution(large_bunch) + + assert np.isclose(ions["x"].mean(), large_bunch["x"].mean(), rtol=0.1, atol=1e-5) + assert np.isclose(ions["x"].std(), large_bunch["x"].std(), rtol=0.1, atol=1e-5) + assert np.isclose(ions["y"].mean(), large_bunch["y"].mean(), rtol=0.1, atol=1e-5) + assert np.isclose(ions["y"].std(), large_bunch["y"].std(), rtol=0.1, atol=1e-5) + assert np.all(ions["xp"] == 0) + assert np.all(ions["yp"] == 0) + assert np.all(ions["delta"] == 0) + assert np.all(ions["tau"] >= -ions.ion_element_length) + assert np.all(ions["tau"] <= ions.ion_element_length) + + # Generate random particle samples from electron bunch via generate_from_random_samples() + def test_generate_from_random_samples(self, generate_ion_particles, large_bunch): + ions = generate_ion_particles(mp_number=1e5) + ions.generate_from_random_samples(large_bunch) + + assert np.all(np.isin(ions["x"], large_bunch["x"])) + assert np.all(np.isin(ions["y"], large_bunch["y"])) + assert np.all(ions["xp"] == 0) + assert np.all(ions["yp"] == 0) + assert np.all(ions["delta"] == 0) + assert np.all(ions["tau"] >= -ions.ion_element_length) + assert np.all(ions["tau"] <= ions.ion_element_length) + + # Add two IonParticles instances together and verify combined particle arrays + def test_add_ion_particles(self, generate_ion_particles): + ions1 = generate_ion_particles(mp_number=100) + ions2 = generate_ion_particles(mp_number=50) + + combined = ions1 + ions2 + assert combined.mp_number == 150 + assert all(combined[coord].shape == (150,) for coord in ["x","xp","y","yp","tau","delta"]) + assert np.all(combined.alive == True) + + # Initialize with alive=False and verify all particles marked as dead + def test_init_dead_particles(self, generate_ion_particles): + ions = generate_ion_particles(alive=False) + assert np.all(ions.alive == False) + assert all(ions[coord].shape == (1,) for coord in ["x","xp","y","yp","tau","delta"]) + + # Generate distributions with electron bunch containing no particles + def test_generate_from_empty_bunch(self, generate_ion_particles, small_bunch): + small_bunch.alive[:] = False + ions = generate_ion_particles() + + with pytest.raises(ValueError): + ions.generate_as_a_distribution(small_bunch) + with pytest.raises(ValueError): + ions.generate_from_random_samples(small_bunch) + +@pytest.fixture +def generate_ion_monitor(tmp_path): + def generate(save_every=1, + buffer_size=10, + total_size=10, + file_name=tmp_path / "test_monitor.hdf5"): + monitor = IonMonitor(save_every=save_every, + buffer_size=buffer_size, + total_size=total_size, + file_name=file_name) + return monitor + return generate + +class TestIonMonitor: + + # Monitor initialization with valid parameters creates HDF5 file and sets up data structures + def test_monitor_init_creates_valid_structures(self, generate_ion_monitor, tmp_path): + monitor = generate_ion_monitor() + assert monitor.file is not None + assert monitor.buffer_size == 10 + assert monitor.total_size == 10 + assert monitor.save_every == 1 + assert monitor.buffer_count == 0 + assert monitor.write_count == 0 + assert monitor.track_count == 0 + + # Buffer writes to file when full and resets counter + def test_buffer_writes_when_full(self, generate_ion_monitor, generate_ion_particles): + monitor = generate_ion_monitor(buffer_size=2, total_size=4) + ions = generate_ion_particles() + + for _ in range(2): + monitor.track(ions) + + assert monitor.buffer_count == 0 + assert monitor.write_count == 1 + + # Data structures are properly initialized with correct shapes and types + def test_data_structures_initialization(self, generate_ion_monitor): + monitor = generate_ion_monitor() + + assert monitor.mean.shape == (6, 10) + assert monitor.std.shape == (6, 10) + assert monitor.charge.shape == (10,) + assert monitor.time.shape == (10,) + assert monitor.time.dtype == int + + # Initialize monitor with total_size not divisible by buffer_size + def test_invalid_total_size_raises_error(self, generate_ion_monitor): + with pytest.raises(ValueError, match="total_size must be divisible by buffer_size"): + generate_ion_monitor(buffer_size=3, total_size=10) + + # Initialize with invalid/missing file_name + def test_invalid_filename_handling(self, generate_ion_monitor): + with pytest.raises(OSError): + generate_ion_monitor(file_name="/invalid/path/file.hdf5") + +class TestElipticalAperture: + + # Track a bunch where all particles are within the elliptical aperture + def test_track_particles_within_aperture(self, generate_ion_particles): + ions = generate_ion_particles() + mp = len(ions) + aperture = IonAperture(X_radius=1.0, Y_radius=2.0) + ions["x"] = np.ones_like(ions["x"])*0.5 + ions["y"] = np.ones_like(ions["y"])*1.0 + aperture.track(ions) + assert all(ions.alive) + assert mp == len(ions) + + # Track a bunch where some particles are outside the elliptical aperture + @pytest.mark.parametrize("x, y", [(0.5, 1.75), + (0.5, -1.75), + (1.1, 0.0), + (0.0, 2.1)]) + def test_track_particles_outside_aperture(self, generate_ion_particles, x, y): + ions = generate_ion_particles() + mp = len(ions) + aperture = IonAperture(X_radius=1.0, Y_radius=2.0) + ions["x"] = np.ones_like(ions["x"])*x + ions["y"] = np.ones_like(ions["y"])*y + aperture.track(ions) + assert mp != len(ions) + + # Track a bunch with no particles + def test_track_no_particles(self, generate_ion_particles): + ions = generate_ion_particles() + ions["x"] = np.ones_like(ions["x"])*100 + ions["y"] = np.ones_like(ions["y"])*100 + aperture = IonAperture(X_radius=1.0, Y_radius=2.0) + aperture.track(ions) + assert len(ions) == 0 + + aperture.track(ions) + assert True + + # Track a bunch with particles exactly on the radius boundary + @pytest.mark.parametrize("x, y", [(1.0, 0.0), + (0.0, 2.0), + (-1.0, 0.0), + (0.0, -2.0)]) + def test_track_particles_on_boundary(self, generate_ion_particles, x, y): + ions = generate_ion_particles() + mp = len(ions) + aperture = IonAperture(X_radius=1.0, Y_radius=2.0) + ions["x"] = np.ones_like(ions["x"])*x + ions["y"] = np.ones_like(ions["y"])*y + aperture.track(ions) + assert all(ions.alive) + assert mp == len(ions) + +@pytest.fixture +def generate_beam_ion(demo_ring): + def generate( + ion_mass=1.67e-27, + ion_charge=1.6e-19, + ionization_cross_section=1e-22, + residual_gas_density=1e50, + ring=demo_ring, + ion_field_model="strong", + electron_field_model="strong", + ion_element_length=demo_ring.L, + n_steps=int(demo_ring.h*10), + x_radius=0.1, + y_radius=0.1, + ion_beam_monitor_name=None, + use_ion_phase_space_monitor=False, + n_ion_macroparticles_per_bunch=30, + generate_method='samples'): + + beam_ion = BeamIonElement( + ion_mass=ion_mass, + ion_charge=ion_charge, + ionization_cross_section=ionization_cross_section, + residual_gas_density=residual_gas_density, + ring=ring, + ion_field_model=ion_field_model, + electron_field_model=electron_field_model, + ion_element_length=ion_element_length, + n_steps=n_steps, + x_radius=x_radius, + y_radius=y_radius, + ion_beam_monitor_name=ion_beam_monitor_name, + use_ion_phase_space_monitor=use_ion_phase_space_monitor, + n_ion_macroparticles_per_bunch=n_ion_macroparticles_per_bunch, + generate_method=generate_method) + return beam_ion + return generate + +class TestBeamIonElement: + + @pytest.mark.parametrize('ion_field_model, electron_field_model', + [('weak','weak'), ('weak','strong'), + ('strong','weak'), ('strong', 'strong')]) + def test_track_bunch(self, generate_beam_ion, small_bunch, ion_field_model, electron_field_model): + beam_ion = generate_beam_ion(ion_field_model=ion_field_model, electron_field_model=electron_field_model) + assert_attr_changed(beam_ion, small_bunch, attrs_changed=["xp","yp"]) + + @pytest.mark.parametrize('ion_field_model, electron_field_model', + [('weak','weak'), ('weak','strong'), + ('strong','weak'), ('strong', 'strong')]) + def test_track_bunch_partially_lost(self, generate_beam_ion, small_bunch, ion_field_model, electron_field_model): + small_bunch.alive[0:5] = False + beam_ion = generate_beam_ion(ion_field_model=ion_field_model, electron_field_model=electron_field_model) + assert_attr_changed(beam_ion, small_bunch, attrs_changed=["xp","yp"]) + + @pytest.mark.parametrize('ion_field_model, electron_field_model', + [('weak','weak'), ('weak','strong'), + ('strong','weak'), ('strong', 'strong')]) + def test_track_beam(self, generate_beam_ion, beam_uniform, ion_field_model, electron_field_model): + beam_ion = generate_beam_ion(ion_field_model=ion_field_model, electron_field_model=electron_field_model) + assert_attr_changed(beam_ion, beam_uniform, attrs_changed=["xp","yp"]) + + @pytest.mark.parametrize('ion_field_model, electron_field_model', + [('weak','weak'), ('weak','strong'), + ('strong','weak'), ('strong', 'strong')]) + def test_track_beam_non_uniform(self, generate_beam_ion, beam_non_uniform, ion_field_model, electron_field_model): + beam_ion = generate_beam_ion(ion_field_model=ion_field_model, electron_field_model=electron_field_model) + assert_attr_changed(beam_ion, beam_non_uniform, attrs_changed=["xp","yp"]) + + # Ion generation creates expected number of macroparticles with proper distribution + @pytest.mark.parametrize('generate_method', [('samples'),('distribution')]) + def test_ion_generation(self, generate_beam_ion, large_bunch, generate_method): + n_ions = 1e5 + large_bunch["x"] += 1 + large_bunch["y"] += 1 + beam_ion = generate_beam_ion(n_ion_macroparticles_per_bunch=n_ions, + generate_method=generate_method) + beam_ion.generate_new_ions(large_bunch) + + assert len(beam_ion.ion_beam["x"]) == n_ions + 1 + assert np.isclose(beam_ion.ion_beam["x"].mean(), large_bunch["x"].mean(), rtol=0.1) + assert np.isclose(beam_ion.ion_beam["y"].mean(), large_bunch["y"].mean(), rtol=0.1) + + # Ion drift tracking properly updates ion positions based on momentum + def test_ion_drift_tracking(self, generate_beam_ion, small_bunch): + beam_ion = generate_beam_ion() + beam_ion.generate_new_ions(small_bunch) + beam_ion.ion_beam["xp"] = np.ones_like(beam_ion.ion_beam["x"]) + beam_ion.ion_beam["yp"] = np.ones_like(beam_ion.ion_beam["y"]) + + drift_length = 2.0 + initial_x = beam_ion.ion_beam["x"].copy() + initial_y = beam_ion.ion_beam["y"].copy() + + beam_ion.track_ions_in_a_drift(drift_length) + + assert np.allclose(beam_ion.ion_beam["x"], initial_x + drift_length) + assert np.allclose(beam_ion.ion_beam["y"], initial_y + drift_length) + + # Monitor records ion beam data at specified intervals when enabled + def test_monitor_recording(self, generate_beam_ion, small_bunch, tmp_path): + monitor_file = str(tmp_path / "test_monitor.hdf5") + with pytest.warns(UserWarning): + beam_ion = generate_beam_ion(ion_beam_monitor_name=monitor_file) + + beam_ion.track(small_bunch) + + assert os.path.exists(monitor_file) + with hp.File(monitor_file, 'r') as f: + cond = False + for key in f.keys(): + if key.startswith('IonData'): + cond = True + assert cond + + # Empty electron bunch handling during ion generation + def test_empty_bunch_handling(self, generate_beam_ion, generate_bunch): + beam_ion = generate_beam_ion() + empty_bunch = generate_bunch(mp_number=0, init_gaussian=False) + + with pytest.raises(ValueError): + beam_ion.generate_new_ions(empty_bunch) + + # Boundary conditions at aperture edges + def test_aperture_boundary(self, generate_beam_ion, small_bunch): + x_radius = 0.001 + beam_ion = generate_beam_ion(x_radius=x_radius, y_radius=x_radius) + + beam_ion.generate_new_ions(small_bunch) + + beam_ion.ion_beam["x"] = np.ones_like(beam_ion.ion_beam["x"]) * (x_radius * 1.1) + beam_ion.aperture.track(beam_ion.ion_beam) + + assert len(beam_ion.ion_beam["x"]) == 0 + + # Ion clearing removes all particles as expected + def test_ion_clearing(self, generate_beam_ion, small_bunch): + beam_ion = generate_beam_ion() + beam_ion.generate_new_ions(small_bunch) + assert len(beam_ion.ion_beam["x"]) > 0 + + beam_ion.clear_ions() + assert len(beam_ion.ion_beam["x"]) == 1 + assert beam_ion.ion_beam["x"][0] == 0 \ No newline at end of file diff --git a/tests/unit/tracking/test_element.py b/tests/unit/tracking/test_element.py new file mode 100644 index 0000000..209c5d2 --- /dev/null +++ b/tests/unit/tracking/test_element.py @@ -0,0 +1,360 @@ +import numpy as np +import pytest +from scipy.special import factorial +from utility_test_functions import assert_attr_changed +from mbtrack2 import (Element, + LongitudinalMap, + SynchrotronRadiation, + SkewQuadrupole, + TransverseMapSector, + TransverseMap, + transverse_map_sector_generator) + +class TestElement: + + def test_parallel_decorator_with_mpi_beam(self, beam_1bunch_mpi): + class SubElement(Element): + @Element.parallel + def track(self, bunch): + bunch.charge = 1 + element = SubElement() + element.track(beam_1bunch_mpi) + assert beam_1bunch_mpi[beam_1bunch_mpi.mpi.bunch_num].charge == pytest.approx(1) + + def test_parallel_decorator_with_beam(self, beam_non_uniform): + class SubElement(Element): + @Element.parallel + def track(self, bunch): + bunch.charge = 1 + element = SubElement() + element.track(beam_non_uniform) + for i, bunch in enumerate(beam_non_uniform): + if beam_non_uniform.filling_pattern[i] == True: + assert bunch.charge == pytest.approx(1) + else: + assert bunch.charge == pytest.approx(0) + + def test_parallel_decorator_with_bunch(self, small_bunch): + class SubElement(Element): + @Element.parallel + def track(self, bunch): + bunch.charge = 1 + element = SubElement() + element.track(small_bunch) + assert small_bunch.charge == pytest.approx(1) + + # Decorator correctly skips track method if bunch is empty + def test_skip_track_if_bunch_empty(self, mocker, generate_bunch): + mock_track = mocker.Mock() + decorated_track = Element.track_bunch_if_non_empty(mock_track) + empty_bunch = generate_bunch(mp_number=0) + decorated_track(None, empty_bunch) + mock_track.assert_not_called() + + # Decorator calls track method if bunch is not empty + def test_call_track_if_bunch_not_empty(self, mocker, small_bunch): + mock_track = mocker.Mock() + decorated_track = Element.track_bunch_if_non_empty(mock_track) + decorated_track(None, small_bunch) + mock_track.assert_called_once() + + # Decorator respects track_alive flag and calls track method + def test_respect_track_alive_flag(self, mocker, generate_bunch): + mock_track = mocker.Mock() + decorated_track = Element.track_bunch_if_non_empty(mock_track) + bunch = generate_bunch(track_alive=False) + decorated_track(None, bunch) + mock_track.assert_called_once() + + # Track method executes when bunch is not empty and track_alive is True + def test_executes_with_nonempty_bunch(self, small_bunch): + called = [] + @Element.track_bunch_if_non_empty + def track_method(self, bunch): + called.append(True) + + small_bunch.track_alive = True + track_method(self, small_bunch) + assert called == [True] + + # Track method executes when track_alive is False regardless of bunch size + def test_executes_when_track_alive_false(self, small_bunch): + called = [] + @Element.track_bunch_if_non_empty + def track_method(self, bunch): + called.append(True) + + small_bunch.track_alive = False + track_method(self, small_bunch) + assert called == [True] + + # Empty bunch with track_alive=True skips track method execution + def test_skips_empty_bunch(self, generate_bunch): + called = [] + @Element.track_bunch_if_non_empty + def track_method(self, bunch): + called.append(True) + + empty_bunch = generate_bunch(alive=False) + empty_bunch.track_alive = True + track_method(self, empty_bunch) + assert not called + +class TestLongitudinalMap: + + # Track a Bunch object using the track method + def test_track_bunch(self, small_bunch, demo_ring): + long_map = LongitudinalMap(demo_ring) + assert_attr_changed(long_map, small_bunch, attrs_changed=["tau","delta"]) + +class TestSynchrotronRadiation: + + # SynchrotronRadiation initializes correctly with default switch values + def test_initialization_with_default_switch(self, demo_ring): + sr = SynchrotronRadiation(demo_ring) + assert np.array_equal(sr.switch, np.ones((3,), dtype=bool)) + + # SynchrotronRadiation modifies 'delta', 'xp', and 'yp' attributes of Bunch + def test_modifies_bunch_attributes(self, small_bunch, demo_ring): + sr = SynchrotronRadiation(demo_ring) + assert_attr_changed(sr, small_bunch) + + # switch array has all False values, ensuring no changes to Bunch + def test_no_changes_with_all_false_switch(self, small_bunch, demo_ring): + sr = SynchrotronRadiation(demo_ring, switch=np.zeros((3,), dtype=bool)) + assert_attr_changed(sr, small_bunch, change=False) + +class TestSkewQuadrupole: + + # Initialize SkewQuadrupole with a positive strength and track a Bunch object + def test_modifies_bunch_attributes(self, small_bunch): + skew_quad = SkewQuadrupole(strength=0.1) + assert_attr_changed(skew_quad, small_bunch, attrs_changed=["xp","yp"]) + +class TestTransverseMapSector: + + @pytest.fixture + def generate_trans_map_sector(demo_ring): + def generate(phase_diff = np.array([np.pi, np.pi]), + chro_diff = np.array([0.01, 0.01]), + adts=None): + alpha0 = np.array([1.0, 1.0]) + beta0 = np.array([1.0, 1.0]) + dispersion0 = np.array([0.0, 0.0, 0.0, 0.0]) + alpha1 = np.array([2.0, 2.0]) + beta1 = np.array([2.0, 2.0]) + dispersion1 = np.array([0.1, 0.1, 0.1, 0.1]) + sector = TransverseMapSector(demo_ring, + alpha0, + beta0, + dispersion0, + alpha1, + beta1, + dispersion1, + phase_diff, + chro_diff, + adts=adts) + return sector + return generate + + # Track a Bunch object through TransverseMapSector and ensure coordinates are updated + def test_track_bunch_coordinates_update(self, generate_trans_map_sector, small_bunch): + sector = generate_trans_map_sector() + assert_attr_changed(sector, small_bunch, attrs_changed=["x", "xp", "y","yp"]) + + # Compute chromatic tune advances for a Bunch with non-zero chromaticity differences + @pytest.mark.parametrize("chro_diff", [(np.array([0.02, 0.03])), + (np.array([0.02, 0.03, 0.05, 0.06])), + (np.array([0.02, 0.03, 0.05, 0.06, 0.02, 0.03,])), + (np.array([0.02, 0.03, 0.05, 0.06, 0.02, 0.03, 0.05, 0.06])), + (np.array([0.02, 0.03, 0.05, 0.06, 0.02, 0.03, 0.05, 0.06, 0.05, 0.06])),]) + def test_chromatic_tune_advances(self, generate_trans_map_sector, small_bunch, chro_diff): + # chro_diff = np.array([0.02, 0.03]) + sector = generate_trans_map_sector(chro_diff=chro_diff) + tune_advance_x_chro, tune_advance_y_chro = sector._compute_chromatic_tune_advances(small_bunch) + + order = len(chro_diff) // 2 + coefs = np.array([1 / factorial(i) for i in range(order + 1)]) + coefs[0] = 0 + chro_diff = np.concatenate(([0, 0], chro_diff)) + tune_advance_x = np.polynomial.polynomial.Polynomial(chro_diff[::2] * coefs)(small_bunch['delta']) + tune_advance_y = np.polynomial.polynomial.Polynomial(chro_diff[1::2] * coefs)(small_bunch['delta']) + + assert np.allclose(tune_advance_x, tune_advance_x_chro) + assert np.allclose(tune_advance_y, tune_advance_y_chro) + + + # Check that adts are taken into account in calculation + def test_amplitude_dependent_tune_shifts(self, generate_trans_map_sector, small_bunch): + + sector_no_adts = generate_trans_map_sector() + adts=[np.array([1e10, 1e10, 1e10]), + np.array([1e10, 1e10, 1e10]), + np.array([1e10, 1e10, 1e10]), + np.array([1e10, 1e10, 1e10])] + sector_adts = generate_trans_map_sector(adts=adts) + + attrs = ["x", "xp", "y","yp"] + initial_values = {attr: small_bunch[attr].copy() for attr in attrs} + + sector_no_adts.track(small_bunch) + no_adts = {attr: small_bunch[attr].copy() for attr in attrs} + + for attr in attrs: + small_bunch[attr] = initial_values[attr] + + sector_adts.track(small_bunch) + adts = {attr: small_bunch[attr].copy() for attr in attrs} + + for attr in attrs: + assert not np.array_equal(initial_values[attr], no_adts[attr]) + assert not np.array_equal(initial_values[attr], adts[attr]) + assert not np.array_equal(adts[attr], no_adts[attr]) + +class TestTransverseMap: + + class Old_TransverseMap(Element): + """ + Transverse map from mbtrack2 0.7.0. + + Parameters + ---------- + ring : Synchrotron object + """ + + def __init__(self, ring): + self.ring = ring + self.alpha = self.ring.optics.local_alpha + self.beta = self.ring.optics.local_beta + self.gamma = self.ring.optics.local_gamma + self.dispersion = self.ring.optics.local_dispersion + if self.ring.adts is not None: + self.adts_poly = [ + np.poly1d(self.ring.adts[0]), + np.poly1d(self.ring.adts[1]), + np.poly1d(self.ring.adts[2]), + np.poly1d(self.ring.adts[3]), + ] + + @Element.parallel + def track(self, bunch): + """ + Tracking method for the element. + No bunch to bunch interaction, so written for Bunch objects and + @Element.parallel is used to handle Beam objects. + + Parameters + ---------- + bunch : Bunch or Beam object + """ + + # Compute phase advance which depends on energy via chromaticity and ADTS + if self.ring.adts is None: + phase_advance_x = ( + 2 * np.pi * + (self.ring.tune[0] + self.ring.chro[0] * bunch["delta"])) + phase_advance_y = ( + 2 * np.pi * + (self.ring.tune[1] + self.ring.chro[1] * bunch["delta"])) + else: + Jx = ((self.ring.optics.local_gamma[0] * bunch["x"]**2) + + (2 * self.ring.optics.local_alpha[0] * bunch["x"] * + bunch["xp"]) + + (self.ring.optics.local_beta[0] * bunch["xp"]**2)) + Jy = ((self.ring.optics.local_gamma[1] * bunch["y"]**2) + + (2 * self.ring.optics.local_alpha[1] * bunch["y"] * + bunch["yp"]) + + (self.ring.optics.local_beta[1] * bunch["yp"]**2)) + phase_advance_x = ( + 2 * np.pi * + (self.ring.tune[0] + self.ring.chro[0] * bunch["delta"] + + self.adts_poly[0](Jx) + self.adts_poly[2](Jy))) + phase_advance_y = ( + 2 * np.pi * + (self.ring.tune[1] + self.ring.chro[1] * bunch["delta"] + + self.adts_poly[1](Jx) + self.adts_poly[3](Jy))) + + # 6x6 matrix corresponding to (x, xp, delta, y, yp, delta) + matrix = np.zeros((6, 6, len(bunch)), dtype=np.float64) + + # Horizontal + c_x = np.cos(phase_advance_x) + s_x = np.sin(phase_advance_x) + + matrix[0, 0, :] = c_x + self.alpha[0] * s_x + matrix[0, 1, :] = self.beta[0] * s_x + matrix[0, 2, :] = self.dispersion[0] + matrix[1, 0, :] = -1 * self.gamma[0] * s_x + matrix[1, 1, :] = c_x - self.alpha[0] * s_x + matrix[1, 2, :] = self.dispersion[1] + matrix[2, 2, :] = 1 + + # Vertical + c_y = np.cos(phase_advance_y) + s_y = np.sin(phase_advance_y) + + matrix[3, 3, :] = c_y + self.alpha[1] * s_y + matrix[3, 4, :] = self.beta[1] * s_y + matrix[3, 5, :] = self.dispersion[2] + matrix[4, 3, :] = -1 * self.gamma[1] * s_y + matrix[4, 4, :] = c_y - self.alpha[1] * s_y + matrix[4, 5, :] = self.dispersion[3] + matrix[5, 5, :] = 1 + + x = (matrix[0, 0] * bunch["x"] + matrix[0, 1] * bunch["xp"] + + matrix[0, 2] * bunch["delta"]) + xp = (matrix[1, 0] * bunch["x"] + matrix[1, 1] * bunch["xp"] + + matrix[1, 2] * bunch["delta"]) + y = (matrix[3, 3] * bunch["y"] + matrix[3, 4] * bunch["yp"] + + matrix[3, 5] * bunch["delta"]) + yp = (matrix[4, 3] * bunch["y"] + matrix[4, 4] * bunch["yp"] + + matrix[4, 5] * bunch["delta"]) + + bunch["x"] = x + bunch["xp"] = xp + bunch["y"] = y + bunch["yp"] = yp + + def test_trans_map_base(self, demo_ring, small_bunch): + old_map = self.Old_TransverseMap(demo_ring) + current_map = TransverseMap(demo_ring) + + attrs = ["x", "xp", "y","yp"] + initial_values = {attr: small_bunch[attr].copy() for attr in attrs} + + old_map.track(small_bunch) + old = {attr: small_bunch[attr].copy() for attr in attrs} + + for attr in attrs: + small_bunch[attr] = initial_values[attr] + + current_map.track(small_bunch) + current = {attr: small_bunch[attr].copy() for attr in attrs} + + for attr in attrs: + assert not np.array_equal(initial_values[attr], current[attr]) + assert not np.array_equal(initial_values[attr], old[attr]) + assert np.allclose(current[attr], old[attr]) + + def test_trans_map_adts(self, ring_with_at_lattice, small_bunch): + ring_with_at_lattice.get_adts() + old_map = self.Old_TransverseMap(ring_with_at_lattice) + current_map = TransverseMap(ring_with_at_lattice) + + attrs = ["x", "xp", "y","yp"] + initial_values = {attr: small_bunch[attr].copy() for attr in attrs} + + old_map.track(small_bunch) + old = {attr: small_bunch[attr].copy() for attr in attrs} + + for attr in attrs: + small_bunch[attr] = initial_values[attr] + + current_map.track(small_bunch) + current = {attr: small_bunch[attr].copy() for attr in attrs} + + for attr in attrs: + assert not np.array_equal(initial_values[attr], current[attr]) + assert not np.array_equal(initial_values[attr], old[attr]) + assert np.allclose(current[attr], old[attr]) + diff --git a/tests/unit/tracking/test_emfields.py b/tests/unit/tracking/test_emfields.py new file mode 100644 index 0000000..e7eea3b --- /dev/null +++ b/tests/unit/tracking/test_emfields.py @@ -0,0 +1,102 @@ +import numpy as np + +from mbtrack2.tracking.emfields import ( + _wofz, + _sqrt_sig, + _efieldn_mit, + _efieldn_linearized, + efieldn_gauss_round, + add_sigma_check, + get_displaced_efield, +) + +import pytest + +class Test_particles_electromagnetic_fields: + + def test_wofz_return_float(self): + real, imag = _wofz(1.0, 1.0) + assert isinstance(real, float) + assert isinstance(imag, float) + + def test_sqrt_sig_return_positive(self): + val = _sqrt_sig(1.0, 1.0) + assert isinstance(val, float) + assert val >= 0 + + @pytest.mark.parametrize("func",[(_efieldn_mit), + (efieldn_gauss_round), + (_efieldn_linearized)]) + def test_efieldn_return_float(self, func): + ex, ey = func(1.0, 1.0, 1.0, 1.0) + assert isinstance(ex, float) + assert isinstance(ey, float) + + # Maintains original function behavior when sig_x is greater than sig_y + def test_add_sigma_check_maintain_original_behavior_when_sig_x_greater_than_sig_y(self): + def mock_efieldn(x, y, sig_x, sig_y): + return x + sig_x, y + sig_y + + wrapped_function = add_sigma_check(mock_efieldn) + result = wrapped_function(np.array([1.0]), np.array([1.0]), 2.0, 1.0) + assert result == (3.0, 2.0) + + # Exchanges x and y when sig_x is less than sig_y + def test_add_sigma_check_exchange_x_y_when_sig_x_less_than_sig_y(self): + def mock_efieldn(x, y, sig_x, sig_y): + return x + sig_x, y + sig_y + + wrapped_function = add_sigma_check(mock_efieldn) + result = wrapped_function(np.array([1.0]), np.array([1.0]), 1.0, 2.0) + assert result == (2.0, 3.0) + + # Applies round beam field formula when sig_x is close to sig_y + def test_add_sigma_check_apply_round_beam_formula_when_sigmas_close(self): + wrapped_function = add_sigma_check(efieldn_gauss_round) + result = wrapped_function(np.array([1.0]), np.array([1.0]), 1.0, 1.00001) + assert np.allclose(result, efieldn_gauss_round(np.array([1.0]), np.array([1.0]), 1.0, 1.00001)) + + # Returns zero fields when sig_x and sig_y are both close to zero + def test_add_sigma_check_zero_fields_when_sigmas_close_to_zero(self): + wrapped_function = add_sigma_check(efieldn_gauss_round) + result = wrapped_function(np.array([1.0]), np.array([1.0]), 1e-11, 1e-11) + assert np.allclose(result, (np.zeros(1), np.zeros(1))) + + def test_add_sigma_check_empty_arrays(self): + # Define a mock efieldn function + def mock_efieldn(x, y, sig_x, sig_y): + return np.zeros_like(x), np.zeros_like(y) + + # Wrap the mock function with add_sigma_check + wrapped_function = add_sigma_check(mock_efieldn) + + # Create empty arrays for x and y + x = np.array([]) + y = np.array([]) + sig_x = 1.0 + sig_y = 1.0 + + # Call the wrapped function + en_x, en_y = wrapped_function(x, y, sig_x, sig_y) + + # Assert that the output is also empty arrays + assert en_x.size == 0 + assert en_y.size == 0 + + # Computes electric field for typical Gaussian charge distribution + def test_get_displaced_efield_return_shape(self): + def mock_efieldn(x, y, sig_x, sig_y): + return np.ones_like(x), np.ones_like(y) + + xr = np.array([1.0, 2.0, 3.0]) + yr = np.array([1.0, 2.0, 3.0]) + sig_x = 2.0 + sig_y = 1.0 + mean_x = 0.0 + mean_y = 0.0 + + en_x, en_y = get_displaced_efield(mock_efieldn, xr, yr, sig_x, sig_y, mean_x, mean_y) + + assert np.allclose(en_x, [1.0, 1.0, 1.0]) + assert np.allclose(en_y, [1.0, 1.0, 1.0]) + diff --git a/tests/unit/tracking/test_excite.py b/tests/unit/tracking/test_excite.py new file mode 100644 index 0000000..d4a8042 --- /dev/null +++ b/tests/unit/tracking/test_excite.py @@ -0,0 +1,72 @@ +import numpy as np +import pytest +from mbtrack2 import Sweep +from utility_test_functions import assert_attr_changed + +@pytest.fixture +def generate_sweep(demo_ring): + def generate(ring=demo_ring, + f0=1e3, + f1=2e3, + t1=1e-3, + level=1e3, + plane="tau", + bunch_to_sweep=None): + sweep = Sweep(ring=ring, + f0=f0, + f1=f1, + t1=t1, + level=level, + plane=plane, + bunch_to_sweep=bunch_to_sweep) + return sweep + return generate + +class TestSweep: + + # Sweep applies frequency chirp correctly to a single bunch + @pytest.mark.parametrize("plane, attr", [("x","xp"), ("y","yp"),("tau","delta")]) + def test_single_bunch_chirp(self, generate_sweep, small_bunch, plane, attr): + sweep = generate_sweep(plane=plane) + assert_attr_changed(sweep, small_bunch, attrs_changed=[attr]) + + # Sweep applies frequency chirp correctly to all bunches in a beam + @pytest.mark.parametrize("plane, attr", [("x","xp"), ("y","yp"),("tau","delta")]) + def test_all_bunches_chirp(self, generate_sweep, beam_uniform, plane, attr): + sweep = generate_sweep(plane=plane) + assert_attr_changed(sweep, beam_uniform, attrs_changed=[attr]) + + # Sweep applies frequency chirp correctly to all bunches in a beam + @pytest.mark.parametrize("plane, attr", [("x","xp"), ("y","yp"),("tau","delta")]) + def test_beam_chirp_single_bunch(self, generate_sweep, beam_uniform, plane, attr): + sweep = generate_sweep(plane=plane, bunch_to_sweep=5) + initial_attr = [bunch[attr].copy() for bunch in beam_uniform] + sweep.track(beam_uniform) + for i, bunch in enumerate(beam_uniform): + if i == 5: + assert not np.allclose(initial_attr[i], bunch[attr]), f"Chirp not applied correctly to bunch {i}" + else: + assert np.allclose(initial_attr[i], bunch[attr]), f"Chirp applied to bunch {i}" + + + # Plot method generates a correct sweep voltage plot + def test_plot_sweep_voltage(self, generate_sweep): + sweep = generate_sweep() + fig = sweep.plot() + assert fig is not None, "Plot method did not return a figure" + + # Sweep tracks correctly when MPI is enabled + @pytest.mark.parametrize("plane, attr", [("x","xp"), ("y","yp"),("tau","delta")]) + def test_mpi_tracking(self, generate_sweep, beam_1bunch_mpi, plane, attr): + sweep = generate_sweep(plane=plane) + bunch = beam_1bunch_mpi[beam_1bunch_mpi.mpi.bunch_num] + initial_attr = bunch[attr].copy() + sweep.track(beam_1bunch_mpi) + assert not np.allclose(initial_attr, bunch[attr]), "Chirp not applied correctly with MPI" + + # Sweep correctly resets count after completing a full sweep + def test_count_reset_after_full_sweep(self, generate_sweep, small_bunch): + sweep = generate_sweep() + for _ in range(sweep.N): # Complete one full sweep cycle + sweep.track(small_bunch) + assert sweep.count == 0, "Count did not reset after completing a full sweep" \ No newline at end of file diff --git a/tests/unit/tracking/test_ibs.py b/tests/unit/tracking/test_ibs.py new file mode 100644 index 0000000..974f1e8 --- /dev/null +++ b/tests/unit/tracking/test_ibs.py @@ -0,0 +1,73 @@ +from mbtrack2 import IntrabeamScattering +import numpy as np +import pytest +from utility_test_functions import assert_attr_changed + +@pytest.fixture +def generate_ibs(ring_with_at_lattice): + def generate(ring=ring_with_at_lattice, + model="CIMP", + n_points=100, + n_bin=100): + ibs = IntrabeamScattering(ring=ring, + model=model, + n_points=n_points, + n_bin=n_bin) + return ibs + return generate + +class TestIntrabeamScattering: + + # Track a bunch and validate the momentum changes + @pytest.mark.parametrize("model",[("Bane"),("CIMP"), ("PM"), ("PS")]) + def test_track_model_change_attr(self, small_bunch, generate_ibs, model): + ibs = generate_ibs(model=model) + if model == "Bane": + assert_attr_changed(ibs, small_bunch, attrs_changed=["xp", "delta"]) + else: + assert_attr_changed(ibs, small_bunch) + + # Initialize with an invalid model name and expect a ValueError + def test_initialize_invalid_model(self, demo_ring): + with pytest.warns(UserWarning): + with pytest.raises(ValueError): + IntrabeamScattering(ring=demo_ring, model="InvalidModel") + + # Track a bunch with n_bin set to 1 and verify uniform distribution handling + def test_track_with_n_bin_one(self, small_bunch, generate_ibs): + ibs = generate_ibs(n_bin=1) + assert_attr_changed(ibs, small_bunch) + + # Handle a case where the bunch is empty and ensure no operations are performed + def test_empty_bunch_handling(self, small_bunch, generate_ibs): + small_bunch.alive[:] = False + ibs = generate_ibs() + assert_attr_changed(ibs, small_bunch, change=False) + + # Handle a case where the bunch has lost some macro-particles + def test_partial_empty_bunch_handling(self, small_bunch, generate_ibs): + small_bunch.alive[0:5] = False + ibs = generate_ibs() + assert_attr_changed(ibs, small_bunch) + + # Test the warning when lattice file is not loaded and optics are approximated + def test_warning_for_approximated_optics(self, demo_ring): + demo_ring.optics.use_local_values = True + with pytest.warns(UserWarning): + IntrabeamScattering(ring=demo_ring, model="CIMP") + + # Validate the computation of growth rates for each model + def test_growth_rate_computation(self, demo_ring, small_bunch, generate_ibs): + for model in ["PS", "PM", "Bane", "CIMP"]: + ibs = generate_ibs(model=model) + ibs.initialize(small_bunch) + if model in ["PS", "PM"]: + vabq, v1aq, v1bq = ibs.scatter() + T_x, T_y, T_p = ibs.get_scatter_T(vabq=vabq, v1aq=v1aq, v1bq=v1bq) + elif model == "Bane": + gval = ibs.scatter() + T_x, T_y, T_p = ibs.get_scatter_T(gval=gval) + elif model == "CIMP": + g_ab, g_ba = ibs.scatter() + T_x, T_y, T_p = ibs.get_scatter_T(g_ab=g_ab, g_ba=g_ba) + assert T_x >= 0 and T_y >= 0 and T_p >= 0 \ No newline at end of file diff --git a/tests/unit/tracking/test_parallel.py b/tests/unit/tracking/test_parallel.py new file mode 100644 index 0000000..3404b86 --- /dev/null +++ b/tests/unit/tracking/test_parallel.py @@ -0,0 +1,98 @@ +import pytest +import numpy as np +from mbtrack2.tracking.parallel import Mpi +from mpi4py import MPI + +@pytest.fixture +def mpi_size1(): + filling_pattern = np.array([True]) + return Mpi(filling_pattern) + +@pytest.fixture +def generate_mpi_mock(mocker): + def generate(filling_pattern, + rank=0): + + size = filling_pattern.sum() + mocker.patch.object(MPI, 'COMM_WORLD') + MPI.COMM_WORLD.Get_size.return_value = size + MPI.COMM_WORLD.Get_rank.return_value = rank + + return Mpi(filling_pattern) + return generate + +@pytest.fixture +def mpi_mock_size4_fill6(generate_mpi_mock): + filling_pattern = np.ones((6,), dtype=bool) + filling_pattern[1] = False + filling_pattern[4] = False + return generate_mpi_mock(filling_pattern) + +class TestMpi: + + # Verify table creation + def test_initialize_mpi_size1(self, mpi_size1): + assert np.array_equal(mpi_size1.table, np.array([[0, 0]])) + assert mpi_size1.bunch_num == 0 + assert mpi_size1.next_bunch == 0 + assert mpi_size1.previous_bunch == 0 + + # Verify correct rank-to-bunch and bunch-to-rank mappings + def test_rank_to_bunch_and_bunch_to_rank_mappings(self, mpi_mock_size4_fill6): + assert mpi_mock_size4_fill6.rank_to_bunch(0) == 0 + assert mpi_mock_size4_fill6.bunch_to_rank(0) == 0 + assert mpi_mock_size4_fill6.rank_to_bunch(1) == 2 + assert mpi_mock_size4_fill6.bunch_to_rank(1) == None + assert mpi_mock_size4_fill6.rank_to_bunch(2) == 3 + assert mpi_mock_size4_fill6.bunch_to_rank(2) == 1 + assert mpi_mock_size4_fill6.rank_to_bunch(3) == 5 + assert mpi_mock_size4_fill6.bunch_to_rank(3) == 2 + assert mpi_mock_size4_fill6.bunch_to_rank(4) == None + assert mpi_mock_size4_fill6.bunch_to_rank(5) == 3 + + def test_next_previous_bunch(self, mpi_mock_size4_fill6): + assert mpi_mock_size4_fill6.rank == 0 + assert mpi_mock_size4_fill6.next_bunch == 1 + assert mpi_mock_size4_fill6.previous_bunch == 3 + + @pytest.mark.parametrize("dim", [("tau"), ("delta"), ("x"), ("xp"), ("y"), ("yp")]) + def test_share_distributions(self, beam_1bunch_mpi, dim): + n_bin = 75 + beam_1bunch_mpi.mpi.share_distributions(beam_1bunch_mpi, dimensions=dim, n_bin=n_bin) + assert beam_1bunch_mpi.mpi.__getattribute__(dim + "_center").shape == (beam_1bunch_mpi.mpi.size, n_bin) + assert beam_1bunch_mpi.mpi.__getattribute__(dim + "_profile").shape == (beam_1bunch_mpi.mpi.size, n_bin) + assert beam_1bunch_mpi.mpi.__getattribute__(dim + "_sorted_index").shape == (len(beam_1bunch_mpi[beam_1bunch_mpi.mpi.bunch_num]),) + assert beam_1bunch_mpi.mpi.charge_per_mp_all is not None + + def test_share_distributions_multidim(self, beam_1bunch_mpi): + n_bin = [75,60,22] + dims = ["x","yp","tau"] + beam_1bunch_mpi.mpi.share_distributions(beam_1bunch_mpi, dimensions=dims, n_bin=n_bin) + assert beam_1bunch_mpi.mpi.charge_per_mp_all is not None + for i, dim in enumerate(dims): + assert beam_1bunch_mpi.mpi.__getattribute__(dim + "_center").shape == (beam_1bunch_mpi.mpi.size, n_bin[i]) + assert beam_1bunch_mpi.mpi.__getattribute__(dim + "_profile").shape == (beam_1bunch_mpi.mpi.size, n_bin[i]) + assert beam_1bunch_mpi.mpi.__getattribute__(dim + "_sorted_index").shape == (len(beam_1bunch_mpi[beam_1bunch_mpi.mpi.bunch_num]),) + + def test_share_distributions_nobunch(self, beam_1bunch_mpi): + n_bin = 75 + dim = "x" + beam_1bunch_mpi[beam_1bunch_mpi.mpi.bunch_num].alive[:] = False + beam_1bunch_mpi.mpi.share_distributions(beam_1bunch_mpi, dimensions=dim, n_bin=n_bin) + assert beam_1bunch_mpi.mpi.charge_per_mp_all is not None + assert np.allclose(beam_1bunch_mpi.mpi.__getattribute__(dim + "_center"), np.zeros((n_bin, ), dtype=np.int64)) + assert np.allclose(beam_1bunch_mpi.mpi.__getattribute__(dim + "_profile"), np.zeros((n_bin, ), dtype=np.float64)) + assert beam_1bunch_mpi.mpi.__getattribute__(dim + "_sorted_index") is None + + + def test_share_means(self, beam_1bunch_mpi): + beam_1bunch_mpi.mpi.share_means(beam_1bunch_mpi) + assert beam_1bunch_mpi.mpi.charge_all is not None + assert beam_1bunch_mpi.mpi.mean_all.shape == (beam_1bunch_mpi.mpi.size, 6) + assert beam_1bunch_mpi.mpi.mean_all[0,:] == pytest.approx(beam_1bunch_mpi[beam_1bunch_mpi.mpi.bunch_num].mean) + + def test_share_stds(self, beam_1bunch_mpi): + beam_1bunch_mpi.mpi.share_stds(beam_1bunch_mpi) + assert beam_1bunch_mpi.mpi.charge_all is not None + assert beam_1bunch_mpi.mpi.std_all.shape == (beam_1bunch_mpi.mpi.size, 6) + assert beam_1bunch_mpi.mpi.std_all[0,:] == pytest.approx(beam_1bunch_mpi[beam_1bunch_mpi.mpi.bunch_num].std) \ No newline at end of file diff --git a/tests/unit/tracking/test_particle.py b/tests/unit/tracking/test_particle.py new file mode 100644 index 0000000..7a1a51f --- /dev/null +++ b/tests/unit/tracking/test_particle.py @@ -0,0 +1,306 @@ +import numpy as np +import matplotlib.pyplot as plt +import pytest +from scipy.constants import e, m_p, c +from mbtrack2 import Particle, Bunch, Beam + +class TestParticle: + + # Accessing the E_rest property to calculate rest energy + def test_access_E_rest_property(self): + particle = Particle(mass=m_p, charge=e) + expected_E_rest = m_p * c**2 / e + assert particle.E_rest == expected_E_rest + +@pytest.fixture +def generate_bunch(demo_ring): + def generate(ring=demo_ring, + mp_number=1e3, + current=1e-3, + track_alive=True, + alive=True, + load_from_file=None, + load_suffix=None, + init_gaussian=True, + ): + bunch = Bunch(ring=ring, + mp_number=mp_number, + current=current, + track_alive=track_alive, + alive=alive, + load_from_file=load_from_file, + load_suffix=load_suffix) + if init_gaussian: + bunch.init_gaussian() + return bunch + return generate + +@pytest.fixture +def small_bunch(generate_bunch): + return generate_bunch(mp_number=10) + +@pytest.fixture +def large_bunch(generate_bunch): + return generate_bunch(mp_number=1e5) + +class TestBunch: + + # Calculate and verify the mean, std, skew, and kurtosis of particle positions + def test_statistics_single_mp(self, generate_bunch): + bunch = generate_bunch(mp_number=1) + assert len(bunch.mean) == 6 + assert len(bunch.std) == 6 + assert len(bunch.skew) == 6 + assert len(bunch.kurtosis) == 6 + assert len(bunch.emit) == 3 + assert len(bunch.cs_invariant) == 3 + assert np.any(np.isnan(bunch.mean)) == False + assert np.any(np.isnan(bunch.std)) == False + assert np.any(np.isnan(bunch.skew)) == False + assert np.any(np.isnan(bunch.kurtosis)) == False + assert np.any(np.isnan(bunch.emit)) == False + assert np.any(np.isnan(bunch.cs_invariant)) == False + + # Initialize a Bunch with zero macro-particles and verify behavior + def test_initialize_zero_macro_particles(self, generate_bunch): + bunch = generate_bunch(mp_number=0) + assert bunch.mp_number == 0 + assert bunch.is_empty == True + + def test_drop_zero_macro_particles(self, generate_bunch): + bunch = generate_bunch(mp_number=1) + bunch.alive[0] = False + assert len(bunch) == 0 + assert bunch.is_empty == True + assert pytest.approx(bunch.charge) == 0 + + # Verify the behavior of init_gaussian with custom covariance and mean + def test_init_gaussian_custom_cov_mean(self, large_bunch): + + custom_cov = np.eye(6) * 0.5 + custom_mean = np.ones(6) * 2.0 + + large_bunch.init_gaussian(cov=custom_cov, mean=custom_mean) + + assert np.allclose(large_bunch.mean, custom_mean, atol=1e-1) + assert np.allclose(np.cov(large_bunch.particles['x'], large_bunch.particles['xp']), custom_cov[:2, :2], atol=1e-1) + + def test_bunch_values(self, small_bunch, demo_ring): + mp_number = small_bunch.mp_number + current = small_bunch.current + + assert len(small_bunch) == mp_number + np.testing.assert_allclose(small_bunch.alive, np.ones((mp_number,), dtype=bool)) + assert pytest.approx(small_bunch.charge) == current * demo_ring.T0 + assert pytest.approx(small_bunch.charge_per_mp) == current * demo_ring.T0 / mp_number + assert pytest.approx(small_bunch.particle_number) == current * demo_ring.T0 / e + assert small_bunch.is_empty == False + + def test_bunch_magic(self, generate_bunch): + mybunch = generate_bunch(init_gaussian=False) + for label in mybunch: + np.testing.assert_allclose(mybunch[label], np.zeros(len(mybunch))) + mybunch[label] = np.ones(len(mybunch)) + np.testing.assert_allclose(mybunch[label], np.ones(len(mybunch))) + + def test_bunch_losses(self, small_bunch): + charge_init = small_bunch.charge + small_bunch.alive[0] = False + assert len(small_bunch) == small_bunch.mp_number - 1 + assert pytest.approx(small_bunch.charge) == charge_init * len(small_bunch) / small_bunch.mp_number + + def test_bunch_init_gauss(self, large_bunch): + large_bunch.init_gaussian(mean=np.ones((6,))) + np.testing.assert_allclose(large_bunch.mean, np.ones((6,)), rtol=1e-2) + + def test_bunch_save_load(self, small_bunch, generate_bunch, tmp_path): + small_bunch["x"] += 1 + small_bunch.save(str(tmp_path / "test")) + + mybunch2 = generate_bunch(mp_number=1, current=1e-5) + mybunch2.load(str(tmp_path / "test.hdf5")) + + assert small_bunch.mp_number == mybunch2.mp_number + assert pytest.approx(small_bunch.charge) == mybunch2.charge + for label in small_bunch: + np.testing.assert_allclose(small_bunch[label], mybunch2[label]) + + def test_bunch_stats(self, demo_ring, large_bunch): + np.testing.assert_array_almost_equal(large_bunch.mean, np.zeros((6,)), decimal=5) + sig = np.concatenate((demo_ring.sigma(), [demo_ring.sigma_0, demo_ring.sigma_delta])) + np.testing.assert_allclose(large_bunch.std, sig, rtol=1e-2) + np.testing.assert_allclose(large_bunch.emit[:2], demo_ring.emit, rtol=1e-2) + np.testing.assert_allclose(large_bunch.cs_invariant[:2], demo_ring.emit*2, rtol=1e-2) + + @pytest.mark.parametrize('n_bin', + [(75), + (1), + (2)] + ) + def test_bunch_binning(self, small_bunch, n_bin): + (bins, sorted_index, profile, center) = small_bunch.binning(n_bin=n_bin) + profile0 = np.zeros((len(bins)-1,)) + for i, val in enumerate(sorted_index): + assert bins[val] <= small_bunch["tau"][i] <= bins[val+1] + profile0[val] += 1 + np.testing.assert_allclose(profile0, profile) + + def test_bunch_plots(self, small_bunch): + small_bunch.plot_phasespace() + small_bunch.plot_profile() + assert True + + def test_bunch_emittance(self, generate_bunch, demo_ring): + mp_number = 1_000_000 + mybunch = generate_bunch(mp_number=mp_number, track_alive=False) + + np.testing.assert_allclose(mybunch.emit[0], demo_ring.emit[0], rtol=1e-2, atol=0, + err_msg=f'Emittances do not match. {demo_ring.emit[0]} initialised, {mybunch.emit[0]:} calculated') + np.testing.assert_allclose(mybunch.emit[1], demo_ring.emit[1], rtol=1e-2, atol=0, + err_msg=f'Emittances do not match. {demo_ring.emit[1]} initialised, {mybunch.emit[1]:} calculated') + + np.testing.assert_allclose(mybunch.emit[0], mybunch.cs_invariant[0]/2, rtol=1e-2, atol=0, + err_msg=f'Emittances do not match. {mybunch.cs_invariant[0]/2} calculated with optics functions, {mybunch.emit[0]:} calculated with coordinates only') + np.testing.assert_allclose(mybunch.emit[1], mybunch.cs_invariant[1]/2, rtol=1e-2, atol=0, + err_msg=f'Emittances do not match. {mybunch.cs_invariant[1]/2} calculated with optics functions, {mybunch.emit[1]:} calculated with coordinates only') + + def test_bunch_emittance_with_dispersion(self, generate_bunch, demo_ring): + mp_number = 1_000_000 + mybunch = generate_bunch(mp_number=mp_number, track_alive=False) + + np.testing.assert_allclose(mybunch.emit[0], demo_ring.emit[0], rtol=1e-2, atol=0, + err_msg=f'Emittances do not match. {demo_ring.emit[0]} initialised, {mybunch.emit[0]:} calculated') + np.testing.assert_allclose(mybunch.emit[1], demo_ring.emit[1], rtol=1e-2, atol=0, + err_msg=f'Emittances do not match. {demo_ring.emit[1]} initialised, {mybunch.emit[1]:} calculated') + + np.testing.assert_allclose(mybunch.emit[0], mybunch.cs_invariant[0]/2, rtol=1e-2, atol=0, + err_msg=f'Emittances do not match. {mybunch.cs_invariant[0]/2} calculated with optics functions, {mybunch.emit[0]:} calculated with coordinates only') + np.testing.assert_allclose(mybunch.emit[1], mybunch.cs_invariant[1]/2, rtol=1e-2, atol=0, + err_msg=f'Emittances do not match. {mybunch.cs_invariant[1]/2} calculated with optics functions, {mybunch.emit[1]:} calculated with coordinates only') + + +@pytest.fixture +def generate_beam(demo_ring, generate_bunch): + def generate(ring=demo_ring, + filling_pattern=None, + current_per_bunch=1e-3, + mp_per_bunch=10, + track_alive=True, + mpi=False): + + beam = Beam(ring=ring) + if filling_pattern is None: + filling_pattern = np.ones((ring.h,), dtype=bool) + beam.init_beam(filling_pattern=filling_pattern, + current_per_bunch=current_per_bunch, + mp_per_bunch=mp_per_bunch, + track_alive=track_alive, + mpi=mpi) + + return beam + return generate + +@pytest.fixture +def beam_uniform(generate_beam): + return generate_beam() + +@pytest.fixture +def beam_non_uniform(generate_beam, demo_ring): + filling_pattern = np.ones((demo_ring.h,), dtype=bool) + filling_pattern[4] = False + filling_pattern[-3:] = False + return generate_beam(filling_pattern=filling_pattern) + +@pytest.fixture +def beam_1bunch_mpi(generate_beam, demo_ring): + filling_pattern = np.zeros((demo_ring.h,), dtype=bool) + filling_pattern[0] = True + return generate_beam(filling_pattern=filling_pattern, mpi=True) + +class TestBeam: + + @pytest.mark.parametrize("n_bunch", [(1),(5),(10),(20)]) + def test_initialize_beam(self, generate_beam, demo_ring, n_bunch): + filling_pattern = np.zeros((demo_ring.h,), dtype=bool) + filling_pattern[0:n_bunch] = True + beam = generate_beam(filling_pattern=filling_pattern) + assert len(beam) == n_bunch + + @pytest.mark.parametrize("n_bunch", [(1),(5),(10),(20)]) + def test_calculate_total_beam_properties(self, generate_beam, demo_ring, n_bunch): + filling_pattern = np.zeros((demo_ring.h,), dtype=bool) + filling_pattern[0:n_bunch] = True + beam = generate_beam(filling_pattern=filling_pattern, current_per_bunch=0.002) + assert beam.current == pytest.approx(0.002 * n_bunch) + assert beam.charge == pytest.approx(np.sum([bunch.charge for bunch in beam])) + assert beam.particle_number == pytest.approx(np.sum([bunch.particle_number for bunch in beam])) + + def test_save_and_load_beam_data(self, tmp_path, beam_uniform, demo_ring): + file_path = tmp_path / "beam_data" + beam_uniform.save(str(file_path)) + loaded_beam = Beam(demo_ring) + loaded_beam.load(str(file_path) + ".hdf5", mpi=False) + assert np.array_equal(beam_uniform.bunch_current, loaded_beam.bunch_current) + assert np.array_equal(beam_uniform.bunch_mean, loaded_beam.bunch_mean) + assert np.array_equal(beam_uniform.bunch_std, loaded_beam.bunch_std) + + @pytest.mark.parametrize("var,option", + [("bunch_current", None), + ("bunch_charge", None), + ("bunch_particle", None), + ("bunch_mean","x"), + ("bunch_std","x"), + ("bunch_emit","x")]) + def test_plot_variables_with_respect_to_bunch_number(self, beam_uniform, var, option): + fig = beam_uniform.plot(var, option) + assert fig is not None + plt.close("all") + + def test_initialize_beam_mismatched_bunch_list_length(self, demo_ring, generate_bunch): + mismatched_bunch_list = [generate_bunch() for _ in range(demo_ring.h - 1)] + with pytest.raises(ValueError): + Beam(demo_ring, mismatched_bunch_list) + + def test_filling_pattern_and_distance_between_bunches(self, generate_beam, demo_ring): + filling_pattern = np.ones((demo_ring.h,), dtype=bool) + filling_pattern[5] = False + filling_pattern[8:10] = False + beam = generate_beam(filling_pattern=filling_pattern) + np.testing.assert_array_equal(beam.filling_pattern, filling_pattern) + expected_distances = np.ones((demo_ring.h,)) + expected_distances[4] = 2 + expected_distances[5] = 0 + expected_distances[7] = 3 + expected_distances[8:10] = 0 + np.testing.assert_array_equal(beam.distance_between_bunches, expected_distances) + + def test_update_filling_pattern_and_distance_between_bunches(self, beam_uniform, demo_ring): + for i in [5,8,9]: + beam_uniform[i].charge = 0 + beam_uniform[5].charge = 0 + beam_uniform.update_filling_pattern() + beam_uniform.update_distance_between_bunches() + + expected_filling_pattern = np.ones((demo_ring.h,), dtype=bool) + expected_filling_pattern[5] = False + expected_filling_pattern[8:10] = False + np.testing.assert_array_equal(beam_uniform.filling_pattern, expected_filling_pattern) + + expected_distances = np.ones((demo_ring.h,)) + expected_distances[4] = 2 + expected_distances[5] = 0 + expected_distances[7] = 3 + expected_distances[8:10] = 0 + np.testing.assert_array_equal(beam_uniform.distance_between_bunches, expected_distances) + + def test_mpi_gather_and_close_consistency(self, mocker, demo_ring, generate_bunch): + mock_mpi = mocker.patch('mbtrack2.tracking.parallel.Mpi') + mock_mpi_instance = mock_mpi.return_value + mock_mpi_instance.comm.allgather.return_value = [generate_bunch() for _ in range(demo_ring.h)] + beam = Beam(ring=demo_ring) + beam.mpi_init() + beam.mpi_gather() + assert mock_mpi_instance.comm.allgather.called + beam.mpi_close() + assert not beam.mpi_switch + assert beam.mpi is None \ No newline at end of file diff --git a/tests/unit/tracking/test_rf.py b/tests/unit/tracking/test_rf.py new file mode 100644 index 0000000..d41e51e --- /dev/null +++ b/tests/unit/tracking/test_rf.py @@ -0,0 +1,420 @@ +import pytest +import numpy as np +from utility_test_functions import assert_attr_changed +from mbtrack2 import (RFCavity, + CavityResonator, + ProportionalLoop, + ProportionalIntegralLoop, + TunerLoop, + DirectFeedback) + +class TestRFCavity: + + # Initialize RFCavity with valid parameters and verify attributes are changed when tracked + def test_rf_cavity(self, demo_ring, small_bunch): + cavity = RFCavity(ring=demo_ring, m=1, Vc=1e6, theta=np.pi/4) + assert_attr_changed(cavity, small_bunch, attrs_changed=["delta"]) + +@pytest.fixture +def cav_res(demo_ring): + cav_res = CavityResonator(demo_ring, m=1, Rs=1e6, Q=1e4, QL=1e3, detune=-20e3, Ncav=4) + return cav_res + +@pytest.fixture +def cav_res_tracking(demo_ring, cav_res): + cav_res.Vc = 1e6 + cav_res.theta = np.arccos(demo_ring.U0/cav_res.Vc) + cav_res.set_generator(0.5) + return cav_res + +class TestCavityResonator: + + @pytest.mark.parametrize("n_bin", [(75), (1)]) + def test_track_mpi(self, cav_res_tracking, beam_1bunch_mpi, n_bin): + cav_res_tracking.n_bin = n_bin + initial_phasor = cav_res_tracking.beam_phasor + initial_beam_phasor_record = cav_res_tracking.beam_phasor_record.copy() + + beam_1bunch_mpi.mpi.share_distributions(beam_1bunch_mpi, n_bin=n_bin) + assert_attr_changed(cav_res_tracking, beam_1bunch_mpi,attrs_changed=["delta"]) + + assert not np.array_equal(cav_res_tracking.beam_phasor, initial_phasor) + assert not np.array_equal(cav_res_tracking.beam_phasor_record, initial_beam_phasor_record) + + @pytest.mark.parametrize("n_bin", [(75), (1)]) + def test_track_no_mpi(self, cav_res_tracking, beam_uniform, n_bin): + cav_res_tracking.n_bin = n_bin + initial_phasor = cav_res_tracking.beam_phasor + initial_beam_phasor_record = cav_res_tracking.beam_phasor_record.copy() + + assert_attr_changed(cav_res_tracking, beam_uniform, attrs_changed=["delta"]) + + assert not np.array_equal(cav_res_tracking.beam_phasor, initial_phasor) + assert not np.array_equal(cav_res_tracking.beam_phasor_record, initial_beam_phasor_record) + + @pytest.mark.parametrize("n_bin", [(75), (1)]) + def test_track_no_mpi_non_uniform(self, cav_res_tracking, beam_non_uniform, n_bin): + cav_res_tracking.n_bin = n_bin + initial_phasor = cav_res_tracking.beam_phasor + initial_beam_phasor_record = cav_res_tracking.beam_phasor_record.copy() + + assert_attr_changed(cav_res_tracking, beam_non_uniform, attrs_changed=["delta"]) + + assert not np.array_equal(cav_res_tracking.beam_phasor, initial_phasor) + assert not np.array_equal(cav_res_tracking.beam_phasor_record, initial_beam_phasor_record) + + # Track a beam with zero charge per macro-particle + def test_track_zero_charge_per_mp(self, cav_res_tracking, generate_beam): + beam = generate_beam(current_per_bunch=0) + assert_attr_changed(cav_res_tracking, beam, change=False) + + # Apply RF feedback loops correctly during tracking + def test_apply_rf_feedback(self, cav_res_tracking, beam_uniform, mocker): + cav_res_tracking.feedback.append(mocker.Mock()) + cav_res_tracking.track(beam_uniform) + cav_res_tracking.feedback[0].track.assert_called_once(), "RF feedback should be applied" + + @pytest.mark.parametrize("ref_frame", [("beam"), ("rf")]) + def test_phasor_decay(self, cav_res, ref_frame): + init_phasor = 1 + 1j + cav_res.beam_phasor = init_phasor + cav_res.phasor_decay(10e-6, ref_frame) + assert cav_res.beam_phasor != init_phasor + + @pytest.mark.parametrize("ref_frame", [("beam"), ("rf")]) + def test_phasor_evol(self, cav_res, ref_frame): + init_phasor = 1 + 1j + cav_res.beam_phasor = init_phasor + profile = np.random.randint(1000, size=(10,)) + bin_length = 1e-12 + charge_per_mp = 1e-15 + cav_res.phasor_evol(profile, bin_length, charge_per_mp, ref_frame) + assert cav_res.beam_phasor != init_phasor + + def test_phasor_init(self, cav_res, beam_non_uniform): + init_phasor = cav_res.beam_phasor + cav_res.init_phasor_track(beam_non_uniform) + phasor_init_phasor_track = cav_res.beam_phasor + + cav_res.beam_phasor = init_phasor + cav_res.init_phasor(beam_non_uniform) + phasor_init_phasor = cav_res.beam_phasor + + assert phasor_init_phasor != init_phasor + assert phasor_init_phasor_track != init_phasor + assert np.allclose(phasor_init_phasor, phasor_init_phasor_track, rtol=1e-2) + + # Setting detune updates _detune, _fr, _wr, and _psi correctly + def test_detune(self, cav_res): + detune_value = 1000 + cav_res.detune = detune_value + assert cav_res._detune == detune_value + assert cav_res._fr == detune_value + cav_res.m * cav_res.ring.f1 + assert cav_res._wr == cav_res._fr * 2 * np.pi + assert cav_res._psi == np.arctan(cav_res.QL * (cav_res._fr / (cav_res.m * cav_res.ring.f1) - (cav_res.m * cav_res.ring.f1) / cav_res._fr)) + + # update_feedback is called after setting detune + def test_update_feedback_called(self, mocker, cav_res): + mocker.spy(cav_res, 'update_feedback') + cav_res.detune = 1000 + cav_res.update_feedback.assert_called_once() + + def test_plot_phasor_diagram(self, cav_res_tracking): + fig = cav_res_tracking.plot_phasor(0.5) + assert fig is not None + + # Calculating generator phasor using Vg and theta_g + def test_generator_phasor_calculation(self, cav_res): + cav_res.Vg = 1000 + cav_res.theta_g = np.pi / 4 + expected_phasor = 1000 * np.exp(1j * np.pi / 4) + assert cav_res.generator_phasor == expected_phasor + + # Computing cavity phasor by summing generator and beam phasors + def test_cavity_phasor_computation(self, cav_res): + cav_res.Vg = 1000 + cav_res.theta_g = np.pi / 4 + cav_res.beam_phasor = 500 * np.exp(1j * np.pi / 6) + expected_phasor = cav_res.generator_phasor + cav_res.beam_phasor + assert cav_res.cavity_phasor == expected_phasor + + # Retrieving cavity phasor record for each bunch + def test_cavity_phasor_record_retrieval(self, cav_res): + cav_res.generator_phasor_record = np.array([1+1j, 2+2j]) + cav_res.beam_phasor_record = np.array([0.5+0.5j, 1+1j]) + expected_record = np.array([1.5+1.5j, 3+3j]) + assert np.allclose(cav_res.cavity_phasor_record, expected_record) + + # Accessing ig phasor record when feedback is present + def test_ig_phasor_record_with_feedback(self, cav_res): + pi_loop = ProportionalIntegralLoop(cav_res.ring, cav_res, [1.0, 1.0], 10, 10, 10) + pi_loop.ig_phasor_record = np.array([1+1j, 2+2j]) + cav_res.feedback.append(pi_loop) + assert np.allclose(cav_res.ig_phasor_record, pi_loop.ig_phasor_record) + + # Determining cavity voltage and phase from cavity phasor + def test_cavity_voltage_and_phase_determination(self, cav_res): + cav_res.Vg = 1000 + cav_res.theta_g = np.pi / 4 + cav_res.beam_phasor = 500 * np.exp(1j * np.pi / 6) + expected_voltage = np.abs(cav_res.cavity_phasor) + expected_phase = np.angle(cav_res.cavity_phasor) + assert cav_res.cavity_voltage == expected_voltage + assert cav_res.cavity_phase == expected_phase + + # Calculating beam voltage and phase from beam phasor + def test_beam_voltage_and_phase_calculation(self, cav_res): + cav_res.beam_phasor = 500 * np.exp(1j * np.pi / 6) + expected_voltage = np.abs(cav_res.beam_phasor) + expected_phase = np.angle(cav_res.beam_phasor) + assert cav_res.beam_voltage == expected_voltage + assert cav_res.beam_phase == expected_phase + + # Handling feedback list with no ProportionalIntegralLoop or DirectFeedback + def test_ig_phasor_record_no_feedback(self, cav_res): + cav_res.feedback = [] + expected_record = np.zeros(cav_res.ring.h) + assert np.allclose(cav_res.ig_phasor_record, expected_record) + + # Calculating loaded shunt impedance RL + def test_loaded_shunt_impedance_rl(self, cav_res): + # Set up the initial conditions + cav_res.Rs = 1e6 # Shunt impedance + cav_res.QL = 1e3 # Loaded quality factor + cav_res.Q = 1e4 # Quality factor + + # Calculate expected RL + expected_RL = cav_res.Rs / (1 + (cav_res.Q / cav_res.QL - 1)) + + # Assert that the calculated RL matches the expected value + assert cav_res.RL == expected_RL + + # Set optimal coupling and detuning and verify changes in attributes + def test_set_optimal_coupling_and_detuning(self, demo_ring, cav_res): + initial_beta = cav_res.beta + initial_psi = cav_res.psi + cav_res.set_optimal_coupling(0.5) + cav_res.set_optimal_detune(0.5) + assert cav_res.beta != initial_beta + assert cav_res.psi != initial_psi + + # Verify that is_CBI_stable correctly calculates growth rate and mode number + def test_calculate_growth_rate_and_mode(self, cav_res_tracking): + growth_rate, mu = cav_res_tracking.is_CBI_stable(I0=0.5) + assert isinstance(growth_rate, float) + assert np.issubdtype(type(mu), np.integer) + + # Ensure is_CBI_stable returns growth rates for specified modes when modes is provided + def test_return_growth_rates_for_specified_modes(self, cav_res_tracking): + modes = [0, 1, 2] + growth_rates = cav_res_tracking.is_CBI_stable(I0=0.5, modes=modes) + assert len(growth_rates) == len(modes) + + def test_is_DC_Robinson_stable(self, cav_res_tracking): + assert isinstance(cav_res_tracking.is_DC_Robinson_stable(0.5), (bool,np.bool_)) + + def test_plot_DC_Robinson_stability(self, cav_res_tracking): + fig = cav_res_tracking.plot_DC_Robinson_stability() + assert fig is not None + + @pytest.mark.parametrize("z,expected_type", + [(1e-3, float), + (np.linspace(-1e-3, 1e-3, 100), np.ndarray)]) + def test_VRF(self, cav_res_tracking, z, expected_type): + assert isinstance(cav_res_tracking.VRF(z, 0.5), expected_type) + assert isinstance(cav_res_tracking.dVRF(z, 0.5), expected_type) + assert isinstance(cav_res_tracking.ddVRF(z, 0.5), expected_type) + assert isinstance(cav_res_tracking.deltaVRF(z, 0.5), expected_type) + + def test_sample_voltage_default_parameters(self, cav_res_tracking, beam_uniform): + cav_res_tracking.init_tracking(beam_uniform) + n_points=1e4 + beam_phasor_init = 1 + 1j + cav_res_tracking.beam_phasor = beam_phasor_init + pos, voltage_rec = cav_res_tracking.sample_voltage(n_points=n_points) + assert len(pos) == n_points + assert len(voltage_rec) == n_points + assert np.allclose(cav_res_tracking.beam_phasor, beam_phasor_init) + + def test_set_generator(self, cav_res, demo_ring): + cav_res.Vc = 1e6 + cav_res.theta = np.arccos(demo_ring.U0/cav_res.Vc) + cav_res.set_generator(0.5) + assert cav_res.Pg is not None + assert cav_res.Vgr is not None + assert cav_res.theta_gr is not None + assert cav_res.Vg is not None + assert cav_res.theta_g is not None + assert cav_res.generator_phasor_record is not None + + def test_functions(self, cav_res_tracking): + assert isinstance(cav_res_tracking.Pb(0.5), float) + assert isinstance(cav_res_tracking.Pr(0.5), float) + assert isinstance(cav_res_tracking.Vbr(0.5), float) + assert isinstance(cav_res_tracking.Vb(0.5), float) + assert isinstance(cav_res_tracking.Z(1e9), complex) + + def test_properties(self, cav_res_tracking): + assert cav_res_tracking.Pc > 0 + assert cav_res_tracking.filling_time > 0 + assert cav_res_tracking.loss_factor > 0 + +class TestProportionalLoop: + + @pytest.fixture + def prop_loop(self, demo_ring, cav_res_tracking): + return ProportionalLoop(demo_ring, cav_res_tracking, gain_A=0.5, gain_P=0.5, delay=2) + + # track method updates cav_res.Vg and cav_res.theta_g correctly + def test_track_delay(self, prop_loop): + initial_Vg = prop_loop.cav_res.Vg + initial_theta_g = prop_loop.cav_res.theta_g + prop_loop.track() + assert prop_loop.cav_res.Vg == initial_Vg + assert prop_loop.cav_res.theta_g == initial_theta_g + + # track method updates cav_res.Vg and cav_res.theta_g correctly + def test_track_update(self, prop_loop): + initial_Vg = prop_loop.cav_res.Vg + initial_theta_g = prop_loop.cav_res.theta_g + prop_loop.track() + prop_loop.track() # delay=2 is 3 turns to take action + prop_loop.track() + assert prop_loop.cav_res.Vg != initial_Vg + assert prop_loop.cav_res.theta_g != initial_theta_g + + # Initialization with delay less than 1 raises ValueError + def test_initialization_delay_less_than_one(self, demo_ring, cav_res): + with pytest.raises(ValueError): + ProportionalLoop(demo_ring, cav_res, gain_A=0.5, gain_P=0.5, delay=0) + + # Initialization with non-integer delay converts it to integer + def test_initialization_non_integer_delay(self, demo_ring, cav_res): + loop = ProportionalLoop(demo_ring, cav_res, gain_A=0.5, gain_P=0.5, delay=2.7) + assert loop.delay == 2 + +class TestTunerLoop: + + @pytest.fixture + def tuner_loop(self, demo_ring, cav_res_tracking): + return TunerLoop(demo_ring, cav_res_tracking, gain=1, avering_period=2, offset=0) + + def test_track_once(self, tuner_loop): + initial_psi = tuner_loop.cav_res.psi + tuner_loop.track() + assert tuner_loop.diff != 0 + assert tuner_loop.count == 1 + assert tuner_loop.cav_res.psi == initial_psi + + # Track method correctly adjusts cav_res.psi when count equals avering_period + def test_track_adjusts_psi(self, tuner_loop): + initial_psi = tuner_loop.cav_res.psi + for _ in range(tuner_loop.avering_period+1): + tuner_loop.track() + assert tuner_loop.diff == 0 + assert tuner_loop.cav_res.psi != initial_psi + + # Initialize with avering_period set to None and verify default calculation + def test_default_averaging_period_calculation(self, demo_ring, cav_res_tracking): + tuner_loop = TunerLoop(demo_ring, cav_res_tracking, avering_period=None) + fs = demo_ring.synchrotron_tune(cav_res_tracking.Vc) * demo_ring.f0 + expected_period = int(2 / fs / demo_ring.T0) + assert tuner_loop.avering_period == expected_period + + # Test track method with zero gain to ensure no changes to cav_res.psi + def test_track_with_zero_gain(self, tuner_loop): + tuner_loop.Pgain = 0 + initial_psi = tuner_loop.cav_res.psi + for _ in range(tuner_loop.avering_period): + tuner_loop.track() + assert tuner_loop.cav_res.psi == initial_psi + +class TestProportionalIntegralLoop: + + @pytest.fixture + def pi_loop(self, demo_ring, cav_res_tracking): + loop = ProportionalIntegralLoop(demo_ring, cav_res_tracking, [0.5, 1e4], 8, 7, 5) + return loop + + def test_track(self, pi_loop): + initial_ig_phasor = pi_loop.ig_phasor.copy() + generator_phasor_record_init = pi_loop.cav_res.generator_phasor_record.copy() + pi_loop.track() + assert not np.array_equal(initial_ig_phasor, pi_loop.ig_phasor) + assert not np.array_equal(generator_phasor_record_init, pi_loop.cav_res.generator_phasor_record) + + # Ig2Vg method correctly updates generator phasor record and cavity parameters + def test_Ig2Vg_updates_generator_phasor(self, pi_loop): + generator_phasor_record_init = pi_loop.cav_res.generator_phasor_record.copy() + Vg_init = pi_loop.cav_res.Vg + theta_g_init = pi_loop.cav_res.theta_g + pi_loop.Ig2Vg() + assert not np.allclose(generator_phasor_record_init, pi_loop.cav_res.generator_phasor_record) + assert pi_loop.cav_res.Vg != Vg_init + assert pi_loop.cav_res.theta_g != theta_g_init + + # IIR filter processes input correctly and returns expected output + def test_IIR_filter_output(self, pi_loop): + input_signal = np.array([1.0 + 1.0j]) + output = pi_loop.IIR(input_signal) + assert output is not None + + # Init_FFconst initializes feedforward constant based on FF flag + def test_init_FFconst_with_FF_true(self, pi_loop): + pi_loop.init_FFconst() + assert pi_loop.FFconst != 0 + + def test_parameters(self, pi_loop): + assert isinstance(pi_loop.Vg2Ig(1e6), complex) + pi_loop.IIR_init(1e9) + assert isinstance(pi_loop.IIRcutoff, float) + assert isinstance(pi_loop.IIR(1.0 + 1.0j), complex) + +class TestDirectFeedback: + + @pytest.fixture + def drf_fb(self, demo_ring, cav_res_tracking): + drf_fb = DirectFeedback(DFB_gain=1.0, + DFB_phase_shift=0.1, + ring=demo_ring, + cav_res=cav_res_tracking, + gain=[0.5, 1e4], + sample_num=8, + every=7, + delay=5) + return drf_fb + + def test_properties(self, drf_fb): + assert isinstance(drf_fb.DFB_phase_shift, float) + assert isinstance(drf_fb.phase_shift, float) + assert isinstance(drf_fb.DFB_psi, float) + assert isinstance(drf_fb.DFB_alpha, float) + assert isinstance(drf_fb.DFB_gamma, float) + assert isinstance(drf_fb.DFB_Rs, float) + + def test_methods(self, drf_fb): + vg_main, vg_drf = drf_fb.DFB_Vg() + assert isinstance(vg_main, complex) + assert isinstance(vg_drf, complex) + assert isinstance(drf_fb.DFB_fs(), float) + + def test_track(self, drf_fb): + initial_ig_phasor_record = drf_fb.ig_phasor_record.copy() + initial_dfb_ig_phasor = drf_fb.DFB_ig_phasor.copy() + generator_phasor_record_init = drf_fb.cav_res.generator_phasor_record.copy() + drf_fb.track() + assert not np.array_equal(drf_fb.ig_phasor_record, initial_ig_phasor_record) + assert not np.array_equal(drf_fb.DFB_ig_phasor, initial_dfb_ig_phasor) + assert not np.array_equal(generator_phasor_record_init, drf_fb.cav_res.generator_phasor_record) + + # Set DFB parameters using DFB_parameter_set and verify changes in DFB_ig_phasor + def test_set_dfb_parameters(self, drf_fb): + initial_DFB_ig_phasor = drf_fb.DFB_ig_phasor.copy() + initial_ig_phasor = drf_fb.ig_phasor.copy() + + drf_fb.DFB_parameter_set(DFB_gain=2.0, DFB_phase_shift=0.2) + + assert not np.array_equal(drf_fb.DFB_ig_phasor, initial_DFB_ig_phasor) + assert not np.array_equal(drf_fb.ig_phasor, initial_ig_phasor) + assert drf_fb.DFB_gain == 2.0 + assert drf_fb.DFB_phase_shift == 0.2 \ No newline at end of file diff --git a/tests/unit/tracking/test_spacecharge.py b/tests/unit/tracking/test_spacecharge.py new file mode 100644 index 0000000..b60a215 --- /dev/null +++ b/tests/unit/tracking/test_spacecharge.py @@ -0,0 +1,37 @@ +import pytest +from mbtrack2 import TransverseSpaceCharge +from utility_test_functions import assert_attr_changed +import numpy as np + +class TestTransverseSpaceCharge: + + # Track a bunch with valid x, y coordinates through the space charge element + def test_track_with_track_alive(self, demo_ring, large_bunch): + tsc = TransverseSpaceCharge(demo_ring, 1000.0) + large_bunch.alive[5:700] = False + assert_attr_changed(tsc, large_bunch, attrs_changed=["xp", "yp"]) + + # Track a bunch with valid x, y coordinates through the space charge element + def test_track_without_track_alive(self, demo_ring, large_bunch): + tsc = TransverseSpaceCharge(demo_ring, 1000.0) + large_bunch.track_alive = False + assert_attr_changed(tsc, large_bunch, attrs_changed=["xp", "yp"]) + + # Initialize with zero interaction length and verify no kicks are applied + def test_zero_interaction_length_no_kicks(self, demo_ring, small_bunch): + tsc = TransverseSpaceCharge(demo_ring, 0.0) + assert_attr_changed(tsc, small_bunch, attrs_changed=["xp", "yp"], change=False) + + # Handle a bunch with bins having zero particles without errors + def test_handle_bins_with_zero_particles(self, demo_ring, small_bunch): + small_bunch.alive[:] = False + tsc = TransverseSpaceCharge(demo_ring, 1.0) + tsc.track(small_bunch) + assert True + + # Test with different n_bins + @pytest.mark.parametrize("n_bins",[(1),(2),(100)]) + def test_n_bins_zero_behavior(self, demo_ring, small_bunch, n_bins): + tsc = TransverseSpaceCharge(demo_ring, 1.0, n_bins=n_bins) + tsc.track(small_bunch) + assert True \ No newline at end of file diff --git a/tests/unit/tracking/test_synchrotron.py b/tests/unit/tracking/test_synchrotron.py new file mode 100644 index 0000000..dc59e4a --- /dev/null +++ b/tests/unit/tracking/test_synchrotron.py @@ -0,0 +1,120 @@ +import numpy as np +import pytest +import at +from scipy.constants import c, e +from mbtrack2 import Electron, Synchrotron + +@pytest.fixture +def demo_ring(local_optics): + h = 20 + L = 100 + E0 = 1e9 + particle = Electron() + ac = 1e-3 + U0 = 250e3 + tau = np.array([10e-3, 10e-3, 5e-3]) + tune = np.array([18.2, 10.3]) + emit = np.array([50e-9, 50e-9*0.01]) + sigma_0 = 30e-12 + sigma_delta = 1e-3 + chro = [1.0,1.0] + + ring = Synchrotron(h, local_optics, particle, L=L, E0=E0, ac=ac, U0=U0, tau=tau, + emit=emit, tune=tune, sigma_delta=sigma_delta, + sigma_0=sigma_0, chro=chro) + + return ring + +@pytest.fixture +def demo_ring_h1(demo_ring): + demo_ring.h = 1 + return demo_ring + +@pytest.fixture +def ring_with_at_lattice(at_optics): + h = 416 + tau = np.array([6.56e-3, 6.56e-3, 3.27e-3]) + emit = np.array([3.9e-9, 3.9e-9*0.01]) + sigma_0 = 15e-12 + sigma_delta = 1.025e-3 + particle = Electron() + ring = Synchrotron(h, at_optics, particle, tau=tau, emit=emit, + sigma_0=sigma_0, sigma_delta=sigma_delta) + return ring + +class TestSynchrotron: + + def test_synchrotron_values(self, demo_ring): + h = 20 + L = 100 + E0 = 1e9 + particle = Electron() + ac = 1e-3 + U0 = 250e3 + tau = np.array([10e-3, 10e-3, 5e-3]) + tune = np.array([18.2, 10.3]) + emit = np.array([50e-9, 50e-9*0.01]) + sigma_0 = 30e-12 + sigma_delta = 1e-3 + chro = [1.0,1.0] + + assert pytest.approx(demo_ring.h) == h + assert pytest.approx(demo_ring.L) == L + assert pytest.approx(demo_ring.E0) == E0 + assert pytest.approx(demo_ring.U0) == U0 + assert pytest.approx(demo_ring.ac) == ac + np.testing.assert_allclose(demo_ring.tau, tau) + np.testing.assert_allclose(demo_ring.tune, tune) + np.testing.assert_allclose(demo_ring.emit, emit) + assert pytest.approx(demo_ring.sigma_0) == sigma_0 + assert pytest.approx(demo_ring.sigma_delta) == sigma_delta + np.testing.assert_allclose(demo_ring.chro, chro) + assert pytest.approx(demo_ring.T0) == L/c + assert pytest.approx(demo_ring.T1) == L/c/h + assert pytest.approx(demo_ring.f0) == c/L + assert pytest.approx(demo_ring.f1) == 1/(L/c/h) + assert pytest.approx(demo_ring.omega0) == 2 * np.pi * c/L + assert pytest.approx(demo_ring.omega1) == 2 * np.pi * 1/(L/c/h) + assert pytest.approx(demo_ring.k1) == 2 * np.pi * 1/(L/c/h) / c + assert pytest.approx(demo_ring.gamma) == E0 / (particle.mass * c**2 / e) + assert pytest.approx(demo_ring.beta) == np.sqrt(1 - (E0 / (particle.mass * c**2 / e))**-2) + + def test_synchrotron_mcf(self, demo_ring): + demo_ring.mcf_order = [5e-4, 1e-4, 1e-3] + assert pytest.approx(demo_ring.mcf(0.5)) == 5e-4*(0.5**2) + 1e-4*0.5 + 1e-3 + assert pytest.approx(demo_ring.eta(0.5)) == demo_ring.mcf(0.5) - 1 / (demo_ring.gamma**2) + + def test_synchrotron_tune(self, demo_ring): + tuneS = demo_ring.synchrotron_tune(1e6) + assert pytest.approx(tuneS, rel=1e-4) == 0.0017553 + + def test_synchrotron_sigma(self, demo_ring): + np.testing.assert_allclose(demo_ring.sigma(), np.array([2.23606798e-04, 2.23606798e-04, 2.23606798e-05, 2.23606798e-05])) + + def test_synchrotron_sigma_position(self, ring_with_at_lattice): + pos = np.linspace(0, ring_with_at_lattice.L, 100) + sig = ring_with_at_lattice.sigma(pos) + assert sig.shape == (4, 100) + + def test_get_adts(self, ring_with_at_lattice): + ring_with_at_lattice.get_adts() + assert ring_with_at_lattice.adts is not None + + def test_get_chroma(self, ring_with_at_lattice): + ring_with_at_lattice.get_chroma() + assert len(ring_with_at_lattice.chro) == 8 + + def test_get_mcf_order(self, ring_with_at_lattice): + ring_with_at_lattice.get_mcf_order() + assert len(ring_with_at_lattice.mcf_order) == 3 + + def test_synchrotron_long_twiss(self, demo_ring): + tuneS, long_alpha, long_beta, long_gamma = demo_ring.get_longitudinal_twiss(1e6, add=False) + assert pytest.approx(tuneS, rel=1e-4) == demo_ring.synchrotron_tune(1e6) + assert pytest.approx(long_alpha, rel=1e-4) == -0.0055146 + assert pytest.approx(long_beta, rel=1e-4) == 3.0236e-08 + assert pytest.approx(long_gamma, rel=1e-4) == 3.30736e7 + + def test_to_pyat(self, demo_ring): + pyat_simple_ring = demo_ring.to_pyat(1e6) + assert isinstance(pyat_simple_ring, at.lattice.lattice_object.Lattice) \ No newline at end of file diff --git a/tests/unit/tracking/test_wakepotential.py b/tests/unit/tracking/test_wakepotential.py new file mode 100644 index 0000000..8581839 --- /dev/null +++ b/tests/unit/tracking/test_wakepotential.py @@ -0,0 +1,316 @@ +import pytest +import numpy as np +import pandas as pd +from mbtrack2 import WakePotential, Resonator, LongRangeResistiveWall +from utility_test_functions import assert_attr_changed + +@pytest.fixture +def generate_resonator(): + def generate(time=np.linspace(-1000e-12, 1000e-12, int(1e5)), + frequency=np.linspace(0.1e6, 100e9, int(1e5)), + Rs=100, + fr=1e9, + Q=20, + plane="long"): + res = Resonator(time, frequency, Rs, fr, Q, plane) + return res + return generate + +@pytest.fixture +def generate_wakepotential(demo_ring, generate_resonator): + def generate(ring=demo_ring, + wakefield=None, + n_bin=80, + interp_on_postion=True, + **kwargs): + if wakefield is None: + wakefield = generate_resonator(**kwargs) + wp = WakePotential(ring, wakefield, n_bin, interp_on_postion) + return wp + return generate + +class TestWakePotential: + + # Compute charge density profile for a given Bunch object + def test_charge_density_profile(self, generate_wakepotential, small_bunch): + wp = generate_wakepotential() + wp.charge_density(small_bunch) + assert len(wp.rho) == len(wp.tau) + assert wp.dtau > 0 + + # Calculate dipole moment for a Bunch object on specified plane + @pytest.mark.parametrize("plane", [("x"), ("y")]) + def test_dipole_moment_calculation(self, generate_wakepotential, small_bunch, plane): + wp = generate_wakepotential() + wp.charge_density(small_bunch) + dipole = wp.dipole_moment(small_bunch, plane, wp.tau) + assert len(dipole) == len(wp.tau) + + # Prepare wake function for a specified wake type + @pytest.mark.parametrize("plane", [("x"), ("y"),("long")]) + def test_prepare_wakefunction_smaller_window(self, generate_wakepotential, plane): + wp = generate_wakepotential(plane=plane) + tau = np.linspace(-100e-12, 100e-12, int(1e5)) + if plane == "x" or plane == "y": + comp = f"W{plane}dip" + else: + comp = "Wlong" + tau0, dtau0, W0 = wp.prepare_wakefunction(comp, tau) + assert len(tau0) > 0 + assert len(W0) > 0 + + # Prepare wake function for a specified wake type + @pytest.mark.parametrize("plane", [("x"), ("y"),("long")]) + def test_prepare_wakefunction_larger_window(self, generate_wakepotential, plane): + wp = generate_wakepotential(plane=plane) + tau = np.linspace(-2000e-12, 2000e-12, int(1e5)) + if plane == "x" or plane == "y": + comp = f"W{plane}dip" + else: + comp = "Wlong" + tau0, dtau0, W0 = wp.prepare_wakefunction(comp, tau) + assert len(tau0) > 0 + assert len(W0) > 0 + + # Compute wake potential for a Bunch object and wake type + @pytest.mark.parametrize("plane", [("x"), ("y"),("long")]) + def test_compute_wakepotential(self, generate_wakepotential, small_bunch, plane): + wp = generate_wakepotential(plane=plane) + wp.charge_density(small_bunch) + if plane == "x" or plane == "y": + comp = f"W{plane}dip" + else: + comp = "Wlong" + tau0, Wp = wp.get_wakepotential(small_bunch, comp) + assert len(Wp) == len(tau0) + + # Track a Bunch object through the WakePotential element + @pytest.mark.parametrize("plane, attr", [("x","xp"), ("y","yp"),("long","delta")]) + def test_track_bunch(self, generate_wakepotential, small_bunch, plane, attr): + wp = generate_wakepotential(plane=plane) + assert_attr_changed(wp, small_bunch, attrs_changed=[attr]) + + # Track a Bunch object through the WakePotential element + @pytest.mark.parametrize("plane, attr", [("x","xp"), ("y","yp"),("long","delta")]) + def test_track_bunch_no_exact_interp(self, generate_wakepotential, small_bunch, plane, attr): + wp = generate_wakepotential(plane=plane, interp_on_postion=False) + assert_attr_changed(wp, small_bunch, attrs_changed=[attr]) + + # Handle empty Bunch object in track method + @pytest.mark.parametrize("plane, attr", [("x","xp"), ("y","yp"),("long","delta")]) + def test_track_empty_bunch(self, generate_wakepotential, small_bunch, plane, attr): + wp = generate_wakepotential(plane=plane) + small_bunch.alive[:] = False + assert_attr_changed(wp, small_bunch, attrs_changed=[attr], change=False) + + # Manage non-uniformly sampled wake functions + def test_non_uniform_sampling_error(self, generate_wakepotential): + with pytest.raises(ValueError): + wp = generate_wakepotential(time=np.logspace(1, 5, 50)) + wp.check_sampling() + + # Calculate reference loss factor and compare to Gaussian bunch + @pytest.mark.parametrize("plane", [("x"), ("y"),("long")]) + def test_reference_loss_factor_comparison(self, generate_wakepotential, large_bunch, plane): + # Generate a WakePotential instance + wp = generate_wakepotential(plane=plane) + + # Calculate the reference loss factor using the small bunch + large_bunch["x"] += 1e-3 + large_bunch["y"] += 1e-3 + wp.track(large_bunch) + loss_data = wp.reference_loss(large_bunch) + + # Check if the loss data is a DataFrame and contains expected columns + assert isinstance(loss_data, pd.DataFrame), "Loss data should be a DataFrame" + assert 'TD factor' in loss_data.columns, "DataFrame should contain 'TD factor' column" + assert 'FD factor' in loss_data.columns, "DataFrame should contain 'FD factor' column" + assert 'Relative error [%]' in loss_data.columns, "DataFrame should contain 'Relative error [%]' column" + + # Verify that the calculated loss factors are within a reasonable range + for index, row in loss_data.iterrows(): + assert abs(row['Relative error [%]']) < 1, f"Relative error for {index} is too high" + + # Reduce wake function sampling by a specified factor + def test_reduce_sampling(self, generate_wakepotential): + # Create a WakePotential instance with a mock wakefield + wp = generate_wakepotential() + + # Original length of the wake function data + original_length = len(wp.wakefield.Wlong.data.index) + + # Define a reduction factor + reduction_factor = 2 + + # Reduce the sampling of the wake function + wp.reduce_sampling(reduction_factor) + + # New length of the wake function data after reduction + new_length = len(wp.wakefield.Wlong.data.index) + + # Assert that the new length is approximately half of the original length + assert new_length == original_length // reduction_factor + + # Test plotting functions with different plot options + def test_plot_last_wake_with_various_options(self, generate_wakepotential, small_bunch): + wp = generate_wakepotential(plane="x") + wp.track(small_bunch) + + # Test with default options + fig1 = wp.plot_last_wake('Wxdip') + assert fig1 is not None + + # Test with plot_rho=False + fig2 = wp.plot_last_wake('Wxdip', plot_rho=False) + assert fig2 is not None + + # Test with plot_dipole=True + fig3 = wp.plot_last_wake('Wxdip', plot_dipole=True) + assert fig3 is not None + + # Test with plot_wake_function=False + fig4 = wp.plot_last_wake('Wxdip', plot_wake_function=False) + assert fig4 is not None + + @pytest.mark.parametrize("plane", [("x"), ("y"),("long")]) + def test_get_gaussian_wakepotential(self, generate_wakepotential, plane): + wp = generate_wakepotential(plane=plane) + if plane == "x" or plane == "y": + comp = f"W{plane}dip" + else: + comp = "Wlong" + + tau0, W0, Wp, profile0, dipole0 = wp.get_gaussian_wakepotential(1e-12, wake_type=comp) + + assert tau0 is not None + assert W0 is not None + assert Wp is not None + assert profile0 is not None + assert dipole0 is not None + + @pytest.mark.parametrize("plane", [("x"), ("y"),("long")]) + def test_plot_gaussian_wake(self, generate_wakepotential, plane): + wp = generate_wakepotential(plane=plane) + if plane == "x" or plane == "y": + comp = f"W{plane}dip" + else: + comp = "Wlong" + fig = wp.plot_gaussian_wake(sigma=10e-12, wake_type=comp) + assert fig is not None + +@pytest.fixture +def generate_lrrw(demo_ring, beam_uniform): + def generate(ring=demo_ring, + beam=beam_uniform, + length=demo_ring.L, + rho=1e-6, + radius=6e-12, + types=["Wlong", "Wxdip", "Wydip"], + nt=50, + x3=None, + y3=None): + lrrw = LongRangeResistiveWall(ring=ring, + beam=beam, + length=length, + rho=rho, + radius=radius, + types=types, + nt=nt, + x3=x3, + y3=y3) + return lrrw + return generate + +class TestLongRangeResistiveWall: + + # Test tracking w/ uniform beam + @pytest.mark.parametrize("types, attr", [("Wxdip","xp"), ("Wydip","yp"), ("Wlong","delta")]) + def test_track(self, generate_lrrw, beam_uniform, types, attr): + lrrw = generate_lrrw(types=types) + for i, bunch in enumerate(beam_uniform.not_empty): + setattr(self, "initial_attr_{i}", bunch[attr].copy()) + with np.errstate(over='ignore'): + lrrw.track(beam_uniform) + for i, bunch in enumerate(beam_uniform.not_empty): + assert not np.allclose(getattr(self, "initial_attr_{i}"), bunch[attr]) + + # Test tracking w/ non-uniform beam + @pytest.mark.parametrize("types, attr", [("Wxdip","xp"), ("Wydip","yp"), ("Wlong","delta")]) + def test_track_non_uniform(self, generate_lrrw, beam_non_uniform, types, attr): + lrrw = generate_lrrw(types=types, beam=beam_non_uniform) + for i, bunch in enumerate(beam_non_uniform.not_empty): + setattr(self, "initial_attr_{i}", bunch[attr].copy()) + with np.errstate(over='ignore'): + lrrw.track(beam_non_uniform) + for i, bunch in enumerate(beam_non_uniform.not_empty): + assert not np.allclose(getattr(self, "initial_attr_{i}"), bunch[attr]) + + # Test tracking w/ mpi beam + @pytest.mark.parametrize("types, attr", [("Wxdip","xp"), ("Wydip","yp"), ("Wlong","delta")]) + def test_track_mpi(self, generate_lrrw, beam_1bunch_mpi, types, attr): + lrrw = generate_lrrw(types=types, beam=beam_1bunch_mpi) + bunch = beam_1bunch_mpi[beam_1bunch_mpi.mpi.bunch_num] + initial_attr = bunch[attr].copy() + with np.errstate(over='ignore'): + lrrw.track(beam_1bunch_mpi) + lrrw.track(beam_1bunch_mpi) # two turns are needed to see the effect of the bunch on itself + assert not np.allclose(initial_attr, bunch[attr]) + + # test that two turns are needed to see the effect of the bunch on itself + def test_kick_signle_bunch(self, generate_lrrw, beam_1bunch_mpi): + lrrw = generate_lrrw(beam=beam_1bunch_mpi) + with np.errstate(over='ignore'): + lrrw.track(beam_1bunch_mpi) + kick = lrrw.get_kick(0, "Wlong") + assert kick == 0 + + @pytest.mark.parametrize("plane", [("long"), ("x"), ("y")]) + def test_wake(self, generate_lrrw, plane): + lrrw = generate_lrrw() + t = 1e-9 + if plane == "long": + wake = lrrw.Wlong(t) + else: + wake = lrrw.Wdip(t, plane) + assert isinstance(wake, float) + + # Update tables with a Beam object and verify the correct table updates + @pytest.mark.parametrize("beam", [("beam_uniform"), ("beam_1bunch_mpi")]) + def test_update_tables_with_beam(self, generate_lrrw, request, beam): + beam = request.getfixturevalue(beam) + lrrw = generate_lrrw() + initial_tau = np.copy(lrrw.tau) + initial_x = np.copy(lrrw.x) + initial_y = np.copy(lrrw.y) + initial_charge = np.copy(lrrw.charge) + lrrw.update_tables(beam) + assert not np.array_equal(lrrw.tau, initial_tau) + assert not np.array_equal(lrrw.x, initial_x) + assert not np.array_equal(lrrw.y, initial_y) + assert not np.array_equal(lrrw.charge, initial_charge) + + # Verify the behavior when the approximated wake functions are not valid + def test_invalid_approximated_wake_functions(self, demo_ring, beam_uniform): + demo_ring.T1 = 1e-18 # Set T1 to a very small value to trigger the error + with pytest.raises(ValueError): + LongRangeResistiveWall(demo_ring, beam_uniform, 100, 1e-6, 6e-12) + + # Test track_bunch method with different wake types and verify correct kick application + def test_track_bunch_kick_application(self, generate_lrrw, beam_uniform): + # Arrange + lrrw = generate_lrrw() + bunch = beam_uniform[0] + initial_delta = bunch["delta"].copy() + initial_xp = bunch["xp"].copy() + initial_yp = bunch["yp"].copy() + + # Act + lrrw.track_bunch(bunch, rank=0) + + # Assert + if "Wlong" in lrrw.types: + assert not np.array_equal(bunch["delta"], initial_delta), "Longitudinal kick not applied correctly" + if "Wxdip" in lrrw.types: + assert not np.array_equal(bunch["xp"], initial_xp), "Horizontal dipole kick not applied correctly" + if "Wydip" in lrrw.types: + assert not np.array_equal(bunch["yp"], initial_yp), "Vertical dipole kick not applied correctly" \ No newline at end of file diff --git a/tests/unit/utilities/test_optics.py b/tests/unit/utilities/test_optics.py new file mode 100644 index 0000000..c3d7fe0 --- /dev/null +++ b/tests/unit/utilities/test_optics.py @@ -0,0 +1,295 @@ +import numpy as np +import pytest +import at +import matplotlib.pyplot as plt +from pathlib import Path +from mbtrack2 import Optics, PhysicalModel + +@pytest.fixture +def local_optics(): + beta = np.array([1, 1]) + alpha = np.array([0, 0]) + dispersion = np.array([0, 0, 0, 0]) + local_optics = Optics(local_beta=beta, local_alpha=alpha, + local_dispersion=dispersion) + return local_optics + +@pytest.fixture +def at_optics(): + path_to_file = Path(__file__).parent + lattice_file = path_to_file / ".." / ".." / ".." / "examples" / "SOLEIL_OLD.mat" + at_optics = Optics(lattice_file=lattice_file) + return at_optics + +class TestOptics: + + # Initialize Optics with a valid lattice file and verify attributes are set correctly + def test_initialize_with_at_lattice(self, at_optics): + assert not at_optics.use_local_values + assert at_optics.lattice is not None + + # Initialize Optics with local parameters and verify attributes are set correctly + def test_initialize_with_local_parameters(self, local_optics): + gamma = (1 + local_optics.local_alpha**2) / local_optics.local_beta + assert local_optics.use_local_values + np.testing.assert_allclose(gamma, local_optics.local_gamma) + + # Load a lattice using load_from_AT and verify optic functions are interpolated + def test_interpolation(self, at_optics): + lattice = at_optics.lattice + + refpts = np.arange(0, len(lattice)) + twiss0, tune, chrom, twiss = at.linopt(lattice, + refpts=refpts, + get_chrom=True) + randnum = np.random.randint(0, len(lattice)) + pos = twiss.s_pos[randnum] + + np.testing.assert_allclose(at_optics.beta(pos), twiss.beta[randnum, :], rtol=1e-3, atol=1e-4) + np.testing.assert_allclose(at_optics.alpha(pos), twiss.alpha[randnum, :], rtol=1e-3, atol=1e-4) + np.testing.assert_allclose(at_optics.mu(pos), twiss.mu[randnum, :], rtol=1e-3, atol=1e-4) + np.testing.assert_allclose(at_optics.dispersion(pos), twiss.dispersion[randnum, :], rtol=1e-3, atol=1e-4) + + # Retrieve beta, alpha, gamma, dispersion, and mu functions at specified positions + def test_local_optic_functions(self, local_optics): + position = np.array([0.5]) + beta = np.squeeze(local_optics.beta(position)) + alpha = np.squeeze(local_optics.alpha(position)) + gamma = np.squeeze(local_optics.gamma(position)) + dispersion = np.squeeze(local_optics.dispersion(position)) + mu = np.squeeze(local_optics.mu(position)) + + np.testing.assert_allclose(beta, local_optics.local_beta) + np.testing.assert_allclose(alpha, local_optics.local_alpha) + np.testing.assert_allclose(gamma, local_optics.local_gamma) + np.testing.assert_allclose(dispersion, local_optics.local_dispersion) + np.testing.assert_allclose(mu, np.array([0, 0])) + + # Plot optical variables and verify the plot is generated correctly + def test_optics_plot(self, local_optics): + fig = local_optics.plot('beta', 'x') + assert fig is not None + +class TestPhysicalModel: + + @pytest.fixture + def generate_pm(self, ring_with_at_lattice): + def generate(ring=ring_with_at_lattice, + x_right=0.01, + y_top=0.02, + shape="rect", + rho=1e-7, + x_left=None, + y_bottom=None, + n_points=1e4 + ): + pm = PhysicalModel(ring, + x_right=x_right, + y_top=y_top, + shape=shape, + rho=rho, + x_left=x_left, + y_bottom=y_bottom, + n_points=n_points) + return pm + return generate + + # Initialize PhysicalModel with default symmetric aperture values and verify array shapes + def test_init_default_symmetric_aperture(self, generate_pm): + n_points=1e3 + pm = generate_pm(n_points=n_points) + assert pm.x_right.shape == (int(n_points),) + assert pm.x_left.shape == (int(n_points),) + assert np.allclose(pm.x_left, -pm.x_right) + assert np.allclose(pm.y_bottom, -pm.y_top) + + # Change aperture values in a specific section using change_values() with symmetric settings + def test_change_values_symmetric(self, generate_pm): + x_right_init = 0.01 + y_top_init = 0.02 + shape_init = 'rect' + rho_init = 1e-7 + model = generate_pm(x_right=x_right_init, + y_top=y_top_init, + shape=shape_init, + rho=rho_init) + x_right_modif = 0.015 + y_top_modif = 0.025 + shape_modif = 'elli' + rho_modif = 1e-6 + start_position = 10 + end_position = 20 + model.change_values(start_position, + end_position, + x_right=x_right_modif, + y_top=y_top_modif, + shape=shape_modif, + rho=rho_modif) + idx = (model.position > start_position) & (model.position < end_position) + idx2 = ((model.position[:-1] > start_position) & + (model.position[1:] < end_position)) + + # Assert modif + assert np.allclose(model.x_right[idx], x_right_modif) + assert np.allclose(model.x_left[idx], -x_right_modif) + assert np.allclose(model.y_top[idx], y_top_modif) + assert np.allclose(model.y_bottom[idx], -y_top_modif) + assert np.all(model.shape[idx2] == shape_modif) + assert np.allclose(model.rho[idx2], rho_modif) + # Assert init values + assert np.allclose(model.x_right[~idx], x_right_init) + assert np.allclose(model.x_left[~idx], -x_right_init) + assert np.allclose(model.y_top[~idx], y_top_init) + assert np.allclose(model.y_bottom[~idx], -y_top_init) + assert np.all(model.shape[~idx2] == shape_init) + assert np.allclose(model.rho[~idx2], rho_init) + + # Change values with sym=False and verify asymmetric updates + def test_change_values_asymmetric(self, generate_pm): + x_right_init = 0.01 + y_top_init = 0.02 + model = generate_pm(x_right=x_right_init, y_top=y_top_init) + model.change_values(10, 20, x_right=0.02, x_left=-0.015, sym=False) + idx = (model.position > 10) & (model.position < 20) + assert np.allclose(model.x_right[idx], 0.02) + assert np.allclose(model.x_left[idx], -0.015) + + # Create tapered transition between two positions with different aperture values + def test_taper_transition(self, generate_pm): + x_right_init = 0.01 + y_top_init = 0.02 + shape_init = 'rect' + rho_init = 1e-7 + model = generate_pm(x_right=x_right_init, + y_top=y_top_init, + shape=shape_init, + rho=rho_init) + x_right_start = 0.01 + x_right_end = 0.025 + shape_modif = 'elli' + rho_modif = 1e-6 + start_position = 10 + end_position = 20 + model.taper(start_position, + end_position, + x_right_start=x_right_start, + x_right_end=x_right_end, + shape=shape_modif, + rho=rho_modif) + idx = (model.position > start_position) & (model.position < end_position) + idx2 = ((model.position[:-1] > start_position) & + (model.position[1:] < end_position)) + + # Assert edge points + assert model.x_right[idx][0] == pytest.approx(x_right_start) + assert model.x_right[idx][-1] == pytest.approx(x_right_end) + assert model.x_left[idx][0] == pytest.approx(-x_right_start) + assert model.x_left[idx][-1] == pytest.approx(-x_right_end) + # Assert modif + assert np.all(model.x_right[idx][1:] > x_right_start) + assert np.all(model.x_left[idx][1:] < -x_right_start) + assert np.all(model.x_right[idx][:-1] < x_right_end) + assert np.all(model.x_left[idx][:-1] > -x_right_end) + assert np.all(model.shape[idx2] == shape_modif) + assert np.allclose(model.rho[idx2], rho_modif) + # Assert init values + assert np.allclose(model.x_right[~idx], x_right_init) + assert np.allclose(model.x_left[~idx], -x_right_init) + assert np.allclose(model.y_top, y_top_init) + assert np.allclose(model.y_bottom, -y_top_init) + assert np.all(model.shape[~idx2] == shape_init) + assert np.allclose(model.rho[~idx2], rho_init) + + # Create tapered transition between two positions with different aperture values + def test_taper_nonsym_transition(self, generate_pm): + x_right_init = 0.01 + y_top_init = 0.02 + shape_init = 'rect' + rho_init = 1e-7 + model = generate_pm(x_right=x_right_init, + y_top=y_top_init, + shape=shape_init, + rho=rho_init) + x_right_start = 0.01 + x_right_end = 0.025 + shape_modif = 'elli' + rho_modif = 1e-6 + start_position = 10 + end_position = 20 + model.taper(start_position, + end_position, + x_right_start=x_right_start, + x_right_end=x_right_end, + shape=shape_modif, + rho=rho_modif, + sym=False) + idx = (model.position > start_position) & (model.position < end_position) + idx2 = ((model.position[:-1] > start_position) & + (model.position[1:] < end_position)) + + # Assert edge points + assert model.x_right[idx][0] == pytest.approx(x_right_start) + assert model.x_right[idx][-1] == pytest.approx(x_right_end) + # Assert modif + assert np.all(model.x_right[idx][1:] > x_right_start) + assert np.all(model.x_right[idx][:-1] < x_right_end) + assert np.all(model.shape[idx2] == shape_modif) + assert np.allclose(model.rho[idx2], rho_modif) + # Assert init values + assert np.allclose(model.x_right[~idx], x_right_init) + assert np.allclose(model.x_left, -x_right_init) + assert np.allclose(model.y_top, y_top_init) + assert np.allclose(model.y_bottom, -y_top_init) + assert np.all(model.shape[~idx2] == shape_init) + assert np.allclose(model.rho[~idx2], rho_init) + + # Calculate effective radius for resistive wall with default right/top settings + def test_resistive_wall_effective_radius_default(self, generate_pm, ring_with_at_lattice): + model = generate_pm() + out = model.resistive_wall_effective_radius(ring_with_at_lattice.optics) + for val in out: + assert val > 0 + + # Plot aperture values and verify figure/axes objects are returned + def test_plot_aperture_returns(self, generate_pm): + model = generate_pm() + fig, axs = model.plot_aperture() + assert fig is not None + assert axs is not None + plt.close(fig) + + # Interpolate aperture values at arbitrary position using get_aperture() + def test_get_aperture_interpolation(self, generate_pm): + model = generate_pm() + aperture = model.get_aperture(15.5) + assert len(aperture) == 4 + assert all(isinstance(x, float) for x in aperture) + + # Handle zero resistivity sections in resistive_wall_effective_radius calculations + def test_zero_resistivity_handling(self, generate_pm, ring_with_at_lattice): + model = generate_pm() + model.change_values(10, 20, rho=0) + out = model.resistive_wall_effective_radius(ring_with_at_lattice.optics) + out[0] < ring_with_at_lattice.L + for val in out: + assert val > 0 + + # Handle start_position > end_position in change_values and taper methods + def test_invalid_position_order(self, generate_pm): + model = generate_pm(x_right=0.01) + with pytest.raises(ValueError): + model.change_values(20, 10, x_right=0.02) + with pytest.raises(ValueError): + model.taper(20, 10, x_right_start=0.01, x_right_end=0.03) + + # Handle positions outside ring length range + def test_positions_outside_range(self, generate_pm): + model = generate_pm(x_right=0.01) + with pytest.raises(ValueError): + model.change_values(-10, 10, x_right=0.02) + with pytest.raises(ValueError): + model.change_values(0, model.position[-1] + 10, x_right=0.02) + with pytest.raises(ValueError): + model.taper(-10, 10, x_right_start=0.01, x_right_end=0.03) + with pytest.raises(ValueError): + model.taper(0, model.position[-1] + 10, x_right_start=0.01, x_right_end=0.03) \ No newline at end of file diff --git a/tests/utility_test_functions.py b/tests/utility_test_functions.py new file mode 100644 index 0000000..1be6045 --- /dev/null +++ b/tests/utility_test_functions.py @@ -0,0 +1,63 @@ +import numpy as np +from mbtrack2 import Bunch, Beam + +def assert_attr_changed(element, + bunch, + attrs_changed=["xp", "yp", "delta"], + change=True): + + if isinstance(bunch, Bunch): + assert_attr_changed_bunch(element, bunch, attrs_changed, change=change) + elif isinstance(bunch, Beam): + assert_attr_changed_beam(element, bunch, attrs_changed, change=change) + else: + raise TypeError + +def assert_attr_changed_bunch(element, + bunch, + attrs_changed=["xp", "yp", "delta"], + change=True): + + attrs = ["x","xp","y","yp","tau","delta"] + attrs_unchanged = [attr for attr in attrs if attr not in attrs_changed] + + initial_values_changed = {attr: bunch[attr].copy() for attr in attrs_changed} + + initial_values_unchanged = {attr: bunch[attr].copy() for attr in attrs_unchanged} + + element.track(bunch) + + for attr in attrs_changed: + if change: + assert not np.array_equal(initial_values_changed[attr], bunch[attr]), f"{attr}" + else: + assert np.array_equal(initial_values_changed[attr], bunch[attr]), f"{attr}" + + for attr in attrs_unchanged: + assert np.array_equal(initial_values_unchanged[attr], bunch[attr]), f"{attr}" + +def assert_attr_changed_beam(element, + beam, + attrs_changed=["xp", "yp", "delta"], + change=True): + + attrs = ["x","xp","y","yp","tau","delta"] + attrs_unchanged = [attr for attr in attrs if attr not in attrs_changed] + + initial_values_changed_b = [{attr: bunch[attr].copy() for attr in attrs_changed} for bunch in beam] + + initial_values_unchanged_b = [{attr: bunch[attr].copy() for attr in attrs_unchanged} for bunch in beam] + + element.track(beam) + + for i, bunch in enumerate(beam): + initial_values_changed = initial_values_changed_b[i] + initial_values_unchanged = initial_values_unchanged_b[i] + for attr in attrs_changed: + if change and (bunch.charge != 0): + assert not np.array_equal(initial_values_changed[attr], bunch[attr]), f"{attr}" + else: + assert np.array_equal(initial_values_changed[attr], bunch[attr]), f"{attr}" + + for attr in attrs_unchanged: + assert np.array_equal(initial_values_unchanged[attr], bunch[attr]), f"{attr}" \ No newline at end of file -- GitLab