diff --git a/docs/docs/tutorials/component_collection.ipynb b/docs/docs/tutorials/component_collection.ipynb index 5a957afc..10fde6d3 100644 --- a/docs/docs/tutorials/component_collection.ipynb +++ b/docs/docs/tutorials/component_collection.ipynb @@ -56,18 +56,22 @@ "y = component_collection.evaluate(x)\n", "plt.plot(x, y, label='Component collection')\n", "\n", - "for component in component_collection.components:\n", + "for component in component_collection:\n", " y = component.evaluate(x)\n", " plt.plot(x, y, label=component.display_name)\n", "\n", "plt.legend()\n", - "plt.show()" + "plt.show()\n", + "\n", + "# Accessing components by name\n", + "gaussian_component = component_collection['Gaussian']\n", + "print(gaussian_component)" ] } ], "metadata": { "kernelspec": { - "display_name": "easydynamics_newbase", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -81,7 +85,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.12" + "version": "3.14.4" } }, "nbformat": 4, diff --git a/docs/docs/tutorials/components.ipynb b/docs/docs/tutorials/components.ipynb index 4934347b..145ddf06 100644 --- a/docs/docs/tutorials/components.ipynb +++ b/docs/docs/tutorials/components.ipynb @@ -15,7 +15,7 @@ { "cell_type": "code", "execution_count": null, - "id": "1", + "id": "df408006", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/data/create_fake_data.ipynb b/docs/docs/tutorials/data/create_fake_data.ipynb index 34ddd5f7..e4ed0ee8 100644 --- a/docs/docs/tutorials/data/create_fake_data.ipynb +++ b/docs/docs/tutorials/data/create_fake_data.ipynb @@ -46,7 +46,7 @@ "for i in range(Q.size):\n", " components = model.get_component_collection(i)\n", " offset = 0.0\n", - " components.components[0].area = 3.79 - 0.2 * Q[i].value\n", + " components[0].area = 3.79 - 0.2 * Q[i].value\n", "\n", "energy = sc.linspace(start=-3.0, stop=3.0, num=756, unit='meV', dim='energy')\n", "\n", @@ -86,14 +86,14 @@ "energy = sc.linspace(start=-3.0, stop=3.0, num=756, unit='meV', dim='energy')\n", "intensity_values = np.zeros((Q.size, energy.size))\n", "rng = np.random.default_rng()\n", - "noise = rng.normal(loc=0.0, scale=0.35, size=intensity_dataarray.shape)\n", + "noise = rng.normal(loc=0.0, scale=0.35, size=intensity_values.shape)\n", "\n", "for i in range(Q.size):\n", " components = model.get_component_collection(i)\n", " offset = sc.scalar(value=rng.uniform(0.05, 0.15), unit='meV')\n", - " components.components[0].area = 3.79 - 0.2 * Q[i].value\n", - " components.components[2].center = 1.4 + Q[i].value / 10.0\n", - " components.components[2].area = 2.45 + Q[i].value / 10.0\n", + " components[0].area = 3.79 - 0.2 * Q[i].value\n", + " components[2].center = 1.4 + Q[i].value / 10.0\n", + " components[2].area = 2.45 + Q[i].value / 10.0\n", "\n", " intensity_values[i, :] = components.evaluate(x=energy.values - offset.value)\n", "\n", diff --git a/docs/docs/tutorials/tutorial0_basics.ipynb b/docs/docs/tutorials/tutorial0_basics.ipynb index 4f1ad48e..dffa627c 100644 --- a/docs/docs/tutorials/tutorial0_basics.ipynb +++ b/docs/docs/tutorials/tutorial0_basics.ipynb @@ -24,9 +24,6 @@ "\n", "import easydynamics as edyn\n", "import easydynamics.sample_model as sm\n", - "from easydynamics.analysis import Analysis\n", - "from easydynamics.analysis import ParameterAnalysis\n", - "from easydynamics.analysis.parameter_analysis import FitBinding\n", "\n", "# Make the plots interactive\n", "%matplotlib widget" @@ -125,7 +122,7 @@ " \n", "\n", "\n", - "In this data we see a single Gaussian shaped peak and a background that seems to be zero on average. We now want to fit this data, e.g. to determine how the Gaussian changes with $Q$. We define a `Gaussian` like this:" + "In this data we see a single Gaussian shaped peak and a background that seems to be zero on average. We now want to fit this data, e.g. to determine how the Gaussian changes with $Q$. We define a `Gaussian` like this. The `name` will soon be used for indexing, while the `display_name` is what is displayed in figures. By defalut, `display_name` is the same as `name`." ] }, { @@ -135,7 +132,7 @@ "metadata": {}, "outputs": [], "source": [ - "gaussian = sm.Gaussian(display_name='Gaussian', area=1, width=0.05)" + "gaussian = sm.Gaussian(name='Gaussian', area=1, width=0.05)" ] }, { @@ -201,7 +198,7 @@ "metadata": {}, "outputs": [], "source": [ - "analysis = Analysis(\n", + "analysis = edyn.Analysis(\n", " experiment=experiment,\n", " sample_model=model,\n", ")" @@ -382,7 +379,8 @@ "outputs": [], "source": [ "energy = sc.linspace('energy', -3.5, 3.5, num=1001, unit='meV')\n", - "data_and_model = analysis.data_and_model_to_datagroup(energy=energy)" + "data_and_model = analysis.data_and_model_to_datagroup(energy=energy)\n", + "print(data_and_model)" ] }, { @@ -400,11 +398,13 @@ "metadata": {}, "outputs": [], "source": [ - "fit_func = sm.Polynomial(coefficients=[3.7, -0.5], display_name='Straight line')\n", + "fit_func = sm.Polynomial(\n", + " coefficients=[3.7, -0.5], name='Straight line', display_name='Straight line'\n", + ")\n", "\n", - "binding = FitBinding(parameter_name='Gaussian area', model=fit_func)\n", + "binding = edyn.FitBinding(parameter_name='Gaussian area', model=fit_func)\n", "\n", - "parameter_analysis = ParameterAnalysis(\n", + "parameter_analysis = edyn.ParameterAnalysis(\n", " parameters=analysis,\n", " bindings=[binding],\n", ")" @@ -468,7 +468,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "in16b", "language": "python", "name": "python3" }, diff --git a/docs/docs/tutorials/tutorial0_more_advanced.ipynb b/docs/docs/tutorials/tutorial0_more_advanced.ipynb index ee1e4e57..2ebc7120 100644 --- a/docs/docs/tutorials/tutorial0_more_advanced.ipynb +++ b/docs/docs/tutorials/tutorial0_more_advanced.ipynb @@ -73,9 +73,9 @@ "metadata": {}, "outputs": [], "source": [ - "gaussian = sm.Gaussian(display_name='Gaussian', area=3, width=0.05)\n", - "lorentzian = sm.Lorentzian(display_name='Lorentzian', area=2, width=0.3)\n", - "dho = sm.DampedHarmonicOscillator(display_name='DHO', area=1.5, width=0.2, center=1.5)\n", + "gaussian = sm.Gaussian(name='Gaussian', area=3, width=0.05)\n", + "lorentzian = sm.Lorentzian(name='Lorentzian', area=2, width=0.3)\n", + "dho = sm.DampedHarmonicOscillator(name='DHO', area=1.5, width=0.2, center=1.5)\n", "\n", "collection = sm.ComponentCollection()\n", "collection.append_component(gaussian)\n", @@ -245,7 +245,7 @@ "id": "af4103fb", "metadata": {}, "source": [ - "The fit looks very good. We can again get a list of the parameters for this fit by accesing the corresponding `Analysis1d` object. Note ethat the Gaussian and Lorentzian centers are both zero, but that the `energy_offset` is non-zero." + "The fit looks very good. We can again get a list of the parameters for this fit by accesing the corresponding `Analysis1d` object. Note that the Gaussian and Lorentzian centers are both zero, but that the `energy_offset` is non-zero." ] }, { @@ -310,14 +310,10 @@ "metadata": {}, "outputs": [], "source": [ - "gauss_fit_func = sm.Polynomial(\n", - " coefficients=[3.7, -0.5], unit='1/angstrom', display_name='Gauss area fit'\n", - ")\n", - "dho_area_fit_func = sm.Polynomial(\n", - " coefficients=[2.0, 0.12], unit='1/angstrom', display_name='DHO area fit'\n", - ")\n", + "gauss_fit_func = sm.Polynomial(coefficients=[3.7, -0.5], unit='1/angstrom', name='Gauss area fit')\n", + "dho_area_fit_func = sm.Polynomial(coefficients=[2.0, 0.12], unit='1/angstrom', name='DHO area fit')\n", "dho_center_fit_func = sm.Polynomial(\n", - " coefficients=[1.1, 0.2], unit='1/angstrom', display_name='DHO center fit'\n", + " coefficients=[1.1, 0.2], unit='1/angstrom', name='DHO center fit'\n", ")\n", "\n", "binding1 = edyn.FitBinding(parameter_name='Gaussian area', model=gauss_fit_func)\n", diff --git a/docs/docs/tutorials/tutorial1_brownian.ipynb b/docs/docs/tutorials/tutorial1_brownian.ipynb index ea0b90df..3341067f 100644 --- a/docs/docs/tutorials/tutorial1_brownian.ipynb +++ b/docs/docs/tutorials/tutorial1_brownian.ipynb @@ -8,9 +8,7 @@ "# Brownian Diffusion\n", "We here show how to set up an Analysis object and use it to first fit an artificial vanadium measurement to obtain the resolution. Next, we use the fitted resolution to fit an artificial measurement of a model with diffusion and some elastic scattering. \n", "\n", - "We extract and plot the relevant parameters. Finally, we show how to fit directly to the diffusion model.\n", - "\n", - "In the near future, it will be possible to fit the width and area of the Lorentzian to the diffusion model as well." + "We extract and plot the relevant parameters and fit them to a diffusion model. Finally, we show how to fit all the data simultaneously to the diffusion model." ] }, { @@ -23,20 +21,8 @@ "# Imports\n", "import pooch\n", "\n", - "from easydynamics.analysis.analysis import Analysis\n", - "from easydynamics.analysis.parameter_analysis import FitBinding\n", - "from easydynamics.analysis.parameter_analysis import ParameterAnalysis\n", - "from easydynamics.experiment import Experiment\n", - "from easydynamics.sample_model import BrownianTranslationalDiffusion\n", - "from easydynamics.sample_model import ComponentCollection\n", - "from easydynamics.sample_model import DeltaFunction\n", - "from easydynamics.sample_model import Gaussian\n", - "from easydynamics.sample_model import Lorentzian\n", - "from easydynamics.sample_model import Polynomial\n", - "from easydynamics.sample_model.background_model import BackgroundModel\n", - "from easydynamics.sample_model.instrument_model import InstrumentModel\n", - "from easydynamics.sample_model.resolution_model import ResolutionModel\n", - "from easydynamics.sample_model.sample_model import SampleModel\n", + "import easydynamics as edyn\n", + "import easydynamics.sample_model as sm\n", "\n", "# Make the plots interactive\n", "%matplotlib widget" @@ -60,7 +46,7 @@ "outputs": [], "source": [ "# Load the vanadium data\n", - "vanadium_experiment = Experiment('Vanadium')\n", + "vanadium_experiment = edyn.Experiment('Vanadium')\n", "\n", "file_path = pooch.retrieve(\n", " url='https://github.com/easyscience/dynamics-lib/raw/refs/heads/master/docs/docs/tutorials/data/vanadium_data_example.h5',\n", @@ -120,8 +106,8 @@ "metadata": {}, "outputs": [], "source": [ - "delta_function = DeltaFunction(display_name='DeltaFunction', area=1)\n", - "sample_model = SampleModel(components=delta_function)" + "delta_function = sm.DeltaFunction(name='DeltaFunction', area=1)\n", + "sample_model = sm.SampleModel(components=delta_function)" ] }, { @@ -143,11 +129,11 @@ "metadata": {}, "outputs": [], "source": [ - "resolution_components = ComponentCollection()\n", - "res_gauss = Gaussian(width=0.1, area=1, display_name='Res. Gauss')\n", + "resolution_components = sm.ComponentCollection()\n", + "res_gauss = sm.Gaussian(width=0.1, area=1, name='Res. Gauss')\n", "res_gauss.area.fixed = True\n", "resolution_components.append_component(res_gauss)\n", - "resolution_model = ResolutionModel(components=resolution_components)" + "resolution_model = sm.ResolutionModel(components=resolution_components)" ] }, { @@ -165,7 +151,7 @@ "metadata": {}, "outputs": [], "source": [ - "background_model = BackgroundModel(components=Polynomial(coefficients=[0.001]))" + "background_model = sm.BackgroundModel(components=sm.Polynomial(coefficients=[0.001]))" ] }, { @@ -183,7 +169,7 @@ "metadata": {}, "outputs": [], "source": [ - "instrument_model = InstrumentModel(\n", + "instrument_model = sm.InstrumentModel(\n", " resolution_model=resolution_model,\n", " background_model=background_model,\n", ")" @@ -204,7 +190,7 @@ "metadata": {}, "outputs": [], "source": [ - "vanadium_analysis = Analysis(\n", + "vanadium_analysis = edyn.Analysis(\n", " display_name='Vanadium Full Analysis',\n", " experiment=vanadium_experiment,\n", " sample_model=sample_model,\n", @@ -317,7 +303,7 @@ "metadata": {}, "outputs": [], "source": [ - "diffusion_experiment = Experiment('Diffusion')\n", + "diffusion_experiment = edyn.Experiment('Diffusion')\n", "\n", "file_path = pooch.retrieve(\n", " url='https://github.com/easyscience/dynamics-lib/raw/refs/heads/master/docs/docs/tutorials/data/diffusion_data_example.h5',\n", @@ -352,17 +338,17 @@ "metadata": {}, "outputs": [], "source": [ - "delta_function = DeltaFunction(display_name='DeltaFunction', area=0.2)\n", - "lorentzian = Lorentzian(display_name='Lorentzian', area=0.5, width=0.3)\n", - "component_collection = ComponentCollection(\n", + "delta_function = sm.DeltaFunction(name='DeltaFunction', area=0.2)\n", + "lorentzian = sm.Lorentzian(name='Lorentzian', area=0.5, width=0.3)\n", + "component_collection = sm.ComponentCollection(\n", " components=[delta_function, lorentzian],\n", ")\n", "\n", - "sample_model = SampleModel(\n", + "sample_model = sm.SampleModel(\n", " components=component_collection,\n", ")\n", "\n", - "background_model = BackgroundModel(components=Polynomial(coefficients=[0.001]))" + "background_model = sm.BackgroundModel(components=sm.Polynomial(coefficients=[0.001]))" ] }, { @@ -380,14 +366,14 @@ "metadata": {}, "outputs": [], "source": [ - "instrument_model = InstrumentModel(\n", + "instrument_model = sm.InstrumentModel(\n", " background_model=background_model,\n", " resolution_model=vanadium_analysis.instrument_model.resolution_model,\n", ")\n", "instrument_model.resolution_model.fix_all_parameters()\n", "instrument_model.normalize_resolution()\n", "\n", - "diffusion_analysis = Analysis(\n", + "diffusion_analysis = edyn.Analysis(\n", " display_name='Diffusion Analysis',\n", " experiment=diffusion_experiment,\n", " sample_model=sample_model,\n", @@ -496,17 +482,17 @@ "metadata": {}, "outputs": [], "source": [ - "brownian_diffusion_model = BrownianTranslationalDiffusion(\n", - " display_name='Brownian Translational Diffusion', diffusion_coefficient=2.4e-9, scale=0.5\n", + "brownian_diffusion_model = sm.BrownianTranslationalDiffusion(\n", + " name='Brownian Translational Diffusion', diffusion_coefficient=2.4e-9, scale=0.5\n", ")\n", "\n", - "binding = FitBinding(\n", + "binding = edyn.FitBinding(\n", " parameter_name='Lorentzian',\n", " model=brownian_diffusion_model,\n", " modes=['area', 'width'],\n", ")\n", "\n", - "parameter_analysis = ParameterAnalysis(\n", + "parameter_analysis = edyn.ParameterAnalysis(\n", " parameters=diffusion_analysis,\n", " bindings=[binding],\n", ")" @@ -596,20 +582,20 @@ "metadata": {}, "outputs": [], "source": [ - "delta_function = DeltaFunction(display_name='DeltaFunction', area=0.2)\n", - "component_collection = ComponentCollection(\n", + "delta_function = sm.DeltaFunction(name='DeltaFunction', area=0.2)\n", + "component_collection = sm.ComponentCollection(\n", " components=[delta_function],\n", ")\n", - "diffusion_model = BrownianTranslationalDiffusion(\n", - " display_name='Brownian Translational Diffusion', diffusion_coefficient=2.4e-9, scale=0.5\n", + "diffusion_model = sm.BrownianTranslationalDiffusion(\n", + " name='Brownian Translational Diffusion', diffusion_coefficient=2.4e-9, scale=0.5\n", ")\n", "\n", - "sample_model = SampleModel(\n", + "sample_model = sm.SampleModel(\n", " components=component_collection,\n", " diffusion_models=diffusion_model,\n", ")\n", "\n", - "background_model = BackgroundModel(components=Polynomial(coefficients=[0.001]))" + "background_model = sm.BackgroundModel(components=sm.Polynomial(coefficients=[0.001]))" ] }, { @@ -619,7 +605,7 @@ "metadata": {}, "outputs": [], "source": [ - "instrument_model = InstrumentModel(\n", + "instrument_model = sm.InstrumentModel(\n", " background_model=background_model,\n", " resolution_model=vanadium_analysis.instrument_model.resolution_model,\n", ")" @@ -640,7 +626,7 @@ "metadata": {}, "outputs": [], "source": [ - "diffusion_model_analysis = Analysis(\n", + "diffusion_model_analysis = edyn.Analysis(\n", " display_name='Diffusion Full Analysis',\n", " experiment=diffusion_experiment,\n", " sample_model=sample_model,\n", diff --git a/docs/docs/tutorials/tutorial2_nanoparticles.ipynb b/docs/docs/tutorials/tutorial2_nanoparticles.ipynb index 42eb15ed..2fb4fc4a 100644 --- a/docs/docs/tutorials/tutorial2_nanoparticles.ipynb +++ b/docs/docs/tutorials/tutorial2_nanoparticles.ipynb @@ -42,18 +42,8 @@ "import pooch\n", "import scipp as sc\n", "\n", - "from easydynamics.analysis.analysis import Analysis\n", - "from easydynamics.experiment import Experiment\n", - "from easydynamics.sample_model import ComponentCollection\n", - "from easydynamics.sample_model import DampedHarmonicOscillator\n", - "from easydynamics.sample_model import DeltaFunction\n", - "from easydynamics.sample_model import Gaussian\n", - "from easydynamics.sample_model import Lorentzian\n", - "from easydynamics.sample_model import Polynomial\n", - "from easydynamics.sample_model.background_model import BackgroundModel\n", - "from easydynamics.sample_model.instrument_model import InstrumentModel\n", - "from easydynamics.sample_model.resolution_model import ResolutionModel\n", - "from easydynamics.sample_model.sample_model import SampleModel\n", + "import easydynamics as edyn\n", + "import easydynamics.sample_model as sm\n", "from easydynamics.utils.utils import hbar\n", "\n", "# Make the plots interactive\n", @@ -75,7 +65,7 @@ "metadata": {}, "outputs": [], "source": [ - "resolution_experiment = Experiment(display_name='Nanoparticles, 1.5 K')\n", + "resolution_experiment = edyn.Experiment(display_name='Nanoparticles, 1.5 K')\n", "\n", "file_path = pooch.retrieve(\n", " url='https://github.com/easyscience/dynamics-lib/raw/refs/heads/master/docs/docs/tutorials/data/nano_1p5K.h5',\n", @@ -145,29 +135,29 @@ "metadata": {}, "outputs": [], "source": [ - "delta_function = DeltaFunction(area=100)\n", - "res_sample_model = SampleModel(components=delta_function)\n", + "delta_function = sm.DeltaFunction(area=100)\n", + "res_sample_model = sm.SampleModel(components=delta_function)\n", "\n", - "res_resolution_model = ResolutionModel()\n", - "res_components = ComponentCollection()\n", - "res_gauss = Gaussian(area=1, width=0.02)\n", + "res_resolution_model = sm.ResolutionModel()\n", + "res_components = sm.ComponentCollection()\n", + "res_gauss = sm.Gaussian(area=1, width=0.02)\n", "res_gauss.area.fixed = True\n", "\n", "res_components.append_component(res_gauss)\n", "res_resolution_model.components = res_components\n", "\n", - "background_model = BackgroundModel()\n", - "polynomial = Polynomial(coefficients=[1.5])\n", + "background_model = sm.BackgroundModel()\n", + "polynomial = sm.Polynomial(coefficients=[1.5])\n", "polynomial.coefficients[0].min = 0.0\n", "background_model.components = polynomial\n", "\n", "\n", - "res_instrument_model = InstrumentModel(\n", + "res_instrument_model = sm.InstrumentModel(\n", " resolution_model=res_resolution_model,\n", " background_model=background_model,\n", ")\n", "\n", - "res_analysis = Analysis(\n", + "res_analysis = edyn.Analysis(\n", " experiment=resolution_experiment,\n", " sample_model=res_sample_model,\n", " instrument_model=res_instrument_model,\n", @@ -210,10 +200,10 @@ "metadata": {}, "outputs": [], "source": [ - "experiment = Experiment(display_name='Nanoparticles, 150 K')\n", + "experiment = edyn.Experiment(display_name='Nanoparticles, 150 K')\n", "\n", "\n", - "resolution_experiment = Experiment(display_name='Nanoparticles, 1.5 K')\n", + "resolution_experiment = edyn.Experiment(display_name='Nanoparticles, 1.5 K')\n", "\n", "file_path = pooch.retrieve(\n", " url='https://github.com/easyscience/dynamics-lib/raw/refs/heads/master/docs/docs/tutorials/data/nano_150K.h5',\n", @@ -261,21 +251,21 @@ "metadata": {}, "outputs": [], "source": [ - "sample_model = SampleModel()\n", - "water_delta_function = DeltaFunction(display_name='Water delta function', area=100)\n", - "water_lorentzian = Lorentzian(display_name='Water Lorentzian', area=10, width=0.2)\n", + "sample_model = sm.SampleModel()\n", + "water_delta_function = sm.DeltaFunction(name='Water delta function', area=100)\n", + "water_lorentzian = sm.Lorentzian(name='Water Lorentzian', area=10, width=0.2)\n", "sample_model.append_component(water_delta_function)\n", "sample_model.append_component(water_lorentzian)\n", "sample_model.temperature = 150\n", "\n", "\n", - "background_model = BackgroundModel()\n", - "polynomial = Polynomial(coefficients=[0.15])\n", + "background_model = sm.BackgroundModel()\n", + "polynomial = sm.Polynomial(name='Polynomial', coefficients=[0.15])\n", "polynomial.coefficients[0].min = 0.0\n", "background_model.components = polynomial\n", "\n", "\n", - "instrument_model = InstrumentModel(\n", + "instrument_model = sm.InstrumentModel(\n", " background_model=background_model,\n", " resolution_model=res_analysis.instrument_model.resolution_model,\n", ")\n", @@ -283,7 +273,7 @@ "instrument_model.normalize_resolution()\n", "\n", "\n", - "analysis = Analysis(\n", + "analysis = edyn.Analysis(\n", " experiment=experiment, sample_model=sample_model, instrument_model=instrument_model\n", ")\n", "\n", @@ -344,7 +334,9 @@ "source": [ "We calculate a simple average of the relevant parameters, and fix these in our `Analysis` object at all Q. We plot the resulting model and see that it indeed fits well at low $Q$. At higher $Q$ it obviosuly does not describe all the signal, since there is magnetic scattering there as well.\n", "\n", - "The `DeltaFunction` is the first component (index 0), and the Lorentzian is the second component (index 1). It will soon be possible to refer to components by name as well as index. It will also be made easier to fix parameters at multiple $Q$, but for now we do it manually." + "To access the components we can either use their index, or more conveniently, their name as shown below.\n", + "\n", + "(It will be made easier to fix parameters at multiple $Q$, but for now we do it manually.)" ] }, { @@ -354,23 +346,23 @@ "metadata": {}, "outputs": [], "source": [ - "delta_0 = analysis.sample_model.get_component_collection(Q_index=0).components[0]\n", - "delta_1 = analysis.sample_model.get_component_collection(Q_index=1).components[0]\n", + "delta_0 = analysis.sample_model.get_component_collection(Q_index=0)['Water delta function']\n", + "delta_1 = analysis.sample_model.get_component_collection(Q_index=1)['Water delta function']\n", "delta_area = (delta_0.area + delta_1.area) / 2\n", "\n", "\n", - "lorz_0 = analysis.sample_model.get_component_collection(Q_index=0).components[1]\n", - "lorz_1 = analysis.sample_model.get_component_collection(Q_index=1).components[1]\n", + "lorz_0 = analysis.sample_model.get_component_collection(Q_index=0)['Water Lorentzian']\n", + "lorz_1 = analysis.sample_model.get_component_collection(Q_index=1)['Water Lorentzian']\n", "lorz_area = (lorz_0.area + lorz_1.area) / 2\n", "lorz_width = (lorz_0.width + lorz_1.width) / 2\n", "\n", "\n", "for Q_index in range(analysis.sample_model.Q.size):\n", - " delta = analysis.sample_model.get_component_collection(Q_index=Q_index).components[0]\n", + " delta = analysis.sample_model.get_component_collection(Q_index=Q_index)['Water delta function']\n", " delta.area = delta_area.value\n", " delta.area.fixed = True\n", "\n", - " lorz = analysis.sample_model.get_component_collection(Q_index=Q_index).components[1]\n", + " lorz = analysis.sample_model.get_component_collection(Q_index=Q_index)['Water Lorentzian']\n", " lorz.area = lorz_area.value\n", " lorz.width = lorz_width.value\n", " lorz.area.fixed = True\n", @@ -395,25 +387,25 @@ "outputs": [], "source": [ "# Now make a new analysis with this sample model\n", - "mag_sample_model = SampleModel()\n", - "water_delta_function = DeltaFunction(display_name='Water delta function', area=100)\n", - "water_lorentzian = Lorentzian(display_name='Water Lorentzian', area=100, width=0.2)\n", + "mag_sample_model = sm.SampleModel()\n", + "water_delta_function = sm.DeltaFunction(name='Water delta function', area=100)\n", + "water_lorentzian = sm.Lorentzian(name='Water Lorentzian', area=100, width=0.2)\n", "mag_sample_model.append_component(water_delta_function)\n", "mag_sample_model.append_component(water_lorentzian)\n", "\n", "# Add all the magnetic components\n", - "DHO1 = DampedHarmonicOscillator(display_name='DHO1', area=5, center=0.35, width=0.2)\n", - "DHO2 = DampedHarmonicOscillator(display_name='DHO2', area=1, center=1.1, width=0.1)\n", - "mag_lorz = Lorentzian(display_name='Magnetic Lorentzian', area=30, width=0.01)\n", + "DHO1 = sm.DampedHarmonicOscillator(name='DHO1', area=5, center=0.35, width=0.2)\n", + "DHO2 = sm.DampedHarmonicOscillator(name='DHO2', area=1, center=1.1, width=0.1)\n", + "mag_lorz = sm.Lorentzian(name='Magnetic Lorentzian', area=30, width=0.01)\n", "mag_sample_model.append_component(DHO1)\n", "mag_sample_model.append_component(DHO2)\n", "mag_sample_model.append_component(mag_lorz)\n", "\n", - "background_model = BackgroundModel()\n", - "polynomial = Polynomial(coefficients=[0.15])\n", + "background_model = sm.BackgroundModel()\n", + "polynomial = sm.Polynomial(name='Polynomial', coefficients=[0.15])\n", "background_model.components = polynomial\n", "\n", - "instrument_model = InstrumentModel(\n", + "instrument_model = sm.InstrumentModel(\n", " background_model=background_model,\n", " resolution_model=res_analysis.instrument_model.resolution_model,\n", ")\n", @@ -421,7 +413,7 @@ "instrument_model.normalize_resolution()\n", "\n", "# Create the analysis object\n", - "mag_analysis = Analysis(\n", + "mag_analysis = edyn.Analysis(\n", " experiment=experiment, sample_model=mag_sample_model, instrument_model=instrument_model\n", ")" ] @@ -442,11 +434,13 @@ "outputs": [], "source": [ "for Q_index in range(mag_analysis.sample_model.Q.size):\n", - " delta = mag_analysis.sample_model.get_component_collection(Q_index=Q_index).components[0]\n", + " delta = mag_analysis.sample_model.get_component_collection(Q_index=Q_index)[\n", + " 'Water delta function'\n", + " ]\n", " delta.area = delta_area.value\n", " delta.area.fixed = True\n", "\n", - " lorz = mag_analysis.sample_model.get_component_collection(Q_index=Q_index).components[1]\n", + " lorz = mag_analysis.sample_model.get_component_collection(Q_index=Q_index)['Water Lorentzian']\n", " lorz.area = lorz_area.value\n", " lorz.width = lorz_width.value\n", " lorz.area.fixed = True\n", @@ -469,9 +463,11 @@ "outputs": [], "source": [ "for Q_index in [0, 1]:\n", - " DHO1 = mag_analysis.sample_model.get_component_collection(Q_index=Q_index).components[2]\n", - " DHO2 = mag_analysis.sample_model.get_component_collection(Q_index=Q_index).components[3]\n", - " lorz = mag_analysis.sample_model.get_component_collection(Q_index=Q_index).components[4]\n", + " DHO1 = mag_analysis.sample_model.get_component_collection(Q_index=Q_index)['DHO1']\n", + " DHO2 = mag_analysis.sample_model.get_component_collection(Q_index=Q_index)['DHO2']\n", + " lorz = mag_analysis.sample_model.get_component_collection(Q_index=Q_index)[\n", + " 'Magnetic Lorentzian'\n", + " ]\n", "\n", " DHO1.area = 0.0\n", " DHO1.center = 1.0\n", @@ -524,8 +520,9 @@ "metadata": {}, "outputs": [], "source": [ - "DHO2_highQ = mag_analysis.sample_model.get_component_collection(Q_index=3).components[3]\n", - "DHO2_lowQ = mag_analysis.sample_model.get_component_collection(Q_index=2).components[3]\n", + "DHO2_highQ = mag_analysis.sample_model.get_component_collection(Q_index=3)['DHO2']\n", + "\n", + "DHO2_lowQ = mag_analysis.sample_model.get_component_collection(Q_index=2)['DHO2']\n", "\n", "DHO2_lowQ.width.make_dependent_on('a', {'a': DHO2_highQ.width})\n", "DHO2_lowQ.center.make_dependent_on('a', {'a': DHO2_highQ.center})\n", @@ -549,8 +546,8 @@ "metadata": {}, "outputs": [], "source": [ - "width1 = mag_analysis.sample_model.get_component_collection(Q_index=2).components[4].width\n", - "width2 = mag_analysis.sample_model.get_component_collection(Q_index=3).components[4].width\n", + "width1 = mag_analysis.sample_model.get_component_collection(Q_index=2)['Magnetic Lorentzian'].width\n", + "width2 = mag_analysis.sample_model.get_component_collection(Q_index=3)['Magnetic Lorentzian'].width\n", "\n", "width = (width1 + width2) / 2\n", "print(width1)\n", @@ -576,11 +573,11 @@ "metadata": {}, "outputs": [], "source": [ - "E_minus_003 = mag_analysis.sample_model.get_component_collection(Q_index=2).components[3].center\n", - "E_minus_101 = mag_analysis.sample_model.get_component_collection(Q_index=3).components[3].center\n", + "E_minus_003 = mag_analysis.sample_model.get_component_collection(Q_index=2)['DHO2'].center\n", + "E_minus_101 = mag_analysis.sample_model.get_component_collection(Q_index=3)['DHO2'].center\n", "\n", - "E_plus_003 = mag_analysis.sample_model.get_component_collection(Q_index=2).components[2].center\n", - "E_plus_101 = mag_analysis.sample_model.get_component_collection(Q_index=3).components[2].center\n", + "E_plus_003 = mag_analysis.sample_model.get_component_collection(Q_index=2)['DHO1'].center\n", + "E_plus_101 = mag_analysis.sample_model.get_component_collection(Q_index=3)['DHO1'].center\n", "\n", "print(E_minus_003)\n", "print(E_minus_101)\n", diff --git a/pixi.lock b/pixi.lock index 47a318c6..92f2af3c 100644 --- a/pixi.lock +++ b/pixi.lock @@ -20,7 +20,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.4.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -132,7 +132,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 @@ -181,7 +181,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/12/bbce9472f489cb5c4c23b0d13e5c59c37c1aab11b7ac637dfe6bbdccebe7/copier-9.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/51/ec641c26e6dca1b25a7d2035ba6ecb7c884ef1a100a9e42fbe4ce4405139/coverage-7.14.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl @@ -194,7 +194,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/36/e1/a8933a72c45a87177fbde2696e0d0755c8c9062f8c077a961c6215fa27b1/fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl @@ -220,7 +220,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/5d/f7e914f7d9325abff4057cee62c0fa70263683189f774473cbfb534cd13b/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/71/d6/48f5b9e44e2e760855d7b489b1317cd7620e82dcb73197961e5cc1391348/mdit_py_plugins-0.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -261,19 +261,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/85/545a951eecc270fcd688288c600017e2050a1aacb56c711d208586d3e470/pymdown_extensions-10.21.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/43/fe/ad0ecbe2393cb690a4b3100a8fea47ecfdb49f6e06f40cf2f626635adc0c/scipp-26.3.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -288,7 +288,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl @@ -305,7 +305,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.4.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -413,7 +413,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 @@ -462,7 +462,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/12/bbce9472f489cb5c4c23b0d13e5c59c37c1aab11b7ac637dfe6bbdccebe7/copier-9.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f3/9b/4165a1d56ddc302a0e2d518fd9d412a4fd0b57562618c78c5f21c57194f5/coverage-7.14.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl @@ -475,7 +475,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/27/d2/23d25e3f247b328be58d04a4c9f894178a0d1eda7d42867cfb388adaf416/fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl @@ -501,7 +501,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/86/86231232fff41c9f8e4a1a7d7a597d349a02527109c3af7d618366122139/matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/71/d6/48f5b9e44e2e760855d7b489b1317cd7620e82dcb73197961e5cc1391348/mdit_py_plugins-0.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -542,19 +542,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/85/545a951eecc270fcd688288c600017e2050a1aacb56c711d208586d3e470/pymdown_extensions-10.21.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/0f/0e/0eb94e64f5badef67f11fe1e448dde2a44f00940d8949f4adf71d560552e/scipp-26.3.1-cp314-cp314-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -569,7 +569,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl @@ -585,7 +585,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.4.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -679,7 +679,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py314h2359020_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312h343a6d4_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 @@ -704,9 +704,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_36.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_36.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_36.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda @@ -733,7 +733,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/12/bbce9472f489cb5c4c23b0d13e5c59c37c1aab11b7ac637dfe6bbdccebe7/copier-9.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/58/6e1b8f52fdc3184b47dc5037f5070d83a3d11042db1594b02d2a44d786c8/coverage-7.14.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl @@ -746,7 +746,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl @@ -772,7 +772,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/1a/5a4f747a8b271cbb024946d2dd3c913ab5032ba430626f8c3528ada96b4b/matplotlib-3.10.9-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/71/d6/48f5b9e44e2e760855d7b489b1317cd7620e82dcb73197961e5cc1391348/mdit_py_plugins-0.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -813,19 +813,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/85/545a951eecc270fcd688288c600017e2050a1aacb56c711d208586d3e470/pymdown_extensions-10.21.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/1f/28/3f8aa247d29d010547d52207395cb057ebd0a40b88f64bc1dbac9e17a729/scipp-26.3.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -840,7 +840,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl @@ -866,7 +866,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.4.0-py312h90b7ffd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.5.0-py312h90b7ffd_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -979,7 +979,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 @@ -1028,7 +1028,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/12/bbce9472f489cb5c4c23b0d13e5c59c37c1aab11b7ac637dfe6bbdccebe7/copier-9.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/ff/6699e7b71e60d3049eb2bdcbc95ee3f35707b2b0e48f32e9e63d3ce30c08/coverage-7.14.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl @@ -1041,7 +1041,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl @@ -1067,7 +1067,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/91/d024616abdba99e83120e07a20658976f6a343646710760c4a51df126029/matplotlib-3.10.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/71/d6/48f5b9e44e2e760855d7b489b1317cd7620e82dcb73197961e5cc1391348/mdit_py_plugins-0.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -1108,19 +1108,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/85/545a951eecc270fcd688288c600017e2050a1aacb56c711d208586d3e470/pymdown_extensions-10.21.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/1e/e7/cd78635d0ece7e4d3393f2c1d2ebabf6ff4bd615da142891b1d42ad58abf/scipp-26.3.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -1135,7 +1135,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl @@ -1152,7 +1152,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.4.0-py312h87c4bb7_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.5.0-py312h87c4bb7_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -1259,7 +1259,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 @@ -1308,7 +1308,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/12/bbce9472f489cb5c4c23b0d13e5c59c37c1aab11b7ac637dfe6bbdccebe7/copier-9.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/34/23/35c7aea1274aef7525bdd2dc92f710bdde6d11652239d71d1ec450067939/coverage-7.14.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl @@ -1321,7 +1321,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl @@ -1347,7 +1347,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/18/4880dd762e40cd360c1bf06e890c5a97b997e91cb324602b1a19950ad5ce/matplotlib-3.10.9-cp312-cp312-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/71/d6/48f5b9e44e2e760855d7b489b1317cd7620e82dcb73197961e5cc1391348/mdit_py_plugins-0.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -1388,19 +1388,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/85/545a951eecc270fcd688288c600017e2050a1aacb56c711d208586d3e470/pymdown_extensions-10.21.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/44/7b/537a61906eac58d94131273084d21d4eb219f5453f0ed30de3aca580a2b4/scipp-26.3.1-cp312-cp312-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -1415,7 +1415,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl @@ -1431,7 +1431,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.4.0-py312h06d0912_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.5.0-py312h06d0912_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -1524,7 +1524,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py312h05f76fc_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312h343a6d4_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 @@ -1549,9 +1549,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_36.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_36.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_36.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda @@ -1578,7 +1578,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/12/bbce9472f489cb5c4c23b0d13e5c59c37c1aab11b7ac637dfe6bbdccebe7/copier-9.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/85/19/93853133df2cb371083285ef6a93982a0173e7a233b0f61373ba9fd30eb2/coverage-7.14.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl @@ -1591,7 +1591,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/87/36/cccb9bc2a6ab63d1b2980374f0dca72ce95ae267c9b4cfe77455bb70d0d4/fonttools-4.63.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl @@ -1617,7 +1617,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/a1/4571fc46e7702de8d0c2dc54ad1b2f8e29328dea3ee90831181f7353d93c/matplotlib-3.10.9-cp312-cp312-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/71/d6/48f5b9e44e2e760855d7b489b1317cd7620e82dcb73197961e5cc1391348/mdit_py_plugins-0.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -1658,19 +1658,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/85/545a951eecc270fcd688288c600017e2050a1aacb56c711d208586d3e470/pymdown_extensions-10.21.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/1a/1f/86b4d15221096cb5500bcd73bf350745749e3ba056cdd7a7f75f126f154e/scipp-26.3.1-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -1685,7 +1685,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl @@ -1711,7 +1711,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.4.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -1823,7 +1823,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 @@ -1872,7 +1872,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/12/bbce9472f489cb5c4c23b0d13e5c59c37c1aab11b7ac637dfe6bbdccebe7/copier-9.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/51/ec641c26e6dca1b25a7d2035ba6ecb7c884ef1a100a9e42fbe4ce4405139/coverage-7.14.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl @@ -1885,7 +1885,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/36/e1/a8933a72c45a87177fbde2696e0d0755c8c9062f8c077a961c6215fa27b1/fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl @@ -1911,7 +1911,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/5d/f7e914f7d9325abff4057cee62c0fa70263683189f774473cbfb534cd13b/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/71/d6/48f5b9e44e2e760855d7b489b1317cd7620e82dcb73197961e5cc1391348/mdit_py_plugins-0.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -1952,19 +1952,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/85/545a951eecc270fcd688288c600017e2050a1aacb56c711d208586d3e470/pymdown_extensions-10.21.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/43/fe/ad0ecbe2393cb690a4b3100a8fea47ecfdb49f6e06f40cf2f626635adc0c/scipp-26.3.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -1979,7 +1979,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl @@ -1996,7 +1996,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.4.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -2104,7 +2104,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 @@ -2153,7 +2153,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/12/bbce9472f489cb5c4c23b0d13e5c59c37c1aab11b7ac637dfe6bbdccebe7/copier-9.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f3/9b/4165a1d56ddc302a0e2d518fd9d412a4fd0b57562618c78c5f21c57194f5/coverage-7.14.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl @@ -2166,7 +2166,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/27/d2/23d25e3f247b328be58d04a4c9f894178a0d1eda7d42867cfb388adaf416/fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl @@ -2192,7 +2192,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/86/86231232fff41c9f8e4a1a7d7a597d349a02527109c3af7d618366122139/matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/71/d6/48f5b9e44e2e760855d7b489b1317cd7620e82dcb73197961e5cc1391348/mdit_py_plugins-0.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -2233,19 +2233,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/85/545a951eecc270fcd688288c600017e2050a1aacb56c711d208586d3e470/pymdown_extensions-10.21.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/0f/0e/0eb94e64f5badef67f11fe1e448dde2a44f00940d8949f4adf71d560552e/scipp-26.3.1-cp314-cp314-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -2260,7 +2260,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl @@ -2276,7 +2276,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.4.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -2370,7 +2370,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py314h2359020_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312h343a6d4_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 @@ -2395,9 +2395,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_36.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_36.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_36.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda @@ -2424,7 +2424,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/12/bbce9472f489cb5c4c23b0d13e5c59c37c1aab11b7ac637dfe6bbdccebe7/copier-9.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/58/6e1b8f52fdc3184b47dc5037f5070d83a3d11042db1594b02d2a44d786c8/coverage-7.14.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl @@ -2437,7 +2437,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl @@ -2463,7 +2463,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/1a/5a4f747a8b271cbb024946d2dd3c913ab5032ba430626f8c3528ada96b4b/matplotlib-3.10.9-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/71/d6/48f5b9e44e2e760855d7b489b1317cd7620e82dcb73197961e5cc1391348/mdit_py_plugins-0.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -2504,19 +2504,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/85/545a951eecc270fcd688288c600017e2050a1aacb56c711d208586d3e470/pymdown_extensions-10.21.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/1f/28/3f8aa247d29d010547d52207395cb057ebd0a40b88f64bc1dbac9e17a729/scipp-26.3.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -2531,7 +2531,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl @@ -2557,7 +2557,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.4.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -2658,7 +2658,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 @@ -2703,9 +2703,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/88/34a0ccc7b9d4713fe6f484c678e69ee6c8146b4fab811d16994b939c65c7/easydynamics-0.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/0c/1f04cec38c1921cef464a99b57abc7b8e588a9ae5350378bc45330a60d7f/easydynamics-0.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/36/e1/a8933a72c45a87177fbde2696e0d0755c8c9062f8c077a961c6215fa27b1/fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f1/16/d905e7f53e661ce2c24686c38048d8e2b750ffc4350009d41c4e6c6c9826/h5py-3.16.0-cp314-cp314-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/12/b3/88c0ef22878c86035f058df0ac6c171319ffd0aa52a406455ed3a3847566/ipympl-0.10.0-py3-none-any.whl @@ -2750,7 +2750,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.4.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -2848,7 +2848,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 @@ -2893,9 +2893,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/88/34a0ccc7b9d4713fe6f484c678e69ee6c8146b4fab811d16994b939c65c7/easydynamics-0.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/0c/1f04cec38c1921cef464a99b57abc7b8e588a9ae5350378bc45330a60d7f/easydynamics-0.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/27/d2/23d25e3f247b328be58d04a4c9f894178a0d1eda7d42867cfb388adaf416/fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl - pypi: https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/6a/b7/9366ed44ced9b7ef357ab48c94205280276db9d7f064aa3012a97227e966/h5py-3.16.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/12/b3/88c0ef22878c86035f058df0ac6c171319ffd0aa52a406455ed3a3847566/ipympl-0.10.0-py3-none-any.whl @@ -2939,7 +2939,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.4.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -3032,7 +3032,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py314h2359020_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312h343a6d4_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 @@ -3057,9 +3057,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_36.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_36.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_36.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda @@ -3083,9 +3083,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/88/34a0ccc7b9d4713fe6f484c678e69ee6c8146b4fab811d16994b939c65c7/easydynamics-0.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/0c/1f04cec38c1921cef464a99b57abc7b8e588a9ae5350378bc45330a60d7f/easydynamics-0.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/3e/14/615a450205e1b56d16c6783f5ccd116cde05550faad70ae077c955654a75/h5py-3.16.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/12/b3/88c0ef22878c86035f058df0ac6c171319ffd0aa52a406455ed3a3847566/ipympl-0.10.0-py3-none-any.whl @@ -3497,9 +3497,9 @@ packages: - pkg:pypi/babel?source=hash-mapping size: 7684321 timestamp: 1772555330347 -- conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.4.0-py312h90b7ffd_0.conda - sha256: e8c83696e6529ac1909a96690c58624bb376312fd0768409380cd9b05e248c9b - md5: 542da724e75cdeef19e29cca23935c25 +- conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.5.0-py312h90b7ffd_0.conda + sha256: a2b08a4e5e549b5f67c38edffd175437e2208547a7e67b5fa5373b67ef419e50 + md5: b31dba71fe091e7201826e57e0f7b261 depends: - python - libgcc >=14 @@ -3508,22 +3508,23 @@ packages: - python_abi 3.12.* *_cp312 license: BSD-3-Clause AND MIT AND EPL-2.0 purls: - - pkg:pypi/backports-zstd?source=hash-mapping - size: 238360 - timestamp: 1777848717715 -- conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.4.0-py314h680f03e_0.conda + - pkg:pypi/backports-zstd?source=compressed-mapping + size: 239928 + timestamp: 1778594049826 +- conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda noarch: generic - sha256: de1755a35258eb1b59f2288559bbf0b76da60bd2fa6cd6f768ead442f85bd666 - md5: b712198b257f378e9bd8cde277218296 + sha256: a1c97297e867776760489537bc5ae36fa83a154be30e3b79385a39ca4cb058fe + md5: 1133126d840e75287d83947be3fc3e71 depends: - python >=3.14 license: BSD-3-Clause AND MIT AND EPL-2.0 - purls: [] - size: 7546 - timestamp: 1777848733980 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.4.0-py312h87c4bb7_0.conda - sha256: 7dbd64d3f06622ef8286be6dfceeb8e6008450fb4e6d9309fbb908b12f3937ff - md5: 95a833465ec45ac1e8f2ed1aaba8ec37 + purls: + - pkg:pypi/backports-zstd?source=compressed-mapping + size: 7533 + timestamp: 1778594057496 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.5.0-py312h87c4bb7_0.conda + sha256: a492dcf07b1c58797b3192f11aef7e3beb18ec91646d6a5acfe5c6e61e66118d + md5: 6ec306e02579965dc9c01092a5f4ce4c depends: - python - __osx >=11.0 @@ -3531,12 +3532,12 @@ packages: - python_abi 3.12.* *_cp312 license: BSD-3-Clause AND MIT AND EPL-2.0 purls: - - pkg:pypi/backports-zstd?source=hash-mapping - size: 239305 - timestamp: 1777848727027 -- conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.4.0-py312h06d0912_0.conda - sha256: 71caf40c0fdeb11fafaac639e6e6f9120112aa105a7a5e9dfb5b4b06db9ca97a - md5: 77d0a2bdd46dd8d502bb27eb80353fcd + - pkg:pypi/backports-zstd?source=compressed-mapping + size: 240840 + timestamp: 1778594074672 +- conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.5.0-py312h06d0912_0.conda + sha256: 55173c22b24fd257851f2967d4b0256172be3455bd5246b6b7a5c21eb0863f98 + md5: 891112b1a79fc9800317c5d56e056a8b depends: - python - vc >=14.3,<15 @@ -3546,9 +3547,9 @@ packages: - python_abi 3.12.* *_cp312 license: BSD-3-Clause AND MIT AND EPL-2.0 purls: - - pkg:pypi/backports-zstd?source=hash-mapping - size: 237107 - timestamp: 1777848740547 + - pkg:pypi/backports-zstd?source=compressed-mapping + size: 238601 + timestamp: 1778594083648 - pypi: https://files.pythonhosted.org/packages/36/da/87912ddec6e06feffbaa3d7aa18fc6352bee2e8f1fee185d7d1690f8f4e8/backrefs-7.0-py312-none-any.whl name: backrefs version: '7.0' @@ -4191,10 +4192,10 @@ packages: - pytest-xdist ; extra == 'test-no-images' - wurlitzer ; extra == 'test-no-images' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/0d/12/bbce9472f489cb5c4c23b0d13e5c59c37c1aab11b7ac637dfe6bbdccebe7/copier-9.15.1-py3-none-any.whl name: copier - version: 9.15.0 - sha256: 0f59c2ea36df42f3ded85c091c3f1e2c8d3814b537504f0abc8c2e508f7e013d + version: 9.15.1 + sha256: 040164686e45e7a841dcd4ae39b01e27093ff91242be3563cae883c4e24c55cc requires_dist: - colorama>=0.4.6 - dunamai>=1.7.0 @@ -4459,10 +4460,10 @@ packages: - importlib-metadata>=1.6.0 ; python_full_version < '3.8' - packaging>=20.9 requires_python: '>=3.5' -- pypi: https://files.pythonhosted.org/packages/5a/88/34a0ccc7b9d4713fe6f484c678e69ee6c8146b4fab811d16994b939c65c7/easydynamics-0.5.1-py3-none-any.whl +- pypi: ./ name: easydynamics - version: 0.5.1 - sha256: 35970c56932b2cb34e6f2d7fa508ef67978c4311dd9922529bf006b612f54e8a + version: 0.5.1+dev10 + sha256: 03e3e912335d4cddf2e70179da113fb35cb4cad133450b3a9e8583aaf5927e6e requires_dist: - darkdetect - easyscience @@ -4507,10 +4508,10 @@ packages: - validate-pyproject[all] ; extra == 'dev' - versioningit ; extra == 'dev' requires_python: '>=3.12' -- pypi: ./ +- pypi: https://files.pythonhosted.org/packages/4b/0c/1f04cec38c1921cef464a99b57abc7b8e588a9ae5350378bc45330a60d7f/easydynamics-0.6.0-py3-none-any.whl name: easydynamics - version: 0.5.1+dev4 - sha256: 03e3e912335d4cddf2e70179da113fb35cb4cad133450b3a9e8583aaf5927e6e + version: 0.6.0 + sha256: 05c54e85d48be3f91a4e9b3c921b159d4ae5281584c0943a56a90f04b9fe1fa2 requires_dist: - darkdetect - easyscience @@ -4642,10 +4643,10 @@ packages: version: 3.29.0 sha256: 96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl name: fonttools - version: 4.62.1 - sha256: 8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae + version: 4.63.0 + sha256: 37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4676,10 +4677,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/27/d2/23d25e3f247b328be58d04a4c9f894178a0d1eda7d42867cfb388adaf416/fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl name: fonttools - version: 4.62.1 - sha256: 9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42 + version: 4.63.0 + sha256: fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4710,10 +4711,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl +- pypi: https://files.pythonhosted.org/packages/36/e1/a8933a72c45a87177fbde2696e0d0755c8c9062f8c077a961c6215fa27b1/fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: fonttools - version: 4.62.1 - sha256: fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca + version: 4.63.0 + sha256: 308f957cdeaf8abe4e5f2f124902ef405448af92c90f80e302a3b771c2e6116b requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4744,10 +4745,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl +- pypi: https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: fonttools - version: 4.62.1 - sha256: 90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974 + version: 4.63.0 + sha256: 58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4778,10 +4779,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/87/36/cccb9bc2a6ab63d1b2980374f0dca72ce95ae267c9b4cfe77455bb70d0d4/fonttools-4.63.0-cp312-cp312-win_amd64.whl name: fonttools - version: 4.62.1 - sha256: 1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e + version: 4.63.0 + sha256: 59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4812,10 +4813,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl name: fonttools - version: 4.62.1 - sha256: 149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392 + version: 4.63.0 + sha256: 7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -6690,13 +6691,13 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/matplotlib-inline?source=compressed-mapping + - pkg:pypi/matplotlib-inline?source=hash-mapping size: 15725 timestamp: 1778264403247 -- pypi: https://files.pythonhosted.org/packages/71/d6/48f5b9e44e2e760855d7b489b1317cd7620e82dcb73197961e5cc1391348/mdit_py_plugins-0.6.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.1-py3-none-any.whl name: mdit-py-plugins - version: 0.6.0 - sha256: f7e7a25d8b616fee99cb1e330da73451d11a8061baf39bb9663ab9ce0e005b90 + version: 0.6.1 + sha256: 214c82fb2ac524472ab6a5bcab1de80f73b50443e187f401bfd77efbc7c6481d requires_dist: - markdown-it-py>=2.0.0,<5.0.0 - pre-commit ; extra == 'code-style' @@ -8602,10 +8603,10 @@ packages: - pkg:pypi/pygments?source=hash-mapping size: 893031 timestamp: 1774796815820 -- pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/7e/85/545a951eecc270fcd688288c600017e2050a1aacb56c711d208586d3e470/pymdown_extensions-10.21.3-py3-none-any.whl name: pymdown-extensions - version: 10.21.2 - sha256: 5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638 + version: 10.21.3 + sha256: d7a5d08014fc571e80ca21dd6f854e31f94c489800350564d55d15b3c41e76b6 requires_dist: - markdown>=3.6 - pyyaml @@ -8918,10 +8919,10 @@ packages: - pkg:pypi/python-dateutil?source=hash-mapping size: 233310 timestamp: 1751104122689 -- pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl name: python-discovery - version: 1.3.0 - sha256: 441d9ced3dfce36e113beb35ca302c71c7ef06f3c0f9c227a0b9bb3bd49b9e9f + version: 1.3.1 + sha256: ed188687ebb3b82c01a17cd5ac62fc94d9f6487a7f1a0f9dfe89753fec91039c requires_dist: - filelock>=3.15.4 - platformdirs>=4.3.6,<5 @@ -8929,6 +8930,8 @@ packages: - sphinx-autodoc-typehints>=3.6.3 ; extra == 'docs' - sphinx>=9.1 ; extra == 'docs' - sphinxcontrib-mermaid>=2 ; extra == 'docs' + - sphinxcontrib-towncrier>=0.4 ; extra == 'docs' + - towncrier>=25.8 ; extra == 'docs' - covdefaults>=2.3 ; extra == 'testing' - coverage>=7.5.4 ; extra == 'testing' - pytest-mock>=3.14 ; extra == 'testing' @@ -9313,9 +9316,9 @@ packages: - pkg:pypi/referencing?source=hash-mapping size: 51788 timestamp: 1760379115194 -- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.0-pyhcf101f3_0.conda - sha256: 4487fdb341537e2df47159ed8e546add99080974c52d5b2dc2a710910619115a - md5: a5985537dab1ba7034b5ff4ea22e2fa9 +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.1-pyhcf101f3_0.conda + sha256: 22ffa6f214829b9fb2daac5b25886cca73bd8de1fb9523791ce39478d60a58f7 + md5: 18a1731937516054803c047e1b39dc04 depends: - python >=3.10 - certifi >=2023.5.7 @@ -9328,8 +9331,8 @@ packages: license: Apache-2.0 purls: - pkg:pypi/requests?source=hash-mapping - size: 68658 - timestamp: 1778534036810 + size: 68706 + timestamp: 1778712882916 - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_0.conda sha256: 3b45efeae771f1a20307b36ecdb3a8911a89c05382836b50c62b0a99d8d3dfd8 md5: da94ff04d97ec5efc42cbe5da3c43a84 @@ -9473,20 +9476,20 @@ packages: - pkg:pypi/rpds-py?source=hash-mapping size: 235780 timestamp: 1764543046065 -- pypi: https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl name: ruff - version: 0.15.12 - sha256: c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d + version: 0.15.13 + sha256: 7064884d442b7d477b4e7473d12da7f08851d2b1982763c5d3f388a19468a1a4 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl name: ruff - version: 0.15.12 - sha256: fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5 + version: 0.15.13 + sha256: 1c26d2f66163deeb6e08d8b39fbbe983ce3c71cea06a6d7591cfd1421793c629 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: ruff - version: 0.15.12 - sha256: 83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0 + version: 0.15.13 + sha256: cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/0f/0e/0eb94e64f5badef67f11fe1e448dde2a44f00940d8949f4adf71d560552e/scipp-26.3.1-cp314-cp314-macosx_14_0_arm64.whl name: scipp @@ -10370,9 +10373,9 @@ packages: - tomli>=1.2.1 ; python_full_version < '3.11' and extra == 'all' - validate-pyproject-schema-store ; extra == 'store' requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda - sha256: 9dc40c2610a6e6727d635c62cced5ef30b7b30123f5ef67d6139e23d21744b3a - md5: 1e610f2416b6acdd231c5f573d754a0f +- conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_36.conda + sha256: 7c86d8ed3ac473c3e4dde0dd05aeb1f3189a26ad66c0e250f6cf4018e73358f2 + md5: 3466ff4a8753003eeb173f508d3d5a49 depends: - vc14_runtime >=14.44.35208 track_features: @@ -10380,33 +10383,33 @@ packages: license: BSD-3-Clause license_family: BSD purls: [] - size: 19356 - timestamp: 1767320221521 -- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - sha256: 02732f953292cce179de9b633e74928037fa3741eb5ef91c3f8bae4f761d32a5 - md5: 37eb311485d2d8b2c419449582046a42 + size: 19989 + timestamp: 1778688080106 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_36.conda + sha256: 902984f2282859a76d764d80d74f873df7c7749117cfac15c5106e086fb2b772 + md5: 65f5c81f2796961fcfd808eee8e73596 depends: - ucrt >=10.0.20348.0 - - vcomp14 14.44.35208 h818238b_34 + - vcomp14 14.44.35208 h818238b_36 constrains: - - vs2015_runtime 14.44.35208.* *_34 + - vs2015_runtime 14.44.35208.* *_36 license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime license_family: Proprietary purls: [] - size: 683233 - timestamp: 1767320219644 -- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda - sha256: 878d5d10318b119bd98ed3ed874bd467acbe21996e1d81597a1dbf8030ea0ce6 - md5: 242d9f25d2ae60c76b38a5e42858e51d + size: 683790 + timestamp: 1778688078434 +- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_36.conda + sha256: 0cd5b905ab2b5e9fcb170fe8801b64917effef8e3a73ffd9b2cc4c3ee387f09c + md5: 4aa1884260877bd57d16070d20271e2d depends: - ucrt >=10.0.20348.0 constrains: - - vs2015_runtime 14.44.35208.* *_34 + - vs2015_runtime 14.44.35208.* *_36 license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime license_family: Proprietary purls: [] - size: 115235 - timestamp: 1767320173250 + size: 115995 + timestamp: 1778688058077 - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl name: versioningit version: 3.3.0 @@ -10426,17 +10429,17 @@ packages: - mypy ; extra == 'test' - pretend ; extra == 'test' - pytest ; extra == 'test' -- pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl name: virtualenv - version: 21.3.1 - sha256: d1a71cf58f2f9228fff23a1f6ec15d39785c6b32e03658d104974247145edd35 + version: 21.3.3 + sha256: 7d5987d8369e098e41406efb780a3d4ca79280097293899e351a6407ee153ab3 requires_dist: - distlib>=0.3.7,<1 - filelock>=3.24.2,<4 ; python_full_version >= '3.10' - filelock>=3.16.1,<=3.19.1 ; python_full_version < '3.10' - importlib-metadata>=6.6 ; python_full_version < '3.8' - platformdirs>=3.9.1,<5 - - python-discovery>=1.2.2 + - python-discovery>=1.3.1 - typing-extensions>=4.13.2 ; python_full_version < '3.11' requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl diff --git a/src/easydynamics/analysis/analysis1d.py b/src/easydynamics/analysis/analysis1d.py index a97d0681..b3f0ad85 100644 --- a/src/easydynamics/analysis/analysis1d.py +++ b/src/easydynamics/analysis/analysis1d.py @@ -866,13 +866,11 @@ def _create_components_dataset_single_Q( A dictionary of component names to their corresponding sc.DataArrays. """ scipp_arrays = {} - sample_components = self.sample_model.get_component_collection( - Q_index=self.Q_index - ).components + sample_components = self.sample_model.get_component_collection(Q_index=self.Q_index) background_components = self.instrument_model.background_model.get_component_collection( Q_index=self.Q_index - ).components + ) if energy is None: energy = self._masked_energy diff --git a/src/easydynamics/base_classes/easydynamics_base.py b/src/easydynamics/base_classes/easydynamics_base.py index 1f38c9d3..530ed117 100644 --- a/src/easydynamics/base_classes/easydynamics_base.py +++ b/src/easydynamics/base_classes/easydynamics_base.py @@ -1,69 +1,55 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + from easyscience.base_classes.new_base import NewBase +from easydynamics.base_classes.name_mixin import NameMixin + -class EasyDynamicsBase(NewBase): +class EasyDynamicsBase(NameMixin, NewBase): """Base class for all EasyDynamics classes.""" def __init__( self, - name: str | None = 'MyEasyDynamicsModel', - display_name: str | None = 'MyEasyDynamicsModel', + *args: object, + name: str = 'MyEasyDynamicsModel', + display_name: str | None = None, unique_name: str | None = None, + **kwargs: object, ) -> None: """ Initialize the EasyDynamicsBase. Parameters ---------- - name : str | None, default='MyEasyDynamicsModel' + *args : object + Positional arguments to pass to the parent class. + name : str, default='MyEasyDynamicsModel' Name of the model. - display_name : str | None, default='MyEasyDynamicsModel' - Display name of the model. + display_name : str | None, default=None + Display name of the model. If None, the name will be used. unique_name : str | None, default=None Unique name of the model. If None, a unique name will be generated. + **kwargs : object + Additional keyword arguments to pass to the parent class. Raises ------ TypeError - If name is not a string or None. - """ - super().__init__(display_name=display_name, unique_name=unique_name) - - if name is not None and not isinstance(name, str): - raise TypeError('Name must be a string or None.') - self._name = name - - @property - def name(self) -> str | None: - """ - Get the name of the model. - - Returns - ------- - str | None - The name of the model. + If name is not a string. """ - return self._name - @name.setter - def name(self, name_str: str | None) -> None: - """ - Set the name of the model. + if not isinstance(name, str): + raise TypeError(f'Name must be a string, got {type(name)}') - Parameters - ---------- - name_str : str | None - The new name to set. - - Raises - ------ - TypeError - If name_str is not a string or None. - """ + if display_name is None: + display_name = name - if name_str is not None and not isinstance(name_str, str): - raise TypeError('Name must be a string or None.') - self._name = name_str + super().__init__( + *args, + name=name, + display_name=display_name, + unique_name=unique_name, + **kwargs, + ) diff --git a/src/easydynamics/base_classes/easydynamics_list.py b/src/easydynamics/base_classes/easydynamics_list.py new file mode 100644 index 00000000..b45248b3 --- /dev/null +++ b/src/easydynamics/base_classes/easydynamics_list.py @@ -0,0 +1,252 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +import warnings +from typing import TypeVar + +from easyscience.base_classes.easy_list import EasyList + +from easydynamics.base_classes.easydynamics_base import EasyDynamicsBase +from easydynamics.base_classes.easydynamics_modelbase import EasyDynamicsModelBase +from easydynamics.exceptions import AmbiguousNameError + +ProtectedType_ = TypeVar('T', bound=EasyDynamicsBase | EasyDynamicsModelBase) + + +class EasyDynamicsList(EasyList[ProtectedType_]): + """Base class for all EasyDynamics lists.""" + + def __init__( + self, + *args: ProtectedType_ | list[ProtectedType_], + protected_types: (list[type[ProtectedType_]] | type[ProtectedType_] | None) = None, + display_name: str | None = None, + unique_name: str | None = None, + **kwargs: object, + ) -> None: + """ + Initialize the EasyDynamicsList. + + Parameters + ---------- + *args : ProtectedType_ | list[ProtectedType_] + Initial items to add to the list. Can be a single item or a list of items. Each item + must be an instance of one of the protected types. + protected_types : list[type[ProtectedType_]] | type[ProtectedType_] | None, default=None + Types that are allowed in the list. Can be a single EasyDynamicsBase or + EasyDynamicsModelBase subclass or a list of them. If None, defaults to + [EasyDynamicsBase]. + display_name : str | None, default=None + Display name of the list. If None, the name will be used. + unique_name : str | None, default=None + Unique name of the list. If None, a unique name will be generated. + **kwargs : object + Additional keyword arguments to pass to the EasyList constructor. + """ + + if display_name is None: + display_name = unique_name + + super().__init__( + *args, + protected_types=protected_types, + display_name=display_name, + unique_name=unique_name, + **kwargs, + ) + + # ------------------------------------------------------------------ + # List methods + # ------------------------------------------------------------------ + + def insert(self, index: int, value: ProtectedType_) -> None: + """ + Insert an item into the list at a specific index. + + Parameters + ---------- + index : int + The index at which to insert the item. + value : ProtectedType_ + The item to insert. Must be an instance of one of the protected types. + """ + + # Overwritten to update warning + self._validate_type(value) + if value in self: + warnings.warn( + ( + f'Item with name "{self._get_key(value)}" already ' + f'in EasyDynamicsList, it will be ignored' + ), + UserWarning, + stacklevel=2, + ) + return + + super().insert(index, value) + + def append(self, value: ProtectedType_) -> None: + """ + Append an item to the end of the list. + Parameters + ---------- + value : ProtectedType_ + The item to append. Must be an instance of one of the protected types. + """ + self._validate_type(value) + + # append calls insert which checks for duplicates + super().append(value) + + def pop(self, index: int | str = -1) -> ProtectedType_: + """ + Remove and return an item at a specific index or name. + + Parameters + ---------- + index : int | str, default=-1 + The index or name at which to pop the item. + + Returns + ------- + ProtectedType_ + The item that was popped. + + Raises + ------ + TypeError + If index is not an int or str. + KeyError + If index is a str and no item with that name is found. + """ + + # Overwritten to update warning + if isinstance(index, int): + return self._data.pop(index) + if isinstance(index, str): + for i, item in enumerate(self._data): + if self._get_key(item) == index: + return self._data.pop(i) + raise KeyError(f'No item with name "{index}" found') + raise TypeError('Index must be an int or str') + + # ------------------------------------------------------------------ + # Other methods + # ------------------------------------------------------------------ + + def get_names(self) -> list[str]: + """ + Get a list of the names of all items in the list. + + Returns + ------- + list[str] + A list of the names of all items in the list. + """ + return [item.name for item in self._data] + + def get_duplicate_names(self) -> list[str]: + """ + Get a list of duplicate names in the list. + + Returns + ------- + list[str] + A list of duplicate names in the list. + """ + seen = set() + duplicates = set() + for item in self._data: + name = item.name + if name in seen: + duplicates.add(name) + else: + seen.add(name) + return list(duplicates) + + # ------------------------------------------------------------------ + # Private methods + # ------------------------------------------------------------------ + + def _get_key(self, obj: EasyDynamicsBase | EasyDynamicsModelBase) -> str: + """ + Get the name of an object. + + Parameters + ---------- + obj : EasyDynamicsBase | EasyDynamicsModelBase + Object to get the key for. + + Returns + ------- + str + The name of the object. + """ + return obj.name + + def _validate_type(self, value: object) -> None: + """ + Validate that a value is an instance of one of the protected types. + + Parameters + ---------- + value : object + The value to validate. + + Raises + ------ + TypeError + If the value is not an instance of one of the protected types. + """ + + if not isinstance(value, tuple(self._protected_types)): + allowed = ', '.join(t.__name__ for t in self._protected_types) + raise TypeError( + f'Value must be an instance of type: {allowed}. Got {type(value).__name__} instead.' # noqa: E501 + ) + + # ------------------------------------------------------------------ + # dunder methods + # ------------------------------------------------------------------ + def __getitem__( + self, idx: int | slice | str + ) -> ProtectedType_ | EasyDynamicsList[ProtectedType_]: + """ + Get an item by index, slice, or unique_name. + + Parameters + ---------- + idx : int | slice | str + Index, slice, or name of the item to get. + + Returns + ------- + ProtectedType_ | EasyDynamicsList[ProtectedType_] + The item at the specified index or name, or a new EasyDynamicsList if a slice is + provided. + + Raises + ------ + TypeError + If idx is not an int, slice, or str. + KeyError + If idx is a str and no item with that name is found. + AmbiguousNameError + If idx is a str and multiple items with that name are found. + """ + if isinstance(idx, int): + return self._data[idx] + if isinstance(idx, slice): + return self.__class__(self._data[idx], protected_types=self._protected_types) + if isinstance(idx, str): + matches = [r for r in self._data if self._get_key(r) == idx] + if len(matches) == 1: + return matches[0] + if len(matches) > 1: + raise AmbiguousNameError(idx, matches) + + raise KeyError(f'No item with name "{idx}" found') + raise TypeError('Index must be an int, slice, or str') diff --git a/src/easydynamics/base_classes/easydynamics_modelbase.py b/src/easydynamics/base_classes/easydynamics_modelbase.py index bf266086..6d002f71 100644 --- a/src/easydynamics/base_classes/easydynamics_modelbase.py +++ b/src/easydynamics/base_classes/easydynamics_modelbase.py @@ -4,44 +4,61 @@ import scipp as sc from easyscience.base_classes import ModelBase +from easydynamics.base_classes.name_mixin import NameMixin from easydynamics.utils.utils import _validate_unit -class EasyDynamicsModelBase(ModelBase): +class EasyDynamicsModelBase(NameMixin, ModelBase): """Base class for all EasyDynamics models.""" def __init__( self, + *args: object, unit: str | sc.Unit = 'meV', - name: str | None = 'MyEasyDynamicsModel', - display_name: str | None = 'MyEasyDynamicsModel', + name: str = 'MyEasyDynamicsModel', + display_name: str | None = None, unique_name: str | None = None, + **kwargs: object, ) -> None: """ Initialize the EasyDynamicsModelBase. Parameters ---------- + *args : object + Positional arguments to pass to the parent class. unit : str | sc.Unit, default='meV' Unit of the model. - name : str | None, default='MyEasyDynamicsModel' + name : str, default='MyEasyDynamicsModel' Name of the model. - display_name : str | None, default='MyEasyDynamicsModel' - Display name of the model. + display_name : str | None, default=None + Display name of the model. If None, the name will be used. unique_name : str | None, default=None Unique name of the model. If None, a unique name will be generated. + **kwargs : object + Additional keyword arguments to pass to the parent class. Raises ------ TypeError - If name is not a string or None. + If name is not a string. """ - super().__init__(display_name=display_name, unique_name=unique_name) - self._unit = _validate_unit(unit) - if name is not None and not isinstance(name, str): - raise TypeError('Name must be a string or None.') - self._name = name + if not isinstance(name, str): + raise TypeError(f'Name must be a string, got {type(name)}') + + if display_name is None: + display_name = name + + super().__init__( + *args, + name=name, + display_name=display_name, + unique_name=unique_name, + **kwargs, + ) + + self._unit = _validate_unit(unit) @property def unit(self) -> str | sc.Unit | None: @@ -75,35 +92,3 @@ def unit(self, _unit_str: str) -> None: f'Unit is read-only. Use convert_unit to change the unit between allowed types ' f'or create a new {self.__class__.__name__} with the desired unit.' ) - - @property - def name(self) -> str | None: - """ - Get the name of the model. - - Returns - ------- - str | None - The name of the model. - """ - return self._name - - @name.setter - def name(self, name_str: str) -> None: - """ - Set the name of the model. - - Parameters - ---------- - name_str : str - The new name to set. - - Raises - ------ - TypeError - If name_str is not a string or None. - """ - - if name_str is not None and not isinstance(name_str, str): - raise TypeError('Name must be a string or None.') - self._name = name_str diff --git a/src/easydynamics/base_classes/name_mixin.py b/src/easydynamics/base_classes/name_mixin.py new file mode 100644 index 00000000..608ce561 --- /dev/null +++ b/src/easydynamics/base_classes/name_mixin.py @@ -0,0 +1,67 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + + +class NameMixin: + """Mixin class to add name functionality to EasyDynamics classes.""" + + def __init__( + self, + *args: object, + name: str = 'MyEasyDynamicsModel', + **kwargs: object, + ) -> None: + """ + Initialize the NameMixin. + + Parameters + ---------- + *args : object + Positional arguments to pass to the parent class. + name : str, default='MyEasyDynamicsModel' + Name of the model. + **kwargs : object + Keyword arguments to pass to the parent class. + + Raises + ------ + TypeError + If name is not a string. + """ + + super().__init__(*args, **kwargs) + if not isinstance(name, str): + raise TypeError('Name must be a string.') + self._name = name + + @property + def name(self) -> str: + """ + Get the name of the model. + + Returns + ------- + str + The name of the model. + """ + return self._name + + @name.setter + def name(self, name_str: str) -> None: + """ + Set the name of the model. + + Parameters + ---------- + name_str : str + The new name to set. + + Raises + ------ + TypeError + If name_str is not a string. + """ + + if not isinstance(name_str, str): + raise TypeError('Name must be a string.') + self._name = name_str diff --git a/src/easydynamics/convolution/analytical_convolution.py b/src/easydynamics/convolution/analytical_convolution.py index ce4933b2..5ab305cf 100644 --- a/src/easydynamics/convolution/analytical_convolution.py +++ b/src/easydynamics/convolution/analytical_convolution.py @@ -94,15 +94,12 @@ def convolution( self.energy. """ - sample_components = self.sample_components.components - resolution_components = self.resolution_components.components - total = np.zeros_like(self.energy.values, dtype=float) - for sample_component in sample_components: + for sample_component in self.sample_components: # Go through resolution components, # adding analytical contributions - for resolution_component in resolution_components: + for resolution_component in self.resolution_components: contrib = self._convolute_analytic_pair( sample_component=sample_component, resolution_component=resolution_component, diff --git a/src/easydynamics/convolution/convolution.py b/src/easydynamics/convolution/convolution.py index 63bd66c6..8a11e05f 100644 --- a/src/easydynamics/convolution/convolution.py +++ b/src/easydynamics/convolution/convolution.py @@ -139,7 +139,7 @@ def convolution( total += self._numerical_convolver.convolution() # Delta function components - if self._delta_sample_components.components: + if self._delta_sample_components: total += self._convolve_delta_functions() return total @@ -159,7 +159,7 @@ def _convolve_delta_functions(self) -> np.ndarray: * self._resolution_components.evaluate( self.energy_with_offset.values - delta.center.value ) - for delta in self._delta_sample_components.components + for delta in self._delta_sample_components ) def _check_if_pair_is_analytic( @@ -221,7 +221,7 @@ def _build_convolution_plan(self) -> None: delta_sample_components = ComponentCollection() numerical_sample_components = ComponentCollection() - for sample_component in self._sample_components.components: + for sample_component in self._sample_components: # If delta function, put in delta sample model and go to the # next component if isinstance(sample_component, DeltaFunction): @@ -242,7 +242,7 @@ def _build_convolution_plan(self) -> None: # this sample component pair_is_analytic = [ self._check_if_pair_is_analytic(sample_component, resolution_component) - for resolution_component in self._resolution_components.components + for resolution_component in self._resolution_components ] # If all resolution components can be convolved analytically # with this sample component, add it to analytical @@ -268,7 +268,7 @@ def _set_convolvers(self) -> None: convolution method. """ - if self._analytical_sample_components.components: + if self._analytical_sample_components: self._analytical_convolver = AnalyticalConvolution( energy=self.energy, energy_offset=self.energy_offset, @@ -278,7 +278,7 @@ def _set_convolvers(self) -> None: else: self._analytical_convolver = None - if self._numerical_sample_components.components: + if self._numerical_sample_components: self._numerical_convolver = NumericalConvolution( energy=self.energy, energy_offset=self.energy_offset, diff --git a/src/easydynamics/convolution/numerical_convolution_base.py b/src/easydynamics/convolution/numerical_convolution_base.py index 7001e32b..9a6e80cd 100644 --- a/src/easydynamics/convolution/numerical_convolution_base.py +++ b/src/easydynamics/convolution/numerical_convolution_base.py @@ -429,7 +429,7 @@ def _check_width_thresholds( """ # Handle ComponentCollection or ModelComponent - components = model.components if isinstance(model, ComponentCollection) else [model] + components = model if isinstance(model, ComponentCollection) else [model] for comp in components: if hasattr(comp, 'width'): diff --git a/src/easydynamics/exceptions.py b/src/easydynamics/exceptions.py new file mode 100644 index 00000000..e21f30a2 --- /dev/null +++ b/src/easydynamics/exceptions.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + + +class AmbiguousNameError(Exception): + def __init__(self, name: str, matches: list[str]) -> None: + self.name = name + self.matches = matches + super().__init__(f"Ambiguous name '{name}' matches {len(matches)} elements: {matches}") diff --git a/src/easydynamics/sample_model/component_collection.py b/src/easydynamics/sample_model/component_collection.py index f5f1a45a..11e6574a 100644 --- a/src/easydynamics/sample_model/component_collection.py +++ b/src/easydynamics/sample_model/component_collection.py @@ -3,112 +3,112 @@ from __future__ import annotations +import importlib import warnings from typing import TYPE_CHECKING import numpy as np import scipp as sc -from easyscience.base_classes.model_base import ModelBase from easyscience.variable import DescriptorBase from easyscience.variable import Parameter +from easydynamics.base_classes.easydynamics_list import EasyDynamicsList +from easydynamics.base_classes.easydynamics_modelbase import EasyDynamicsModelBase from easydynamics.sample_model.components.model_component import ModelComponent if TYPE_CHECKING: from easydynamics.utils.utils import Numeric -class ComponentCollection(ModelBase): +class ComponentCollection(EasyDynamicsList, EasyDynamicsModelBase): """ - Collection of model components representing a sample, background or resolution model. + Collection of model components. + + Examples + -------- + Create a ComponentCollection with two components: + >>> import easydynamics.sample_model as sm + >>> component1 = sm.Gaussian(name='Gaussian1', area=1.0, width=1.0) + >>> component2 = sm.Lorentzian(name='Lorentzian1', area=2.0, width=0.5) + >>> collection = sm.ComponentCollection(components=[component1, component2]) + + Append a component to the collection: + >>> component3 = sm.Gaussian(name='Gaussian2', area=0.5, width=0.8) + >>> collection.append(component3) + + Evaluate the collection at a given energy axis: + >>> import numpy as np + >>> x = np.linspace(-5, 5, 100) + >>> values = collection.evaluate(x) + + Remove a component by name: + >>> collection.remove('Gaussian1') + + List component names: + >>> collection.list_component_names() + ['Lorentzian1', 'Gaussian2'] """ def __init__( self, + components: ModelComponent | list[ModelComponent] | None = None, unit: str | sc.Unit = 'meV', - display_name: str | None = 'MyComponentCollection', + name: str = 'ComponentCollection', + display_name: str | None = None, unique_name: str | None = None, - components: list[ModelComponent] | None = None, ) -> None: """ Initialize a new ComponentCollection. Parameters ---------- + components : ModelComponent | list[ModelComponent] | None, default=None + Initial model components to add to the ComponentCollection. unit : str | sc.Unit, default='meV' Unit of the collection. - display_name : str | None, default='MyComponentCollection' + name : str, default='ComponentCollection' + Name of the collection. + display_name : str | None, default=None Display name of the collection. unique_name : str | None, default=None Unique name of the collection. - components : list[ModelComponent] | None, default=None - Initial model components to add to the ComponentCollection. Raises ------ TypeError If unit is not a string or sc.Unit, or if components is not a list of ModelComponent. """ - - super().__init__(display_name=display_name, unique_name=unique_name) - - if unit is not None and not isinstance(unit, (str, sc.Unit)): + if components is None: + components = [] + if isinstance(components, ModelComponent): + components = [components] + elif not isinstance(components, list): raise TypeError( - f'unit must be None, a string, or a scipp Unit, got {type(unit).__name__}' + f'components must be a ModelComponent or a list of ModelComponent, got {type(components).__name__} instead.' # noqa: E501 ) - self._unit = unit - self._components = [] - - # Add initial components if provided. Used for serialization. - if components is not None: - if not isinstance(components, list): - raise TypeError('components must be a list of ModelComponent instances.') - for comp in components: - self.append_component(comp) - - # ------------------------------------------------------------------ - # Properties - # ------------------------------------------------------------------ - - @property - def components(self) -> list[ModelComponent]: - """ - Get the list of components in the collection. - - Returns - ------- - list[ModelComponent] - The components in the collection. - """ - - return list(self._components) - - @components.setter - def components(self, components: list[ModelComponent]) -> None: - """ - Set the list of components in the collection. - - Parameters - ---------- - components : list[ModelComponent] - The new list of components. - - Raises - ------ - TypeError - If components is not a list of ModelComponent. - """ - - if not isinstance(components, list): - raise TypeError('components must be a list of ModelComponent instances.') for comp in components: if not isinstance(comp, ModelComponent): raise TypeError( - 'All items in components must be instances of ModelComponent. ' - f'Got {type(comp).__name__} instead.' + f'All items in components must be instances of ModelComponent, got {type(comp).__name__} instead.' # noqa: E501 ) - self._components = components + EasyDynamicsList.__init__( + self, + *components, + protected_types=ModelComponent, + ) + + EasyDynamicsModelBase.__init__( + self, + unit=unit, + name=name, + display_name=display_name, + unique_name=unique_name, + ) + + # ------------------------------------------------------------------ + # Properties + # ------------------------------------------------------------------ @property def is_empty(self) -> bool: @@ -120,7 +120,7 @@ def is_empty(self) -> bool: bool True if the collection has no components, False otherwise. """ - return not self._components + return not self @is_empty.setter def is_empty(self, _value: bool) -> None: @@ -142,39 +142,6 @@ def is_empty(self, _value: bool) -> None: 'whether the collection has components.' ) - @property - def unit(self) -> str | sc.Unit | None: - """ - Get the unit of the ComponentCollection. - - Returns - ------- - str | sc.Unit | None - The unit of the ComponentCollection, which is the same as the unit of its components. - """ - return self._unit - - @unit.setter - def unit(self, _unit_str: str) -> None: - """ - Unit is read-only and cannot be set directly. - - Parameters - ---------- - _unit_str : str - The unit to set (ignored). - - Raises - ------ - AttributeError - Always raised since unit is read-only. - """ - - raise AttributeError( - f'Unit is read-only. Use convert_unit to change the unit between allowed types ' - f'or create a new {self.__class__.__name__} with the desired unit.' - ) - def convert_unit(self, unit: str | sc.Unit) -> None: """ Convert the unit of the ComponentCollection and all its components. @@ -198,13 +165,13 @@ def convert_unit(self, unit: str | sc.Unit) -> None: old_unit = self._unit try: - for component in self.components: + for component in self: component.convert_unit(unit) self._unit = unit except Exception as e: # Attempt to rollback on failure try: - for component in self.components: + for component in self: component.convert_unit(old_unit) except Exception: # noqa: S110 pass # Best effort rollback @@ -224,133 +191,11 @@ def append_component(self, component: ModelComponent | ComponentCollection) -> N component : ModelComponent | ComponentCollection The component to append. If a ComponentCollection is provided, all of its components will be appended. - - Raises - ------ - TypeError - If component is not a ModelComponent or ComponentCollection. - ValueError - If a component with the same unique name already exists in the collection. """ - if not isinstance(component, (ModelComponent, ComponentCollection)): - raise TypeError( - 'Component must be an instance of ModelComponent or ComponentCollection. ' - f'Got {type(component).__name__} instead.' - ) - if isinstance(component, ModelComponent): - components = (component,) if isinstance(component, ComponentCollection): - components = component.components - - for comp in components: - if comp in self._components: - raise ValueError( - f"Component '{comp.unique_name}' is already in the collection. " - f'Existing components: {self.list_component_names()}' - ) - - self._components.append(comp) - - def remove_component(self, unique_name: str) -> None: - """ - Remove a component from the collection by its unique name. - - Parameters - ---------- - unique_name : str - Unique name of the component to remove. - - Raises - ------ - TypeError - If unique_name is not a string. - KeyError - If no component with the given unique name exists in the collection. - """ - - if not isinstance(unique_name, str): - raise TypeError('Component name must be a string.') - - for comp in self._components: - if comp.unique_name == unique_name: - self._components.remove(comp) - return - - raise KeyError( - f"No component named '{unique_name}' exists. " - f'Did you accidentally use the display_name? ' - f'Here is a list of the components in the collection: {self.list_component_names()}' - ) - - @property - def components(self) -> list[ModelComponent]: - """ - Get the list of components in the collection. - - Returns - ------- - list[ModelComponent] - The components in the collection. - """ - return list(self._components) - - @components.setter - def components(self, components: list[ModelComponent]) -> None: - """ - Set the components in the collection. - - Parameters - ---------- - components : list[ModelComponent] - The new components in the collection. - - Raises - ------ - TypeError - If components is not a list of ModelComponent. - """ - if not isinstance(components, list): - raise TypeError('components must be a list of ModelComponent instances.') - for comp in components: - if not isinstance(comp, ModelComponent): - raise TypeError( - 'All items in components must be instances of ModelComponent. ' - f'Got {type(comp).__name__} instead.' - ) - - self._components = components - - @property - def is_empty(self) -> bool: - """ - Returns True if the collection has no components, otherwise False. - - Returns - ------- - bool - True if the collection has no components, otherwise False. - """ - return not self._components - - @is_empty.setter - def is_empty(self, _value: bool) -> None: - """ - Is_empty is read-only. - - Parameters - ---------- - _value : bool - Ignored. - - Raises - ------ - AttributeError - Always raised since is_empty is read-only. - """ - raise AttributeError( - 'is_empty is a read-only property that indicates ' - 'whether the collection has components.' - ) + self.extend(component) + else: + self.append(component) def list_component_names(self) -> list[str]: """ @@ -359,14 +204,10 @@ def list_component_names(self) -> list[str]: Returns ------- list[str] - List of unique names of the components in the collection. + List of names of the components in the collection. """ - return [component.unique_name for component in self._components] - - def clear_components(self) -> None: - """Remove all components.""" - self._components.clear() + return [component.name for component in self] def normalize_area(self) -> None: """ @@ -380,19 +221,19 @@ def normalize_area(self) -> None: If there are no components in the model or if the total area is zero or not finite, which would prevent normalization. """ - if not self.components: + if not self: raise ValueError('No components in the model to normalize.') area_params = [] total_area = Parameter(name='total_area', value=0.0, unit=self._unit) - for component in self.components: + for component in self: if hasattr(component, 'area'): area_params.append(component.area) total_area += component.area else: warnings.warn( - f"Component '{component.unique_name}' does not have an 'area' attribute " + f"Component '{component.name}' does not have an 'area' attribute " f'and will be skipped in normalization.', UserWarning, stacklevel=2, @@ -421,7 +262,7 @@ def get_all_variables(self) -> list[DescriptorBase]: List of parameters in the component. """ - return [var for component in self.components for var in component.get_all_variables()] + return [var for component in self for var in component.get_all_variables()] def evaluate(self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray) -> np.ndarray: """ @@ -438,14 +279,14 @@ def evaluate(self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray) Evaluated model values. """ - if not self.components: + if not self: return np.zeros_like(x) - return sum(component.evaluate(x) for component in self.components) + return sum(component.evaluate(x) for component in self) def evaluate_component( self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray, - unique_name: str, + name: str, ) -> np.ndarray: """ Evaluate a single component by name. @@ -454,34 +295,32 @@ def evaluate_component( ---------- x : Numeric | list | np.ndarray | sc.Variable | sc.DataArray Energy axis. - unique_name : str - Component unique name. + name : str + Component name. Raises ------ ValueError If there are no components in the model. TypeError - If unique_name is not a string. + If name is not a string. KeyError - If no component with the given unique name exists in the collection. + If no component with the given name exists in the collection. Returns ------- np.ndarray Evaluated values for the specified component. """ - if not self.components: + if not self: raise ValueError('No components in the model to evaluate.') - if not isinstance(unique_name, str): - raise TypeError( - f'Component unique name must be a string, got {type(unique_name)} instead.' - ) + if not isinstance(name, str): + raise TypeError(f'Component name must be a string, got {type(name)} instead.') - matches = [comp for comp in self.components if comp.unique_name == unique_name] + matches = [comp for comp in self if comp.name == name] if not matches: - raise KeyError(f"No component named '{unique_name}' exists.") + raise KeyError(f"No component named '{name}' exists.") component = matches[0] @@ -517,11 +356,11 @@ def __contains__(self, item: str | ModelComponent) -> bool: """ if isinstance(item, str): - # Check by component unique name - return any(comp.unique_name == item for comp in self.components) + # Check by component name + return any(comp.name == item for comp in self) if isinstance(item, ModelComponent): # Check by component instance - return any(comp is item for comp in self.components) + return any(comp is item for comp in self) return False def __repr__(self) -> str: @@ -533,6 +372,56 @@ def __repr__(self) -> str: str String representation of the ComponentCollection. """ - comp_names = ', '.join(c.unique_name for c in self.components) or 'No components' + comp_names = ', '.join(c.name for c in self) or 'No components' + + return f"" + + def to_dict(self) -> dict: + return { + '@module': self.__class__.__module__, + '@class': self.__class__.__name__, + 'unit': str(self.unit), + 'name': self.name, + 'display_name': self.display_name, + 'components': [c.to_dict() for c in self._data], + } + + @classmethod + def from_dict(cls, obj_dict: dict) -> ComponentCollection: + + def deserialise_component(d: dict) -> ModelComponent: + """ + Deserialise a component from its dictionary representation. + Parameters + ---------- + d : dict + The dictionary representation of the component. + Returns + ------- + ModelComponent + The deserialised component. + """ + module = importlib.import_module(d['@module']) + cls = getattr(module, d['@class']) + return cls.from_dict(d) + + components = [deserialise_component(c) for c in obj_dict.get('components', [])] + + return cls( + components=components, + unit=obj_dict.get('unit', 'meV'), + name=obj_dict.get('name', 'ComponentCollection'), + display_name=obj_dict.get('display_name'), + ) + + def __copy__(self) -> ComponentCollection: + """ + Create a deep copy of the ComponentCollection. + + Returns + ------- + ComponentCollection + A deep copy of the ComponentCollection. + """ - return f"" + return self.from_dict(self.to_dict()) diff --git a/src/easydynamics/sample_model/components/damped_harmonic_oscillator.py b/src/easydynamics/sample_model/components/damped_harmonic_oscillator.py index d74b9116..86fd6c0b 100644 --- a/src/easydynamics/sample_model/components/damped_harmonic_oscillator.py +++ b/src/easydynamics/sample_model/components/damped_harmonic_oscillator.py @@ -30,7 +30,8 @@ def __init__( center: Numeric | Parameter = 1.0, width: Numeric | Parameter = 1.0, unit: str | sc.Unit = 'meV', - display_name: str | None = 'DampedHarmonicOscillator', + name: str = 'DampedHarmonicOscillator', + display_name: str | None = None, unique_name: str | None = None, ) -> None: """ @@ -47,7 +48,9 @@ def __init__( default, 1.0. unit : str | sc.Unit, default='meV' Unit of the parameters. - display_name : str | None, default='DampedHarmonicOscillator' + name : str, default='DampedHarmonicOscillator' + Name of the component for indexing. + display_name : str | None, default=None Display name of the component. unique_name : str | None, default=None Unique name of the component. If None, a unique_name is automatically generated. By @@ -55,22 +58,23 @@ def __init__( """ super().__init__( + name=name, display_name=display_name, unique_name=unique_name, unit=unit, ) # These methods live in ValidationMixin - area = self._create_area_parameter(area=area, name=display_name, unit=self._unit) + area = self._create_area_parameter(area=area, name=name, unit=self._unit) center = self._create_center_parameter( center=center, - name=display_name, + name=name, fix_if_none=False, unit=self._unit, enforce_minimum_center=True, ) - width = self._create_width_parameter(width=width, name=display_name, unit=self._unit) + width = self._create_width_parameter(width=width, name=name, unit=self._unit) self._area = area self._center = center @@ -217,7 +221,9 @@ def __repr__(self) -> str: A string representation of the Damped Harmonic Oscillator. """ return ( - f'DampedHarmonicOscillator(display_name = {self.display_name}, ' + f'DampedHarmonicOscillator(name = {self.name}, display_name = {self.display_name}, ' f'unit = {self._unit},\n ' - f'area = {self.area},\n center = {self.center},\n width = {self.width})' + f' area = {self.area},\n ' + f' center = {self.center},\n ' + f' width = {self.width})' ) diff --git a/src/easydynamics/sample_model/components/delta_function.py b/src/easydynamics/sample_model/components/delta_function.py index e3720f8b..d63780fa 100644 --- a/src/easydynamics/sample_model/components/delta_function.py +++ b/src/easydynamics/sample_model/components/delta_function.py @@ -33,7 +33,8 @@ def __init__( center: Numeric | Parameter | None = None, area: Numeric | Parameter = 1.0, unit: str | sc.Unit = 'meV', - display_name: str | None = 'DeltaFunction', + name: str = 'DeltaFunction', + display_name: str | None = None, unique_name: str | None = None, ) -> None: """ @@ -42,28 +43,31 @@ def __init__( Parameters ---------- center : Numeric | Parameter | None, default=None - Center of the delta function. If None. + Center of the delta function. If None, it will be centered at 0 and fixed. area : Numeric | Parameter, default=1.0 Total area under the curve. unit : str | sc.Unit, default='meV' Unit of the parameters. - display_name : str | None, default='DeltaFunction' - Name of the component. + name : str, default='DeltaFunction' + Name of the component for indexing. + display_name : str | None, default=None + Display name of the component. unique_name : str | None, default=None Unique name of the component. If None, a unique_name is automatically generated. By default, None. """ # Validate inputs and create Parameters if not given super().__init__( - display_name=display_name, unit=unit, + name=name, + display_name=display_name, unique_name=unique_name, ) # These methods live in ValidationMixin - area = self._create_area_parameter(area=area, name=display_name, unit=self._unit) + area = self._create_area_parameter(area=area, name=name, unit=self._unit) center = self._create_center_parameter( - center=center, name=display_name, fix_if_none=True, unit=self._unit + center=center, name=name, fix_if_none=True, unit=self._unit ) self._area = area @@ -196,6 +200,8 @@ def __repr__(self) -> str: """ return ( - f'DeltaFunction(unique_name = {self.unique_name}, unit = {self._unit},\n' - f'area = {self.area},\n center = {self.center})' + f'DeltaFunction(name = {self.name}, display_name = {self.display_name}, ' + f'unit = {self.unit},\n' + f' area = {self.area},\n' + f' center = {self.center})' ) diff --git a/src/easydynamics/sample_model/components/exponential.py b/src/easydynamics/sample_model/components/exponential.py index f06e8138..b331b162 100644 --- a/src/easydynamics/sample_model/components/exponential.py +++ b/src/easydynamics/sample_model/components/exponential.py @@ -29,7 +29,8 @@ def __init__( center: Numeric | Parameter | None = None, rate: Numeric | Parameter = 1.0, unit: str | sc.Unit = 'meV', - display_name: str | None = 'Exponential', + name: str = 'Exponential', + display_name: str | None = None, unique_name: str | None = None, ) -> None: """ @@ -45,10 +46,12 @@ def __init__( Decay or growth constant of the Exponential. unit : str | sc.Unit, default='meV' Unit of the parameters. - display_name : str | None, default='Exponential' - Name of the component. + name : str, default='Exponential' + Name of the component for indexing. + display_name : str | None, default=None + Display name of the component. unique_name : str | None, default=None - Unique name of the component. if None, a unique_name is automatically generated. By + Unique name of the component. If None, a unique_name is automatically generated. By default, None. Raises @@ -60,8 +63,9 @@ def __init__( """ # Validate inputs and create Parameters if not given super().__init__( - display_name=display_name, unit=unit, + name=name, + display_name=display_name, unique_name=unique_name, ) @@ -72,12 +76,10 @@ def __init__( if not np.isfinite(amplitude): raise ValueError('amplitude must be a finite number or a Parameter') - amplitude = Parameter( - name=display_name + ' amplitude', value=float(amplitude), unit=unit - ) + amplitude = Parameter(name=name + ' amplitude', value=float(amplitude), unit=unit) center = self._create_center_parameter( - center=center, name=display_name, fix_if_none=True, unit=self._unit + center=center, name=name, fix_if_none=True, unit=self._unit ) if not isinstance(rate, (Parameter, Numeric)): @@ -87,7 +89,7 @@ def __init__( if not np.isfinite(rate): raise ValueError('rate must be a finite number or a Parameter') - rate = Parameter(name=display_name + ' rate', value=float(rate), unit='1/' + str(unit)) + rate = Parameter(name=name + ' rate', value=float(rate), unit='1/' + str(unit)) self._amplitude = amplitude self._center = center @@ -270,5 +272,10 @@ def __repr__(self) -> str: A string representation of the Exponential. """ - return f'Exponential(unique_name = {self.unique_name}, unit = {self._unit},\n \ - amplitude = {self.amplitude},\n center = {self.center},\n rate = {self.rate})' + return ( + f'Exponential(name = {self.name}, display_name = {self.display_name}, ' + f'unit = {self._unit},\n ' + f' amplitude = {self.amplitude},\n ' + f' center = {self.center},\n ' + f' rate = {self.rate})' + ) diff --git a/src/easydynamics/sample_model/components/expression_component.py b/src/easydynamics/sample_model/components/expression_component.py index e51de905..5255a940 100644 --- a/src/easydynamics/sample_model/components/expression_component.py +++ b/src/easydynamics/sample_model/components/expression_component.py @@ -71,7 +71,8 @@ def __init__( expression: str, parameters: dict[str, Numeric] | None = None, unit: str | sc.Unit = 'meV', - display_name: str | None = 'Expression', + name: str = 'Expression', + display_name: str | None = None, unique_name: str | None = None, ) -> None: """ @@ -85,7 +86,9 @@ def __init__( Dictionary of parameter names and their initial values. unit : str | sc.Unit, default='meV' Unit of the output. - display_name : str | None, default='Expression' + name : str, default='Expression' + Name of the component for indexing. + display_name : str | None, default=None Display name for the component. unique_name : str | None, default=None Unique name for the component. @@ -109,7 +112,7 @@ def __init__( >>> expr.A = 5 >>> y = expr.evaluate(x) """ - super().__init__(unit=unit, display_name=display_name, unique_name=unique_name) + super().__init__(unit=unit, name=name, display_name=display_name, unique_name=unique_name) if 'np.' in expression: raise ValueError( @@ -367,9 +370,8 @@ def __repr__(self) -> str: """ param_str = ', '.join(f'{k}={v.value}' for k, v in self._parameters.items()) return ( - f'{self.__class__.__name__}(\n' - f" expr='{self._expression_str}',\n" - f' unit={self._unit},\n' - f' parameters={{ {param_str} }}\n' - f')' + f'ExpressionComponent(name={self.name}, display_name={self.display_name}, ' + f'unit={self._unit},\n' + f" expr='{self._expression_str}',\n" + f' parameters={{ {param_str} }} )' ) diff --git a/src/easydynamics/sample_model/components/gaussian.py b/src/easydynamics/sample_model/components/gaussian.py index 4e9629c1..54cfe97c 100644 --- a/src/easydynamics/sample_model/components/gaussian.py +++ b/src/easydynamics/sample_model/components/gaussian.py @@ -37,7 +37,8 @@ def __init__( center: Numeric | Parameter | None = None, width: Numeric | Parameter = 1.0, unit: str | sc.Unit = 'meV', - display_name: str | None = 'Gaussian', + name: str = 'Gaussian', + display_name: str | None = None, unique_name: str | None = None, ) -> None: """ @@ -53,7 +54,9 @@ def __init__( Standard deviation. unit : str | sc.Unit, default='meV' Unit of the parameters. - display_name : str | None, default='Gaussian' + name : str, default='Gaussian' + Name of the component for indexing. + display_name : str | None, default=None Name of the component. unique_name : str | None, default=None Unique name of the component. if None, a unique_name is automatically generated. By @@ -61,17 +64,18 @@ def __init__( """ # Validate inputs and create Parameters if not given super().__init__( - display_name=display_name, unit=unit, + name=name, + display_name=display_name, unique_name=unique_name, ) # These methods live in ValidationMixin - area = self._create_area_parameter(area=area, name=display_name, unit=self._unit) + area = self._create_area_parameter(area=area, name=name, unit=self._unit) center = self._create_center_parameter( - center=center, name=display_name, fix_if_none=True, unit=self._unit + center=center, name=name, fix_if_none=True, unit=self._unit ) - width = self._create_width_parameter(width=width, name=display_name, unit=self._unit) + width = self._create_width_parameter(width=width, name=name, unit=self._unit) self._area = area self._center = center @@ -225,6 +229,9 @@ def __repr__(self) -> str: """ return ( - f'Gaussian(unique_name = {self.unique_name}, unit = {self._unit},\n' - f'area = {self.area},\n center = {self.center},\n width = {self.width})' + f'Gaussian(name = {self.name}, display_name = {self.display_name}, ' + f'unit = {self._unit},\n' + f' area = {self.area},\n' + f' center = {self.center},\n' + f' width = {self.width})' ) diff --git a/src/easydynamics/sample_model/components/lorentzian.py b/src/easydynamics/sample_model/components/lorentzian.py index d86b1ed6..81453fb8 100644 --- a/src/easydynamics/sample_model/components/lorentzian.py +++ b/src/easydynamics/sample_model/components/lorentzian.py @@ -34,7 +34,8 @@ def __init__( center: Numeric | Parameter | None = None, width: Numeric | Parameter = 1.0, unit: str | sc.Unit = 'meV', - display_name: str | None = 'Lorentzian', + name: str = 'Lorentzian', + display_name: str | None = None, unique_name: str | None = None, ) -> None: """ @@ -45,30 +46,33 @@ def __init__( area : Numeric | Parameter, default=1.0 Area of the Lorentzian. center : Numeric | Parameter | None, default=None - Center of the Lorentzian. If None. + Center of the Lorentzian. If None, defaults to 0 and is fixed. width : Numeric | Parameter, default=1.0 Half width at half maximum (HWHM). unit : str | sc.Unit, default='meV' Unit of the parameters. - display_name : str | None, default='Lorentzian' - Name of the component. + name : str, default='Lorentzian' + Name of the component for indexing. + display_name : str | None, default=None + Display name for the component. unique_name : str | None, default=None Unique name of the component. If None, a unique_name is automatically generated. By default, None. """ super().__init__( - display_name=display_name, unit=unit, + name=name, + display_name=display_name, unique_name=unique_name, ) # These methods live in ValidationMixin - area = self._create_area_parameter(area=area, name=display_name, unit=self._unit) + area = self._create_area_parameter(area=area, name=name, unit=self._unit) center = self._create_center_parameter( - center=center, name=display_name, fix_if_none=True, unit=self._unit + center=center, name=name, fix_if_none=True, unit=self._unit ) - width = self._create_width_parameter(width=width, name=display_name, unit=self._unit) + width = self._create_width_parameter(width=width, name=name, unit=self._unit) self._area = area self._center = center @@ -216,6 +220,9 @@ def __repr__(self) -> str: A string representation of the Lorentzian. """ return ( - f'Lorentzian(unique_name = {self.unique_name}, unit = {self._unit},\n' - f'area = {self.area},\n center = {self.center},\n width = {self.width})' + f'Lorentzian(name = {self.name}, display_name = {self.display_name}, ' + f'unit = {self._unit},\n' + f' area = {self.area},\n' + f' center = {self.center},\n' + f' width = {self.width})' ) diff --git a/src/easydynamics/sample_model/components/model_component.py b/src/easydynamics/sample_model/components/model_component.py index 6d0f9a0e..84c88453 100644 --- a/src/easydynamics/sample_model/components/model_component.py +++ b/src/easydynamics/sample_model/components/model_component.py @@ -20,6 +20,7 @@ class ModelComponent(EasyDynamicsModelBase): def __init__( self, unit: str | sc.Unit = 'meV', + name: str = 'ModelComponent', display_name: str | None = None, unique_name: str | None = None, ) -> None: @@ -30,13 +31,19 @@ def __init__( ---------- unit : str | sc.Unit, default='meV' The unit of the model component. + name : str, default='ModelComponent' + The name of the model component for indexing. display_name : str | None, default=None A human-readable name for the component. unique_name : str | None, default=None A unique identifier for the component. """ - super().__init__(unit=unit, display_name=display_name, unique_name=unique_name) - self._unit = unit + super().__init__( + unit=unit, + name=name, + display_name=display_name, + unique_name=unique_name, + ) @property def unit(self) -> str: diff --git a/src/easydynamics/sample_model/components/polynomial.py b/src/easydynamics/sample_model/components/polynomial.py index cd3cd997..03e7eae7 100644 --- a/src/easydynamics/sample_model/components/polynomial.py +++ b/src/easydynamics/sample_model/components/polynomial.py @@ -31,7 +31,8 @@ def __init__( self, coefficients: Sequence[Numeric | Parameter] = (0.0,), unit: str | sc.Unit = 'meV', - display_name: str | None = 'Polynomial', + name: str = 'Polynomial', + display_name: str | None = None, unique_name: str | None = None, ) -> None: """ @@ -43,7 +44,9 @@ def __init__( Coefficients c0, c1, ..., cN. unit : str | sc.Unit, default='meV' Unit of the Polynomial component. - display_name : str | None, default='Polynomial' + name : str, default='Polynomial' + Name of the component for indexing. + display_name : str | None, default=None Display name of the Polynomial component. unique_name : str | None, default=None Unique name of the component. If None, a unique_name is automatically generated. By @@ -58,7 +61,12 @@ def __init__( If coefficients is an empty sequence. """ - super().__init__(display_name=display_name, unit=unit, unique_name=unique_name) + super().__init__( + unit=unit, + name=name, + display_name=display_name, + unique_name=unique_name, + ) if not isinstance(coefficients, (list, tuple, np.ndarray)): raise TypeError( @@ -77,7 +85,7 @@ def __init__( if isinstance(coef, Parameter): param = coef elif isinstance(coef, Numeric): - param = Parameter(name=f'{display_name}_c{i}', value=float(coef)) + param = Parameter(name=f'{name}_c{i}', value=float(coef)) else: raise TypeError('Each coefficient must be either a numeric value or a Parameter.') self._coefficients.append(param) @@ -264,6 +272,7 @@ def __repr__(self) -> str: coeffs_str = ', '.join(f'{param.name}={param.value}' for param in self._coefficients) return ( - f'Polynomial(unique_name = {self.unique_name}, ' - f'unit = {self._unit},\n coefficients = [{coeffs_str}])' + f'Polynomial(name = {self.name}, display_name = {self.display_name}, ' + f'unit = {self._unit},\n' + f' coefficients = [{coeffs_str}])' ) diff --git a/src/easydynamics/sample_model/components/voigt.py b/src/easydynamics/sample_model/components/voigt.py index 4aafd03b..5c5787b6 100644 --- a/src/easydynamics/sample_model/components/voigt.py +++ b/src/easydynamics/sample_model/components/voigt.py @@ -34,7 +34,8 @@ def __init__( gaussian_width: Numeric | Parameter = 1.0, lorentzian_width: Numeric | Parameter = 1.0, unit: str | sc.Unit = 'meV', - display_name: str | None = 'Voigt', + name: str = 'Voigt', + display_name: str | None = None, unique_name: str | None = None, ) -> None: """ @@ -52,7 +53,9 @@ def __init__( Half width at half max (HWHM) of the Lorentzian part. unit : str | sc.Unit, default='meV' Unit of the parameters. - display_name : str | None, default='Voigt' + name : str, default='Voigt' + Name of the component for indexing. + display_name : str | None, default=None Display name of the component. unique_name : str | None, default=None Unique name of the component. If None, a unique_name is automatically generated. By @@ -60,25 +63,26 @@ def __init__( """ super().__init__( - display_name=display_name, unit=unit, + name=name, + display_name=display_name, unique_name=unique_name, ) # These methods live in ValidationMixin - area = self._create_area_parameter(area=area, name=display_name, unit=self._unit) + area = self._create_area_parameter(area=area, name=name, unit=self._unit) center = self._create_center_parameter( - center=center, name=display_name, fix_if_none=True, unit=self._unit + center=center, name=name, fix_if_none=True, unit=self._unit ) gaussian_width = self._create_width_parameter( width=gaussian_width, - name=display_name, + name=name, param_name='gaussian_width', unit=self._unit, ) lorentzian_width = self._create_width_parameter( width=lorentzian_width, - name=display_name, + name=name, param_name='lorentzian_width', unit=self._unit, ) @@ -261,9 +265,9 @@ def __repr__(self) -> str: """ return ( - f'Voigt(unique_name = {self.unique_name}, unit = {self._unit},\n' - f'area = {self.area},\n' - f'center = {self.center},\n' - f'gaussian_width = {self.gaussian_width},\n' - f'lorentzian_width = {self.lorentzian_width})' + f'Voigt(name = {self.name}, display_name = {self.display_name}, unit = {self._unit},\n' + f' area = {self.area},\n' + f' center = {self.center},\n' + f' gaussian_width = {self.gaussian_width},\n' + f' lorentzian_width = {self.lorentzian_width})' ) diff --git a/src/easydynamics/sample_model/diffusion_model/brownian_translational_diffusion.py b/src/easydynamics/sample_model/diffusion_model/brownian_translational_diffusion.py index b3872d9a..0883db57 100644 --- a/src/easydynamics/sample_model/diffusion_model/brownian_translational_diffusion.py +++ b/src/easydynamics/sample_model/diffusion_model/brownian_translational_diffusion.py @@ -26,38 +26,45 @@ class BrownianTranslationalDiffusion(DiffusionModelBase): have units of 1/angstrom. Creates ComponentCollections with Lorentzian components for given Q-values. - Example: >>>Q=np.linspace(0.5,2,7) >>>energy=np.linspace(-2, 2, 501) >>>scale=1.0 - >>>diffusion_coefficient = 2.4e-9 # m^2/s - >>>diffusion_model=BrownianTranslationalDiffusion(display_name="DiffusionModel", - >>>scale=scale, diffusion_coefficient= diffusion_coefficient) - >>>component_collections=diffusion_model.create_component_collections(Q) See also the - tutorials. + Examples + -------- + >>> Q=np.linspace(0.5,2,7) >>>energy=np.linspace(-2, 2, 501) + >>> scale = 1.0 + >>> diffusion_coefficient = 2.4e-9 # m^2/s + >>> diffusion_model=BrownianTranslationalDiffusion(name="DiffusionModel", + >>> scale=scale, diffusion_coefficient= diffusion_coefficient,) + >>> component_collections = diffusion_model.create_component_collections(Q) + + See also the tutorials. """ def __init__( self, - display_name: str | None = 'BrownianTranslationalDiffusion', - unique_name: str | None = None, - unit: str | sc.Unit = 'meV', scale: Numeric = 1.0, diffusion_coefficient: Numeric = 1.0, + unit: str | sc.Unit = 'meV', + name: str = 'BrownianTranslationalDiffusion', + display_name: str | None = 'BrownianTranslationalDiffusion', + unique_name: str | None = None, ) -> None: """ Initialize a new BrownianTranslationalDiffusion model. Parameters ---------- + scale : Numeric, default=1.0 + Scale factor for the diffusion model. Must be a non-negative number. + diffusion_coefficient : Numeric, default=1.0 + Diffusion coefficient D in m^2/s. + unit : str | sc.Unit, default='meV' + Unit of the diffusion model. Must be convertible to meV. + name : str, default='BrownianTranslationalDiffusion' + Name of the diffusion model. display_name : str | None, default='BrownianTranslationalDiffusion' Display name of the diffusion model. unique_name : str | None, default=None Unique name of the diffusion model. If None, a unique name will be generated. By default, None. - unit : str | sc.Unit, default='meV' - Unit of the diffusion model. Must be convertible to meV. - scale : Numeric, default=1.0 - Scale factor for the diffusion model. Must be a non-negative number. - diffusion_coefficient : Numeric, default=1.0 - Diffusion coefficient D in m^2/s. Raises ------ @@ -78,10 +85,11 @@ def __init__( min=0.0, ) super().__init__( - display_name=display_name, - unique_name=unique_name, unit=unit, scale=scale, + name=name, + display_name=display_name, + unique_name=unique_name, ) self._hbar = hbar self._angstrom = angstrom @@ -191,7 +199,8 @@ def calculate_QISF(self, Q: Q_type) -> np.ndarray: def create_component_collections( self, Q: Q_type, - component_display_name: str = 'Brownian diffusion', + component_name: str = 'Brownian diffusion', + component_display_name: str | None = None, ) -> list[ComponentCollection]: r""" Create ComponentCollection components for the Brownian translational diffusion model at @@ -201,13 +210,15 @@ def create_component_collections( ---------- Q : Q_type Scattering vector values. - component_display_name : str, default='Brownian diffusion' - Name of the Lorentzian component. + component_name : str, default='Brownian diffusion' + Name of the Brownian diffusion component. + component_display_name : str | None, default=None + Display name of the Brownian diffusion component. Raises ------ TypeError - If component_display_name is not a string. + If component_display_name is not a string. If component_name is not a string. Returns ------- @@ -218,9 +229,15 @@ def create_component_collections( """ Q = _validate_and_convert_Q(Q) - if not isinstance(component_display_name, str): + if not isinstance(component_name, str): raise TypeError('component_name must be a string.') + if component_display_name is None: + component_display_name = component_name + + if not isinstance(component_display_name, str): + raise TypeError('component_display_name must be a string.') + component_collection_list = [None] * len(Q) # In more complex models, this is used to scale the area of the # Lorentzians and the delta function. @@ -231,10 +248,13 @@ def create_component_collections( # No delta function, as the EISF is 0. for i, Q_value in enumerate(Q): component_collection_list[i] = ComponentCollection( - display_name=f'{self.display_name}_Q{Q_value:.2f}', unit=self.unit + name=f'{self.name}_Q{Q_value:.2f}', + display_name=f'{self.display_name}_Q{Q_value:.2f}', + unit=self.unit, ) lorentzian_component = Lorentzian( + name=component_name, display_name=component_display_name, unit=self.unit, ) @@ -356,6 +376,8 @@ def __repr__(self) -> str: String representation of the BrownianTranslationalDiffusion model. """ return ( - f'BrownianTranslationalDiffusion(display_name={self.display_name},' - f'diffusion_coefficient={self.diffusion_coefficient}, scale={self.scale})' + f'BrownianTranslationalDiffusion(name={self.name}, ' + f'display_name={self.display_name}, \n' + f' diffusion_coefficient={self.diffusion_coefficient}, \n' + f' scale={self.scale})' ) diff --git a/src/easydynamics/sample_model/diffusion_model/diffusion_model_base.py b/src/easydynamics/sample_model/diffusion_model/diffusion_model_base.py index 0e83351e..95732680 100644 --- a/src/easydynamics/sample_model/diffusion_model/diffusion_model_base.py +++ b/src/easydynamics/sample_model/diffusion_model/diffusion_model_base.py @@ -15,25 +15,28 @@ class DiffusionModelBase(EasyDynamicsModelBase): def __init__( self, - display_name: str | None = 'MyDiffusionModel', - unique_name: str | None = None, scale: Numeric = 1.0, unit: str | sc.Unit = 'meV', + name: str = 'DiffusionModel', + display_name: str | None = 'MyDiffusionModel', + unique_name: str | None = None, ) -> None: """ Initialize a new DiffusionModel. Parameters ---------- + scale : Numeric, default=1.0 + Scale factor for the diffusion model. Must be a non-negative number. + unit : str | sc.Unit, default='meV' + Unit of the diffusion model. Must be convertible to meV. + name : str, default='DiffusionModel' + Name of the diffusion model. display_name : str | None, default='MyDiffusionModel' Display name of the diffusion model. unique_name : str | None, default=None Unique name of the diffusion model. If None, a unique name will be generated. By default, None. - scale : Numeric, default=1.0 - Scale factor for the diffusion model. Must be a non-negative number. - unit : str | sc.Unit, default='meV' - Unit of the diffusion model. Must be convertible to meV. Raises ------ @@ -56,7 +59,7 @@ def __init__( scale = Parameter(name='scale', value=float(scale), fixed=False, min=0.0, unit=unit) - super().__init__(display_name=display_name, unique_name=unique_name, unit=unit) + super().__init__(unit=unit, name=name, display_name=display_name, unique_name=unique_name) self._scale = scale # ------------------------------------------------------------------ @@ -97,7 +100,7 @@ def scale(self, scale: Numeric) -> None: if float(scale) < 0: raise ValueError('scale must be non-negative.') - self._scale.value = scale + self._scale.value = float(scale) # ------------------------------------------------------------------ # dunder methods @@ -112,4 +115,8 @@ def __repr__(self) -> str: str String representation of the DiffusionModel. """ - return f'{self.__class__.__name__}(display_name={self.display_name}, unit={self.unit})' + return ( + f'{self.__class__.__name__}(name={self.name}, display_name={self.display_name}, ' + f'unit={self.unit}), \n' + f' scale={self.scale})' + ) diff --git a/src/easydynamics/sample_model/diffusion_model/jump_translational_diffusion.py b/src/easydynamics/sample_model/diffusion_model/jump_translational_diffusion.py index 346ce152..0d4b4441 100644 --- a/src/easydynamics/sample_model/diffusion_model/jump_translational_diffusion.py +++ b/src/easydynamics/sample_model/diffusion_model/jump_translational_diffusion.py @@ -29,40 +29,53 @@ class JumpTranslationalDiffusion(DiffusionModelBase): units of 1/angstrom. Creates ComponentCollections with Lorentzian components for given Q-values. - Example: >>> Q = np.linspace(0.5, 2, 7) >>> energy = np.linspace(-2, 2, 501) >>> scale = 1.0 - >>> diffusion_coefficient = 2.4e-9 # m^2/s >>> relaxation_time = 1.0 # ps >>> - diffusion_model=JumpTranslationalDiffusion( >>> scale = scale, diffusion_coefficient = - (diffusion_coefficient,) >>> relaxation_time=relaxation_time) >>> component_collections= >>> - diffusion_model.create_component_collections(Q) See also the tutorials.. + Examples + -------- + >>> Q = np.linspace(0.5, 2, 7) + >>> energy = np.linspace(-2, 2, 501) + >>> scale = 1.0 + >>> diffusion_coefficient = 2.4e-9 # m^2/s + >>> relaxation_time = 1.0 # ps + >>> diffusion_model = JumpTranslationalDiffusion( + ... scale=scale, + ... diffusion_coefficient=diffusion_coefficient, + ... relaxation_time=relaxation_time, + ... ) + >>> component_collections = diffusion_model.create_component_collections(Q) + + See also the tutorials.. """ def __init__( self, - display_name: str | None = 'JumpTranslationalDiffusion', - unique_name: str | None = None, - unit: str | sc.Unit = 'meV', scale: Numeric = 1.0, diffusion_coefficient: Numeric = 1.0, relaxation_time: Numeric = 1.0, + unit: str | sc.Unit = 'meV', + name: str = 'JumpTranslationalDiffusion', + display_name: str | None = 'JumpTranslationalDiffusion', + unique_name: str | None = None, ) -> None: """ Initialize a new JumpTranslationalDiffusion model. Parameters ---------- - display_name : str | None, default='JumpTranslationalDiffusion' - Display name of the diffusion model. - unique_name : str | None, default=None - Unique name of the diffusion model. If None, a unique name will be generated. By - default, None. - unit : str | sc.Unit, default='meV' - Unit of the diffusion model. Must be convertible to meV. scale : Numeric, default=1.0 Scale factor for the diffusion model. Must be a non-negative number. diffusion_coefficient : Numeric, default=1.0 Diffusion coefficient D in m^2/s. relaxation_time : Numeric, default=1.0 Relaxation time t in ps. + unit : str | sc.Unit, default='meV' + Unit of the diffusion model. Must be convertible to meV. + name : str, default='JumpTranslationalDiffusion' + Name of the diffusion model. + display_name : str | None, default='JumpTranslationalDiffusion' + Display name of the diffusion model. + unique_name : str | None, default=None + Unique name of the diffusion model. If None, a unique name will be generated. By + default, None. Raises ------ @@ -70,6 +83,7 @@ def __init__( If scale, diffusion_coefficient, or relaxation_time are not numbers. """ super().__init__( + name=name, display_name=display_name, unique_name=unique_name, unit=unit, @@ -251,6 +265,7 @@ def calculate_QISF(self, Q: Q_type) -> np.ndarray: def create_component_collections( self, Q: Q_type, + component_name: str = 'Jump translational diffusion', component_display_name: str = 'Jump translational diffusion', ) -> list[ComponentCollection]: """ @@ -260,13 +275,15 @@ def create_component_collections( ---------- Q : Q_type Scattering vector in 1/angstrom. Can be a single value or an array of values. + component_name : str, default='Jump translational diffusion' + Name of the Jump Diffusion Lorentzian component. component_display_name : str, default='Jump translational diffusion' Name of the Jump Diffusion Lorentzian component. Raises ------ TypeError - If component_display_name is not a string. + If component_display_name is not a string. If component_name is not a string. Returns ------- @@ -276,6 +293,9 @@ def create_component_collections( Q = _validate_and_convert_Q(Q) if not isinstance(component_display_name, str): + raise TypeError('component_display_name must be a string.') + + if not isinstance(component_name, str): raise TypeError('component_name must be a string.') component_collection_list = [None] * len(Q) @@ -288,10 +308,13 @@ def create_component_collections( # is 0. for i, Q_value in enumerate(Q): component_collection_list[i] = ComponentCollection( - display_name=f'{self.display_name}_Q{Q_value:.2f}', unit=self.unit + name=f'{self.name}_Q{Q_value:.2f}', + display_name=f'{self.display_name}_Q{Q_value:.2f}', + unit=self.unit, ) lorentzian_component = Lorentzian( + name=component_name, display_name=component_display_name, unit=self.unit, ) @@ -415,6 +438,7 @@ def __repr__(self) -> str: String representation of the JumpTranslationalDiffusion model. """ return ( - f'JumpTranslationalDiffusion(display_name={self.display_name}, ' - f'diffusion_coefficient={self._diffusion_coefficient}, scale={self._scale})' + f'JumpTranslationalDiffusion(name={self.name}, display_name={self.display_name},\n ' + f' diffusion_coefficient={self._diffusion_coefficient}, \n' + f' scale={self._scale})' ) diff --git a/src/easydynamics/sample_model/model_base.py b/src/easydynamics/sample_model/model_base.py index e76d4370..4895dc6b 100644 --- a/src/easydynamics/sample_model/model_base.py +++ b/src/easydynamics/sample_model/model_base.py @@ -120,21 +120,21 @@ def append_component(self, component: ModelComponent | ComponentCollection) -> N self._components.append_component(component) self._on_components_change() - def remove_component(self, unique_name: str) -> None: + def remove_component(self, name: str) -> None: """ - Remove a ModelComponent from the SampleModel by its unique name. + Remove a ModelComponent from the SampleModel by its name. Parameters ---------- - unique_name : str - The unique name of the ModelComponent to remove. + name : str + The name of the ModelComponent to remove. """ - self._components.remove_component(unique_name) + self._components.pop(name) self._on_components_change() def clear_components(self) -> None: """Clear all ModelComponents from the SampleModel.""" - self._components.clear_components() + self._components.clear() self._on_components_change() # ------------------------------------------------------------------ @@ -184,7 +184,7 @@ def components(self) -> list[ModelComponent]: list[ModelComponent] The components of the SampleModel. """ - return self._components.components + return self._components @components.setter def components(self, value: ModelComponent | ComponentCollection | None) -> None: diff --git a/src/easydynamics/sample_model/resolution_model.py b/src/easydynamics/sample_model/resolution_model.py index 3d2385e7..4f35cc30 100644 --- a/src/easydynamics/sample_model/resolution_model.py +++ b/src/easydynamics/sample_model/resolution_model.py @@ -67,10 +67,7 @@ def append_component(self, component: ModelComponent | ComponentCollection) -> N TypeError If the component is a DeltaFunction or Polynomial. """ - if isinstance(component, ComponentCollection): - components = component.components - else: - components = (component,) + components = component if isinstance(component, ComponentCollection) else (component,) for comp in components: if isinstance(comp, (DeltaFunction, Polynomial)): diff --git a/src/easydynamics/sample_model/sample_model.py b/src/easydynamics/sample_model/sample_model.py index fdf8eb09..bcb598f7 100644 --- a/src/easydynamics/sample_model/sample_model.py +++ b/src/easydynamics/sample_model/sample_model.py @@ -146,28 +146,28 @@ def append_diffusion_model(self, diffusion_model: DiffusionModelBase) -> None: self._diffusion_models.append(diffusion_model) self._generate_component_collections() - def remove_diffusion_model(self, name: 'str') -> None: + def remove_diffusion_model(self, name: str) -> None: """ - Remove a DiffusionModel from the SampleModel by unique name. + Remove a DiffusionModel from the SampleModel by name. Parameters ---------- - name : 'str' - The unique name of the DiffusionModel to remove. + name : str + The name of the DiffusionModel to remove. Raises ------ ValueError - If no DiffusionModel with the given unique name is found. + If no DiffusionModel with the given name is found. """ for i, dm in enumerate(self._diffusion_models): - if dm.unique_name == name: + if dm.name == name: del self._diffusion_models[i] self._generate_component_collections() return raise ValueError( - f'No DiffusionModel with unique name {name} found. \n' - f'The available unique names are: {[dm.unique_name for dm in self._diffusion_models]}' + f'No DiffusionModel with name {name} found. \n' + f'The available names are: {[dm.name for dm in self._diffusion_models]}' ) def clear_diffusion_models(self) -> None: @@ -508,16 +508,21 @@ def _generate_component_collections(self) -> None: """ super()._generate_component_collections() - if self._Q is None: + if self.Q is None: return # Generate components from diffusion models # and add to component collections for diffusion_model in self._diffusion_models: - diffusion_collections = diffusion_model.create_component_collections(Q=self._Q) + diffusion_collections = diffusion_model.create_component_collections( + Q=self.Q, + component_name=diffusion_model.name, + ) for target, source in zip( - self._component_collections, diffusion_collections, strict=True + self._component_collections, + diffusion_collections, + strict=True, ): - for component in source.components: + for component in source: target.append_component(component) def _on_diffusion_models_change(self) -> None: @@ -540,7 +545,7 @@ def __repr__(self) -> str: return ( f'{self.__class__.__name__}(unique_name={self.unique_name}, unit={self.unit}), ' - f'Q = {self.Q}, ' + f'Q = {self.Q}, \n ' f'components = {self.components}, diffusion_models = {self.diffusion_models}, ' f'temperature = {self.temperature}, ' f'detailed_balance_settings = {self.detailed_balance_settings}' diff --git a/tests/integration/fitting/test_fitting_with_diffusion_model.py b/tests/integration/fitting/test_fitting_with_diffusion_model.py index 0b3abc82..f9912c24 100644 --- a/tests/integration/fitting/test_fitting_with_diffusion_model.py +++ b/tests/integration/fitting/test_fitting_with_diffusion_model.py @@ -36,7 +36,7 @@ def test_fitting_with_diffusion_model(self): sample_model = SampleModel(components=delta_function) resolution_components = ComponentCollection() - res_gauss = Gaussian(width=0.1, area=1, display_name='Res. Gauss') + res_gauss = Gaussian(width=0.1, area=1, name='Res. Gauss') res_gauss.area.fixed = True resolution_components.append_component(res_gauss) resolution_model = ResolutionModel(components=resolution_components) diff --git a/tests/unit/easydynamics/analysis/test_analysis.py b/tests/unit/easydynamics/analysis/test_analysis.py index 64949fa6..209a6d92 100644 --- a/tests/unit/easydynamics/analysis/test_analysis.py +++ b/tests/unit/easydynamics/analysis/test_analysis.py @@ -30,7 +30,9 @@ def analysis(self): data_array = sc.DataArray(data=data, coords={'Q': Q, 'energy': energy}) experiment = Experiment(data=data_array) - sample_model = SampleModel(components=Gaussian(), display_name='Gaussian') + sample_model = SampleModel( + components=Gaussian(name='GaussianName'), display_name='Gaussian' + ) instrument_model = InstrumentModel() return Analysis( @@ -55,7 +57,9 @@ def analysis_single_Q(self): experiment = Experiment(data=data_array) experiment.rebin({'Q': 1}) - sample_model = SampleModel(components=Gaussian(), display_name='Gaussian') + sample_model = SampleModel( + components=Gaussian(name='GaussianName'), display_name='Gaussian' + ) instrument_model = InstrumentModel() return Analysis( @@ -400,19 +404,21 @@ def test_data_and_model_to_datagroup_include_components_not_bool_raises(self, an def test_parameters_to_dataset(self, analysis): # WHEN - analysis.sample_model.append_component(Gaussian(display_name='Gaussian2', area=0.5)) + analysis.sample_model.append_component( + Gaussian(name='Gaussian2Name', display_name='Gaussian2', area=0.5) + ) # THEN parameters_dataset = analysis.parameters_to_dataset() # EXPECT assert isinstance(parameters_dataset, sc.Dataset) parameter_names = [ - 'Gaussian area', - 'Gaussian center', - 'Gaussian width', - 'Gaussian2 area', - 'Gaussian2 center', - 'Gaussian2 width', + 'GaussianName area', + 'GaussianName center', + 'GaussianName width', + 'Gaussian2Name area', + 'Gaussian2Name center', + 'Gaussian2Name width', 'energy_offset', ] for parameter_name in parameter_names: @@ -422,10 +428,12 @@ def test_parameters_to_dataset(self, analysis): def test_parameters_to_dataset_different_units(self, analysis): # WHEN - analysis.sample_model.append_component(Gaussian(display_name='Gaussian2', area=0.5)) + analysis.sample_model.append_component( + Gaussian(name='Gaussian2Name', display_name='Gaussian2', area=0.5) + ) # Convert the unit of a component to eV. - analysis.sample_model.get_component_collection(Q_index=1).components[0].convert_unit('eV') + analysis.sample_model.get_component_collection(Q_index=1)[0].convert_unit('eV') # THEN parameters_dataset = analysis.parameters_to_dataset() @@ -433,12 +441,12 @@ def test_parameters_to_dataset_different_units(self, analysis): # EXPECT assert isinstance(parameters_dataset, sc.Dataset) parameter_names = [ - 'Gaussian area', - 'Gaussian center', - 'Gaussian width', - 'Gaussian2 area', - 'Gaussian2 center', - 'Gaussian2 width', + 'GaussianName area', + 'GaussianName center', + 'GaussianName width', + 'Gaussian2Name area', + 'Gaussian2Name center', + 'Gaussian2Name width', 'energy_offset', ] for parameter_name in parameter_names: @@ -774,14 +782,14 @@ def test_create_components_dataset_raises(self, analysis): def test_create_components_dataset(self, analysis): # WHEN # Add another component so that there are two components - analysis.sample_model.append_component(Gaussian(display_name='Gaussian2', area=0.5)) + analysis.sample_model.append_component(Gaussian(name='Gaussian2', area=0.5)) # THEN components_dataset = analysis._create_components_dataset(add_background=True) # THEN EXPECT assert isinstance(components_dataset, sc.Dataset) - component_names = [comp.display_name for comp in analysis.sample_model.components] + component_names = [comp.name for comp in analysis.sample_model.components] for component_name in component_names: assert component_name in components_dataset assert 'Q' in components_dataset[component_name].dims @@ -792,16 +800,14 @@ def test_create_components_dataset_single_Q(self, analysis_single_Q): # WHEN # Add another component so that there are two components - analysis_single_Q.sample_model.append_component( - Gaussian(display_name='Gaussian2', area=0.5) - ) + analysis_single_Q.sample_model.append_component(Gaussian(name='Gaussian2', area=0.5)) # THEN components_dataset = analysis_single_Q._create_components_dataset(add_background=True) # THEN EXPECT assert isinstance(components_dataset, sc.Dataset) - component_names = [comp.display_name for comp in analysis_single_Q.sample_model.components] + component_names = [comp.name for comp in analysis_single_Q.sample_model.components] for component_name in component_names: assert component_name in components_dataset assert 'Q' in components_dataset[component_name].dims diff --git a/tests/unit/easydynamics/analysis/test_analysis1d.py b/tests/unit/easydynamics/analysis/test_analysis1d.py index 49f08465..8de35cf7 100644 --- a/tests/unit/easydynamics/analysis/test_analysis1d.py +++ b/tests/unit/easydynamics/analysis/test_analysis1d.py @@ -876,7 +876,8 @@ def test_create_components_dataset_single_Q( sample_component.display_name = 'sample_comp' sample_collection = MagicMock() - sample_collection.components = [sample_component] + sample_collection.__iter__.return_value = iter([sample_component]) + sample_collection.__len__.return_value = 1 analysis1d.sample_model.get_component_collection = MagicMock( return_value=sample_collection @@ -887,7 +888,8 @@ def test_create_components_dataset_single_Q( background_component.display_name = 'background_comp' background_collection = MagicMock() - background_collection.components = [background_component] + background_collection.__iter__.return_value = iter([background_component]) + background_collection.__len__.return_value = 1 analysis1d.instrument_model.background_model.get_component_collection = MagicMock( return_value=background_collection diff --git a/tests/unit/easydynamics/analysis/test_analysis_base.py b/tests/unit/easydynamics/analysis/test_analysis_base.py index 079404e4..120e0af0 100644 --- a/tests/unit/easydynamics/analysis/test_analysis_base.py +++ b/tests/unit/easydynamics/analysis/test_analysis_base.py @@ -45,8 +45,8 @@ def analysis_base_with_components(self): experiment = Experiment(data=data_array) - comp1 = Gaussian(area=1, width=2, center=3) - comp2 = Gaussian(area=4, width=5, center=6) + comp1 = Gaussian(name='Gaussian1', area=1, width=2, center=3) + comp2 = Gaussian(name='Gaussian2', area=4, width=5, center=6) sample_model = SampleModel() sample_model.append_component(comp1) sample_model.append_component(comp2) @@ -408,9 +408,7 @@ def test_get_parameters_near_bounds_no_bounds(self, analysis_base_with_component def test_get_parameters_near_bounds_at_bounds(self, analysis_base_with_components): # WHEN - components = analysis_base_with_components.sample_model.get_component_collection( - Q_index=0 - ).components + components = analysis_base_with_components.sample_model.get_component_collection(Q_index=0) components[0].area.min = 1.0 components[1].center.max = 6.0 @@ -425,9 +423,7 @@ def test_get_parameters_near_bounds_at_bounds(self, analysis_base_with_component def test_get_parameters_near_bounds_with_tolerances(self, analysis_base_with_components): # WHEN - components = analysis_base_with_components.sample_model.get_component_collection( - Q_index=0 - ).components + components = analysis_base_with_components.sample_model.get_component_collection(Q_index=0) components[0].area.min = 0.99999 components[1].center.max = 6.00001 @@ -472,9 +468,7 @@ def test_get_parameters_near_bounds_errors( def test_not_finite_parameters(self, analysis_base_with_components): # WHEN - components = analysis_base_with_components.sample_model.get_component_collection( - Q_index=0 - ).components + components = analysis_base_with_components.sample_model.get_component_collection(Q_index=0) components[0].area.value = np.inf components[1].center.value = np.nan diff --git a/tests/unit/easydynamics/base_classes/test_easydynamics_base.py b/tests/unit/easydynamics/base_classes/test_easydynamics_base.py index 90d62c1a..78c5c936 100644 --- a/tests/unit/easydynamics/base_classes/test_easydynamics_base.py +++ b/tests/unit/easydynamics/base_classes/test_easydynamics_base.py @@ -20,22 +20,20 @@ def test_initialization(self, easy_dynamics_base): # WHEN THEN EXPECT assert easy_dynamics_base.name == 'TestModel' - assert easy_dynamics_base.display_name == 'MyEasyDynamicsModel' + assert easy_dynamics_base.display_name == 'TestModel' assert easy_dynamics_base.unique_name is not None def test_init_raises_type_error_for_invalid_name(self): """Test that initializing with an invalid name raises a TypeError.""" # WHEN THEN EXPECT - with pytest.raises(TypeError, match=r'Name must be a string or None.'): + with pytest.raises(TypeError, match=r'Name must be a string.'): EasyDynamicsBase(name=123) # Not a string - def test_init_name_can_be_none(self): - """Test that initializing with name as None works correctly.""" + def test_init_name_cannot_be_none(self): + """Test that initializing with name as None raises a TypeError.""" # WHEN THEN EXPECT - model = EasyDynamicsBase(name=None) - - # THEN EXPECT - assert model.name is None + with pytest.raises(TypeError, match=r'Name must be a string.'): + EasyDynamicsBase(name=None) def test_name_setter_and_getter(self, easy_dynamics_base): """Test that the name setter and getter work correctly.""" @@ -49,10 +47,8 @@ def test_name_setter_and_getter(self, easy_dynamics_base): assert easy_dynamics_base.name == 'NewName' # THEN - easy_dynamics_base.name = None - - # EXPECT - assert easy_dynamics_base.name is None + with pytest.raises(TypeError, match=r'Name must be a string.'): + easy_dynamics_base.name = None @pytest.mark.parametrize( 'invalid_name', @@ -66,5 +62,5 @@ def test_name_setter_and_getter(self, easy_dynamics_base): def test_name_setter_invalid_type(self, easy_dynamics_base, invalid_name): """Test that setting the name to an invalid type raises a TypeError.""" # WHEN THEN EXPECT - with pytest.raises(TypeError, match=r'Name must be a string or None.'): + with pytest.raises(TypeError, match=r'Name must be a string.'): easy_dynamics_base.name = invalid_name diff --git a/tests/unit/easydynamics/base_classes/test_easydynamics_list.py b/tests/unit/easydynamics/base_classes/test_easydynamics_list.py new file mode 100644 index 00000000..3d81ec60 --- /dev/null +++ b/tests/unit/easydynamics/base_classes/test_easydynamics_list.py @@ -0,0 +1,259 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + +from easydynamics.base_classes.easydynamics_list import EasyDynamicsList +from easydynamics.exceptions import AmbiguousNameError +from easydynamics.sample_model import Gaussian +from easydynamics.sample_model import Lorentzian +from easydynamics.sample_model.components.model_component import ModelComponent + + +class TestEasyDynamicsList: + """Tests for the EasyDynamicsList class.""" + + @pytest.fixture + def easy_dynamics_list(self): + """Fixture for creating an instance of EasyDynamicsList.""" + gaussian = Gaussian(name='Gaussian') + lorentzian = Lorentzian(name='Lorentzian') + return EasyDynamicsList( + gaussian, + lorentzian, + protected_types=ModelComponent, + display_name='TestList', + ) + + def test_initialization(self, easy_dynamics_list): + """Test that the EasyDynamicsList is initialized correctly.""" + # WHEN THEN EXPECT + assert easy_dynamics_list.display_name == 'TestList' + assert len(easy_dynamics_list) == 2 + assert isinstance(easy_dynamics_list[0], Gaussian) + assert isinstance(easy_dynamics_list[1], Lorentzian) + + def test_initialization_invalid_type(self): + """Test that initializing with an invalid type raises a TypeError.""" + # WHEN THEN EXPECT + with pytest.raises(TypeError): + EasyDynamicsList('Not a ModelComponent', protected_types=ModelComponent) + + def test_initialization_invalid_type_in_list(self): + """Test that initializing with a list containing an invalid type raises a TypeError.""" + # WHEN THEN EXPECT + with pytest.raises(TypeError): + EasyDynamicsList( + [Gaussian(name='Gaussian'), 'Not a ModelComponent'], + protected_types=ModelComponent, + ) + + def test_insert(self, easy_dynamics_list): + """Test that the insert method works correctly.""" + # WHEN + new_gaussian = Gaussian(name='NewGaussian') + + # THEN + easy_dynamics_list.insert(1, new_gaussian) + + # EXPECT + assert len(easy_dynamics_list) == 3 + assert isinstance(easy_dynamics_list[1], Gaussian) + assert easy_dynamics_list[1].name == 'NewGaussian' + + def test_insert_invalid_type(self, easy_dynamics_list): + """Test that inserting an invalid type raises a TypeError.""" + # WHEN THEN EXPECT + with pytest.raises(TypeError): + easy_dynamics_list.insert(0, 'Not a ModelComponent') + + def test_insert_repeated_name(self, easy_dynamics_list): + """Test that inserting an item with a repeated name works""" + # WHEN + new_gaussian = Gaussian(name='Gaussian') + + # THEN + easy_dynamics_list.insert(1, new_gaussian) + + # EXPECT + assert len(easy_dynamics_list) == 3 + assert easy_dynamics_list[1] is new_gaussian + + def test_insert_repeated_component_warns(self, easy_dynamics_list): + """Test that inserting a repeated component raises a warning.""" + # WHEN THEN EXPECT + with pytest.warns(UserWarning, match=r'already in EasyDynamicsList'): + easy_dynamics_list.insert(1, easy_dynamics_list[0]) + + assert len(easy_dynamics_list) == 2 + assert easy_dynamics_list[1] is not easy_dynamics_list[0] + + def test_append(self, easy_dynamics_list): + """Test that the append method works correctly.""" + # WHEN + new_lorentzian = Lorentzian(name='NewLorentzian') + + # THEN + easy_dynamics_list.append(new_lorentzian) + + # EXPECT + assert len(easy_dynamics_list) == 3 + assert isinstance(easy_dynamics_list[2], Lorentzian) + assert easy_dynamics_list[2].name == 'NewLorentzian' + + def test_append_invalid_type(self, easy_dynamics_list): + """Test that appending an invalid type raises a TypeError.""" + # WHEN THEN EXPECT + with pytest.raises(TypeError): + easy_dynamics_list.append('Not a ModelComponent') + + def test_append_repeated_component_warns(self, easy_dynamics_list): + """Test that appending a repeated component raises a warning.""" + # WHEN THEN EXPECT + with pytest.warns(UserWarning, match=r'already in EasyDynamicsList'): + easy_dynamics_list.append(easy_dynamics_list[0]) + + assert len(easy_dynamics_list) == 2 + + def test_extend(self, easy_dynamics_list): + """Test that the extend method works correctly.""" + # WHEN + new_gaussian = Gaussian(name='NewGaussian') + new_lorentzian = Lorentzian(name='NewLorentzian') + + # THEN + easy_dynamics_list.extend([new_gaussian, new_lorentzian]) + + # EXPECT + assert len(easy_dynamics_list) == 4 + assert isinstance(easy_dynamics_list[2], Gaussian) + assert isinstance(easy_dynamics_list[3], Lorentzian) + assert easy_dynamics_list[2].name == 'NewGaussian' + assert easy_dynamics_list[3].name == 'NewLorentzian' + + def test_extend_invalid_type(self, easy_dynamics_list): + """Test that extending with an invalid type raises a TypeError.""" + # WHEN THEN EXPECT + with pytest.raises(TypeError): + easy_dynamics_list.extend(['Not a ModelComponent']) + + def test_extend_non_iterable(self, easy_dynamics_list): + """Test that extending with a non-iterable raises a TypeError.""" + # WHEN THEN EXPECT + with pytest.raises(TypeError): + easy_dynamics_list.extend('Not an iterable') + + def test_extend_repeated_component_warns(self, easy_dynamics_list): + """Test that extending with a repeated component raises a warning.""" + # WHEN THEN EXPECT + with pytest.warns(UserWarning, match=r'already in EasyDynamicsList'): + easy_dynamics_list.extend([easy_dynamics_list[0]]) + + assert len(easy_dynamics_list) == 2 + + def test_pop(self, easy_dynamics_list): + """Test that the pop method works correctly.""" + # WHEN THEN + popped_item = easy_dynamics_list.pop(0) + + # EXPECT + assert isinstance(popped_item, Gaussian) + assert popped_item.name == 'Gaussian' + assert len(easy_dynamics_list) == 1 + + # WHEN THEN + popped_item = easy_dynamics_list.pop('Lorentzian') + + # EXPECT + assert isinstance(popped_item, Lorentzian) + assert popped_item.name == 'Lorentzian' + assert len(easy_dynamics_list) == 0 + + def test_pop_invalid_index_type(self, easy_dynamics_list): + """Test that popping with an invalid index type raises a TypeError.""" + # WHEN THEN EXPECT + with pytest.raises(TypeError): + easy_dynamics_list.pop(1.5) + + def test_pop_nonexistent_name(self, easy_dynamics_list): + """Test that popping with a nonexistent name raises a KeyError.""" + # WHEN THEN EXPECT + with pytest.raises(KeyError, match=r'No item with name "Nonexistent" found'): + easy_dynamics_list.pop('Nonexistent') + + def test_names(self, easy_dynamics_list): + """Test that the names method returns the correct list of names.""" + # WHEN THEN EXPECT + assert easy_dynamics_list.get_names() == ['Gaussian', 'Lorentzian'] + + def test_duplicate_names_no_duplicates(self, easy_dynamics_list): + """Test that the get_duplicate_names method returns an empty list + when there are no duplicates.""" + # WHEN THEN EXPECT + assert easy_dynamics_list.get_duplicate_names() == [] + + def test_duplicate_names_with_duplicates(self, easy_dynamics_list): + """Test that the get_duplicate_names method returns the correct + list of duplicate names.""" + # WHEN + new_gaussian = Gaussian(name='Gaussian') + easy_dynamics_list.append(new_gaussian) + + # THEN EXPECT + assert easy_dynamics_list.get_duplicate_names() == ['Gaussian'] + + def test_getitem_int(self, easy_dynamics_list): + """Test getting an item by integer index.""" + # WHEN + + # THEN + item = easy_dynamics_list[0] + + # EXPECT + assert isinstance(item, Gaussian) + assert item.name == 'Gaussian' + + def test_getitem_slice(self, easy_dynamics_list): + """Test getting items by slice.""" + # WHEN + + # THEN + sliced = easy_dynamics_list[:1] + + # EXPECT + assert isinstance(sliced, EasyDynamicsList) + assert len(sliced) == 1 + assert isinstance(sliced[0], Gaussian) + assert sliced[0].name == 'Gaussian' + + def test_getitem_name(self, easy_dynamics_list): + """Test getting an item by name.""" + # WHEN + + # THEN + item = easy_dynamics_list['Gaussian'] + + # EXPECT + assert isinstance(item, Gaussian) + assert item.name == 'Gaussian' + + def test_getitem_nonexistent_name(self, easy_dynamics_list): + """Test getting an item with a nonexistent name raises KeyError.""" + # WHEN THEN EXPECT + with pytest.raises(KeyError, match=r'No item with name "Nonexistent" found'): + easy_dynamics_list['Nonexistent'] + + def test_getitem_ambiguous_name(self, easy_dynamics_list): + """Test getting an item with duplicate names raises AmbiguousNameError.""" + # WHEN + easy_dynamics_list.append(Gaussian(name='Gaussian')) + + # THEN EXPECT + with pytest.raises(AmbiguousNameError): + easy_dynamics_list['Gaussian'] + + def test_getitem_invalid_type(self, easy_dynamics_list): + """Test getting an item with an invalid index type raises TypeError.""" + # WHEN THEN EXPECT + with pytest.raises(TypeError, match=r'Index must be an int, slice, or str'): + easy_dynamics_list[1.5] diff --git a/tests/unit/easydynamics/base_classes/test_easydynamics_modelbase.py b/tests/unit/easydynamics/base_classes/test_easydynamics_modelbase.py index c2d30f71..6767640a 100644 --- a/tests/unit/easydynamics/base_classes/test_easydynamics_modelbase.py +++ b/tests/unit/easydynamics/base_classes/test_easydynamics_modelbase.py @@ -20,22 +20,20 @@ def test_initialization(self, easy_dynamics_modelbase): # WHEN THEN EXPECT assert easy_dynamics_modelbase.name == 'TestModel' - assert easy_dynamics_modelbase.display_name == 'MyEasyDynamicsModel' + assert easy_dynamics_modelbase.display_name == 'TestModel' assert easy_dynamics_modelbase.unique_name is not None def test_init_raises_type_error_for_invalid_name(self): """Test that initializing with an invalid name raises a TypeError.""" # WHEN THEN EXPECT - with pytest.raises(TypeError, match=r'Name must be a string or None.'): + with pytest.raises(TypeError, match=r'Name must be a string.'): EasyDynamicsModelBase(name=123) # Not a string - def test_init_name_can_be_none(self): - """Test that initializing with name as None works correctly.""" + def test_init_name_cannot_be_none(self): + """Test that initializing with name as None raises a TypeError.""" # WHEN THEN EXPECT - model = EasyDynamicsModelBase(name=None) - - # THEN EXPECT - assert model.name is None + with pytest.raises(TypeError, match=r'Name must be a string.'): + EasyDynamicsModelBase(name=None) def test_name_setter_and_getter(self, easy_dynamics_modelbase): """Test that the name setter and getter work correctly.""" @@ -48,11 +46,9 @@ def test_name_setter_and_getter(self, easy_dynamics_modelbase): # EXPECT assert easy_dynamics_modelbase.name == 'NewName' - # THEN - easy_dynamics_modelbase.name = None - - # EXPECT - assert easy_dynamics_modelbase.name is None + # THEN EXPECT + with pytest.raises(TypeError, match=r'Name must be a string.'): + easy_dynamics_modelbase.name = None @pytest.mark.parametrize( 'invalid_name', @@ -66,7 +62,7 @@ def test_name_setter_and_getter(self, easy_dynamics_modelbase): def test_name_setter_invalid_type(self, easy_dynamics_modelbase, invalid_name): """Test that setting the name to an invalid type raises a TypeError.""" # WHEN THEN EXPECT - with pytest.raises(TypeError, match=r'Name must be a string or None.'): + with pytest.raises(TypeError, match=r'Name must be a string.'): easy_dynamics_modelbase.name = invalid_name def test_unit_property(self, easy_dynamics_modelbase): diff --git a/tests/unit/easydynamics/base_classes/test_name_mixin.py b/tests/unit/easydynamics/base_classes/test_name_mixin.py new file mode 100644 index 00000000..5ea93163 --- /dev/null +++ b/tests/unit/easydynamics/base_classes/test_name_mixin.py @@ -0,0 +1,64 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + +from easydynamics.base_classes.name_mixin import NameMixin + + +class TestNameMixin: + """Tests for the NameMixin class.""" + + @pytest.fixture + def name_mixin(self): + """Fixture for creating an instance of NameMixin.""" + + return NameMixin(name='TestModel') + + def test_initialization(self, name_mixin): + """Test that the NameMixin is initialized correctly.""" + + # WHEN THEN EXPECT + assert name_mixin.name == 'TestModel' + + def test_init_raises_type_error_for_invalid_name(self): + """Test that initializing with an invalid name raises a TypeError.""" + # WHEN THEN EXPECT + with pytest.raises(TypeError, match=r'Name must be a string.'): + NameMixin(name=123) # Not a string + + def test_init_name_cannot_be_none(self): + """Test that initializing with name as None raises a TypeError.""" + # WHEN THEN EXPECT + with pytest.raises(TypeError, match=r'Name must be a string.'): + NameMixin(name=None) + + def test_name_setter_and_getter(self, name_mixin): + """Test that the name setter and getter work correctly.""" + # WHEN THEN EXPECT + assert name_mixin.name == 'TestModel' + + # THEN + name_mixin.name = 'NewName' + + # EXPECT + assert name_mixin.name == 'NewName' + + # THEN + with pytest.raises(TypeError, match=r'Name must be a string.'): + name_mixin.name = None + + @pytest.mark.parametrize( + 'invalid_name', + [ + 123, # Not a string + [1, 2, 3], # Not a string + {'name': 'Test'}, # Not a string + ], + ids=['integer', 'list', 'dict'], + ) + def test_name_setter_invalid_type(self, name_mixin, invalid_name): + """Test that setting the name to an invalid type raises a TypeError.""" + # WHEN THEN EXPECT + with pytest.raises(TypeError, match=r'Name must be a string.'): + name_mixin.name = invalid_name diff --git a/tests/unit/easydynamics/convolution/test_convolution.py b/tests/unit/easydynamics/convolution/test_convolution.py index f3aff801..59191f7c 100644 --- a/tests/unit/easydynamics/convolution/test_convolution.py +++ b/tests/unit/easydynamics/convolution/test_convolution.py @@ -29,20 +29,18 @@ def default_convolution(self): sample_components = ComponentCollection(display_name='ComponentCollection') sample_components.append_component( - Gaussian(display_name='Gaussian1', area=2.0, center=0.1, width=0.4) + Gaussian(name='Gaussian1', area=2.0, center=0.1, width=0.4) ) sample_components.append_component( - DampedHarmonicOscillator(display_name='DHO1', area=2.0, center=1.0, width=0.1) + DampedHarmonicOscillator(name='DHO1', area=2.0, center=1.0, width=0.1) ) - sample_components.append_component( - DeltaFunction(display_name='Delta1', area=2.0, center=0.3) - ) + sample_components.append_component(DeltaFunction(name='Delta1', area=2.0, center=0.3)) - resolution_components = ComponentCollection(display_name='ResolutionModel') + resolution_components = ComponentCollection(name='ResolutionModel') resolution_components.append_component( - Gaussian(display_name='GaussianRes', area=3.0, center=0.2, width=0.5) + Gaussian(name='GaussianRes', area=3.0, center=0.2, width=0.5) ) return Convolution( @@ -54,11 +52,9 @@ def default_convolution(self): @pytest.fixture def convolution_with_components(self): energy = np.linspace(-10, 10, 5001) - sample_components = Gaussian(display_name='Gaussian1', area=2.0, center=0.1, width=0.4) + sample_components = Gaussian(name='Gaussian1', area=2.0, center=0.1, width=0.4) - resolution_components = Gaussian( - display_name='GaussianRes', area=3.0, center=0.2, width=0.5 - ) + resolution_components = Gaussian(name='GaussianRes', area=3.0, center=0.2, width=0.5) return Convolution( energy=energy, @@ -83,19 +79,19 @@ def test_init(self, default_convolution): assert isinstance(default_convolution._analytical_sample_components, ComponentCollection) assert ( - default_convolution._analytical_sample_components.components[0] - is default_convolution.sample_components.components[0] + default_convolution._analytical_sample_components[0] + is default_convolution.sample_components[0] ) assert isinstance(default_convolution._numerical_sample_components, ComponentCollection) assert ( - default_convolution._numerical_sample_components.components[0] - is default_convolution.sample_components.components[1] + default_convolution._numerical_sample_components[0] + is default_convolution.sample_components[1] ) assert isinstance(default_convolution._delta_sample_components, ComponentCollection) assert ( - default_convolution._delta_sample_components.components[0] - is default_convolution.sample_components.components[2] + default_convolution._delta_sample_components[0] + is default_convolution.sample_components[2] ) assert default_convolution.convolution_settings.convolution_plan_is_valid is True assert default_convolution._reactions_enabled is True @@ -123,8 +119,8 @@ def test_init_components(self, convolution_with_components): ComponentCollection, ) assert ( - convolution_with_components._analytical_sample_components.components[0] - is convolution_with_components.sample_components.components[0] + convolution_with_components._analytical_sample_components[0] + is convolution_with_components.sample_components[0] ) assert isinstance( convolution_with_components._numerical_sample_components, @@ -228,7 +224,7 @@ def test_convolution_calls_correct_methods( if analytical_component: sample_components.append_component( - Gaussian(display_name='Gaussian', area=1.0, center=0.0, width=0.1) + Gaussian(name='Gaussian', area=1.0, center=0.0, width=0.1) ) if numerical_component: @@ -313,7 +309,7 @@ def test_convolve_delta_functions(self, default_convolution): expected_center = 0.3 + 0.2 # Delta center + Resolution center expected_width = 0.5 # Resolution width expected_values = Gaussian( - display_name='ExpectedGaussian', + name='ExpectedGaussian', area=expected_area, center=expected_center, width=expected_width, @@ -323,7 +319,7 @@ def test_convolve_delta_functions(self, default_convolution): # List of analytic functions analytic_functions: ClassVar[list[object]] = [ - Gaussian(display_name='G', area=1.0, center=0.0, width=0.1), + Gaussian(name='G', area=1.0, center=0.0, width=0.1), Lorentzian(display_name='L', area=1.0, center=0.0, width=0.1), Voigt( display_name='V', @@ -382,7 +378,7 @@ def test_check_if_pair_is_analytic_raises_with_delta_in_resolution(self, default """ # WHEN conv = default_convolution - sample_component = Gaussian(display_name='G', area=1.0, center=0.0, width=0.1) + sample_component = Gaussian(name='G', area=1.0, center=0.0, width=0.1) resolution_component = DeltaFunction(display_name='Delta', area=1.0, center=0.0) # THEN EXPECT @@ -400,10 +396,10 @@ def test_check_if_pair_is_analytic_raises_with_delta_in_resolution(self, default [ ( 'NotAModelComponent', - Gaussian(display_name='G', area=1.0, center=0.0, width=0.1), + Gaussian(name='G', area=1.0, center=0.0, width=0.1), ), ( - Gaussian(display_name='G', area=1.0, center=0.0, width=0.1), + Gaussian(name='G', area=1.0, center=0.0, width=0.1), 'NotAModelComponent', ), ], @@ -462,13 +458,13 @@ def test_build_convolution_plan( if analytical_component: sample_components.append_component( - Gaussian(display_name='Gaussian', area=1.0, center=0.0, width=0.1) + Gaussian(name='Gaussian', area=1.0, center=0.0, width=0.1) ) if numerical_component: sample_components.append_component( DampedHarmonicOscillator( - display_name='DampedHarmonicOscillator', + name='DampedHarmonicOscillator', area=1.0, center=1.0, width=0.1, @@ -477,7 +473,7 @@ def test_build_convolution_plan( if delta_component: sample_components.append_component( - DeltaFunction(display_name='DeltaFunction', area=1.0, center=0.0) + DeltaFunction(name='DeltaFunction', area=1.0, center=0.0) ) # THEN @@ -489,29 +485,26 @@ def test_build_convolution_plan( # EXPECT assert isinstance(conv._analytical_sample_components, ComponentCollection) if analytical_component and not temperature: - assert len(conv._analytical_sample_components.components) == 1 - assert conv._analytical_sample_components.components[0].display_name == 'Gaussian' + assert len(conv._analytical_sample_components) == 1 + assert conv._analytical_sample_components[0].name == 'Gaussian' else: - assert len(conv._analytical_sample_components.components) == 0 + assert len(conv._analytical_sample_components) == 0 assert isinstance(conv._delta_sample_components, ComponentCollection) if delta_component: - assert len(conv._delta_sample_components.components) == 1 - assert conv._delta_sample_components.components[0].display_name == 'DeltaFunction' + assert len(conv._delta_sample_components) == 1 + assert conv._delta_sample_components[0].name == 'DeltaFunction' else: - assert len(conv._delta_sample_components.components) == 0 + assert len(conv._delta_sample_components) == 0 assert isinstance(conv._numerical_sample_components, ComponentCollection) if not temperature: if numerical_component: - assert len(conv._numerical_sample_components.components) == 1 - assert ( - conv._numerical_sample_components.components[0].display_name - == 'DampedHarmonicOscillator' - ) + assert len(conv._numerical_sample_components) == 1 + assert conv._numerical_sample_components[0].name == 'DampedHarmonicOscillator' else: - assert len(conv._numerical_sample_components.components) == 0 + assert len(conv._numerical_sample_components) == 0 else: # analytical and numerical components go to numerical when # temperature is set @@ -520,7 +513,7 @@ def test_build_convolution_plan( expected_numerical_count += 1 if analytical_component: expected_numerical_count += 1 - assert len(conv._numerical_sample_components.components) == expected_numerical_count + assert len(conv._numerical_sample_components) == expected_numerical_count assert conv.convolution_settings.convolution_plan_is_valid is True @@ -551,7 +544,7 @@ def test_set_convolvers( if analytical_component: sample_components.append_component( - Gaussian(display_name='Gaussian', area=1.0, center=0.0, width=0.1) + Gaussian(name='Gaussian', area=1.0, center=0.0, width=0.1) ) if numerical_component: diff --git a/tests/unit/easydynamics/convolution/test_convolution_base.py b/tests/unit/easydynamics/convolution/test_convolution_base.py index eceb8d7d..a300890b 100644 --- a/tests/unit/easydynamics/convolution/test_convolution_base.py +++ b/tests/unit/easydynamics/convolution/test_convolution_base.py @@ -51,8 +51,8 @@ def test_init_with_model_component(self): assert np.allclose(convolution_base.energy.values, np.linspace(-10, 10, 100)) assert isinstance(convolution_base.sample_components, ComponentCollection) assert isinstance(convolution_base.resolution_components, ComponentCollection) - assert convolution_base.sample_components.components[0] == sample_component - assert convolution_base.resolution_components.components[0] == resolution_component + assert convolution_base.sample_components[0] == sample_component + assert convolution_base.resolution_components[0] == resolution_component def test_init_energy_numerical_none_offset(self): # WHEN diff --git a/tests/unit/easydynamics/convolution/test_numerical_convolution.py b/tests/unit/easydynamics/convolution/test_numerical_convolution.py index 7b6ada94..755381c2 100644 --- a/tests/unit/easydynamics/convolution/test_numerical_convolution.py +++ b/tests/unit/easydynamics/convolution/test_numerical_convolution.py @@ -19,11 +19,11 @@ def default_numerical_convolution(self): energy = np.linspace(-10, 10, 5001) sample_components = ComponentCollection(display_name='ComponentCollection') sample_components.append_component( - Gaussian(display_name='Gaussian1', area=2.0, center=0.1, width=0.4) + Gaussian(name='Gaussian1', area=2.0, center=0.1, width=0.4) ) resolution_components = ComponentCollection(display_name='ResolutionModel') resolution_components.append_component( - Gaussian(display_name='GaussianRes', area=3.0, center=0.2, width=0.5) + Gaussian(name='GaussianRes', area=3.0, center=0.2, width=0.5) ) return NumericalConvolution( @@ -73,7 +73,7 @@ def test_convolution(self, default_numerical_convolution, upsample_factor): ) # center of sample_components + center of resolution_components expected_width = np.sqrt(0.4**2 + 0.5**2) # sqrt(width_sample^2 + width_res^2) expected_result = Gaussian( - display_name='ExpectedConvolution', + name='ExpectedConvolution', area=expected_area, center=expected_center, width=expected_width, diff --git a/tests/unit/easydynamics/convolution/test_numerical_convolution_base.py b/tests/unit/easydynamics/convolution/test_numerical_convolution_base.py index d349d591..a084b898 100644 --- a/tests/unit/easydynamics/convolution/test_numerical_convolution_base.py +++ b/tests/unit/easydynamics/convolution/test_numerical_convolution_base.py @@ -569,9 +569,7 @@ def test_check_width_large_threshold(self, default_numerical_convolution_base): too large compared to energy grid span. """ # WHEN - wide_gaussian = Gaussian( - display_name='ComponentCollection', area=1.0, center=0.0, width=15.0 - ) + wide_gaussian = Gaussian(name='ComponentCollection', area=1.0, center=0.0, width=15.0) # THEN EXPECT with pytest.warns( @@ -590,7 +588,7 @@ def test_check_width_small_threshold(self, default_numerical_convolution_base): """ # WHEN narrow_gaussian = Gaussian( - display_name='ComponentCollection', area=1.0, center=0.0, width=0.000001 + name='ComponentCollection', area=1.0, center=0.0, width=0.000001 ) # THEN EXPECT @@ -610,9 +608,7 @@ def test_check_width_no_warnings(self, default_numerical_convolution_base): ComponentCollection components are checked correctly. """ # WHEN - good_gaussian = Gaussian( - display_name='ComponentCollection', area=1.0, center=0.0, width=1.0 - ) + good_gaussian = Gaussian(name='ComponentCollection', area=1.0, center=0.0, width=1.0) sample_components = ComponentCollection(display_name='ComponentCollection') sample_components.append_component(good_gaussian) diff --git a/tests/unit/easydynamics/sample_model/components/test_damped_harmonic_oscillator.py b/tests/unit/easydynamics/sample_model/components/test_damped_harmonic_oscillator.py index 79e1913f..a420fa40 100644 --- a/tests/unit/easydynamics/sample_model/components/test_damped_harmonic_oscillator.py +++ b/tests/unit/easydynamics/sample_model/components/test_damped_harmonic_oscillator.py @@ -15,7 +15,12 @@ class TestDampedHarmonicOscillator: @pytest.fixture def dho(self): return DampedHarmonicOscillator( - display_name='TestDHO', area=2.0, center=1.5, width=0.3, unit='meV' + name='TestDHOName', + display_name='TestDHO', + area=2.0, + center=1.5, + width=0.3, + unit='meV', ) def test_init_no_inputs(self): @@ -164,9 +169,9 @@ def test_get_all_parameters(self, dho: DampedHarmonicOscillator): assert len(params) == 3 assert all(isinstance(param, Parameter) for param in params) expected_names = { - 'TestDHO area', - 'TestDHO center', - 'TestDHO width', + 'TestDHOName area', + 'TestDHOName center', + 'TestDHOName width', } actual_names = {param.name for param in params} assert actual_names == expected_names @@ -219,7 +224,7 @@ def test_repr(self, dho: DampedHarmonicOscillator): # EXPECT assert 'DampedHarmonicOscillator' in repr_str - assert 'name = TestDHO' in repr_str + assert 'name = TestDHOName' in repr_str assert 'unit = meV' in repr_str assert 'area =' in repr_str assert 'center =' in repr_str diff --git a/tests/unit/easydynamics/sample_model/components/test_delta_function.py b/tests/unit/easydynamics/sample_model/components/test_delta_function.py index be1e33a0..0747731b 100644 --- a/tests/unit/easydynamics/sample_model/components/test_delta_function.py +++ b/tests/unit/easydynamics/sample_model/components/test_delta_function.py @@ -15,7 +15,13 @@ class TestDeltaFunction: @pytest.fixture def delta_function(self): - return DeltaFunction(display_name='TestDeltaFunction', area=2.0, center=0.5, unit='meV') + return DeltaFunction( + name='DeltaFunctionName', + display_name='TestDeltaFunction', + area=2.0, + center=0.5, + unit='meV', + ) def test_init_no_inputs(self): # WHEN THEN @@ -178,8 +184,8 @@ def test_get_all_parameters(self, delta_function: DeltaFunction): assert len(params) == 2 assert all(isinstance(param, Parameter) for param in params) expected_names = { - 'TestDeltaFunction area', - 'TestDeltaFunction center', + 'DeltaFunctionName area', + 'DeltaFunctionName center', } actual_names = {param.name for param in params} assert actual_names == expected_names @@ -215,7 +221,7 @@ def test_repr(self, delta_function: DeltaFunction): # EXPECT assert 'DeltaFunction' in repr_str - assert 'unique_name = DeltaFunction' in repr_str + assert 'name = DeltaFunctionName' in repr_str assert 'unit = meV' in repr_str assert 'area =' in repr_str assert 'center =' in repr_str diff --git a/tests/unit/easydynamics/sample_model/components/test_exponential.py b/tests/unit/easydynamics/sample_model/components/test_exponential.py index 2a824532..97df632e 100644 --- a/tests/unit/easydynamics/sample_model/components/test_exponential.py +++ b/tests/unit/easydynamics/sample_model/components/test_exponential.py @@ -15,6 +15,7 @@ class TestExponential: @pytest.fixture def exponential(self): return Exponential( + name='ExponentialName', display_name='TestExponential', amplitude=2.0, center=0.5, @@ -156,9 +157,9 @@ def test_get_all_parameters(self, exponential: Exponential): assert all(isinstance(param, Parameter) for param in params) expected_names = { - 'TestExponential amplitude', - 'TestExponential center', - 'TestExponential rate', + 'ExponentialName amplitude', + 'ExponentialName center', + 'ExponentialName rate', } actual_names = {param.name for param in params} @@ -226,7 +227,7 @@ def test_repr(self, exponential: Exponential): # THEN EXPECT assert 'Exponential' in repr_str - assert 'unique_name = Exponential' in repr_str + assert 'name = ExponentialName' in repr_str assert 'unit = meV' in repr_str assert 'amplitude =' in repr_str assert 'center =' in repr_str diff --git a/tests/unit/easydynamics/sample_model/components/test_gaussian.py b/tests/unit/easydynamics/sample_model/components/test_gaussian.py index ef339cc2..9bf0b13d 100644 --- a/tests/unit/easydynamics/sample_model/components/test_gaussian.py +++ b/tests/unit/easydynamics/sample_model/components/test_gaussian.py @@ -14,7 +14,14 @@ class TestGaussian: @pytest.fixture def gaussian(self): - return Gaussian(display_name='TestGaussian', area=2.0, center=0.5, width=0.6, unit='meV') + return Gaussian( + name='GaussianName', + display_name='TestGaussian', + area=2.0, + center=0.5, + width=0.6, + unit='meV', + ) def test_init_no_inputs(self): # WHEN THEN @@ -164,9 +171,9 @@ def test_get_all_parameters(self, gaussian: Gaussian): assert all(isinstance(param, Parameter) for param in params) expected_names = { - 'TestGaussian area', - 'TestGaussian center', - 'TestGaussian width', + 'GaussianName area', + 'GaussianName center', + 'GaussianName width', } actual_names = {param.name for param in params} assert actual_names == expected_names @@ -208,6 +215,7 @@ def test_copy(self, gaussian: Gaussian): # EXPECT assert gaussian_copy is not gaussian assert gaussian_copy.display_name == gaussian.display_name + assert gaussian_copy.name == gaussian.name assert gaussian_copy.area.value == gaussian.area.value assert gaussian_copy.area.fixed == gaussian.area.fixed @@ -231,7 +239,7 @@ def test_repr(self, gaussian: Gaussian): repr_str = repr(gaussian) # EXPECT assert 'Gaussian' in repr_str - assert 'unique_name = Gaussian' in repr_str + assert 'name = GaussianName' in repr_str assert 'unit = meV' in repr_str assert 'area =' in repr_str assert 'center =' in repr_str diff --git a/tests/unit/easydynamics/sample_model/components/test_lorentzian.py b/tests/unit/easydynamics/sample_model/components/test_lorentzian.py index 4ce1e1db..3c7809c6 100644 --- a/tests/unit/easydynamics/sample_model/components/test_lorentzian.py +++ b/tests/unit/easydynamics/sample_model/components/test_lorentzian.py @@ -15,7 +15,12 @@ class TestLorentzian: @pytest.fixture def lorentzian(self): return Lorentzian( - display_name='TestLorentzian', area=2.0, center=0.5, width=0.6, unit='meV' + name='LorentzianName', + display_name='TestLorentzian', + area=2.0, + center=0.5, + width=0.6, + unit='meV', ) def test_init_no_inputs(self): @@ -163,9 +168,9 @@ def test_get_all_parameters(self, lorentzian: Lorentzian): assert len(params) == 3 assert all(isinstance(param, Parameter) for param in params) expected_names = { - 'TestLorentzian area', - 'TestLorentzian center', - 'TestLorentzian width', + 'LorentzianName area', + 'LorentzianName center', + 'LorentzianName width', } actual_names = {param.name for param in params} assert actual_names == expected_names @@ -218,7 +223,7 @@ def test_repr(self, lorentzian: Lorentzian): # EXPECT assert 'Lorentzian' in repr_str - assert 'unique_name = Lorentzian' in repr_str + assert 'name = LorentzianName' in repr_str assert 'unit = meV' in repr_str assert 'area =' in repr_str assert 'center =' in repr_str diff --git a/tests/unit/easydynamics/sample_model/components/test_polynomial.py b/tests/unit/easydynamics/sample_model/components/test_polynomial.py index 5e42e677..3111c00a 100644 --- a/tests/unit/easydynamics/sample_model/components/test_polynomial.py +++ b/tests/unit/easydynamics/sample_model/components/test_polynomial.py @@ -14,7 +14,11 @@ class TestPolynomial: @pytest.fixture def polynomial(self): - return Polynomial(display_name='TestPolynomial', coefficients=[1.0, -2.0, 3.0]) + return Polynomial( + name='PolynomialName', + display_name='TestPolynomial', + coefficients=[1.0, -2.0, 3.0], + ) def test_init_no_inputs(self): # WHEN THEN @@ -150,9 +154,9 @@ def test_get_all_parameters(self, polynomial: Polynomial): assert len(params) == 3 assert all(isinstance(param, Parameter) for param in params) expected_names = { - 'TestPolynomial_c0', - 'TestPolynomial_c1', - 'TestPolynomial_c2', + 'PolynomialName_c0', + 'PolynomialName_c1', + 'PolynomialName_c2', } actual_names = {param.name for param in params} assert actual_names == expected_names @@ -193,6 +197,5 @@ def test_repr(self, polynomial: Polynomial): repr_str = repr(polynomial) # EXPECT - assert 'Polynomial' in repr_str - assert 'unique_name = Polynomial' in repr_str + assert 'name = PolynomialName' in repr_str assert 'coefficients =' in repr_str diff --git a/tests/unit/easydynamics/sample_model/components/test_voigt.py b/tests/unit/easydynamics/sample_model/components/test_voigt.py index 5bf866d8..4d28c73a 100644 --- a/tests/unit/easydynamics/sample_model/components/test_voigt.py +++ b/tests/unit/easydynamics/sample_model/components/test_voigt.py @@ -16,6 +16,7 @@ class TestVoigt: @pytest.fixture def voigt(self): return Voigt( + name='VoigtName', display_name='TestVoigt', area=2.0, center=0.5, @@ -234,7 +235,6 @@ def test_evaluate(self, voigt: Voigt): def test_center_is_fixed_if_init_to_None(self): # WHEN THEN test_voigt = Voigt( - display_name='TestVoigt', area=2.0, center=None, gaussian_width=0.6, @@ -265,10 +265,10 @@ def test_get_all_parameters(self, voigt: Voigt): assert len(params) == 4 assert all(isinstance(param, Parameter) for param in params) expected_names = { - 'TestVoigt area', - 'TestVoigt center', - 'TestVoigt gaussian_width', - 'TestVoigt lorentzian_width', + 'VoigtName area', + 'VoigtName center', + 'VoigtName gaussian_width', + 'VoigtName lorentzian_width', } actual_names = {param.name for param in params} assert actual_names == expected_names @@ -318,7 +318,7 @@ def test_repr(self, voigt: Voigt): # EXPECT assert 'Voigt' in repr_str - assert 'unique_name = Voigt' in repr_str + assert 'name = VoigtName' in repr_str assert 'unit = meV' in repr_str assert 'area =' in repr_str assert 'center =' in repr_str diff --git a/tests/unit/easydynamics/sample_model/diffusion_model/test_brownian_translational_diffusion.py b/tests/unit/easydynamics/sample_model/diffusion_model/test_brownian_translational_diffusion.py index be25890e..4ffd2a5e 100644 --- a/tests/unit/easydynamics/sample_model/diffusion_model/test_brownian_translational_diffusion.py +++ b/tests/unit/easydynamics/sample_model/diffusion_model/test_brownian_translational_diffusion.py @@ -113,8 +113,8 @@ def test_calculate_EISF(self, brownian_diffusion_model): EISF = brownian_diffusion_model.calculate_EISF(Q_values) # EXPECT - expected_EISHF = np.zeros_like(Q_values) - np.testing.assert_array_equal(EISF, expected_EISHF) + expected_EISF = np.zeros_like(Q_values) + np.testing.assert_array_equal(EISF, expected_EISF) def test_calculate_EISF_type_error(self, brownian_diffusion_model): # WHEN THEN EXPECT @@ -160,8 +160,8 @@ def test_create_component_collections(self, brownian_diffusion_model, Q): expected_widths = brownian_diffusion_model.calculate_width(Q) for model_index in range(len(component_collections)): model = component_collections[model_index] - assert len(model.components) == 1 - component = model.components[0] + assert len(model) == 1 + component = model[0] assert component.width.unit == brownian_diffusion_model.unit assert np.isclose(component.width.value, expected_widths[model_index]) assert component.width.independent is False @@ -171,6 +171,15 @@ def test_create_component_collections_component_name_must_be_string( ): # WHEN THEN EXPECT with pytest.raises(TypeError, match=r'component_name must be a string.'): + brownian_diffusion_model.create_component_collections( + Q=np.array([0.1, 0.2, 0.3]), component_name=123 + ) + + def test_create_component_collections_component_display_name_must_be_string( + self, brownian_diffusion_model + ): + # WHEN THEN EXPECT + with pytest.raises(TypeError, match=r'component_display_name must be a string.'): brownian_diffusion_model.create_component_collections( Q=np.array([0.1, 0.2, 0.3]), component_display_name=123 ) diff --git a/tests/unit/easydynamics/sample_model/diffusion_model/test_jump_translational_diffusion.py b/tests/unit/easydynamics/sample_model/diffusion_model/test_jump_translational_diffusion.py index 9388a2db..d1fbf8ff 100644 --- a/tests/unit/easydynamics/sample_model/diffusion_model/test_jump_translational_diffusion.py +++ b/tests/unit/easydynamics/sample_model/diffusion_model/test_jump_translational_diffusion.py @@ -198,8 +198,8 @@ def test_create_component_collections(self, jump_diffusion_model, Q): expected_widths = jump_diffusion_model.calculate_width(Q) for model_index in range(len(component_collections)): model = component_collections[model_index] - assert len(model.components) == 1 - component = model.components[0] + assert len(model) == 1 + component = model[0] assert component.width.unit == jump_diffusion_model.unit assert np.isclose(component.width.value, expected_widths[model_index]) assert component.width.independent is False @@ -209,6 +209,15 @@ def test_create_component_collections_component_name_must_be_string( ): # WHEN THEN EXPECT with pytest.raises(TypeError, match=r'component_name must be a string.'): + jump_diffusion_model.create_component_collections( + Q=np.array([0.1, 0.2, 0.3]), component_name=123 + ) + + def test_create_component_collections_component_display_name_must_be_string( + self, jump_diffusion_model + ): + # WHEN THEN EXPECT + with pytest.raises(TypeError, match=r'component_display_name must be a string.'): jump_diffusion_model.create_component_collections( Q=np.array([0.1, 0.2, 0.3]), component_display_name=123 ) diff --git a/tests/unit/easydynamics/sample_model/test_background_model.py b/tests/unit/easydynamics/sample_model/test_background_model.py index cacf1198..a698a1bf 100644 --- a/tests/unit/easydynamics/sample_model/test_background_model.py +++ b/tests/unit/easydynamics/sample_model/test_background_model.py @@ -14,7 +14,7 @@ class TestBackgroundModel: @pytest.fixture def background_model(self): component1 = Gaussian( - display_name='TestGaussian1', + name='TestGaussian1', area=1.0, center=0.0, width=1.0, diff --git a/tests/unit/easydynamics/sample_model/test_component_collection.py b/tests/unit/easydynamics/sample_model/test_component_collection.py index 692486ff..14835ec3 100644 --- a/tests/unit/easydynamics/sample_model/test_component_collection.py +++ b/tests/unit/easydynamics/sample_model/test_component_collection.py @@ -19,20 +19,20 @@ class TestComponentCollection: def component_collection(self): model = ComponentCollection(display_name='TestComponentCollection') component1 = Gaussian( + name='TestGaussian1Name', display_name='TestGaussian1', area=1.0, center=0.0, width=1.0, unit='meV', - unique_name='TestGaussian1', ) component2 = Lorentzian( + name='TestLorentzian1Name', display_name='TestLorentzian1', area=2.0, center=1.0, width=0.5, unit='meV', - unique_name='TestLorentzian1', ) model.append_component(component1) model.append_component(component2) @@ -44,15 +44,23 @@ def test_init(self): # EXPECT assert component_collection.display_name == 'InitModel' - assert component_collection.components == [] + assert not component_collection + + def test_init_with_component(self): + # WHEN THEN + component1 = Gaussian(name='TestGaussian1', area=1.0, center=0.0, width=1.0, unit='meV') + component_collection = ComponentCollection(display_name='InitModel', components=component1) + + # EXPECT + assert component_collection.display_name == 'InitModel' + assert len(component_collection) == 1 + assert component_collection[0] is component1 def test_init_with_components(self): # WHEN THEN - component1 = Gaussian( - display_name='TestGaussian1', area=1.0, center=0.0, width=1.0, unit='meV' - ) + component1 = Gaussian(name='TestGaussian1', area=1.0, center=0.0, width=1.0, unit='meV') component2 = Lorentzian( - display_name='TestLorentzian1', area=2.0, center=1.0, width=0.5, unit='meV' + name='TestLorentzian1', area=2.0, center=1.0, width=0.5, unit='meV' ) component_collection = ComponentCollection( display_name='InitModel', components=[component1, component2] @@ -60,18 +68,24 @@ def test_init_with_components(self): # EXPECT assert component_collection.display_name == 'InitModel' - assert len(component_collection.components) == 2 - assert component_collection.components[0] is component1 - assert component_collection.components[1] is component2 + assert len(component_collection) == 2 + assert component_collection[0] is component1 + assert component_collection[1] is component2 def test_init_with_invalid_components_raises(self): # WHEN THEN EXPECT - with pytest.raises(TypeError, match='Component must be'): + with pytest.raises( + TypeError, + match='All items in components must be instances of ModelComponent', + ): ComponentCollection(components=['NotAComponent']) def test_init_with_invalid_list_of_components_raises(self): # WHEN THEN EXPECT - with pytest.raises(TypeError, match='components must be a list of'): + with pytest.raises( + TypeError, + match='components must be a ModelComponent or a list of ModelComponent', + ): ComponentCollection(components='NotAList') def test_init_with_invalid_unit_raises(self): @@ -83,80 +97,41 @@ def test_init_with_invalid_unit_raises(self): def test_append_component(self, component_collection): # WHEN - component = Gaussian( - display_name='TestComponent', area=1.0, center=0.0, width=1.0, unit='meV' - ) + component = Gaussian(name='TestComponent', area=1.0, center=0.0, width=1.0, unit='meV') # THEN component_collection.append_component(component) # EXPECT - assert component_collection.components[-1] is component + assert component_collection[-1] is component def test_append_component_collection(self, component_collection): # WHEN - component = Gaussian( - display_name='TestComponent', area=1.0, center=0.0, width=1.0, unit='meV' - ) + component = Gaussian(name='TestComponent', area=1.0, center=0.0, width=1.0, unit='meV') component_collection2 = ComponentCollection() component_collection2.append_component(component) # THEN component_collection.append_component(component_collection2) # EXPECT - assert component_collection.components[-1] is component + assert component_collection[-1] is component - def test_append_existing_component_raises(self, component_collection): + def test_append_existing_component_warns(self, component_collection): # WHEN THEN - component = component_collection.components[0] + component = component_collection[0] # EXPECT - with pytest.raises(ValueError, match='is already in the collection'): + with pytest.warns(UserWarning, match='it will be ignored'): component_collection.append_component(component) def test_append_invalid_component_raises(self, component_collection): # WHEN THEN EXPECT - with pytest.raises(TypeError, match='Component must be '): + with pytest.raises(TypeError, match='Value must be an instance of type'): component_collection.append_component('NotAComponent') - def test_remove_component(self, component_collection): - # WHEN THEN - component_collection.remove_component('TestGaussian1') - # EXPECT - assert 'TestGaussian1' not in component_collection.components - - def test_remove_component_raises(self, component_collection): - # WHEN THEN EXPECT - with pytest.raises(TypeError, match='Component name must be a string'): - component_collection.remove_component(123) - - def test_remove_nonexistent_component_raises(self, component_collection): - # WHEN THEN EXPECT - with pytest.raises(KeyError, match="No component named 'NonExistentComponent' exists"): - component_collection.remove_component('NonExistentComponent') - def test_getitem(self, component_collection): # WHEN - component = Gaussian( - display_name='TestComponent', area=1.0, center=0.0, width=1.0, unit='meV' - ) + component = Gaussian(name='TestComponent', area=1.0, center=0.0, width=1.0, unit='meV') # THEN component_collection.append_component(component) # EXPECT - assert component_collection.components[-1] is component - - def test_component_setter(self, component_collection): - # WHEN - new_components = [Lorentzian()] - # THEN - component_collection.components = new_components - # EXPECT - assert len(component_collection.components) == 1 - assert component_collection.components[0] is new_components[0] - - def test_component_setter_invalid_raises(self, component_collection): - # WHEN THEN EXPECT - with pytest.raises(TypeError, match=r' must be instances of ModelComponent.'): - component_collection.components = ['NotAComponent'] - - with pytest.raises(TypeError, match='components must be a list of'): - component_collection.components = 'NotAList' + assert component_collection[-1] is component def test_is_empty(self): # WHEN THEN @@ -165,9 +140,7 @@ def test_is_empty(self): assert component_collection.is_empty is True # WHEN THEN - component = Gaussian( - display_name='TestComponent', area=1.0, center=0.0, width=1.0, unit='meV' - ) + component = Gaussian(name='TestComponent', area=1.0, center=0.0, width=1.0, unit='meV') component_collection.append_component(component) # EXPECT assert component_collection.is_empty is False @@ -177,29 +150,19 @@ def test_is_empty_setter(self, component_collection): with pytest.raises(AttributeError, match=r'is_empty is a read-only property.'): component_collection.is_empty = True - def test_component_setter_empty_list(self, component_collection): - component_collection.components = [] - assert component_collection.components == [] - def test_list_component_names(self, component_collection): # WHEN THEN components = component_collection.list_component_names() # EXPECT assert len(components) == 2 - assert components[0] == 'TestGaussian1' - assert components[1] == 'TestLorentzian1' - - def test_clear_components(self, component_collection): - # WHEN THEN - component_collection.clear_components() - # EXPECT - assert len(component_collection.components) == 0 + assert components[0] == 'TestGaussian1Name' + assert components[1] == 'TestLorentzian1Name' def test_convert_unit(self, component_collection): # WHEN THEN component_collection.convert_unit('eV') # EXPECT - for component in component_collection.components: + for component in component_collection: assert component.unit == 'eV' def test_convert_unit_incorrect_unit_raises(self, component_collection): @@ -215,21 +178,19 @@ def convert_unit(self, _unit: str) -> None: raise RuntimeError('Conversion failed.') faulty_component = FaultyComponent( - display_name='FaultyComponent', area=1.0, center=0.0, width=1.0, unit='meV' + name='FaultyComponent', area=1.0, center=0.0, width=1.0, unit='meV' ) component_collection.append_component(faulty_component) - original_units = { - component.display_name: component.unit for component in component_collection.components - } + original_units = {component.name: component.unit for component in component_collection} # EXPECT with pytest.raises(RuntimeError, match=r'Conversion failed.'): component_collection.convert_unit('eV') # Check that all components have their original units - for component in component_collection.components: - assert component.unit == original_units[component.display_name] + for component in component_collection: + assert component.unit == original_units[component.name] def test_set_unit(self, component_collection): # WHEN THEN EXPECT @@ -244,9 +205,7 @@ def test_evaluate(self, component_collection): x = np.linspace(-5, 5, 100) result = component_collection.evaluate(x) # EXPECT - expected_result = component_collection.components[0].evaluate( - x - ) + component_collection.components[1].evaluate(x) + expected_result = component_collection[0].evaluate(x) + component_collection[1].evaluate(x) np.testing.assert_allclose(result, expected_result, rtol=1e-5) def test_evaluate_no_components_returns_zero(self): @@ -261,12 +220,12 @@ def test_evaluate_no_components_returns_zero(self): def test_evaluate_component(self, component_collection): # WHEN THEN x = np.linspace(-5, 5, 100) - result1 = component_collection.evaluate_component(x, 'TestGaussian1') - result2 = component_collection.evaluate_component(x, 'TestLorentzian1') + result1 = component_collection.evaluate_component(x, 'TestGaussian1Name') + result2 = component_collection.evaluate_component(x, 'TestLorentzian1Name') # EXPECT - expected_result1 = component_collection.components[0].evaluate(x) - expected_result2 = component_collection.components[1].evaluate(x) + expected_result1 = component_collection[0].evaluate(x) + expected_result2 = component_collection[1].evaluate(x) np.testing.assert_allclose(result1, expected_result1, rtol=1e-5) np.testing.assert_allclose(result2, expected_result2, rtol=1e-5) @@ -293,7 +252,7 @@ def test_evaluate_component_invalid_name_type_raises(self, component_collection) # THEN EXPECT with pytest.raises( TypeError, - match=r"Component unique name must be a string, got instead.", + match=r"Component name must be a string, got instead.", ): component_collection.evaluate_component(x, 123) @@ -322,8 +281,8 @@ def test_normalize_area_no_components_raises(self): ) def test_normalize_area_not_finite_area_raises(self, component_collection, area_value): # WHEN THEN - component_collection.components[0].area = area_value - component_collection.components[1].area = area_value + component_collection[0].area = area_value + component_collection[1].area = area_value # EXPECT with pytest.raises(ValueError, match=r'cannot normalize'): @@ -345,12 +304,12 @@ def test_get_all_parameters(self, component_collection): assert len(parameters) == 6 expected_names = { - 'TestGaussian1 area', - 'TestGaussian1 center', - 'TestGaussian1 width', - 'TestLorentzian1 area', - 'TestLorentzian1 center', - 'TestLorentzian1 width', + 'TestGaussian1Name area', + 'TestGaussian1Name center', + 'TestGaussian1Name width', + 'TestLorentzian1Name area', + 'TestLorentzian1Name center', + 'TestLorentzian1Name width', } actual_names = {param.name for param in parameters} assert actual_names == expected_names @@ -367,10 +326,10 @@ def test_get_fit_parameters(self, component_collection): # WHEN # Fix one parameter and make another dependent - component_collection.components[0].area.fixed = True - component_collection.components[1].width.make_dependent_on( + component_collection[0].area.fixed = True + component_collection[1].width.make_dependent_on( 'comp1_width', - {'comp1_width': component_collection.components[0].width}, + {'comp1_width': component_collection[0].width}, ) # THEN @@ -380,10 +339,10 @@ def test_get_fit_parameters(self, component_collection): assert len(fit_parameters) == 4 expected_names = { - 'TestGaussian1 center', - 'TestGaussian1 width', - 'TestLorentzian1 area', - 'TestLorentzian1 center', + 'TestGaussian1Name center', + 'TestGaussian1Name width', + 'TestLorentzian1Name area', + 'TestLorentzian1Name center', } actual_names = {param.name for param in fit_parameters} assert actual_names == expected_names @@ -405,19 +364,17 @@ def test_fix_and_free_all_parameters(self, component_collection): assert param.fixed is False def test_contains(self, component_collection): - assert 'TestGaussian1' in component_collection - assert 'TestLorentzian1' in component_collection + assert 'TestGaussian1Name' in component_collection + assert 'TestLorentzian1Name' in component_collection assert 'NonExistentComponent' not in component_collection - gaussian_component = component_collection.components[0] - lorentzian_component = component_collection.components[1] + gaussian_component = component_collection[0] + lorentzian_component = component_collection[1] assert gaussian_component in component_collection assert lorentzian_component in component_collection # WHEN THEN - fake_component = Gaussian( - display_name='FakeGaussian', area=1.0, center=0.0, width=1.0, unit='meV' - ) + fake_component = Gaussian(name='FakeGaussian', area=1.0, center=0.0, width=1.0, unit='meV') # EXPECT assert fake_component not in component_collection assert 123 not in component_collection # Invalid type @@ -427,20 +384,18 @@ def test_repr_contains_name_and_components(self, component_collection): rep = repr(component_collection) # EXPECT assert 'ComponentCollection' in rep - assert 'TestGaussian' in rep + assert 'TestGaussian1Name' in rep def test_to_dict(self, component_collection): - # WHEN THEN + # WHEN model_dict = component_collection.to_dict() # EXPECT assert model_dict['display_name'] == component_collection.display_name assert model_dict['unit'] == component_collection.unit - assert len(model_dict['components']) == len(component_collection.components) + assert len(model_dict['components']) == len(component_collection) - for comp, comp_dict in zip( - component_collection.components, model_dict['components'], strict=True - ): + for comp, comp_dict in zip(component_collection, model_dict['components'], strict=True): assert comp_dict['@class'] == type(comp).__name__ assert comp_dict['display_name'] == comp.display_name assert comp_dict['unit'] == comp.unit @@ -454,19 +409,18 @@ def test_from_dict(self, component_collection): # EXPECT assert new_model.display_name == component_collection.display_name - assert len(new_model.components) == len(component_collection.components) + assert len(new_model) == len(component_collection) - # Compare each component and its parameters - for orig_comp, new_comp in zip( - component_collection.components, new_model.components, strict=True - ): + for orig_comp, new_comp in zip(component_collection, new_model, strict=True): assert type(new_comp) is type(orig_comp) assert new_comp.display_name == orig_comp.display_name assert new_comp.unit == orig_comp.unit orig_params = orig_comp.get_all_parameters() new_params = new_comp.get_all_parameters() + assert len(orig_params) == len(new_params) + for param_orig, param_new in zip(orig_params, new_params, strict=True): assert param_new.name == param_orig.name assert param_new.value == param_orig.value @@ -474,13 +428,12 @@ def test_from_dict(self, component_collection): def test_copy(self, component_collection): # WHEN - component_collection.temperature = 300 - component_collection.components[0].area.min = 0.5 - component_collection.components[0].area.fixed = True - component_collection.components[0].area.max = 5.0 - component_collection.components[1].width.min = 0.1 - component_collection.components[1].width.fixed = True - component_collection.components[1].width.max = 2.0 + component_collection[0].area.min = 0.5 + component_collection[0].area.fixed = True + component_collection[0].area.max = 5.0 + component_collection[1].width.min = 0.1 + component_collection[1].width.fixed = True + component_collection[1].width.max = 2.0 # THEN model_copy = copy(component_collection) @@ -488,12 +441,10 @@ def test_copy(self, component_collection): # EXPECT collection-level checks assert model_copy is not component_collection assert model_copy.display_name == component_collection.display_name - assert len(model_copy.components) == len(component_collection.components) + assert len(model_copy) == len(component_collection) # EXPECT: deep copy, same order - for orig_comp, copied_comp in zip( - component_collection.components, model_copy.components, strict=True - ): + for orig_comp, copied_comp in zip(component_collection, model_copy, strict=True): # New object assert copied_comp is not orig_comp diff --git a/tests/unit/easydynamics/sample_model/test_model_base.py b/tests/unit/easydynamics/sample_model/test_model_base.py index 5ad066a2..7f568c5b 100644 --- a/tests/unit/easydynamics/sample_model/test_model_base.py +++ b/tests/unit/easydynamics/sample_model/test_model_base.py @@ -18,6 +18,7 @@ class TestModelBase: @pytest.fixture def model_base(self): component1 = Gaussian( + name='TestGaussian1Name', display_name='TestGaussian1', area=1.0, center=0.0, @@ -25,6 +26,7 @@ def model_base(self): unit='meV', ) component2 = Lorentzian( + name='TestLorentzian1Name', display_name='TestLorentzian1', area=2.0, center=1.0, @@ -99,11 +101,11 @@ def test_generate_component_collections_with_Q(self, model_base): assert len(model_base._component_collections) == len(model_base.Q) for collection in model_base._component_collections: assert isinstance(collection, ComponentCollection) - assert len(collection.components) == 2 - assert isinstance(collection.components[0], Gaussian) - assert collection.components[0].display_name == 'TestGaussian1' - assert isinstance(collection.components[1], Lorentzian) - assert collection.components[1].display_name == 'TestLorentzian1' + assert len(collection) == 2 + assert isinstance(collection[0], Gaussian) + assert collection[0].display_name == 'TestGaussian1' + assert isinstance(collection[1], Lorentzian) + assert collection[1].display_name == 'TestLorentzian1' def test_fix_free_all_parameters(self, model_base): # WHEN @@ -126,12 +128,12 @@ def test_get_all_variables(self, model_base): # THEN expected_var_display_names = { - 'TestGaussian1 area', - 'TestGaussian1 center', - 'TestGaussian1 width', - 'TestLorentzian1 area', - 'TestLorentzian1 center', - 'TestLorentzian1 width', + 'TestGaussian1Name area', + 'TestGaussian1Name center', + 'TestGaussian1Name width', + 'TestLorentzian1Name area', + 'TestLorentzian1Name center', + 'TestLorentzian1Name width', } retrieved_var_display_names = {var.display_name for var in all_vars} @@ -145,12 +147,12 @@ def test_get_all_variables_with_Q_index(self, model_base): # THEN expected_var_display_names = { - 'TestGaussian1 area', - 'TestGaussian1 center', - 'TestGaussian1 width', - 'TestLorentzian1 area', - 'TestLorentzian1 center', - 'TestLorentzian1 width', + 'TestGaussian1Name area', + 'TestGaussian1Name center', + 'TestGaussian1Name width', + 'TestLorentzian1Name area', + 'TestLorentzian1Name center', + 'TestLorentzian1Name width', } retrieved_var_display_names = {var.display_name for var in all_vars} @@ -198,7 +200,7 @@ def test_get_component_collection_invalid_index_raises(self, model_base): def test_append_and_remove_and_clear_component(self, model_base): # WHEN - new_component = Gaussian(unique_name='NewGaussian') + new_component = Gaussian(name='NewGaussian') # THEN model_base.append_component(new_component) @@ -273,7 +275,7 @@ def test_convert_unit_incorrect_unit_raises(self, model_base): def test_components_setter(self, model_base): # WHEN - new_component = Lorentzian(unique_name='NewLorentzian') + new_component = Lorentzian(name='NewLorentzian') model_base.components = new_component # THEN / EXPECT @@ -329,9 +331,11 @@ def test_Q_setter_with_similar_Q(self, model_base, new_Q): def test_Q_setter_with_none(self, model_base): # WHEN - model_base.Q = None old_Q = model_base.Q + # THEN + model_base.Q = None + # THEN / EXPECT assert model_base.Q is old_Q @@ -348,9 +352,11 @@ def test_Q_setter_when_current_Q_is_none(self, model_base): def test_clear_Q(self, model_base): # WHEN + # + # THEN model_base.clear_Q(confirm=True) - # THEN / EXPECT + # EXPECT assert model_base.Q is None def test_clear_Q_raises_without_confirm(self, model_base): @@ -366,7 +372,7 @@ def test_normalize_area(self, model_base): # EXPECT for collection in model_base._component_collections: - total_area = sum(component.area.value for component in collection.components) + total_area = sum(component.area.value for component in collection) assert total_area == pytest.approx(1.0) def test_repr(self, model_base): diff --git a/tests/unit/easydynamics/sample_model/test_resolution_model.py b/tests/unit/easydynamics/sample_model/test_resolution_model.py index 92f8a391..9febca15 100644 --- a/tests/unit/easydynamics/sample_model/test_resolution_model.py +++ b/tests/unit/easydynamics/sample_model/test_resolution_model.py @@ -16,7 +16,7 @@ class TestResolutionModel: @pytest.fixture def resolution_model(self): component1 = Gaussian( - display_name='TestGaussian1', + name='TestGaussian1', area=1.0, center=0.0, width=1.0, @@ -89,7 +89,7 @@ def test_init_raises_with_invalid_components(self, invalid_component, expected_e def test_append_and_remove_and_clear_component(self, resolution_model): # WHEN - new_component = Gaussian(unique_name='NewGaussian') + new_component = Gaussian(name='NewGaussian') # THEN resolution_model.append_component(new_component) @@ -113,8 +113,8 @@ def test_append_and_remove_and_clear_component(self, resolution_model): def test_append_component_collection(self, resolution_model): # WHEN new_collection = ComponentCollection() - new_component1 = Lorentzian() - new_component2 = Gaussian() + new_component1 = Lorentzian(name='NewLorentzian') + new_component2 = Gaussian(name='NewGaussian') new_collection.append_component(new_component1) new_collection.append_component(new_component2) diff --git a/tests/unit/easydynamics/sample_model/test_sample_model.py b/tests/unit/easydynamics/sample_model/test_sample_model.py index 712dfa8b..337266f5 100644 --- a/tests/unit/easydynamics/sample_model/test_sample_model.py +++ b/tests/unit/easydynamics/sample_model/test_sample_model.py @@ -22,16 +22,16 @@ class TestSampleModel: @pytest.fixture def sample_model(self): component1 = Gaussian( - display_name='TestGaussian1', - unique_name='TestGaussian1', + name='TestGaussian1Name', + display_name='TestGaussian1Display', area=1.0, center=0.0, width=1.0, unit='meV', ) component2 = Lorentzian( - display_name='TestLorentzian1', - unique_name='TestLorentzian1', + name='TestLorentzian1Name', + display_name='TestLorentzian1Display', area=2.0, center=1.0, width=0.5, @@ -42,7 +42,7 @@ def sample_model(self): component_collection.append_component(component2) diffusion_model = BrownianTranslationalDiffusion( - display_name='DiffusionModel', unique_name='DiffusionModel' + display_name='DiffusionModelDisplay', name='DiffusionModelName' ) return SampleModel( @@ -141,7 +141,7 @@ def test_append_and_remove_and_clear_diffusion_model(self, sample_model): # WHEN model = sample_model new_diffusion_model = BrownianTranslationalDiffusion( - unique_name='new_diffusion_model', + name='new_diffusion_model', ) # THEN @@ -181,8 +181,8 @@ def test_remove_diffusion_model_raises_with_invalid_name(self, sample_model): def test_diffusion_model_setter(self, sample_model): # WHEN model = sample_model - new_diffusion_model1 = BrownianTranslationalDiffusion() - new_diffusion_model2 = BrownianTranslationalDiffusion() + new_diffusion_model1 = BrownianTranslationalDiffusion(name='new_diffusion_model1') + new_diffusion_model2 = BrownianTranslationalDiffusion(name='new_diffusion_model2') # THEN model.diffusion_models = [new_diffusion_model1, new_diffusion_model2] @@ -210,7 +210,10 @@ def test_diffusion_model_setter(self, sample_model): [ 'invalid_diffusion_model', 123, - [BrownianTranslationalDiffusion(), 'invalid_diffusion_model'], + [ + BrownianTranslationalDiffusion(name='valid_diffusion_model'), + 'invalid_diffusion_model', + ], ], ids=['string', 'integer', 'list with invalid type'], ) @@ -461,13 +464,13 @@ def test_generate_component_collections(self, sample_model): assert len(sample_model._component_collections) == 3 # 3 Q values for collection in sample_model._component_collections: assert isinstance(collection, ComponentCollection) - assert len(collection.components) == 3 # 3 components - assert collection.components[0].display_name == 'TestGaussian1' - assert collection.components[0].area.value == pytest.approx(1.0) - assert collection.components[1].display_name == 'TestLorentzian1' - assert collection.components[1].area.value == pytest.approx(2.0) - assert collection.components[2].display_name == 'Brownian diffusion' - assert isinstance(collection.components[2], Lorentzian) + assert len(list(collection)) == 3 # 3 components + assert collection[0].name == 'TestGaussian1Name' + assert collection[0].area.value == pytest.approx(1.0) + assert collection[1].name == 'TestLorentzian1Name' + assert collection[1].area.value == pytest.approx(2.0) + assert collection[2].name == 'DiffusionModelName' + assert isinstance(collection[2], Lorentzian) def test_get_all_variables(self, sample_model): # WHEN diff --git a/tests/unit/easydynamics/test_exceptions.py b/tests/unit/easydynamics/test_exceptions.py new file mode 100644 index 00000000..494f6f7c --- /dev/null +++ b/tests/unit/easydynamics/test_exceptions.py @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + + +from easydynamics.exceptions import AmbiguousNameError + + +class TestAmbiguousNameError: + def test_initialization(self): + name = 'test' + matches = ['test1', 'test2', 'test3'] + + error = AmbiguousNameError(name, matches) + + assert error.name == name + assert error.matches == matches + assert str(error) == ( + "Ambiguous name 'test' matches 3 elements: ['test1', 'test2', 'test3']" + ) + + def test_empty_matches(self): + name = 'unknown' + matches = [] + + error = AmbiguousNameError(name, matches) + + assert error.name == name + assert error.matches == matches + assert str(error) == ("Ambiguous name 'unknown' matches 0 elements: []")