From 560ca5cae8ca80257371e02fe72efc4fd86c3089 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Mon, 21 Oct 2024 16:04:55 -0400 Subject: [PATCH 01/29] removing needless file --- .../config_diffusion_mace.yaml | 86 ------------------- 1 file changed, 86 deletions(-) delete mode 100644 experiments/si_diffusion_1x1x1/config_diffusion_mace.yaml diff --git a/experiments/si_diffusion_1x1x1/config_diffusion_mace.yaml b/experiments/si_diffusion_1x1x1/config_diffusion_mace.yaml deleted file mode 100644 index c6b79aa1..00000000 --- a/experiments/si_diffusion_1x1x1/config_diffusion_mace.yaml +++ /dev/null @@ -1,86 +0,0 @@ -# general -exp_name: difface_ode -run_name: run1 -max_epoch: 25 -log_every_n_steps: 1 -gradient_clipping: 0.1 - -# set to null to avoid setting a seed (can speed up GPU computation, but -# results will not be reproducible) -seed: 1234 - -# data -data: - batch_size: 1024 - num_workers: 8 - max_atom: 8 - -# architecture -spatial_dimension: 3 -model: - score_network: - architecture: diffusion_mace - number_of_atoms: 8 - r_max: 5.0 - num_bessel: 8 - num_polynomial_cutoff: 5 - max_ell: 2 - interaction_cls: RealAgnosticResidualInteractionBlock - interaction_cls_first: RealAgnosticInteractionBlock - num_interactions: 2 - hidden_irreps: 64x0e + 64x1o + 64x2e - mlp_irreps: 64x0e - number_of_mlp_layers: 3 - avg_num_neighbors: 1 - correlation: 3 - gate: silu - radial_MLP: [64, 64, 64] - radial_type: bessel - noise: - total_time_steps: 100 - sigma_min: 0.001 # default value - sigma_max: 0.5 # default value' - -# optimizer and scheduler -optimizer: - name: adamw - learning_rate: 0.001 - weight_decay: 1.0e-8 - -scheduler: - name: ReduceLROnPlateau - factor: 0.1 - patience: 20 - -# early stopping -early_stopping: - metric: validation_epoch_loss - mode: min - patience: 10 - -model_checkpoint: - monitor: validation_epoch_loss - mode: min - -# Sampling from the generative model -diffusion_sampling: - noise: - total_time_steps: 100 - sigma_min: 0.001 # default value - sigma_max: 0.5 # default value - sampling: - algorithm: ode - spatial_dimension: 3 - number_of_atoms: 8 - number_of_samples: 1000 - sample_every_n_epochs: 5 - record_samples: True - cell_dimensions: [5.43, 5.43, 5.43] - -# A callback to check the loss vs. sigma -loss_monitoring: - number_of_bins: 50 - sample_every_n_epochs: 5 - -logging: - - comet \ No newline at end of file From 89bec5f2be865b7c6738e2f72856ec7498508191 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Mon, 21 Oct 2024 16:08:38 -0400 Subject: [PATCH 02/29] Move dataset analysis code to another folder. --- {experiment_analysis => experiments}/dataset_analysis/__init__.py | 0 .../dataset_analysis/dataset_covariance.py | 0 .../dataset_analysis/energy_consistency_analysis.py | 0 .../dataset_analysis/plot_si_phonon_DOS.py | 0 .../dataset_analysis/si_diffusion_analysis.py | 0 .../dataset_analysis/si_diffusion_v1_analysis.py | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename {experiment_analysis => experiments}/dataset_analysis/__init__.py (100%) rename {experiment_analysis => experiments}/dataset_analysis/dataset_covariance.py (100%) rename {experiment_analysis => experiments}/dataset_analysis/energy_consistency_analysis.py (100%) rename {experiment_analysis => experiments}/dataset_analysis/plot_si_phonon_DOS.py (100%) rename {experiment_analysis => experiments}/dataset_analysis/si_diffusion_analysis.py (100%) rename {experiment_analysis => experiments}/dataset_analysis/si_diffusion_v1_analysis.py (100%) diff --git a/experiment_analysis/dataset_analysis/__init__.py b/experiments/dataset_analysis/__init__.py similarity index 100% rename from experiment_analysis/dataset_analysis/__init__.py rename to experiments/dataset_analysis/__init__.py diff --git a/experiment_analysis/dataset_analysis/dataset_covariance.py b/experiments/dataset_analysis/dataset_covariance.py similarity index 100% rename from experiment_analysis/dataset_analysis/dataset_covariance.py rename to experiments/dataset_analysis/dataset_covariance.py diff --git a/experiment_analysis/dataset_analysis/energy_consistency_analysis.py b/experiments/dataset_analysis/energy_consistency_analysis.py similarity index 100% rename from experiment_analysis/dataset_analysis/energy_consistency_analysis.py rename to experiments/dataset_analysis/energy_consistency_analysis.py diff --git a/experiment_analysis/dataset_analysis/plot_si_phonon_DOS.py b/experiments/dataset_analysis/plot_si_phonon_DOS.py similarity index 100% rename from experiment_analysis/dataset_analysis/plot_si_phonon_DOS.py rename to experiments/dataset_analysis/plot_si_phonon_DOS.py diff --git a/experiment_analysis/dataset_analysis/si_diffusion_analysis.py b/experiments/dataset_analysis/si_diffusion_analysis.py similarity index 100% rename from experiment_analysis/dataset_analysis/si_diffusion_analysis.py rename to experiments/dataset_analysis/si_diffusion_analysis.py diff --git a/experiment_analysis/dataset_analysis/si_diffusion_v1_analysis.py b/experiments/dataset_analysis/si_diffusion_v1_analysis.py similarity index 100% rename from experiment_analysis/dataset_analysis/si_diffusion_v1_analysis.py rename to experiments/dataset_analysis/si_diffusion_v1_analysis.py From 6e01d0f35e4bea2527630d66002d98077f7e5fb2 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Mon, 21 Oct 2024 16:18:04 -0400 Subject: [PATCH 03/29] Remove files that are not really needed from revision. --- .../sampling_analysis/__init__.py | 0 .../diffusion_mace_ode_sampling_analysis.py | 199 ------------------ .../diffusion_mace_ode_trajctory_analysis.py | 63 ------ ...ion_mace_score_and_partial_ode_analysis.py | 178 ---------------- .../generate_partial_ode_trajectories.py | 165 --------------- .../ode_sample_positions_to_cif_files.py | 50 ----- .../plot_energy_samples_distribution.py | 56 ----- .../sampling_EGNN_sept_10/__init__.py | 0 .../generate_trajectory_visualization.py | 44 ---- .../identify_trajectories_by_energy.py | 37 ---- .../sampling_EGNN_sept_10/plot_energies.py | 156 -------------- .../sampling_si_diffusion.py | 102 --------- 12 files changed, 1050 deletions(-) delete mode 100644 experiment_analysis/sampling_analysis/__init__.py delete mode 100644 experiment_analysis/sampling_analysis/diffusion_mace_ode_sampling_analysis.py delete mode 100644 experiment_analysis/sampling_analysis/diffusion_mace_ode_trajctory_analysis.py delete mode 100644 experiment_analysis/sampling_analysis/diffusion_mace_score_and_partial_ode_analysis.py delete mode 100644 experiment_analysis/sampling_analysis/generate_partial_ode_trajectories.py delete mode 100644 experiment_analysis/sampling_analysis/ode_sample_positions_to_cif_files.py delete mode 100644 experiment_analysis/sampling_analysis/plot_energy_samples_distribution.py delete mode 100644 experiment_analysis/sampling_analysis/sampling_EGNN_sept_10/__init__.py delete mode 100644 experiment_analysis/sampling_analysis/sampling_EGNN_sept_10/generate_trajectory_visualization.py delete mode 100644 experiment_analysis/sampling_analysis/sampling_EGNN_sept_10/identify_trajectories_by_energy.py delete mode 100644 experiment_analysis/sampling_analysis/sampling_EGNN_sept_10/plot_energies.py delete mode 100644 experiment_analysis/sampling_analysis/sampling_si_diffusion.py diff --git a/experiment_analysis/sampling_analysis/__init__.py b/experiment_analysis/sampling_analysis/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/experiment_analysis/sampling_analysis/diffusion_mace_ode_sampling_analysis.py b/experiment_analysis/sampling_analysis/diffusion_mace_ode_sampling_analysis.py deleted file mode 100644 index 45709973..00000000 --- a/experiment_analysis/sampling_analysis/diffusion_mace_ode_sampling_analysis.py +++ /dev/null @@ -1,199 +0,0 @@ -import logging -from pathlib import Path - -import matplotlib.pyplot as plt -import torch -from einops import einops - -from crystal_diffusion import DATA_DIR -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from crystal_diffusion.analysis.generator_sample_analysis_utils import \ - get_interatomic_distances -from crystal_diffusion.data.diffusion.data_loader import ( - LammpsForDiffusionDataModule, LammpsLoaderParameters) -from crystal_diffusion.utils.basis_transformations import \ - map_relative_coordinates_to_unit_cell -from experiment_analysis import EXPERIMENT_ANALYSIS_DIR - -logger = logging.getLogger(__name__) - -plt.style.use(PLOT_STYLE_PATH) - - -# Some hardcoded paths and parameters. Change as needed! -epoch = 30 -base_data_dir = Path("/Users/bruno/courtois/difface_ode/run7") -position_samples_dir = base_data_dir / "diffusion_position_samples" -energy_samples_dir = base_data_dir / "energy_samples" - - -dataset_name = "si_diffusion_1x1x1" -lammps_run_dir = str(DATA_DIR / dataset_name) -processed_dataset_dir = str(DATA_DIR / dataset_name / 'processed') -data_params = LammpsLoaderParameters(batch_size=64, max_atom=8) -cache_dir = str(EXPERIMENT_ANALYSIS_DIR / "cache" / dataset_name) - - -if __name__ == '__main__': - - datamodule = LammpsForDiffusionDataModule( - lammps_run_dir=lammps_run_dir, - processed_dataset_dir=processed_dataset_dir, - hyper_params=data_params, - working_cache_dir=cache_dir, - ) - - datamodule.setup() - - train_dataset = datamodule.train_dataset - batch = train_dataset[:1000] - - positions_data = torch.load(position_samples_dir / f"diffusion_position_sample_epoch={epoch}_steps=0.pt", - map_location=torch.device('cpu')) - - unit_cell = positions_data['unit_cell'] - - batch_times = positions_data['time'][0] - batch_noisy_relative_coordinates = positions_data['relative_coordinates'][0] - number_of_atoms, spatial_dimension = batch_noisy_relative_coordinates.shape[-2:] - - batch_flat_noisy_relative_coordinates = einops.rearrange(batch_noisy_relative_coordinates, - "b t n d -> b t (n d)") - - fig1 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig1.suptitle(f'ODE Trajectories: Sample at Epoch {epoch}') - - ax = fig1.add_subplot(111) - ax.set_xlabel('Diffusion Time') - ax.set_ylabel('Raw Relative Coordinate') - ax.yaxis.tick_right() - ax.spines['top'].set_visible(True) - ax.spines['right'].set_visible(True) - ax.spines['bottom'].set_visible(True) - ax.spines['left'].set_visible(True) - - time = batch_times[0] # all time arrays are the same - for flat_relative_coordinates in batch_flat_noisy_relative_coordinates: - for i in range(number_of_atoms * spatial_dimension): - coordinate = flat_relative_coordinates[:, i] - ax.plot(time.cpu(), coordinate.cpu(), '-', color='b', alpha=0.05) - - ax.set_xlim([1.01, -0.01]) - ax.set_ylim([-2.0, 2.0]) - plt.show() - - training_relative_coordinates = batch['relative_coordinates'] - training_center_of_mass = training_relative_coordinates.mean(dim=1).mean(dim=0) - - raw_sample_relative_coordinates = map_relative_coordinates_to_unit_cell(batch_noisy_relative_coordinates[:, -1]) - raw_sample_centers_of_mass = raw_sample_relative_coordinates.mean(dim=1) - - zero_centered_sample_relative_coordinates = (raw_sample_relative_coordinates - - raw_sample_centers_of_mass.unsqueeze(1)) - sample_relative_coordinates = (zero_centered_sample_relative_coordinates - + training_center_of_mass.unsqueeze(0).unsqueeze(0)) - - sample_relative_coordinates = map_relative_coordinates_to_unit_cell(sample_relative_coordinates) - - fig2 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig2.suptitle(f'ODE Marginal Distributions, Sample at Epoch {epoch}') - ax1 = fig2.add_subplot(131, aspect='equal') - ax2 = fig2.add_subplot(132, aspect='equal') - ax3 = fig2.add_subplot(133, aspect='equal') - - xs = einops.rearrange(sample_relative_coordinates, 'b n d -> (b n) d') - zs = einops.rearrange(training_relative_coordinates, 'b n d -> (b n) d') - ax1.set_title('XY Projection') - ax1.plot(xs[:, 0], xs[:, 1], 'ro', alpha=0.5, mew=0, label='ODE Solver Samples') - ax1.plot(zs[:, 0], zs[:, 1], 'go', alpha=0.05, mew=0, label='Training Data') - - ax2.set_title('XZ Projection') - ax2.plot(xs[:, 0], xs[:, 2], 'ro', alpha=0.5, mew=0, label='ODE Solver Samples') - ax2.plot(zs[:, 0], zs[:, 2], 'go', alpha=0.05, mew=0, label='Training Data') - - ax3.set_title('YZ Projection') - ax3.plot(xs[:, 1], xs[:, 2], 'ro', alpha=0.5, mew=0, label='ODE Solver Samples') - ax3.plot(zs[:, 1], zs[:, 2], 'go', alpha=0.05, mew=0, label='Training Data') - - for ax in [ax1, ax2, ax3]: - ax.set_xlim(-0.01, 1.01) - ax.set_ylim(-0.01, 1.01) - ax.vlines(x=[0, 1], ymin=0, ymax=1, color='k', lw=2) - ax.hlines(y=[0, 1], xmin=0, xmax=1, color='k', lw=2) - - ax2.legend(loc='lower center', bbox_to_anchor=(0.5, -0.5), ncol=2, fancybox=True, shadow=True) - fig2.tight_layout() - plt.show() - - fig3 = plt.figure(figsize=PLEASANT_FIG_SIZE) - ax1 = fig3.add_subplot(131) - ax2 = fig3.add_subplot(132) - ax3 = fig3.add_subplot(133) - fig3.suptitle(f"Marginal Distributions of t=0 Samples, Sample at Epoch {epoch}") - - common_params = dict(histtype='stepfilled', alpha=0.5, bins=50) - - ax1.hist(xs[:, 0], **common_params, facecolor='r', label='ODE solver') - ax2.hist(xs[:, 1], **common_params, facecolor='r', label='ODE solver') - ax3.hist(xs[:, 2], **common_params, facecolor='r', label='ODE solver') - - ax1.hist(zs[:, 0], **common_params, facecolor='g', label='Training Data') - ax2.hist(zs[:, 1], **common_params, facecolor='g', label='Training Data') - ax3.hist(zs[:, 2], **common_params, facecolor='g', label='Training Data') - - ax1.set_xlabel('X') - ax2.set_xlabel('Y') - ax3.set_xlabel('Z') - - for ax in [ax1, ax2, ax3]: - ax.set_xlim(-0.01, 1.01) - ax.set_yscale('log') - - ax1.legend(loc=0) - fig3.tight_layout() - plt.show() - - radial_cutoff = 5.4 - training_cartesian_positions = batch['cartesian_positions'] - basis_vectors = torch.diag_embed(batch['box']) - training_interatomic_distances = get_interatomic_distances(training_cartesian_positions, - basis_vectors, - radial_cutoff=radial_cutoff) - - sample_relative_coordinates = map_relative_coordinates_to_unit_cell(batch_noisy_relative_coordinates[:, -1]) - sample_cartesian_positions = torch.bmm(sample_relative_coordinates, unit_cell) - sample_interatomic_distances = get_interatomic_distances(sample_cartesian_positions, - unit_cell, - radial_cutoff=radial_cutoff) - - fig4 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig4.suptitle(f'Interatomic Distance Distribution: Sample at Epoch {epoch}') - - ax1 = fig4.add_subplot(121) - ax2 = fig4.add_subplot(122) - - ax1.set_title('Training vs. Samples') - ax2.set_title('Intermediate Diffusion') - - common_params = dict(histtype='stepfilled', alpha=0.5, bins=75) - ax1.hist(training_interatomic_distances, **common_params, facecolor='g', label='Training Data') - ax1.hist(sample_interatomic_distances, **common_params, facecolor='r', label='ODE Sample, t = 0') - - for time_idx, color in zip([0, len(time) // 2 + 1, -1], ['blue', 'yellow', 'red']): - sample_relative_coordinates = map_relative_coordinates_to_unit_cell( - batch_noisy_relative_coordinates[:, time_idx]) - sample_cartesian_positions = torch.bmm(sample_relative_coordinates, unit_cell) - sample_interatomic_distances = get_interatomic_distances(sample_cartesian_positions, - unit_cell, - radial_cutoff=radial_cutoff) - ax2.hist(sample_interatomic_distances, **common_params, facecolor=color, - label=f'Noisy Sample t = {time[time_idx]:2.1f}') - - for ax in [ax1, ax2]: - ax.set_xlabel('Distance (Angstrom)') - ax.set_ylabel('Count') - ax.set_xlim([-0.01, radial_cutoff]) - ax.legend(loc=0) - ax.set_yscale('log') - fig4.tight_layout() - plt.show() diff --git a/experiment_analysis/sampling_analysis/diffusion_mace_ode_trajctory_analysis.py b/experiment_analysis/sampling_analysis/diffusion_mace_ode_trajctory_analysis.py deleted file mode 100644 index 6237081c..00000000 --- a/experiment_analysis/sampling_analysis/diffusion_mace_ode_trajctory_analysis.py +++ /dev/null @@ -1,63 +0,0 @@ -import logging -from pathlib import Path - -import matplotlib.pyplot as plt -import torch - -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH - -logger = logging.getLogger(__name__) - -plt.style.use(PLOT_STYLE_PATH) - - -# Some hardcoded paths and parameters. Change as needed! -epoch = 30 -base_data_dir = Path("/Users/bruno/courtois/difface_ode/run7") -position_samples_dir = base_data_dir / "diffusion_position_samples" -energy_samples_dir = base_data_dir / "energy_samples" -energy_data_directory = base_data_dir / "energy_samples" - - -if __name__ == '__main__': - energies = torch.load(energy_data_directory / f"energies_sample_epoch={epoch}.pt") - positions_data = torch.load(position_samples_dir / f"diffusion_position_sample_epoch={epoch}_steps=0.pt", - map_location=torch.device('cpu')) - - unit_cell = positions_data['unit_cell'] - - batch_times = positions_data['time'][0] - batch_noisy_relative_coordinates = positions_data['relative_coordinates'][0] - number_of_atoms, spatial_dimension = batch_noisy_relative_coordinates.shape[-2:] - - idx = energies.argmax() - relative_coordinates = batch_noisy_relative_coordinates[idx] - - fig1 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig1.suptitle(f'ODE trajectory: Sample {idx} at Epoch {epoch} - Energy = {energies.max():4.2f}') - - ax1 = fig1.add_subplot(131) - ax2 = fig1.add_subplot(132) - ax3 = fig1.add_subplot(133) - - time = batch_times[0] # all time arrays are the same - - for atom_idx in range(number_of_atoms): - ax1.plot(time, relative_coordinates[:, atom_idx, 0], '-', alpha=0.5) - ax2.plot(time, relative_coordinates[:, atom_idx, 1], '-', alpha=0.5) - ax3.plot(time, relative_coordinates[:, atom_idx, 2], '-', alpha=0.5) - - for ax in [ax1, ax2, ax3]: - ax.set_xlabel('Diffusion Time') - ax.set_ylabel('Raw Relative Coordinate') - ax.yaxis.tick_right() - ax.spines['top'].set_visible(True) - ax.spines['right'].set_visible(True) - ax.spines['bottom'].set_visible(True) - ax.spines['left'].set_visible(True) - ax.set_xlim([1.01, -0.01]) - ax1.set_ylabel('X') - ax2.set_ylabel('Y') - ax3.set_ylabel('Z') - fig1.tight_layout() - plt.show() diff --git a/experiment_analysis/sampling_analysis/diffusion_mace_score_and_partial_ode_analysis.py b/experiment_analysis/sampling_analysis/diffusion_mace_score_and_partial_ode_analysis.py deleted file mode 100644 index ba295470..00000000 --- a/experiment_analysis/sampling_analysis/diffusion_mace_score_and_partial_ode_analysis.py +++ /dev/null @@ -1,178 +0,0 @@ -import logging -import tempfile -from pathlib import Path - -import einops -import matplotlib.patches as mpatches -import matplotlib.pyplot as plt -import numpy as np -import torch -from tqdm import tqdm - -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from crystal_diffusion.analysis.generator_sample_analysis_utils import \ - PartialODEPositionGenerator -from crystal_diffusion.models.position_diffusion_lightning_model import \ - PositionDiffusionLightningModel -from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, - NOISY_RELATIVE_COORDINATES, TIME, - UNIT_CELL) -from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps -from crystal_diffusion.samplers.noisy_relative_coordinates_sampler import \ - NoisyRelativeCoordinatesSampler -from crystal_diffusion.samplers.variance_sampler import NoiseParameters -from crystal_diffusion.utils.tensor_utils import \ - broadcast_batch_tensor_to_all_dimensions - -logger = logging.getLogger(__name__) - -plt.style.use(PLOT_STYLE_PATH) - - -# Some hardcoded paths and parameters. Change as needed! -base_data_dir = Path("/Users/bruno/courtois/difface_ode/run1") -position_samples_dir = base_data_dir / "diffusion_position_samples" -energy_data_directory = base_data_dir / "energy_samples" -model_path = base_data_dir / "best_model" / "best_model-epoch=016-step=001666.ckpt" - -partial_samples_dir = base_data_dir / "partial_samples" -partial_samples_dir.mkdir(exist_ok=True) - - -canonical_relative_coordinates = torch.tensor([[0.00, 0.00, 0.00], - [0.00, 0.50, 0.50], - [0.50, 0.00, 0.50], - [0.00, 0.50, 0.50], - [0.25, 0.25, 0.25], - [0.25, 0.75, 0.75], - [0.75, 0.25, 0.75], - [0.75, 0.75, 0.25]]) - -reference_relative_coordinates = torch.tensor([[0.0166, 0.0026, 0.9913], - [0.9936, 0.4954, 0.5073], - [0.4921, 0.9992, 0.4994], - [0.4954, 0.5009, 0.9965], - [0.2470, 0.2540, 0.2664], - [0.2481, 0.7434, 0.7445], - [0.7475, 0.2483, 0.7489], - [0.7598, 0.7563, 0.2456]]) - -sigma_min = 0.001 -sigma_max = 0.5 -total_time_steps = 100 - -noise_parameters = NoiseParameters(total_time_steps=total_time_steps, sigma_min=sigma_min, sigma_max=sigma_max) - - -cell_dimensions = torch.tensor([5.43, 5.43, 5.43]) - -number_of_atoms = 8 -spatial_dimension = 3 -batch_size = 100 -if __name__ == '__main__': - noisy_relative_coordinates_sampler = NoisyRelativeCoordinatesSampler() - unit_cell = torch.diag(torch.Tensor(cell_dimensions)).unsqueeze(0).repeat(batch_size, 1, 1) - - x0 = einops.repeat(reference_relative_coordinates, "n d -> b n d", b=batch_size) - - x_bad = torch.rand(x0.shape) - - model = PositionDiffusionLightningModel.load_from_checkpoint(model_path) - model.eval() - - list_tf = np.linspace(0.1, 1, 10) - - atom_types = np.ones(number_of_atoms, dtype=int) - - on_manifold_dataset = [] - off_manifold_dataset = [] - with torch.no_grad(): - for tf in tqdm(list_tf, 'times'): - times = torch.ones(batch_size) * tf - sigmas = sigma_min ** (1.0 - times) * sigma_max ** times - - broadcast_sigmas = broadcast_batch_tensor_to_all_dimensions(batch_values=sigmas, final_shape=x0.shape) - xt = noisy_relative_coordinates_sampler.get_noisy_relative_coordinates_sample(x0, broadcast_sigmas) - - noise_parameters.total_time_steps = int(100 * tf) + 1 - generator = PartialODEPositionGenerator(noise_parameters, - number_of_atoms, - spatial_dimension, - model.sigma_normalized_score_network, - initial_relative_coordinates=xt, - record_samples=True, - tf=tf) - - logger.info("Generating Samples") - batch_relative_coordinates = generator.sample(number_of_samples=batch_size, - device=torch.device('cpu'), - unit_cell=unit_cell) - sample_output_path = str(partial_samples_dir / f"diffusion_position_sample_time={tf:2.1f}.pt") - # write trajectories to disk and reset to save memory - generator.sample_trajectory_recorder.write_to_pickle(sample_output_path) - logger.info("Done Generating Samples") - - batch_cartesian_positions = torch.bmm(batch_relative_coordinates, unit_cell) - - box = unit_cell[0].numpy() - - list_energy = [] - - logger.info("Compute energy from Oracle") - with tempfile.TemporaryDirectory() as tmp_work_dir: - for positions in batch_cartesian_positions.numpy(): - energy, forces = get_energy_and_forces_from_lammps(positions, - box, - atom_types, - tmp_work_dir=tmp_work_dir) - list_energy.append(energy) - - energies = torch.tensor(list_energy) - energy_output_path = str(partial_samples_dir / f"diffusion_energies_sample_time={tf:2.1f}.pt") - with open(energy_output_path, 'wb') as fd: - torch.save(energies, fd) - - batch = {NOISY_RELATIVE_COORDINATES: xt, - NOISE: sigmas.unsqueeze(-1), - TIME: times.unsqueeze(-1), - UNIT_CELL: unit_cell, - CARTESIAN_FORCES: torch.zeros_like(x0) - } - normalized_scores = model.sigma_normalized_score_network(batch) - rms_norm_scores = torch.sqrt((torch.linalg.norm(normalized_scores, dim=2) ** 2).mean(dim=1)) - on_manifold_dataset.append(rms_norm_scores.numpy()) - - bad_batch = {NOISY_RELATIVE_COORDINATES: x_bad, - NOISE: sigmas.unsqueeze(-1), - TIME: times.unsqueeze(-1), - UNIT_CELL: unit_cell, - CARTESIAN_FORCES: torch.zeros_like(x0) - } - bad_normalized_scores = model.sigma_normalized_score_network(bad_batch) - bad_rms_norm_scores = torch.sqrt((torch.linalg.norm(bad_normalized_scores, dim=2) ** 2).mean(dim=1)) - off_manifold_dataset.append(bad_rms_norm_scores.numpy()) - - labels = [] - - def add_label(violin, label): - """Add some labels to the plot.""" - color = violin["bodies"][0].get_facecolor().flatten() - labels.append((mpatches.Patch(color=color), label)) - - fig1 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig1.suptitle('Mean Score Norm') - - ax1 = fig1.add_subplot(111) - - add_label(ax1.violinplot(on_manifold_dataset, positions=list_tf, widths=0.05, showmeans=True), - "ON Manifold") - add_label(ax1.violinplot(off_manifold_dataset, positions=list_tf, widths=0.05, showmeans=True), - "OFF Manifold") - - ax1.set_xlabel('Diffusion Time') - ax1.set_ylabel('RMS Normalized Score') - ax1.set_yscale('log') - - ax1.legend(*zip(*labels), loc=0) - fig1.tight_layout() - plt.show() diff --git a/experiment_analysis/sampling_analysis/generate_partial_ode_trajectories.py b/experiment_analysis/sampling_analysis/generate_partial_ode_trajectories.py deleted file mode 100644 index f6e0eeb8..00000000 --- a/experiment_analysis/sampling_analysis/generate_partial_ode_trajectories.py +++ /dev/null @@ -1,165 +0,0 @@ -import logging -import tempfile -from pathlib import Path - -import einops -import numpy as np -import torch -from pymatgen.core import Lattice, Structure -from tqdm import tqdm - -from crystal_diffusion.analysis.generator_sample_analysis_utils import \ - PartialODEPositionGenerator -from crystal_diffusion.data.diffusion.data_loader import ( - LammpsForDiffusionDataModule, LammpsLoaderParameters) -from crystal_diffusion.generators.ode_position_generator import \ - ODESamplingParameters -from crystal_diffusion.models.position_diffusion_lightning_model import \ - PositionDiffusionLightningModel -from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps -from crystal_diffusion.samplers.noisy_relative_coordinates_sampler import \ - NoisyRelativeCoordinatesSampler -from crystal_diffusion.samplers.variance_sampler import NoiseParameters -from crystal_diffusion.utils.logging_utils import setup_analysis_logger -from crystal_diffusion.utils.tensor_utils import \ - broadcast_batch_tensor_to_all_dimensions - -logger = logging.getLogger(__name__) - -setup_analysis_logger() -# Some hardcoded paths and parameters. Change as needed! - -data_directory = Path("/home/mila/r/rousseab/scratch/data/") -dataset_name = "si_diffusion_2x2x2" -lammps_run_dir = data_directory / dataset_name -processed_dataset_dir = lammps_run_dir / "processed" -cache_dir = lammps_run_dir / "cache" - -data_params = LammpsLoaderParameters(batch_size=1024, max_atom=64) - -checkpoint_path = "/network/scratch/r/rousseab/checkpoints/EGNN_Sept_10/last_model-epoch=045-step=035972.ckpt" - -partial_samples_dir = Path("/network/scratch/r/rousseab/partial_samples_EGNN_Sept_10/") -partial_samples_dir.mkdir(exist_ok=True) - -sigma_min = 0.001 -sigma_max = 0.5 -total_time_steps = 100 - -noise_parameters = NoiseParameters( - total_time_steps=total_time_steps, sigma_min=sigma_min, sigma_max=sigma_max -) - -absolute_solver_tolerance = 1.0e-3 -relative_solver_tolerance = 1.0e-2 - -spatial_dimension = 3 -batch_size = 32 -device = torch.device("cuda") - -if __name__ == "__main__": - logger.info("Extracting a validation configuration") - # Extract a configuration from the validation set - datamodule = LammpsForDiffusionDataModule( - lammps_run_dir=lammps_run_dir, - processed_dataset_dir=processed_dataset_dir, - hyper_params=data_params, - working_cache_dir=cache_dir, - ) - datamodule.setup() - - validation_example = datamodule.valid_dataset[0] - reference_relative_coordinates = validation_example["relative_coordinates"] - number_of_atoms = int(validation_example["natom"]) - cell_dimensions = validation_example["box"] - - logger.info("Writing validation configuration to cif file") - a, b, c = cell_dimensions.numpy() - lattice = Lattice.from_parameters(a=a, b=b, c=c, alpha=90, beta=90, gamma=90) - - reference_structure = Structure( - lattice=lattice, - species=number_of_atoms * ["Si"], - coords=reference_relative_coordinates.numpy(), - ) - - reference_structure.to( - str(partial_samples_dir / "reference_validation_structure.cif") - ) - - logger.info("Extracting checkpoint") - noisy_relative_coordinates_sampler = NoisyRelativeCoordinatesSampler() - - unit_cell = ( - torch.diag(torch.Tensor(cell_dimensions)).unsqueeze(0).repeat(batch_size, 1, 1) - ) - box = unit_cell[0].numpy() - - x0 = einops.repeat(reference_relative_coordinates, "n d -> b n d", b=batch_size) - - model = PositionDiffusionLightningModel.load_from_checkpoint(checkpoint_path) - model.eval() - - list_tf = np.linspace(0.1, 1, 20) - atom_types = np.ones(number_of_atoms, dtype=int) - - logger.info("Draw samples") - with torch.no_grad(): - for tf in tqdm(list_tf, "times"): - times = torch.ones(batch_size) * tf - sigmas = sigma_min ** (1.0 - times) * sigma_max**times - - broadcast_sigmas = broadcast_batch_tensor_to_all_dimensions( - batch_values=sigmas, final_shape=x0.shape - ) - xt = noisy_relative_coordinates_sampler.get_noisy_relative_coordinates_sample( - x0, broadcast_sigmas - ) - - noise_parameters.total_time_steps = int(1000 * tf) + 1 - sampling_parameters = ODESamplingParameters( - number_of_atoms=number_of_atoms, - number_of_samples=batch_size, - record_samples=True, - cell_dimensions=list(cell_dimensions.cpu().numpy()), - absolute_solver_tolerance=absolute_solver_tolerance, - relative_solver_tolerance=relative_solver_tolerance, - ) - - generator = PartialODEPositionGenerator( - noise_parameters=noise_parameters, - sampling_parameters=sampling_parameters, - sigma_normalized_score_network=model.sigma_normalized_score_network, - initial_relative_coordinates=xt, - tf=tf, - ) - - logger.info("Generating Samples") - batch_relative_coordinates = generator.sample( - number_of_samples=batch_size, device=device, unit_cell=unit_cell - ).cpu() - sample_output_path = str( - partial_samples_dir / f"diffusion_position_sample_time={tf:4.3f}.pt" - ) - generator.sample_trajectory_recorder.write_to_pickle(sample_output_path) - logger.info("Done Generating Samples") - - batch_cartesian_positions = torch.bmm(batch_relative_coordinates, unit_cell) - - list_energy = [] - logger.info("Compute energy from Oracle") - with tempfile.TemporaryDirectory() as tmp_work_dir: - for positions in batch_cartesian_positions.numpy(): - energy, forces = get_energy_and_forces_from_lammps( - positions, box, atom_types, tmp_work_dir=tmp_work_dir - ) - list_energy.append(energy) - - energies = torch.tensor(list_energy) - logger.info("Done Computing energy from Oracle") - - energy_output_path = str( - partial_samples_dir / f"diffusion_energies_sample_time={tf:4.3f}.pt" - ) - with open(energy_output_path, "wb") as fd: - torch.save(energies, fd) diff --git a/experiment_analysis/sampling_analysis/ode_sample_positions_to_cif_files.py b/experiment_analysis/sampling_analysis/ode_sample_positions_to_cif_files.py deleted file mode 100644 index 2ef2f0b8..00000000 --- a/experiment_analysis/sampling_analysis/ode_sample_positions_to_cif_files.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Position to cif files for the ODE sampler. - -A simple script to extract the diffusion positions from a pickle on disk and output -in cif format for visualization. -""" -from pathlib import Path - -import torch -from pymatgen.core import Lattice, Structure - -from crystal_diffusion.utils.sample_trajectory import ODESampleTrajectory - -# Hard coding some paths to local results. Modify as needed... -epoch = 15 - -base_data_dir = Path("/Users/bruno/courtois/difface_ode/run1") -trajectory_data_directory = base_data_dir / "diffusion_position_samples" -energy_data_directory = base_data_dir / "energy_samples" -output_top_dir = trajectory_data_directory.parent / "visualization" - - -if __name__ == '__main__': - energies = torch.load(energy_data_directory / f"energies_sample_epoch={epoch}.pt") - - sample_idx = energies.argmax() - output_dir = output_top_dir / f"visualise_sampling_trajectory_epoch_{epoch}_sample_{sample_idx}" - output_dir.mkdir(exist_ok=True, parents=True) - - pickle_path = trajectory_data_directory / f"diffusion_position_sample_epoch={epoch}_steps=0.pt" - sample_trajectory = ODESampleTrajectory.read_from_pickle(pickle_path) - - basis_vectors = sample_trajectory.data['unit_cell'][sample_idx].numpy() - lattice = Lattice(matrix=basis_vectors, pbc=(True, True, True)) - - # Shape [batch, time, number of atoms, space dimension] - batch_noisy_relative_coordinates = sample_trajectory.data['relative_coordinates'][0] - - noisy_relative_coordinates = batch_noisy_relative_coordinates[sample_idx].numpy() - - for idx, coordinates in enumerate(noisy_relative_coordinates): - number_of_atoms = coordinates.shape[0] - species = number_of_atoms * ['Si'] - - structure = Structure(lattice=lattice, - species=species, - coords=coordinates, - coords_are_cartesian=False) - - file_path = str(output_dir / f"diffusion_positions_{idx}.cif") - structure.to_file(file_path) diff --git a/experiment_analysis/sampling_analysis/plot_energy_samples_distribution.py b/experiment_analysis/sampling_analysis/plot_energy_samples_distribution.py deleted file mode 100644 index fdd7e138..00000000 --- a/experiment_analysis/sampling_analysis/plot_energy_samples_distribution.py +++ /dev/null @@ -1,56 +0,0 @@ -from pathlib import Path - -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd - -from crystal_diffusion import ANALYSIS_RESULTS_DIR -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from experiment_analysis.analysis_utils import get_thermo_dataset - -plt.style.use(PLOT_STYLE_PATH) - -dataset_name = 'si_diffusion_2x2x2' - - -if dataset_name == 'si_diffusion_1x1x1': - dataset_name_for_data_fetching = 'si_diffusion_1x1x1' - upper_limit_factor = 0.1 -elif dataset_name == 'si_diffusion_2x2x2': - dataset_name_for_data_fetching = 'si_diffusion_small' - upper_limit_factor = 5. - -sample_pickle_path = Path(__file__).parent.joinpath(f'{dataset_name}_energy_samples.pkl') - - -if __name__ == '__main__': - sample_df = pd.read_pickle(sample_pickle_path) - - train_df, _ = get_thermo_dataset(dataset_name_for_data_fetching) - - train_energies = train_df['energy'].values - sample_energies = sample_df['energy'].values - - delta_e = train_energies.max() - train_energies.min() - emin = train_energies.min() - 0.1 * delta_e - emax = train_energies.max() + upper_limit_factor * delta_e - - number_of_samples_in_range = np.logical_and(sample_energies >= emin, sample_energies <= emax).sum() - bins = np.linspace(emin, emax, 101) - - fig = plt.figure(figsize=PLEASANT_FIG_SIZE) - - fig.suptitle(f'Comparing Training and Sampling Energy Distributions\n Dataset = {dataset_name}') - - ax1 = fig.add_subplot(111) - - common_params = dict(density=True, bins=bins, histtype="stepfilled", alpha=0.25) - ax1.hist(sample_energies, **common_params, - label=f'Samples (total count = {len(sample_df)}, in range = {number_of_samples_in_range})', color='red') - ax1.hist(train_energies, **common_params, label=f'Training Data (count = {len(train_df)})', color='green') - ax1.set_xlabel('Energy (eV)') - ax1.set_ylabel('Density') - ax1.legend(loc=0) - - fig.tight_layout() - fig.savefig(ANALYSIS_RESULTS_DIR.joinpath(f"{dataset_name}_sample_energy_distribution.png")) diff --git a/experiment_analysis/sampling_analysis/sampling_EGNN_sept_10/__init__.py b/experiment_analysis/sampling_analysis/sampling_EGNN_sept_10/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/experiment_analysis/sampling_analysis/sampling_EGNN_sept_10/generate_trajectory_visualization.py b/experiment_analysis/sampling_analysis/sampling_EGNN_sept_10/generate_trajectory_visualization.py deleted file mode 100644 index 73840d51..00000000 --- a/experiment_analysis/sampling_analysis/sampling_EGNN_sept_10/generate_trajectory_visualization.py +++ /dev/null @@ -1,44 +0,0 @@ -import logging -from pathlib import Path - -from crystal_diffusion.utils.logging_utils import setup_analysis_logger -from crystal_diffusion.utils.ovito_utils import (create_cif_files, - create_ovito_session_state) - -logger = logging.getLogger(__name__) - -setup_analysis_logger() - -results_dir = Path( - "/Users/bruno/courtois/partial_trajectory_sampling_EGNN_sept_10/partial_samples_EGNN_Sept_10" -) -number_of_trajectories = 32 # the 'batch' dimension - -reference_cif_file = results_dir / "reference_validation_structure_Hydrogen.cif" - -list_sample_times = [1.0] -if __name__ == "__main__": - for sample_time in list_sample_times: - logger.info(f"Processing sample time = {sample_time}") - pickle_path = ( - results_dir / f"diffusion_position_sample_time={sample_time:4.3f}.pt" - ) - - trajectory_directory = ( - results_dir / f"trajectories_sample_time={sample_time:4.3f}" - ) - for trj_idx in range(number_of_trajectories): - logger.info( - f" - Computing Ovito trajectory session state for trajectory index {trj_idx}" - ) - create_cif_files( - visualization_artifacts_path=trajectory_directory, - trajectory_index=trj_idx, - ode_trajectory_pickle=pickle_path, - ) - - create_ovito_session_state( - visualization_artifacts_path=trajectory_directory, - trajectory_index=trj_idx, - reference_cif_file=reference_cif_file, - ) diff --git a/experiment_analysis/sampling_analysis/sampling_EGNN_sept_10/identify_trajectories_by_energy.py b/experiment_analysis/sampling_analysis/sampling_EGNN_sept_10/identify_trajectories_by_energy.py deleted file mode 100644 index 8d67a18f..00000000 --- a/experiment_analysis/sampling_analysis/sampling_EGNN_sept_10/identify_trajectories_by_energy.py +++ /dev/null @@ -1,37 +0,0 @@ -import glob -import logging -from pathlib import Path - -import matplotlib.pyplot as plt -import pandas as pd -import torch - -from crystal_diffusion.analysis import PLOT_STYLE_PATH -from crystal_diffusion.utils.logging_utils import setup_analysis_logger - -plt.style.use(PLOT_STYLE_PATH) - -logger = logging.getLogger(__name__) -setup_analysis_logger() - -results_dir = Path("/Users/bruno/courtois/partial_trajectory_sampling_EGNN_sept_10/partial_samples_EGNN_Sept_10") - - -tf = 1.0 - -if __name__ == '__main__': - - list_rows = [] - for pickle_path in glob.glob(str(results_dir / 'diffusion_energies_sample_time=*.pt')): - energies = torch.load(pickle_path).numpy() - time = float(pickle_path.split('=')[1].split('.pt')[0]) - - for idx, energy in enumerate(energies): - row = dict(tf=time, trajectory_index=idx, energy=energy) - list_rows.append(row) - - df = pd.DataFrame(list_rows).sort_values(by=['tf', 'energy']) - - groups = df.groupby('tf') - - sub_df = groups.get_group(tf) diff --git a/experiment_analysis/sampling_analysis/sampling_EGNN_sept_10/plot_energies.py b/experiment_analysis/sampling_analysis/sampling_EGNN_sept_10/plot_energies.py deleted file mode 100644 index d7ee5812..00000000 --- a/experiment_analysis/sampling_analysis/sampling_EGNN_sept_10/plot_energies.py +++ /dev/null @@ -1,156 +0,0 @@ -import glob -import logging -import tempfile -from pathlib import Path - -import matplotlib.pyplot as plt -import numpy as np -import torch -from pymatgen.core import Structure - -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps -from crystal_diffusion.utils.logging_utils import setup_analysis_logger - -plt.style.use(PLOT_STYLE_PATH) - -logger = logging.getLogger(__name__) -setup_analysis_logger() - -results_dir = Path( - "/Users/bruno/courtois/partial_trajectory_sampling_EGNN_sept_10/partial_samples_EGNN_Sept_10" -) -reference_cif = results_dir / "reference_validation_structure.cif" - - -if __name__ == "__main__": - times = np.linspace(0, 1, 1001) - sigma_min = 0.001 - sigma_max = 0.5 - - def sigma_function(times): - """Compute sigma.""" - return sigma_min ** (1.0 - times) * sigma_max**times - - sigmas = sigma_function(times) - - special_times = [0.479, 0.668, 0.905] - list_colors = ["green", "black", "red"] - - fig = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig.suptitle("Noise Schedule") - ax = fig.add_subplot(111) - ax.plot(times, sigmas, "b-") - - for tf, c in zip(special_times, list_colors): - sf = sigma_function(tf) - ax.vlines( - tf, 0, 0.5, colors=c, linestyles="dashed", label=r"$\sigma$ = " + f"{sf:5.4f}" - ) - - ax.set_xlabel(r"Time ($t_f$)") - ax.set_ylabel(r"Noise $\sigma$") - ax.set_xlim([0, 1]) - ax.set_ylim([0, 0.5]) - ax.legend(loc=0) - plt.show() - - reference_structure = Structure.from_file(reference_cif) - list_energy = [] - logger.info("Compute reference energy from Oracle") - with tempfile.TemporaryDirectory() as tmp_work_dir: - atom_types = np.array(len(reference_structure) * [1]) - positions = reference_structure.frac_coords @ reference_structure.lattice.matrix - reference_energy, _ = get_energy_and_forces_from_lammps( - positions, - reference_structure.lattice.matrix, - atom_types, - tmp_work_dir=tmp_work_dir, - ) - - list_times = [] - list_energies = [] - for pickle_path in glob.glob( - str(results_dir / "diffusion_energies_sample_time=*.pt") - ): - energies = torch.load(pickle_path).numpy() - time = float(pickle_path.split("=")[1].split(".pt")[0]) - - list_times.append(time) - list_energies.append(energies) - - times = np.array(list_times) - energies = np.array(list_energies) - - sorting_indices = np.argsort(times) - times = times[sorting_indices] - energies = energies[sorting_indices] - - fig = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig.suptitle("Energy Quantiles for Partial Trajectories Final Point") - ax1 = fig.add_subplot(221) - ax2 = fig.add_subplot(222) - ax3 = fig.add_subplot(223) - ax4 = fig.add_subplot(224) - list_axes = [ax1, ax2, ax3, ax4] - list_q = np.linspace(0, 1, 21) - number_of_times = len(times) - - for ax, indices in zip( - list_axes, np.split(np.arange(number_of_times), len(list_axes)) - ): - # Ad Hoc hack so we can see something - e_min = np.min([energies[indices].min(), reference_energy]) - e_95 = np.quantile(energies[indices], 0.95) - delta = (e_95 - e_min) / 10.0 - e_max = e_95 + delta - - ax.set_ylim(e_min - 0.1, e_max) - - ax.hlines( - reference_energy, - 0, - 100, - color="black", - linestyles="dashed", - label="Reference Energy", - ) - - for idx in indices: - tf = times[idx] - time_energies = energies[idx] - energy_quantiles = np.quantile(time_energies, list_q) - ax.plot(100 * list_q, energy_quantiles, "-", label=f"time = {tf:3.2f}") - - ax.legend(loc=0, fontsize=7) - ax.set_xlim([-0.1, 100.1]) - ax.set_xlabel("Quantile (%)") - ax.set_ylabel("Energy (eV)") - fig.tight_layout() - plt.show() - - fig2 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig2.suptitle("Energy Extrema of Trajectory End Points") - ax = fig2.add_subplot(111) - ax.plot(times, energies.min(axis=1), "o-", label="Minimum Energy") - ax.plot( - times, np.quantile(energies, 0.5, axis=1), "o-", label="50% Quantile Energy" - ) - ax.plot(times, energies.max(axis=1), "o-", label="Maximum Energy") - ax.set_xlabel("Starting Diffusion Time, $t_f$") - ax.set_ylabel("Energy (eV)") - - ax.hlines( - reference_energy, - 0, - 100, - color="black", - linestyles="dashed", - label="Reference Energy", - ) - - ax.set_ylim(-280, -120) - ax.set_xlim(0.0, 1.01) - ax.legend(loc=0) - - plt.show() diff --git a/experiment_analysis/sampling_analysis/sampling_si_diffusion.py b/experiment_analysis/sampling_analysis/sampling_si_diffusion.py deleted file mode 100644 index dd7d4518..00000000 --- a/experiment_analysis/sampling_analysis/sampling_si_diffusion.py +++ /dev/null @@ -1,102 +0,0 @@ -"""Sampling Si diffusion 1x1x1. - -This script loads a pre-trained model, creates the sampler and draws samples. -The energy of the samples are then obtained from the LAMMPS oracle. The dump -files are renamed and kept so that they can be visualized. -""" -import logging -import os -from pathlib import Path - -import numpy as np -import pandas as pd -import torch -import yaml -from yaml import load - -from crystal_diffusion import DATA_DIR, TOP_DIR -from crystal_diffusion.generators.langevin_generator import LangevinGenerator -from crystal_diffusion.models.instantiate_diffusion_model import \ - load_diffusion_model -from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps -from crystal_diffusion.samplers.variance_sampler import NoiseParameters -from crystal_diffusion.utils.logging_utils import setup_analysis_logger - -logger = logging.getLogger(__name__) - -setup_analysis_logger() - -dataset_name = 'si_diffusion_2x2x2' -# dataset_name = 'si_diffusion_1x1x1' - - -# TODO: cell_size should be easily available programmatically. -if dataset_name == 'si_diffusion_1x1x1': - cell_size = 5.43 - experiment_dir = TOP_DIR.joinpath("experiments/si_diffusion_1x1x1/run1/") - config_path = str(experiment_dir.joinpath("config_diffusion.yaml")) - checkpoint_path = str(experiment_dir.joinpath("best_model/best_model-epoch=156-step=061386.ckpt")) -elif dataset_name == 'si_diffusion_2x2x2': - cell_size = 10.86 - experiment_dir = TOP_DIR.joinpath("experiments/si_diffusion_2x2x2/run1/") - config_path = str(experiment_dir.joinpath("config_diffusion.yaml")) - checkpoint_path = str(experiment_dir.joinpath("best_model/best_model-epoch=1972-step=173623.ckpt")) - - -lammps_work_directory = Path(__file__).parent.joinpath(f"lammps_work_directory/{dataset_name}") -lammps_work_directory.mkdir(exist_ok=True, parents=True) -lammps_work_directory = str(lammps_work_directory) - -number_of_corrector_steps = 3 -total_time_steps = 100 -noise_parameters = NoiseParameters(total_time_steps=total_time_steps) - -box = np.diag([cell_size, cell_size, cell_size]) - -number_of_samples = 4096 - - -if __name__ == '__main__': - logger.info("Loading checkpoint") - checkpoint = torch.load(checkpoint_path, map_location=torch.device('cpu')) - - with open(config_path, 'r') as stream: - hyper_params = load(stream, Loader=yaml.FullLoader) - pl_model = load_diffusion_model(hyper_params) - pl_model.load_state_dict(state_dict=checkpoint['state_dict']) - - score_network_parameters = pl_model.hyper_params.score_network_parameters - number_of_atoms = score_network_parameters.number_of_atoms - atom_types = np.ones(number_of_atoms, dtype=int) - - sigma_normalized_score_network = pl_model.sigma_normalized_score_network - - logger.info("Creating sampler") - pc_sampler = LangevinGenerator(noise_parameters=noise_parameters, - number_of_corrector_steps=number_of_corrector_steps, - number_of_atoms=number_of_atoms, - spatial_dimension=score_network_parameters.spatial_dimension, - sigma_normalized_score_network=sigma_normalized_score_network) - - logger.info("Draw samples") - samples = pc_sampler.sample(number_of_samples) - - batch_relative_positions = samples.cpu().numpy() - batch_positions = np.dot(batch_relative_positions, box) - - list_energy = [] - - logger.info("Compute energy from Oracle") - for idx, positions in enumerate(batch_positions): - energy, forces = get_energy_and_forces_from_lammps(positions, - box, - atom_types, - tmp_work_dir=lammps_work_directory, - pair_coeff_dir=DATA_DIR) - list_energy.append(energy) - src = os.path.join(lammps_work_directory, "dump.yaml") - dst = os.path.join(lammps_work_directory, f"dump_{idx}.yaml") - os.rename(src, dst) - - df = pd.DataFrame({'energy': list_energy}) - pd.to_pickle(df, f'{dataset_name}_energy_samples.pkl') From 6d7501b08f1fb577a1d2474493fea29305d31d8a Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Mon, 21 Oct 2024 16:20:56 -0400 Subject: [PATCH 04/29] Move stuff. --- .../sampling_sota_model/plot_repaint_score_trajectories.py | 0 .../sampling_sota_model/repaint_with_sota_score.py | 0 .../sampling_sota_model/sota_score_sampling_and_plotting.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {experiment_analysis => experiments}/sampling_sota_model/plot_repaint_score_trajectories.py (100%) rename {experiment_analysis => experiments}/sampling_sota_model/repaint_with_sota_score.py (100%) rename {experiment_analysis => experiments}/sampling_sota_model/sota_score_sampling_and_plotting.py (100%) diff --git a/experiment_analysis/sampling_sota_model/plot_repaint_score_trajectories.py b/experiments/sampling_sota_model/plot_repaint_score_trajectories.py similarity index 100% rename from experiment_analysis/sampling_sota_model/plot_repaint_score_trajectories.py rename to experiments/sampling_sota_model/plot_repaint_score_trajectories.py diff --git a/experiment_analysis/sampling_sota_model/repaint_with_sota_score.py b/experiments/sampling_sota_model/repaint_with_sota_score.py similarity index 100% rename from experiment_analysis/sampling_sota_model/repaint_with_sota_score.py rename to experiments/sampling_sota_model/repaint_with_sota_score.py diff --git a/experiment_analysis/sampling_sota_model/sota_score_sampling_and_plotting.py b/experiments/sampling_sota_model/sota_score_sampling_and_plotting.py similarity index 100% rename from experiment_analysis/sampling_sota_model/sota_score_sampling_and_plotting.py rename to experiments/sampling_sota_model/sota_score_sampling_and_plotting.py From 06aa6e9254fc2746b7efc3b2db913b8780007db2 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Mon, 21 Oct 2024 16:22:16 -0400 Subject: [PATCH 05/29] Clean needless stuff. --- .../score_analysis/__init__.py | 0 .../compute_scores_along_path.py | 102 ------------ .../score_analysis/plot_scores_along_path.py | 146 ------------------ 3 files changed, 248 deletions(-) delete mode 100644 experiment_analysis/score_analysis/__init__.py delete mode 100644 experiment_analysis/score_analysis/compute_scores_along_path.py delete mode 100644 experiment_analysis/score_analysis/plot_scores_along_path.py diff --git a/experiment_analysis/score_analysis/__init__.py b/experiment_analysis/score_analysis/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/experiment_analysis/score_analysis/compute_scores_along_path.py b/experiment_analysis/score_analysis/compute_scores_along_path.py deleted file mode 100644 index de979e43..00000000 --- a/experiment_analysis/score_analysis/compute_scores_along_path.py +++ /dev/null @@ -1,102 +0,0 @@ -import logging -from pathlib import Path - -import torch -from tqdm import tqdm - -from crystal_diffusion.models.position_diffusion_lightning_model import \ - PositionDiffusionLightningModel -from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, - NOISY_RELATIVE_COORDINATES, TIME, - UNIT_CELL) -from crystal_diffusion.samplers.noisy_relative_coordinates_sampler import \ - NoisyRelativeCoordinatesSampler -from crystal_diffusion.samplers.variance_sampler import ( - ExplodingVarianceSampler, NoiseParameters) -from crystal_diffusion.utils.basis_transformations import \ - map_relative_coordinates_to_unit_cell - -logger = logging.getLogger(__name__) - -# Some hardcoded paths and parameters. Change as needed! -# base_data_dir = Path("/home/mila/r/rousseab/experiments/mlp_june12/run5/output/") -# model_path = base_data_dir / "best_model/best_model-epoch=135-step=013328.ckpt" -# model_name = "mlp_jun12_run5" - - -base_data_dir = Path("/home/mila/r/rousseab/experiments/diffusion_mace_ode/run7/output/") -model_path = base_data_dir / "best_model/best_model-epoch=027-step=000924.ckpt" -model_name = "diffusion_mace_ode_run7" - - -output_dir = base_data_dir / "scores_along_a_path" -output_dir.mkdir(exist_ok=True) -output_path = output_dir / f"{model_name}_path_scores.pkl" - -if torch.cuda.is_available(): - device = torch.device('cuda') -else: - device = torch.device('cpu') - -canonical_relative_coordinates = torch.tensor([[0.00, 0.00, 0.00], - [0.00, 0.50, 0.50], - [0.50, 0.00, 0.50], - [0.50, 0.50, 0.00], - [0.25, 0.25, 0.25], - [0.25, 0.75, 0.75], - [0.75, 0.25, 0.75], - [0.75, 0.75, 0.25]]).to(device) - -sigma_min = 0.001 -sigma_max = 0.5 -total_time_steps = 10 - -noise_parameters = NoiseParameters(total_time_steps=total_time_steps, sigma_min=sigma_min, sigma_max=sigma_max) -noise_sampler = ExplodingVarianceSampler(noise_parameters) - -cell_dimensions = torch.tensor([5.43, 5.43, 5.43]).to(device) - - -number_of_atoms = 8 -spatial_dimension = 3 - -number_of_steps = 101 - -if __name__ == '__main__': - - torch.manual_seed(42) - epsilon = torch.randn((number_of_atoms, spatial_dimension)).to(device) - record = dict(epsilon=epsilon) - - trajectory_sigmas = torch.linspace(sigma_min, sigma_max, number_of_steps).to(device) - record['trajectory_sigmas'] = trajectory_sigmas - - noisy_relative_coordinates_sampler = NoisyRelativeCoordinatesSampler() - unit_cell = torch.diag(torch.Tensor(cell_dimensions)).unsqueeze(0).repeat(number_of_steps, 1, 1) - - x = torch.stack([canonical_relative_coordinates + sigma * epsilon for sigma in trajectory_sigmas]).to(device) - x = map_relative_coordinates_to_unit_cell(x) - - times = noise_sampler._time_array - sigmas = noise_sampler._sigma_array - record['times'] = times - record['sigmas'] = sigmas - - model = PositionDiffusionLightningModel.load_from_checkpoint(model_path) - model.eval() - - normalized_scores = [] - for time, sigma in tqdm(zip(times, sigmas), 'Times'): - batch = {NOISY_RELATIVE_COORDINATES: x, - NOISE: sigma * torch.ones(number_of_steps, 1).to(device), - TIME: time * torch.ones(number_of_steps, 1).to(device), - UNIT_CELL: unit_cell, - CARTESIAN_FORCES: torch.zeros_like(x).to(device) - } - with torch.no_grad(): - normalized_scores.append(model.sigma_normalized_score_network(batch)) - - record['normalized_scores'] = torch.stack(normalized_scores) - - with open(str(output_path), 'wb') as fd: - torch.save(record, fd) diff --git a/experiment_analysis/score_analysis/plot_scores_along_path.py b/experiment_analysis/score_analysis/plot_scores_along_path.py deleted file mode 100644 index cc7b998f..00000000 --- a/experiment_analysis/score_analysis/plot_scores_along_path.py +++ /dev/null @@ -1,146 +0,0 @@ -import logging -from pathlib import Path - -import matplotlib.pyplot as plt -import torch -from tqdm import tqdm - -from crystal_diffusion import ANALYSIS_RESULTS_DIR -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from crystal_diffusion.score.wrapped_gaussian_score import \ - get_sigma_normalized_score -from crystal_diffusion.utils.basis_transformations import \ - map_relative_coordinates_to_unit_cell -from crystal_diffusion.utils.logging_utils import setup_analysis_logger -from crystal_diffusion.utils.tensor_utils import \ - broadcast_batch_tensor_to_all_dimensions - -setup_analysis_logger() - -logger = logging.getLogger(__name__) - -plt.style.use(PLOT_STYLE_PATH) - -base_path = Path("/Users/bruno/courtois/scores_along_a_path") - -model_name1 = 'diffusion_mace_ode-run7' -data_path1 = base_path / "diffusion_mace_ode_run7_path_scores.pkl" - -model_name2 = 'mlp_jun12-run5' -data_path2 = base_path / "mlp_jun12_run5_path_scores.pkl" - -output_dir = ANALYSIS_RESULTS_DIR / "scores_along_path" -output_dir.mkdir(exist_ok=True) - - -x0 = torch.tensor([[0.00, 0.00, 0.00], - [0.00, 0.50, 0.50], - [0.50, 0.00, 0.50], - [0.50, 0.50, 0.00], - [0.25, 0.25, 0.25], - [0.25, 0.75, 0.75], - [0.75, 0.25, 0.75], - [0.75, 0.75, 0.25]]) - - -lambda_data = 0.0075 # estimated from the training data. -if __name__ == '__main__': - - data1 = torch.load(data_path1, map_location=torch.device('cpu')) - data2 = torch.load(data_path2, map_location=torch.device('cpu')) - - logging.info(f"Checking that {data_path1} and {data_path2} are consistent") - # Double check that the reference data is the same for both models. - for key in ['epsilon', 'trajectory_sigmas', 'times', 'sigmas']: - values1 = data1[key] - values2 = data2[key] - torch.testing.assert_close(values1, values2) - - epsilon = data1['epsilon'] - trajectory_lambda = data1['trajectory_sigmas'] - sigmas = data1['sigmas'] - - number_of_trajectory_points = len(trajectory_lambda) - - normalized_scores1 = data1['normalized_scores'] - normalized_scores2 = data2['normalized_scores'] - - logging.info("Computing the x0 targets") - # xt is different points along a trajectory - # shape: [batch_size = number_of_trajectory_points, natoms, spatial dimension] - xt = torch.stack([x0 + lbda * epsilon for lbda in trajectory_lambda]) - # yes, it's silly to add sigma * epsilon only to remove it after, but I think it makes the script clearer. - delta_relative_coordinates = map_relative_coordinates_to_unit_cell(xt - x0) - - list_target_normalized_scores_x0 = [] - list_target_normalized_scores_x_data = [] - - for sigma in sigmas: - broadcast_sigmas = broadcast_batch_tensor_to_all_dimensions( - batch_values=sigma * torch.ones(number_of_trajectory_points), final_shape=xt.shape - ) - target_normalized_scores = get_sigma_normalized_score(delta_relative_coordinates, broadcast_sigmas, kmax=4) - list_target_normalized_scores_x0.append(target_normalized_scores) - - broadcast_sigmas = broadcast_batch_tensor_to_all_dimensions( - batch_values=(sigma + lambda_data) * torch.ones(number_of_trajectory_points), final_shape=xt.shape) - target_normalized_scores = get_sigma_normalized_score(delta_relative_coordinates, broadcast_sigmas, kmax=4) - list_target_normalized_scores_x_data.append(target_normalized_scores) - - target_normalized_scores_x0 = torch.stack(list_target_normalized_scores_x0) - target_normalized_scores_x_data = torch.stack(list_target_normalized_scores_x_data) - - logging.info("Generating plots...") - scores = [target_normalized_scores_x0, target_normalized_scores_x_data, normalized_scores1, normalized_scores2] - labels = ['${\\bf x}_0$ TARGET', '${\\bf x}_0 +\\lambda_{data} \\epsilon$ TARGET', - f'Model {model_name1}', f'Model {model_name2}'] - linestyles = ['--', '--', '-', '-'] - - for time_idx in tqdm(range(1, len(sigmas)), "time"): - reference_sigma = sigmas[time_idx] - list_x = trajectory_lambda / reference_sigma - - on_manifold_range_min = lambda_data / reference_sigma - on_manifold_range_max = lambda_data / reference_sigma + 2. - - idx_min = torch.where(list_x < on_manifold_range_min)[0][-1] - try: - idx_max = torch.where(list_x > on_manifold_range_max)[0][0] - except KeyError: - idx_max = len(list_x) - in_range_mask = torch.zeros_like(list_x, dtype=torch.bool) - in_range_mask[idx_min:idx_max + 1] = True - - for space_index, component in zip([0, 1, 2], ['X', 'Y', 'Z']): - for atom_idx in range(8): - fig1 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig1.suptitle(f"Comparing the {component} component of the Score Atom {atom_idx}" - f"\n $\\sigma(t)$ = {reference_sigma:4.3f}") - - ax1 = fig1.add_subplot(121) - ax2 = fig1.add_subplot(122) - - for score, label, ls in zip(scores, labels, linestyles): - list_y = score[time_idx, :, atom_idx, space_index] - ax1.plot(list_x[in_range_mask], list_y[in_range_mask], ls=ls, label='__nolabel__') - ax2.plot(list_x, list_y, ls=ls, label=label) - - ax1.axvspan(on_manifold_range_min, on_manifold_range_max, - ymin=0, ymax=1, alpha=0.1, color='gray', label='On Manifold') - ax2.axvspan(on_manifold_range_min, on_manifold_range_max, - ymin=0, ymax=1, alpha=0.1, color='gray', label='__nolabel__') - - ax1.legend(loc=0) - ax2.legend(loc=0) - ax1.set_xlim([on_manifold_range_min - 0.1, on_manifold_range_max + 0.1]) - ax2.set_xlim(xmin=0) - - ax1.set_title('Zoom In') - ax2.set_title('Broad View') - for ax in [ax1, ax2]: - ax.set_xlabel('$\\lambda / \\sigma(t)$') - ax.set_ylabel('$\\sigma(t)\\times {\\bf s}( x(\\lambda), \\sigma(t))$') - - fig1.tight_layout() - fig1.savefig(output_dir / f"path_scores_{component}_atom_idx={atom_idx}_time_idx={time_idx}.png") - plt.close(fig1) From 55b903be7f6ae07014ee772789bdc54e7e1e687e Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Mon, 21 Oct 2024 16:24:02 -0400 Subject: [PATCH 06/29] Move utils file to relevant location. --- {experiment_analysis => experiments}/analysis_utils.py | 0 experiments/dataset_analysis/energy_consistency_analysis.py | 2 +- experiments/dataset_analysis/si_diffusion_analysis.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename {experiment_analysis => experiments}/analysis_utils.py (100%) diff --git a/experiment_analysis/analysis_utils.py b/experiments/analysis_utils.py similarity index 100% rename from experiment_analysis/analysis_utils.py rename to experiments/analysis_utils.py diff --git a/experiments/dataset_analysis/energy_consistency_analysis.py b/experiments/dataset_analysis/energy_consistency_analysis.py index c3ed4afe..f2445fce 100644 --- a/experiments/dataset_analysis/energy_consistency_analysis.py +++ b/experiments/dataset_analysis/energy_consistency_analysis.py @@ -22,7 +22,7 @@ from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps from crystal_diffusion.utils.logging_utils import setup_analysis_logger from experiment_analysis import EXPERIMENT_ANALYSIS_DIR -from experiment_analysis.analysis_utils import get_thermo_dataset +from experiments.analysis_utils import get_thermo_dataset plt.style.use(PLOT_STYLE_PATH) diff --git a/experiments/dataset_analysis/si_diffusion_analysis.py b/experiments/dataset_analysis/si_diffusion_analysis.py index 850edc36..d0de054b 100644 --- a/experiments/dataset_analysis/si_diffusion_analysis.py +++ b/experiments/dataset_analysis/si_diffusion_analysis.py @@ -19,7 +19,7 @@ from crystal_diffusion import ANALYSIS_RESULTS_DIR, DATA_DIR from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH from crystal_diffusion.utils.logging_utils import setup_analysis_logger -from experiment_analysis.analysis_utils import get_thermo_dataset +from experiments.analysis_utils import get_thermo_dataset plt.style.use(PLOT_STYLE_PATH) From b44e8184af31ddfe340878ffbb84ca1620be2924 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Mon, 21 Oct 2024 16:25:46 -0400 Subject: [PATCH 07/29] Moving more stuff around. --- {experiment_analysis => experiments}/__init__.py | 0 experiments/analysis_utils.py | 2 +- experiments/dataset_analysis/energy_consistency_analysis.py | 2 +- .../score_stability_analysis/draw_samples_from_equilibrium.py | 0 .../score_stability_analysis/plot_hessian_eigenvalues.py | 3 +-- .../score_stability_analysis/plot_score_norm.py | 3 +-- .../score_stability_analysis/util.py | 0 7 files changed, 4 insertions(+), 6 deletions(-) rename {experiment_analysis => experiments}/__init__.py (100%) rename {experiment_analysis => experiments}/score_stability_analysis/draw_samples_from_equilibrium.py (100%) rename {experiment_analysis => experiments}/score_stability_analysis/plot_hessian_eigenvalues.py (98%) rename {experiment_analysis => experiments}/score_stability_analysis/plot_score_norm.py (97%) rename {experiment_analysis => experiments}/score_stability_analysis/util.py (100%) diff --git a/experiment_analysis/__init__.py b/experiments/__init__.py similarity index 100% rename from experiment_analysis/__init__.py rename to experiments/__init__.py diff --git a/experiments/analysis_utils.py b/experiments/analysis_utils.py index 6c9a5189..156de546 100644 --- a/experiments/analysis_utils.py +++ b/experiments/analysis_utils.py @@ -7,7 +7,7 @@ from crystal_diffusion import DATA_DIR from crystal_diffusion.data.parse_lammps_outputs import parse_lammps_thermo_log -from experiment_analysis import EXPERIMENT_ANALYSIS_DIR +from experiments import EXPERIMENT_ANALYSIS_DIR logger = logging.getLogger(__name__) diff --git a/experiments/dataset_analysis/energy_consistency_analysis.py b/experiments/dataset_analysis/energy_consistency_analysis.py index f2445fce..1c6113f0 100644 --- a/experiments/dataset_analysis/energy_consistency_analysis.py +++ b/experiments/dataset_analysis/energy_consistency_analysis.py @@ -21,7 +21,7 @@ LammpsForDiffusionDataModule, LammpsLoaderParameters) from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps from crystal_diffusion.utils.logging_utils import setup_analysis_logger -from experiment_analysis import EXPERIMENT_ANALYSIS_DIR +from experiments import EXPERIMENT_ANALYSIS_DIR from experiments.analysis_utils import get_thermo_dataset plt.style.use(PLOT_STYLE_PATH) diff --git a/experiment_analysis/score_stability_analysis/draw_samples_from_equilibrium.py b/experiments/score_stability_analysis/draw_samples_from_equilibrium.py similarity index 100% rename from experiment_analysis/score_stability_analysis/draw_samples_from_equilibrium.py rename to experiments/score_stability_analysis/draw_samples_from_equilibrium.py diff --git a/experiment_analysis/score_stability_analysis/plot_hessian_eigenvalues.py b/experiments/score_stability_analysis/plot_hessian_eigenvalues.py similarity index 98% rename from experiment_analysis/score_stability_analysis/plot_hessian_eigenvalues.py rename to experiments/score_stability_analysis/plot_hessian_eigenvalues.py index b48669be..6fd5a0a4 100644 --- a/experiment_analysis/score_stability_analysis/plot_hessian_eigenvalues.py +++ b/experiments/score_stability_analysis/plot_hessian_eigenvalues.py @@ -16,8 +16,7 @@ from crystal_diffusion.samplers.exploding_variance import ExplodingVariance from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.utils.logging_utils import setup_analysis_logger -from experiment_analysis.score_stability_analysis.util import \ - get_normalized_score_function +from experiments import get_normalized_score_function plt.style.use(PLOT_STYLE_PATH) diff --git a/experiment_analysis/score_stability_analysis/plot_score_norm.py b/experiments/score_stability_analysis/plot_score_norm.py similarity index 97% rename from experiment_analysis/score_stability_analysis/plot_score_norm.py rename to experiments/score_stability_analysis/plot_score_norm.py index ef4ddbc7..7236d8e3 100644 --- a/experiment_analysis/score_stability_analysis/plot_score_norm.py +++ b/experiments/score_stability_analysis/plot_score_norm.py @@ -16,8 +16,7 @@ from crystal_diffusion.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell from crystal_diffusion.utils.logging_utils import setup_analysis_logger -from experiment_analysis.score_stability_analysis.util import \ - create_fixed_time_normalized_score_function +from experiments import create_fixed_time_normalized_score_function plt.style.use(PLOT_STYLE_PATH) diff --git a/experiment_analysis/score_stability_analysis/util.py b/experiments/score_stability_analysis/util.py similarity index 100% rename from experiment_analysis/score_stability_analysis/util.py rename to experiments/score_stability_analysis/util.py From 277736bc52cf8ff16d01b1fbedaddb1e173091d8 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Mon, 21 Oct 2024 16:27:44 -0400 Subject: [PATCH 08/29] Update the readme. --- experiment_analysis/README | 7 ------- experiments/README | 6 ++++++ 2 files changed, 6 insertions(+), 7 deletions(-) delete mode 100644 experiment_analysis/README create mode 100644 experiments/README diff --git a/experiment_analysis/README b/experiment_analysis/README deleted file mode 100644 index 8b12aad6..00000000 --- a/experiment_analysis/README +++ /dev/null @@ -1,7 +0,0 @@ -# Analysis - -This folder contains various analysis scripts. These scripts may point to -data in the data/ folder or the experiment/ folder: these artifacts will not -be under revision, it will be assumed that these data sources are present -when executing the scripts. - diff --git a/experiments/README b/experiments/README new file mode 100644 index 00000000..8be7f5c8 --- /dev/null +++ b/experiments/README @@ -0,0 +1,6 @@ +# Experiments + +This folder contains various experiments and analysis scripts. These various scripts +may have been useful at some point during the development of the code, but are not +actively maintained. They should be viewed as (perhaps outdated) examples of how +one can analyse the data generated by this code base. From 1ff53c6d33a01ff4acade815f0b2379699f9954d Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Mon, 21 Oct 2024 16:29:26 -0400 Subject: [PATCH 09/29] ignore mac system files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 33223421..6b691186 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea +.DS_Store mlruns analysis_results/ From 1a708bb18877c60544d2b7e9b04a10084c4e3f9d Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Mon, 21 Oct 2024 16:30:12 -0400 Subject: [PATCH 10/29] ignore mlip-3 folder --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 6b691186..2cc0b025 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ mlruns analysis_results/ +mlip-3/ + examples/data/ examples/*/output/ examples/*/lightning_logs/ From 9384c71a3b16959f117eb0f55eff0adf3c61ded3 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Mon, 21 Oct 2024 16:32:53 -0400 Subject: [PATCH 11/29] Remove needless stuff. --- .../__init__.py | 3 - .../fake_data_utils.py | 83 ------------------- .../generate_fake_trajectory_visualization.py | 30 ------- 3 files changed, 116 deletions(-) delete mode 100644 sanity_checks/visualizing_fake_trajectories_with_ovito/__init__.py delete mode 100644 sanity_checks/visualizing_fake_trajectories_with_ovito/fake_data_utils.py delete mode 100644 sanity_checks/visualizing_fake_trajectories_with_ovito/generate_fake_trajectory_visualization.py diff --git a/sanity_checks/visualizing_fake_trajectories_with_ovito/__init__.py b/sanity_checks/visualizing_fake_trajectories_with_ovito/__init__.py deleted file mode 100644 index b703e3ee..00000000 --- a/sanity_checks/visualizing_fake_trajectories_with_ovito/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from pathlib import Path - -VISUALIZATION_SANITY_CHECK_DIRECTORY = Path(__file__).parent diff --git a/sanity_checks/visualizing_fake_trajectories_with_ovito/fake_data_utils.py b/sanity_checks/visualizing_fake_trajectories_with_ovito/fake_data_utils.py deleted file mode 100644 index 35c6c69e..00000000 --- a/sanity_checks/visualizing_fake_trajectories_with_ovito/fake_data_utils.py +++ /dev/null @@ -1,83 +0,0 @@ -from pathlib import Path - -import einops -import torch - -from crystal_diffusion.utils.basis_transformations import \ - map_relative_coordinates_to_unit_cell -from crystal_diffusion.utils.sample_trajectory import ODESampleTrajectory - - -def generate_fake_trajectories_pickle( - acell: float, - number_of_atoms: int, - number_of_frames: int, - number_of_trajectories: int, - pickle_path: Path, -): - """Generate fake trajectories pickle. - - This function creates a torch pickle with the needed data fields to sanity check that - we can visualize paths with Ovito. - - Args: - acell : A cell dimension parameter - number_of_atoms : number of atoms in the cell - number_of_frames : number of time steps in the trajectories - number_of_trajectories : number of trajectories - pickle_path : where the pickle should be written - - Returns: - None. - """ - spatial_dimension = 3 - t0 = 0.0 - tf = 1.0 - # These parameters don't really matter for the purpose of generating fake trajectories. - sigma_min = 0.01 - sigma_max = 0.5 - - # Times have dimension [number_of_time_steps] - times = torch.linspace(tf, t0, number_of_frames) - - # evaluation_times have dimension [batch_size, number_of_time_steps] - evaluation_times = einops.repeat( - times, "t -> batch t", batch=number_of_trajectories - ) - - shifts = torch.rand(number_of_trajectories) - - a = acell + torch.cos(2 * torch.pi * shifts) - b = acell + torch.sin(2 * torch.pi * shifts) - c = acell + shifts - - # unit_cells have dimensions [number_of_samples, spatial_dimension, spatial_dimension] - unit_cells = torch.diag_embed(einops.rearrange([a, b, c], "d batch -> batch d")) - - sigmas = sigma_min ** (1.0 - evaluation_times) * sigma_max**evaluation_times - - normalized_scores = 0.1 * torch.rand( - number_of_trajectories, number_of_frames, number_of_atoms, spatial_dimension - ) - - initial_relative_coordinates = torch.rand( - [number_of_trajectories, 1, number_of_atoms, spatial_dimension] - ) - relative_coordinates = map_relative_coordinates_to_unit_cell( - initial_relative_coordinates + normalized_scores - ) - - sample_trajectory_recorder = ODESampleTrajectory() - - sample_trajectory_recorder.record_unit_cell(unit_cells) - - sample_trajectory_recorder.record_ode_solution( - times=evaluation_times, - sigmas=sigmas, - relative_coordinates=relative_coordinates, - normalized_scores=normalized_scores, - stats="not applicable", - status="not applicable", - ) - - sample_trajectory_recorder.write_to_pickle(pickle_path) diff --git a/sanity_checks/visualizing_fake_trajectories_with_ovito/generate_fake_trajectory_visualization.py b/sanity_checks/visualizing_fake_trajectories_with_ovito/generate_fake_trajectory_visualization.py deleted file mode 100644 index 121007be..00000000 --- a/sanity_checks/visualizing_fake_trajectories_with_ovito/generate_fake_trajectory_visualization.py +++ /dev/null @@ -1,30 +0,0 @@ -from crystal_diffusion.utils.ovito_utils import (create_cif_files, - create_ovito_session_state) -from sanity_checks.visualizing_fake_trajectories_with_ovito import \ - VISUALIZATION_SANITY_CHECK_DIRECTORY -from sanity_checks.visualizing_fake_trajectories_with_ovito.fake_data_utils import \ - generate_fake_trajectories_pickle - -acell = 5 -number_of_atoms = 8 -number_of_frames = 101 # the 'time' dimension -number_of_trajectories = 4 # the 'batch' dimension -if __name__ == '__main__': - - pickle_path = VISUALIZATION_SANITY_CHECK_DIRECTORY / "trajectories.pt" - - generate_fake_trajectories_pickle(acell=acell, - number_of_atoms=number_of_atoms, - number_of_frames=number_of_frames, - number_of_trajectories=number_of_trajectories, - pickle_path=pickle_path) - - trajectory_directory = VISUALIZATION_SANITY_CHECK_DIRECTORY / "trajectories" - for trj_idx in range(number_of_trajectories): - print(f"Computing Ovito trajectory session state for trajectory index {trj_idx}") - create_cif_files(visualization_artifacts_path=trajectory_directory, - trajectory_index=trj_idx, - ode_trajectory_pickle=pickle_path) - - create_ovito_session_state(visualization_artifacts_path=trajectory_directory, - trajectory_index=trj_idx) From 7b5feb02918425233b1ea27ebf9bd193174c5300 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Mon, 21 Oct 2024 16:35:04 -0400 Subject: [PATCH 12/29] Remove more noise. --- sanity_checks/__init__.py | 3 - sanity_checks/overfit_fake_data.py | 106 ------------ sanity_checks/sanity_check_callbacks.py | 205 ------------------------ 3 files changed, 314 deletions(-) delete mode 100644 sanity_checks/__init__.py delete mode 100644 sanity_checks/overfit_fake_data.py delete mode 100644 sanity_checks/sanity_check_callbacks.py diff --git a/sanity_checks/__init__.py b/sanity_checks/__init__.py deleted file mode 100644 index e311896f..00000000 --- a/sanity_checks/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from pathlib import Path - -SANITY_CHECK_FOLDER = str(Path(__file__).parent) diff --git a/sanity_checks/overfit_fake_data.py b/sanity_checks/overfit_fake_data.py deleted file mode 100644 index 69c790e5..00000000 --- a/sanity_checks/overfit_fake_data.py +++ /dev/null @@ -1,106 +0,0 @@ -"""Overfit fake data. - -A simple sanity check experiment to check the learning behavior of the position diffusion model. -The training data is taken to be a large batch of identical configurations composed of one atom in 1D at x0. -This highly artificial case is useful to sanity check that the code behaves as expected: - - the loss should converge towards zero - - the trained score network should reproduce the perturbation kernel, at least in the regions where it is sampled. - - the generated samples should be tightly clustered around x0. -""" -import dataclasses -import os - -import pytorch_lightning -import torch -from pytorch_lightning import Callback, Trainer -from pytorch_lightning.callbacks import LearningRateMonitor -from pytorch_lightning.loggers import TensorBoardLogger -from torch.utils.data import DataLoader - -from crystal_diffusion.models.optimizer import OptimizerParameters -from crystal_diffusion.models.position_diffusion_lightning_model import ( - PositionDiffusionLightningModel, PositionDiffusionParameters) -from crystal_diffusion.models.score_networks.mlp_score_network import \ - MLPScoreNetworkParameters -from crystal_diffusion.samplers.variance_sampler import NoiseParameters -from sanity_checks import SANITY_CHECK_FOLDER -from sanity_checks.sanity_check_callbacks import ( - TensorboardGeneratedSamplesLoggingCallback, - TensorboardHistogramLoggingCallback, TensorboardSamplesLoggingCallback, - TensorboardScoreAndErrorLoggingCallback) - - -class HPLoggingCallback(Callback): - """This callback is responsible for logging hyperparameters.""" - - def on_train_start(self, trainer, pl_module): - """Log hyperparameters when training starts.""" - assert hasattr( - pl_module, "hyper_params" - ), "The lightning module should have a hyper_params attribute for HP logging." - hp_dict = dataclasses.asdict(pl_module.hyper_params) - trainer.logger.log_hyperparams(hp_dict) - - -batch_size = 4096 -number_of_atoms = 1 -spatial_dimension = 1 -total_time_steps = 100 -number_of_corrector_steps = 1 - -x0 = 0.5 - -sigma_min = 0.005 -sigma_max = 0.5 - -lr = 0.001 -max_epochs = 2000 - -hidden_dimensions = [64, 128, 256] - - -score_network_parameters = MLPScoreNetworkParameters( - number_of_atoms=number_of_atoms, - hidden_dimensions=hidden_dimensions, - spatial_dimension=spatial_dimension, -) - -optimizer_parameters = OptimizerParameters(name="adam", learning_rate=lr) - -noise_parameters = NoiseParameters(total_time_steps=total_time_steps, sigma_min=sigma_min, sigma_max=sigma_max) - -hyper_params = PositionDiffusionParameters( - score_network_parameters=score_network_parameters, - optimizer_parameters=optimizer_parameters, - noise_parameters=noise_parameters, -) - -generated_samples_callback = ( - TensorboardGeneratedSamplesLoggingCallback(noise_parameters=noise_parameters, - number_of_corrector_steps=number_of_corrector_steps, - score_network_parameters=score_network_parameters, - number_of_samples=1024)) - -score_error_callback = TensorboardScoreAndErrorLoggingCallback(x0=x0) - -tbx_logger = TensorBoardLogger(save_dir=os.path.join(SANITY_CHECK_FOLDER, "tensorboard"), name="overfit_fake_data") - -if __name__ == '__main__': - pytorch_lightning.seed_everything(123) - all_positions = x0 * torch.ones(batch_size, number_of_atoms, spatial_dimension) - data = [dict(relative_positions=configuration) for configuration in all_positions] - train_dataloader = DataLoader(data, batch_size=batch_size) - - lightning_model = PositionDiffusionLightningModel(hyper_params) - - trainer = Trainer(accelerator='cpu', - max_epochs=max_epochs, - logger=tbx_logger, - log_every_n_steps=25, - callbacks=[HPLoggingCallback(), - generated_samples_callback, - score_error_callback, - TensorboardHistogramLoggingCallback(), - TensorboardSamplesLoggingCallback(), - LearningRateMonitor(logging_interval='step')]) - trainer.fit(lightning_model, train_dataloaders=train_dataloader) diff --git a/sanity_checks/sanity_check_callbacks.py b/sanity_checks/sanity_check_callbacks.py deleted file mode 100644 index 88e3c94e..00000000 --- a/sanity_checks/sanity_check_callbacks.py +++ /dev/null @@ -1,205 +0,0 @@ -import torch -from matplotlib import pyplot as plt -from pytorch_lightning import Callback -from pytorch_lightning.loggers import TensorBoardLogger - -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from crystal_diffusion.generators.langevin_generator import LangevinGenerator -from crystal_diffusion.models.score_networks.mlp_score_network import \ - MLPScoreNetworkParameters -from crystal_diffusion.namespace import NOISY_RELATIVE_COORDINATES -from crystal_diffusion.samplers.variance_sampler import NoiseParameters -from crystal_diffusion.score.wrapped_gaussian_score import \ - get_sigma_normalized_score -from crystal_diffusion.utils.basis_transformations import \ - map_relative_coordinates_to_unit_cell - -plt.style.use(PLOT_STYLE_PATH) - - -class TensorBoardDebuggingLoggingCallback(Callback): - """Base class to log debugging information for plotting on TensorBoard.""" - - def __init__(self): - """Init method.""" - self.training_step_outputs = [] - - @staticmethod - def _get_tensorboard_logger(trainer): - if type(trainer.logger) is TensorBoardLogger: - return trainer.logger.experiment - return None - - def on_train_batch_end(self, trainer, pl_module, outputs, batch, batch_idx): - """Action to perform at the end of a training batch.""" - if self._get_tensorboard_logger(trainer) is None: - return - self.training_step_outputs.append(outputs) - - def on_train_epoch_end(self, trainer, pl_module): - """Action to perform at the end of a training epoch.""" - tbx_logger = self._get_tensorboard_logger(trainer) - if tbx_logger is None: - return - - if pl_module.global_step % trainer.log_every_n_steps == 0: - self.log_artifact(pl_module, tbx_logger) - # free up the memory - self.training_step_outputs.clear() - - def log_artifact(self, pl_module, tbx_logger): - """This method must create logging artifacts and log to the tbx logger.""" - raise NotImplementedError( - "This method should be implemented to specific logging." - ) - - -class TensorboardGeneratedSamplesLoggingCallback(TensorBoardDebuggingLoggingCallback): - """This callback will log an image of a histogram of generated samples on tensorboard.""" - - def __init__(self, noise_parameters: NoiseParameters, - number_of_corrector_steps: int, - score_network_parameters: MLPScoreNetworkParameters, number_of_samples: int): - """Init method.""" - super().__init__() - self.noise_parameters = noise_parameters - self.number_of_corrector_steps = number_of_corrector_steps - self.score_network_parameters = score_network_parameters - self.number_of_atoms = score_network_parameters.number_of_atoms - self.spatial_dimension = score_network_parameters.spatial_dimension - self.number_of_samples = number_of_samples - - def log_artifact(self, pl_module, tbx_logger): - """Create artifact and log to tensorboard.""" - sigma_normalized_score_network = pl_module.sigma_normalized_score_network - pc_generator = LangevinGenerator(noise_parameters=self.noise_parameters, - number_of_corrector_steps=self.number_of_corrector_steps, - number_of_atoms=self.number_of_atoms, - spatial_dimension=self.spatial_dimension, - sigma_normalized_score_network=sigma_normalized_score_network) - - samples = pc_generator.sample(self.number_of_samples).flatten() - - fig = plt.figure(figsize=PLEASANT_FIG_SIZE) - ax = fig.add_subplot(111) - ax.set_title(f"Generated Samples: global step = {pl_module.global_step}") - ax.set_xlabel('$x$') - ax.hist(samples, bins=101, range=(0, 1), label=f'{self.number_of_samples} samples') - ax.set_title("Samples Count") - ax.set_xlim([-0.05, 1.05]) - ax.set_ylim([0., self.number_of_samples]) - fig.tight_layout() - tbx_logger.add_figure("train/generated_samples", fig, global_step=pl_module.global_step) - - -class TensorboardScoreAndErrorLoggingCallback(TensorBoardDebuggingLoggingCallback): - """This callback will log histograms of the labels, predictions and errors on tensorboard.""" - - def __init__(self, x0: float): - """Init method.""" - super().__init__() - self.x0 = x0 - - def log_artifact(self, pl_module, tbx_logger): - """Create artifact and log to tensorboard.""" - fig = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig.suptitle("Scores within 2 $\\sigma$ of Data") - ax1 = fig.add_subplot(121) - ax2 = fig.add_subplot(122) - ax1.set_ylabel('$\\sigma \\times S_{\\theta}(x, t)$') - ax2.set_ylabel('$\\sigma \\times S_{\\theta}(x, t) - \\sigma \\nabla \\log P(x | 0)$') - for ax in [ax1, ax2]: - ax.set_xlabel('$x$') - - list_x = torch.linspace(0, 1, 1001)[:-1] - - times = torch.tensor([0.25, 0.75, 1.]) - sigmas = pl_module.variance_sampler._create_sigma_array(pl_module.variance_sampler.noise_parameters, times) - - with torch.no_grad(): - for time, sigma in zip(times, sigmas): - times = time * torch.ones(1000).reshape(-1, 1) - - sigma_normalized_kernel = get_sigma_normalized_score(map_relative_coordinates_to_unit_cell(list_x - - self.x0), - sigma * torch.ones_like(list_x), - kmax=4) - predicted_normalized_scores = pl_module._get_predicted_normalized_score(list_x.reshape(-1, 1, 1), - times).flatten() - - error = predicted_normalized_scores - sigma_normalized_kernel - - # only plot the errors in the sampling region! These regions might be disconnected, let's make - # sure the continuous lines make sense. - mask1 = torch.abs(list_x - self.x0) < 2 * sigma - mask2 = torch.abs(1. - list_x + self.x0) < 2 * sigma - - lines = ax1.plot(list_x[mask1], predicted_normalized_scores[mask1], lw=1, label='Prediction') - color = lines[0].get_color() - ax1.plot(list_x[mask2], predicted_normalized_scores[mask2], lw=1, color=color, label='_none_') - - ax1.plot(list_x[mask1], sigma_normalized_kernel[mask1], '--', lw=2, color=color, label='Target') - ax1.plot(list_x[mask2], sigma_normalized_kernel[mask2], '--', lw=2, color=color, label='_none_') - - ax2.plot(list_x[mask1], error[mask1], '-', color=color, - label=f't = {time:4.3f}, $\\sigma$ = {sigma:4.3f}') - ax2.plot(list_x[mask2], error[mask2], '-', color=color, label='_none_') - - for ax in [ax1, ax2]: - ax.set_xlim([-0.05, 1.05]) - ax.legend(loc=3, prop={'size': 6}) - - ax1.set_ylim([-3., 3.]) - ax2.set_ylim([-1., 1.]) - - fig.tight_layout() - - tbx_logger.add_figure("train/scores", fig, global_step=pl_module.global_step) - - -class TensorboardHistogramLoggingCallback(TensorBoardDebuggingLoggingCallback): - """This callback will log histograms of the predictions on tensorboard.""" - - def log_artifact(self, pl_module, tbx_logger): - """Create artifact and log to tensorboard.""" - targets = [] - predictions = [] - for output in self.training_step_outputs: - targets.append(output["target_normalized_conditional_scores"].flatten()) - predictions.append(output["predicted_normalized_scores"].flatten()) - - targets = torch.cat(targets) - predictions = torch.cat(predictions) - - tbx_logger.add_histogram( - "train/targets", targets, global_step=pl_module.global_step - ) - tbx_logger.add_histogram( - "train/predictions", predictions, global_step=pl_module.global_step - ) - tbx_logger.add_histogram( - "train/errors", targets - predictions, global_step=pl_module.global_step - ) - - -class TensorboardSamplesLoggingCallback(TensorBoardDebuggingLoggingCallback): - """This callback will log histograms of the labels, predictions and errors on tensorboard.""" - - def log_artifact(self, pl_module, tbx_logger): - """Create artifact and log to tensorboard.""" - list_xt = [] - list_sigmas = [] - for output in self.training_step_outputs: - list_xt.append(output[NOISY_RELATIVE_COORDINATES].flatten()) - list_sigmas.append(output["sigmas"].flatten()) - list_xt = torch.cat(list_xt) - list_sigmas = torch.cat(list_sigmas) - fig = plt.figure(figsize=PLEASANT_FIG_SIZE) - ax = fig.add_subplot(111) - ax.set_title(f"Position Samples: global step = {pl_module.global_step}") - ax.set_ylabel("$\\sigma$") - ax.set_xlabel("position samples $x(t)$") - ax.plot(list_xt, list_sigmas, "bo") - ax.set_xlim([-0.05, 1.05]) - fig.tight_layout() - tbx_logger.add_figure("train/samples", fig, global_step=pl_module.global_step) From 66cb4dfe21214958e90dc34676789b44078724bc Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Mon, 21 Oct 2024 16:36:22 -0400 Subject: [PATCH 13/29] Moved stuff. --- {sanity_checks => experiments}/generators/__init__.py | 0 .../generators/sde_generator_sanity_check.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename {sanity_checks => experiments}/generators/__init__.py (100%) rename {sanity_checks => experiments}/generators/sde_generator_sanity_check.py (98%) diff --git a/sanity_checks/generators/__init__.py b/experiments/generators/__init__.py similarity index 100% rename from sanity_checks/generators/__init__.py rename to experiments/generators/__init__.py diff --git a/sanity_checks/generators/sde_generator_sanity_check.py b/experiments/generators/sde_generator_sanity_check.py similarity index 98% rename from sanity_checks/generators/sde_generator_sanity_check.py rename to experiments/generators/sde_generator_sanity_check.py index 94643a6d..9c891d0d 100644 --- a/sanity_checks/generators/sde_generator_sanity_check.py +++ b/experiments/generators/sde_generator_sanity_check.py @@ -17,7 +17,7 @@ from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell -from sanity_checks.generators import GENERATOR_SANITY_CHECK_DIRECTORY +from experiments.generators import GENERATOR_SANITY_CHECK_DIRECTORY plt.style.use(PLOT_STYLE_PATH) From 3564331b2f4a77bcdc064b0dcae032a507f25da6 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Mon, 21 Oct 2024 16:50:46 -0400 Subject: [PATCH 14/29] Moved stuff. --- .../analysis/analytic_score/__init__.py | 0 .../analytical_score_sampling_and_plotting.py | 9 ++++----- .../exploring_langevin_generator/__init__.py | 0 .../generate_sample_energies.py | 8 ++++---- .../exploring_langevin_generator/plot_sample_energies.py | 2 +- .../analytic_score/perfect_score_loss_analysis.py | 4 ++-- .../analysis/analytic_score}/repaint/__init__.py | 0 .../plot_repaint_analytical_score_trajectories.py | 5 +++-- .../repaint/repaint_with_analytic_score.py | 5 +++-- .../analytic_score/score_convergence_analysis.py | 5 ++--- .../analysis/analytic_score/utils.py | 0 .../ad_hoc_experiments_with_various_score_networks.py | 8 ++++---- .../overfit_diffusion_mace.py | 4 ++-- experiments/generators/sde_generator_sanity_check.py | 2 +- .../draw_samples_from_equilibrium.py | 7 +++---- .../score_stability_analysis/plot_hessian_eigenvalues.py | 3 +-- experiments/score_stability_analysis/plot_score_norm.py | 3 +-- 17 files changed, 31 insertions(+), 34 deletions(-) rename {crystal_diffusion => experiments}/analysis/analytic_score/__init__.py (100%) rename {crystal_diffusion => experiments}/analysis/analytic_score/analytical_score_sampling_and_plotting.py (98%) rename {crystal_diffusion => experiments}/analysis/analytic_score/exploring_langevin_generator/__init__.py (100%) rename {crystal_diffusion => experiments}/analysis/analytic_score/exploring_langevin_generator/generate_sample_energies.py (97%) rename {crystal_diffusion => experiments}/analysis/analytic_score/exploring_langevin_generator/plot_sample_energies.py (96%) rename {crystal_diffusion => experiments}/analysis/analytic_score/perfect_score_loss_analysis.py (98%) rename {crystal_diffusion/analysis => experiments/analysis/analytic_score}/repaint/__init__.py (100%) rename {crystal_diffusion/analysis => experiments/analysis/analytic_score}/repaint/plot_repaint_analytical_score_trajectories.py (96%) rename {crystal_diffusion/analysis => experiments/analysis/analytic_score}/repaint/repaint_with_analytic_score.py (96%) rename {crystal_diffusion => experiments}/analysis/analytic_score/score_convergence_analysis.py (96%) rename {crystal_diffusion => experiments}/analysis/analytic_score/utils.py (100%) diff --git a/crystal_diffusion/analysis/analytic_score/__init__.py b/experiments/analysis/analytic_score/__init__.py similarity index 100% rename from crystal_diffusion/analysis/analytic_score/__init__.py rename to experiments/analysis/analytic_score/__init__.py diff --git a/crystal_diffusion/analysis/analytic_score/analytical_score_sampling_and_plotting.py b/experiments/analysis/analytic_score/analytical_score_sampling_and_plotting.py similarity index 98% rename from crystal_diffusion/analysis/analytic_score/analytical_score_sampling_and_plotting.py rename to experiments/analysis/analytic_score/analytical_score_sampling_and_plotting.py index 605bc592..0d34dae9 100644 --- a/crystal_diffusion/analysis/analytic_score/analytical_score_sampling_and_plotting.py +++ b/experiments/analysis/analytic_score/analytical_score_sampling_and_plotting.py @@ -14,11 +14,6 @@ from einops import einops from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from crystal_diffusion.analysis.analytic_score import \ - ANALYTIC_SCORE_RESULTS_DIR -from crystal_diffusion.analysis.analytic_score.utils import ( - get_exact_samples, get_samples_harmonic_energy, get_silicon_supercell, - get_unit_cells) from crystal_diffusion.generators.langevin_generator import LangevinGenerator from crystal_diffusion.generators.ode_position_generator import ( ExplodingVarianceODEPositionGenerator, ODESamplingParameters) @@ -28,6 +23,10 @@ AnalyticalScoreNetwork, AnalyticalScoreNetworkParameters) from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.utils.logging_utils import setup_analysis_logger +from experiments.analysis.analytic_score import ANALYTIC_SCORE_RESULTS_DIR +from experiments.analysis.analytic_score.utils import ( + get_exact_samples, get_samples_harmonic_energy, get_silicon_supercell, + get_unit_cells) logger = logging.getLogger(__name__) setup_analysis_logger() diff --git a/crystal_diffusion/analysis/analytic_score/exploring_langevin_generator/__init__.py b/experiments/analysis/analytic_score/exploring_langevin_generator/__init__.py similarity index 100% rename from crystal_diffusion/analysis/analytic_score/exploring_langevin_generator/__init__.py rename to experiments/analysis/analytic_score/exploring_langevin_generator/__init__.py diff --git a/crystal_diffusion/analysis/analytic_score/exploring_langevin_generator/generate_sample_energies.py b/experiments/analysis/analytic_score/exploring_langevin_generator/generate_sample_energies.py similarity index 97% rename from crystal_diffusion/analysis/analytic_score/exploring_langevin_generator/generate_sample_energies.py rename to experiments/analysis/analytic_score/exploring_langevin_generator/generate_sample_energies.py index 64d5c4fc..ac991d62 100644 --- a/crystal_diffusion/analysis/analytic_score/exploring_langevin_generator/generate_sample_energies.py +++ b/experiments/analysis/analytic_score/exploring_langevin_generator/generate_sample_energies.py @@ -7,10 +7,6 @@ import torch from tqdm import tqdm -from crystal_diffusion.analysis.analytic_score.exploring_langevin_generator import \ - LANGEVIN_EXPLORATION_DIRECTORY -from crystal_diffusion.analysis.analytic_score.utils import ( - get_exact_samples, get_silicon_supercell) from crystal_diffusion.callbacks.sampling_visualization_callback import logger from crystal_diffusion.generators.langevin_generator import LangevinGenerator from crystal_diffusion.generators.predictor_corrector_position_generator import \ @@ -21,6 +17,10 @@ from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.utils.basis_transformations import ( get_positions_from_coordinates, map_relative_coordinates_to_unit_cell) +from experiments.analysis.analytic_score.exploring_langevin_generator import \ + LANGEVIN_EXPLORATION_DIRECTORY +from experiments.analysis.analytic_score.utils import (get_exact_samples, + get_silicon_supercell) class EnergyCalculator: diff --git a/crystal_diffusion/analysis/analytic_score/exploring_langevin_generator/plot_sample_energies.py b/experiments/analysis/analytic_score/exploring_langevin_generator/plot_sample_energies.py similarity index 96% rename from crystal_diffusion/analysis/analytic_score/exploring_langevin_generator/plot_sample_energies.py rename to experiments/analysis/analytic_score/exploring_langevin_generator/plot_sample_energies.py index be2838fc..36def5d5 100644 --- a/crystal_diffusion/analysis/analytic_score/exploring_langevin_generator/plot_sample_energies.py +++ b/experiments/analysis/analytic_score/exploring_langevin_generator/plot_sample_energies.py @@ -4,7 +4,7 @@ import numpy as np from crystal_diffusion.analysis import PLOT_STYLE_PATH -from crystal_diffusion.analysis.analytic_score.exploring_langevin_generator import \ +from experiments.analysis.analytic_score.exploring_langevin_generator import \ LANGEVIN_EXPLORATION_DIRECTORY plt.style.use(PLOT_STYLE_PATH) diff --git a/crystal_diffusion/analysis/analytic_score/perfect_score_loss_analysis.py b/experiments/analysis/analytic_score/perfect_score_loss_analysis.py similarity index 98% rename from crystal_diffusion/analysis/analytic_score/perfect_score_loss_analysis.py rename to experiments/analysis/analytic_score/perfect_score_loss_analysis.py index 182ac5c4..2b3af48c 100644 --- a/crystal_diffusion/analysis/analytic_score/perfect_score_loss_analysis.py +++ b/experiments/analysis/analytic_score/perfect_score_loss_analysis.py @@ -12,8 +12,6 @@ from crystal_diffusion import ANALYSIS_RESULTS_DIR from crystal_diffusion.analysis import PLOT_STYLE_PATH -from crystal_diffusion.analysis.analytic_score.utils import ( - get_exact_samples, get_silicon_supercell) from crystal_diffusion.callbacks.loss_monitoring_callback import \ LossMonitoringCallback from crystal_diffusion.callbacks.sampling_visualization_callback import \ @@ -38,6 +36,8 @@ ExplodingVarianceSampler, NoiseParameters) from crystal_diffusion.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell +from experiments.analysis.analytic_score.utils import (get_exact_samples, + get_silicon_supercell) logger = logging.getLogger(__name__) diff --git a/crystal_diffusion/analysis/repaint/__init__.py b/experiments/analysis/analytic_score/repaint/__init__.py similarity index 100% rename from crystal_diffusion/analysis/repaint/__init__.py rename to experiments/analysis/analytic_score/repaint/__init__.py diff --git a/crystal_diffusion/analysis/repaint/plot_repaint_analytical_score_trajectories.py b/experiments/analysis/analytic_score/repaint/plot_repaint_analytical_score_trajectories.py similarity index 96% rename from crystal_diffusion/analysis/repaint/plot_repaint_analytical_score_trajectories.py rename to experiments/analysis/analytic_score/repaint/plot_repaint_analytical_score_trajectories.py index 88ebc35a..c7d58a8b 100644 --- a/crystal_diffusion/analysis/repaint/plot_repaint_analytical_score_trajectories.py +++ b/experiments/analysis/analytic_score/repaint/plot_repaint_analytical_score_trajectories.py @@ -9,9 +9,10 @@ from crystal_diffusion import ANALYSIS_RESULTS_DIR from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from crystal_diffusion.analysis.analytic_score.utils import ( - get_exact_samples, get_samples_harmonic_energy, get_silicon_supercell) from crystal_diffusion.utils.logging_utils import setup_analysis_logger +from experiments.analysis.analytic_score import (get_exact_samples, + get_samples_harmonic_energy, + get_silicon_supercell) logger = logging.getLogger(__name__) setup_analysis_logger() diff --git a/crystal_diffusion/analysis/repaint/repaint_with_analytic_score.py b/experiments/analysis/analytic_score/repaint/repaint_with_analytic_score.py similarity index 96% rename from crystal_diffusion/analysis/repaint/repaint_with_analytic_score.py rename to experiments/analysis/analytic_score/repaint/repaint_with_analytic_score.py index 7d81dbcc..65312ea1 100644 --- a/crystal_diffusion/analysis/repaint/repaint_with_analytic_score.py +++ b/experiments/analysis/analytic_score/repaint/repaint_with_analytic_score.py @@ -6,8 +6,6 @@ from crystal_diffusion import ANALYSIS_RESULTS_DIR from crystal_diffusion.analysis import PLOT_STYLE_PATH -from crystal_diffusion.analysis.analytic_score.utils import ( - get_samples_harmonic_energy, get_silicon_supercell, get_unit_cells) from crystal_diffusion.generators.constrained_langevin_generator import ( ConstrainedLangevinGenerator, ConstrainedLangevinGeneratorParameters) from crystal_diffusion.models.score_networks.analytical_score_network import ( @@ -15,6 +13,9 @@ from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.utils.logging_utils import setup_analysis_logger from crystal_diffusion.utils.structure_utils import create_structure +from experiments.analysis.analytic_score import (get_samples_harmonic_energy, + get_silicon_supercell, + get_unit_cells) logger = logging.getLogger(__name__) setup_analysis_logger() diff --git a/crystal_diffusion/analysis/analytic_score/score_convergence_analysis.py b/experiments/analysis/analytic_score/score_convergence_analysis.py similarity index 96% rename from crystal_diffusion/analysis/analytic_score/score_convergence_analysis.py rename to experiments/analysis/analytic_score/score_convergence_analysis.py index a057b037..aad84e78 100644 --- a/crystal_diffusion/analysis/analytic_score/score_convergence_analysis.py +++ b/experiments/analysis/analytic_score/score_convergence_analysis.py @@ -9,13 +9,12 @@ from einops import einops from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from crystal_diffusion.analysis.analytic_score import \ - ANALYTIC_SCORE_RESULTS_DIR -from crystal_diffusion.analysis.analytic_score.utils import get_unit_cells from crystal_diffusion.models.score_networks.analytical_score_network import ( AnalyticalScoreNetwork, AnalyticalScoreNetworkParameters) from crystal_diffusion.namespace import (NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) +from experiments.analysis.analytic_score import ANALYTIC_SCORE_RESULTS_DIR +from experiments.analysis.analytic_score.utils import get_unit_cells logger = logging.getLogger(__name__) diff --git a/crystal_diffusion/analysis/analytic_score/utils.py b/experiments/analysis/analytic_score/utils.py similarity index 100% rename from crystal_diffusion/analysis/analytic_score/utils.py rename to experiments/analysis/analytic_score/utils.py diff --git a/experiments/diffusion_mace_harmonic_data/ad_hoc_experiments_with_various_score_networks.py b/experiments/diffusion_mace_harmonic_data/ad_hoc_experiments_with_various_score_networks.py index cc54bbdb..026045d2 100644 --- a/experiments/diffusion_mace_harmonic_data/ad_hoc_experiments_with_various_score_networks.py +++ b/experiments/diffusion_mace_harmonic_data/ad_hoc_experiments_with_various_score_networks.py @@ -8,10 +8,6 @@ from torch.utils.data import DataLoader from crystal_diffusion.analysis import PLOT_STYLE_PATH -from crystal_diffusion.analysis.analytic_score.utils import ( - get_exact_samples, get_relative_harmonic_energy) -from crystal_diffusion.callbacks.analysis_callbacks import \ - HarmonicEnergyDiffusionSamplingCallback from crystal_diffusion.callbacks.callback_loader import create_all_callbacks from crystal_diffusion.generators.position_generator import SamplingParameters from crystal_diffusion.models.optimizer import OptimizerParameters @@ -29,6 +25,10 @@ MaceEquivariantScorePredictionHeadParameters from crystal_diffusion.namespace import CARTESIAN_FORCES, RELATIVE_COORDINATES from crystal_diffusion.samplers.variance_sampler import NoiseParameters +from experiments.analysis.analytic_score import (get_exact_samples, + get_relative_harmonic_energy) +from experiments.diffusion_mace_harmonic_data.analysis_callbacks import \ + HarmonicEnergyDiffusionSamplingCallback logger = logging.getLogger(__name__) diff --git a/experiments/diffusion_mace_harmonic_data/overfit_diffusion_mace.py b/experiments/diffusion_mace_harmonic_data/overfit_diffusion_mace.py index 13a7cb46..11879e90 100644 --- a/experiments/diffusion_mace_harmonic_data/overfit_diffusion_mace.py +++ b/experiments/diffusion_mace_harmonic_data/overfit_diffusion_mace.py @@ -11,8 +11,6 @@ from torch import optim from torch.utils.data import DataLoader -from crystal_diffusion.analysis.analytic_score.utils import (get_exact_samples, - get_unit_cells) from crystal_diffusion.callbacks.standard_callbacks import CustomProgressBar from crystal_diffusion.models.score_networks.analytical_score_network import ( AnalyticalScoreNetwork, AnalyticalScoreNetworkParameters) @@ -29,6 +27,8 @@ map_relative_coordinates_to_unit_cell from crystal_diffusion.utils.tensor_utils import \ broadcast_batch_tensor_to_all_dimensions +from experiments.analysis.analytic_score import (get_exact_samples, + get_unit_cells) torch.set_default_dtype(torch.float64) diff --git a/experiments/generators/sde_generator_sanity_check.py b/experiments/generators/sde_generator_sanity_check.py index 9c891d0d..ef4e1554 100644 --- a/experiments/generators/sde_generator_sanity_check.py +++ b/experiments/generators/sde_generator_sanity_check.py @@ -9,7 +9,6 @@ from matplotlib import pyplot as plt from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from crystal_diffusion.analysis.analytic_score.utils import get_exact_samples from crystal_diffusion.generators.sde_position_generator import ( ExplodingVarianceSDEPositionGenerator, SDESamplingParameters) from crystal_diffusion.models.score_networks.analytical_score_network import ( @@ -17,6 +16,7 @@ from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell +from experiments.analysis.analytic_score.utils import get_exact_samples from experiments.generators import GENERATOR_SANITY_CHECK_DIRECTORY plt.style.use(PLOT_STYLE_PATH) diff --git a/experiments/score_stability_analysis/draw_samples_from_equilibrium.py b/experiments/score_stability_analysis/draw_samples_from_equilibrium.py index 700b0b4c..0dc28164 100644 --- a/experiments/score_stability_analysis/draw_samples_from_equilibrium.py +++ b/experiments/score_stability_analysis/draw_samples_from_equilibrium.py @@ -6,10 +6,6 @@ import torch from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from crystal_diffusion.analysis.analytic_score.exploring_langevin_generator.generate_sample_energies import \ - EnergyCalculator -from crystal_diffusion.analysis.analytic_score.utils import \ - get_silicon_supercell from crystal_diffusion.generators.langevin_generator import LangevinGenerator from crystal_diffusion.generators.ode_position_generator import ( ExplodingVarianceODEPositionGenerator, ODESamplingParameters) @@ -22,6 +18,9 @@ from crystal_diffusion.models.score_networks import ScoreNetwork from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.utils.logging_utils import setup_analysis_logger +from experiments.analysis.analytic_score.exploring_langevin_generator.generate_sample_energies import \ + EnergyCalculator +from experiments.analysis.analytic_score.utils import get_silicon_supercell plt.style.use(PLOT_STYLE_PATH) diff --git a/experiments/score_stability_analysis/plot_hessian_eigenvalues.py b/experiments/score_stability_analysis/plot_hessian_eigenvalues.py index 6fd5a0a4..dcfbe27c 100644 --- a/experiments/score_stability_analysis/plot_hessian_eigenvalues.py +++ b/experiments/score_stability_analysis/plot_hessian_eigenvalues.py @@ -9,14 +9,13 @@ from tqdm import tqdm from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from crystal_diffusion.analysis.analytic_score.utils import \ - get_silicon_supercell from crystal_diffusion.models.position_diffusion_lightning_model import \ PositionDiffusionLightningModel from crystal_diffusion.samplers.exploding_variance import ExplodingVariance from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.utils.logging_utils import setup_analysis_logger from experiments import get_normalized_score_function +from experiments.analysis.analytic_score.utils import get_silicon_supercell plt.style.use(PLOT_STYLE_PATH) diff --git a/experiments/score_stability_analysis/plot_score_norm.py b/experiments/score_stability_analysis/plot_score_norm.py index 7236d8e3..57f050df 100644 --- a/experiments/score_stability_analysis/plot_score_norm.py +++ b/experiments/score_stability_analysis/plot_score_norm.py @@ -7,8 +7,6 @@ from tqdm import tqdm from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from crystal_diffusion.analysis.analytic_score.utils import \ - get_silicon_supercell from crystal_diffusion.models.position_diffusion_lightning_model import \ PositionDiffusionLightningModel from crystal_diffusion.samplers.exploding_variance import ExplodingVariance @@ -17,6 +15,7 @@ map_relative_coordinates_to_unit_cell from crystal_diffusion.utils.logging_utils import setup_analysis_logger from experiments import create_fixed_time_normalized_score_function +from experiments.analysis.analytic_score.utils import get_silicon_supercell plt.style.use(PLOT_STYLE_PATH) From 683daa9d2caa10ba7aa19a8191290dcfa1453c6f Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Mon, 21 Oct 2024 16:50:55 -0400 Subject: [PATCH 15/29] Moved stuff. --- .../diffusion_mace_harmonic_data}/analysis_callbacks.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename {crystal_diffusion/callbacks => experiments/diffusion_mace_harmonic_data}/analysis_callbacks.py (96%) diff --git a/crystal_diffusion/callbacks/analysis_callbacks.py b/experiments/diffusion_mace_harmonic_data/analysis_callbacks.py similarity index 96% rename from crystal_diffusion/callbacks/analysis_callbacks.py rename to experiments/diffusion_mace_harmonic_data/analysis_callbacks.py index 7785ec3d..28e417c3 100644 --- a/crystal_diffusion/callbacks/analysis_callbacks.py +++ b/experiments/diffusion_mace_harmonic_data/analysis_callbacks.py @@ -10,12 +10,11 @@ import torch from crystal_diffusion.analysis import PLOT_STYLE_PATH -from crystal_diffusion.analysis.analytic_score.utils import \ - get_relative_harmonic_energy from crystal_diffusion.callbacks.sampling_visualization_callback import \ SamplingVisualizationCallback from crystal_diffusion.generators.position_generator import SamplingParameters from crystal_diffusion.samplers.variance_sampler import NoiseParameters +from experiments.analysis.analytic_score import get_relative_harmonic_energy logger = logging.getLogger(__name__) From 8882e700ada21ae810fc846b3d944cebc02a0755 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Mon, 21 Oct 2024 16:55:02 -0400 Subject: [PATCH 16/29] Moving more ad hoc scripts out of the way. --- .../diffusion_sample_position_analysis.py | 0 .../analysis/exploding_variance_analysis.py | 0 .../analysis/perturbation_kernel_analysis.py | 0 .../analysis/plot_diffusion_poster_image.py | 44 ++++++++++++++ experiments/analysis/plot_repulsive_force.py | 60 +++++++++++++++++++ .../plotting_gaussian_vs_wrapped_gaussian.py | 0 .../analysis/target_score_analysis.py | 0 7 files changed, 104 insertions(+) rename {crystal_diffusion => experiments}/analysis/diffusion_sample_position_analysis.py (100%) rename {crystal_diffusion => experiments}/analysis/exploding_variance_analysis.py (100%) rename {crystal_diffusion => experiments}/analysis/perturbation_kernel_analysis.py (100%) create mode 100644 experiments/analysis/plot_diffusion_poster_image.py create mode 100644 experiments/analysis/plot_repulsive_force.py rename {crystal_diffusion => experiments}/analysis/plotting_gaussian_vs_wrapped_gaussian.py (100%) rename {crystal_diffusion => experiments}/analysis/target_score_analysis.py (100%) diff --git a/crystal_diffusion/analysis/diffusion_sample_position_analysis.py b/experiments/analysis/diffusion_sample_position_analysis.py similarity index 100% rename from crystal_diffusion/analysis/diffusion_sample_position_analysis.py rename to experiments/analysis/diffusion_sample_position_analysis.py diff --git a/crystal_diffusion/analysis/exploding_variance_analysis.py b/experiments/analysis/exploding_variance_analysis.py similarity index 100% rename from crystal_diffusion/analysis/exploding_variance_analysis.py rename to experiments/analysis/exploding_variance_analysis.py diff --git a/crystal_diffusion/analysis/perturbation_kernel_analysis.py b/experiments/analysis/perturbation_kernel_analysis.py similarity index 100% rename from crystal_diffusion/analysis/perturbation_kernel_analysis.py rename to experiments/analysis/perturbation_kernel_analysis.py diff --git a/experiments/analysis/plot_diffusion_poster_image.py b/experiments/analysis/plot_diffusion_poster_image.py new file mode 100644 index 00000000..061197a8 --- /dev/null +++ b/experiments/analysis/plot_diffusion_poster_image.py @@ -0,0 +1,44 @@ +import matplotlib.pyplot as plt +import numpy as np + +if __name__ == "__main__": + x_max = 1000 + t_max = 1500 + + x = np.linspace(0, 1, x_max) + t = np.linspace(0, 1, t_max) + + T, X = np.meshgrid(t, x) + + SIGMA = 0.5 * T + 0.001 + + P = np.zeros([x_max, t_max]) + for x0 in [0.25, 0.5, 0.85]: + P += np.exp(-((X - x0) ** 2) / SIGMA**2) + + noise = np.random.randn(100) + list_times = np.linspace(0, t_max - 1, 100) + + noise_scale = 0.05 * list_times / t_max + list_x = (0.5 + noise_scale * np.cumsum(noise)) * x_max + + fig1 = plt.figure(figsize=(6.5, 4)) + fig2 = plt.figure(figsize=(6.5, 4)) + + ax1 = fig1.add_subplot(111) + ax2 = fig2.add_subplot(111) + ax1.imshow(P) + ax2.imshow(P[:, ::-1]) + + ax2.plot(list_times[::-1], list_x, "r-", lw=3) + for ax in [ax1, ax2]: + ax.set_xticks([]) + ax.set_yticks([]) + + fig1.tight_layout() + fig2.tight_layout() + + fig1.savefig("forward_diffusion.png") + fig2.savefig("backward_diffusion.png") + + plt.show() diff --git a/experiments/analysis/plot_repulsive_force.py b/experiments/analysis/plot_repulsive_force.py new file mode 100644 index 00000000..f6e0a379 --- /dev/null +++ b/experiments/analysis/plot_repulsive_force.py @@ -0,0 +1,60 @@ +import torch +from matplotlib import pyplot as plt + +from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH +from crystal_diffusion.models.score_networks.force_field_augmented_score_network import ( + ForceFieldAugmentedScoreNetwork, ForceFieldParameters) +from crystal_diffusion.namespace import NOISY_RELATIVE_COORDINATES, UNIT_CELL + +plt.style.use(PLOT_STYLE_PATH) + +natoms = 2 +spatial_dimension = 3 +batch_size = 1 + + +radial_cutoff = 1.5 +strength = 20 + +acell = 10.86 + +force_field_parameters = ForceFieldParameters( + radial_cutoff=radial_cutoff, strength=strength +) + +if __name__ == "__main__": + force_field_score_network = ForceFieldAugmentedScoreNetwork( + score_network=None, force_field_parameters=force_field_parameters + ) + + list_x = torch.linspace(0.0, 1.0, 1001) + list_f = [] + + for x in list_x: + relative_coordinates = torch.tensor( + [[[0.5 - 0.5 * x, 0.5, 0.0], [0.5 + 0.5 * x, 0.5, 0.0]]] + ) + + basis_vectors = acell * torch.diag(torch.ones(spatial_dimension)).unsqueeze(0) + batch = { + NOISY_RELATIVE_COORDINATES: relative_coordinates, + UNIT_CELL: basis_vectors, + } + forces = force_field_score_network.get_relative_coordinates_pseudo_force(batch) + f = torch.abs(forces[0, 0, 0]) + list_f.append(f) + + list_f = torch.tensor(list_f) + + fig = plt.figure(figsize=PLEASANT_FIG_SIZE) + fig.suptitle( + f"Repulsive Pseudo Force for Strength = {strength} and radial cutoff = {radial_cutoff} $\\AA$" + ) + ax = fig.add_subplot(111) + + ax.plot(acell * list_x, list_f, "b-") + ax.set_xlim(0, 4) + ax.set_ylim(ymin=-0.1) + ax.set_xlabel(r"Interatomic Distance ($\AA$)") + ax.set_ylabel("Magnitude of Pseudo Force") + plt.show() diff --git a/crystal_diffusion/analysis/plotting_gaussian_vs_wrapped_gaussian.py b/experiments/analysis/plotting_gaussian_vs_wrapped_gaussian.py similarity index 100% rename from crystal_diffusion/analysis/plotting_gaussian_vs_wrapped_gaussian.py rename to experiments/analysis/plotting_gaussian_vs_wrapped_gaussian.py diff --git a/crystal_diffusion/analysis/target_score_analysis.py b/experiments/analysis/target_score_analysis.py similarity index 100% rename from crystal_diffusion/analysis/target_score_analysis.py rename to experiments/analysis/target_score_analysis.py From 20efccb3ef5f3ff9540240b40526bcd99399c668 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Mon, 21 Oct 2024 16:57:31 -0400 Subject: [PATCH 17/29] Remove needless scripts. --- .../analysis/positions_to_cif_files.py | 50 ----------- .../timing_adjacency_matrix_calculations.py | 82 ------------------- 2 files changed, 132 deletions(-) delete mode 100644 crystal_diffusion/analysis/positions_to_cif_files.py delete mode 100644 crystal_diffusion/analysis/timing_adjacency_matrix_calculations.py diff --git a/crystal_diffusion/analysis/positions_to_cif_files.py b/crystal_diffusion/analysis/positions_to_cif_files.py deleted file mode 100644 index 994c7d9d..00000000 --- a/crystal_diffusion/analysis/positions_to_cif_files.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Position to cif files. - -A simple script to extract the diffusion positions from a pickle on disk and output -in cif format for visualization. -""" - -from pymatgen.core import Lattice, Structure - -from crystal_diffusion import TOP_DIR -from crystal_diffusion.utils.sample_trajectory import \ - PredictorCorrectorSampleTrajectory - -# Hard coding some paths to local results. Modify as needed... -epoch = 35 -sample_idx = 0 -experiment_name = "mace_plus_prediction_head/run2" -# experiment_name = "mlp/run1" - -trajectory_top_output_directory = TOP_DIR / "experiments/diffusion_mace_harmonic_data/output/" - -trajectory_data_directory = trajectory_top_output_directory / experiment_name / "diffusion_position_samples" - -output_top_dir = trajectory_data_directory.parent / "visualization" -output_dir = output_top_dir / f"visualise_sampling_trajectory_epoch_{epoch}_sample_{sample_idx}" -output_dir.mkdir(exist_ok=True, parents=True) - -if __name__ == '__main__': - pickle_path = trajectory_data_directory / f"diffusion_position_sample_epoch={epoch}.pt" - sample_trajectory = PredictorCorrectorSampleTrajectory.read_from_pickle(pickle_path) - - pickle_path = trajectory_data_directory / f"diffusion_position_sample_epoch={epoch}.pt" - sample_trajectory = PredictorCorrectorSampleTrajectory.read_from_pickle(pickle_path) - - basis_vectors = sample_trajectory.data['unit_cell'][sample_idx].numpy() - lattice = Lattice(matrix=basis_vectors, pbc=(True, True, True)) - - list_predictor_coordinates = sample_trajectory.data['predictor_x_i'] - - for idx, sample_predictor_coordinates in enumerate(list_predictor_coordinates): - coordinates = sample_predictor_coordinates[sample_idx].numpy() - number_of_atoms = coordinates.shape[0] - species = number_of_atoms * ['Si'] - - structure = Structure(lattice=lattice, - species=species, - coords=coordinates, - coords_are_cartesian=False) - - file_path = str(output_dir / f"diffusion_positions_{idx}.cif") - structure.to_file(file_path) diff --git a/crystal_diffusion/analysis/timing_adjacency_matrix_calculations.py b/crystal_diffusion/analysis/timing_adjacency_matrix_calculations.py deleted file mode 100644 index 6608e588..00000000 --- a/crystal_diffusion/analysis/timing_adjacency_matrix_calculations.py +++ /dev/null @@ -1,82 +0,0 @@ -import logging -import time - -import matplotlib.pyplot as plt -import numpy as np -import torch - -from crystal_diffusion import ANALYSIS_RESULTS_DIR -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from crystal_diffusion.utils.logging_utils import setup_analysis_logger -from crystal_diffusion.utils.neighbors import \ - get_periodic_adjacency_information - -plt.style.use(PLOT_STYLE_PATH) - -logger = logging.getLogger(__name__) - - -def create_basis_vectors(batch_size, min_dimension, max_dimension): - """Create random basis vectors.""" - delta = max_dimension - min_dimension - # orthogonal boxes with dimensions between min_dimension and max_dimension. - orthogonal_boxes = torch.stack([torch.diag(min_dimension + delta * torch.rand(3)) for _ in range(batch_size)]) - # add a bit of noise to make the vectors not quite orthogonal - basis_vectors = orthogonal_boxes + 0.1 * torch.randn(batch_size, 3, 3) - return basis_vectors - - -batch_size = 64 -powers = np.arange(3, 13) - -min_dimension = 5 -max_dimension = 8 - -dimension_increasing_factor = 2**(1. / 3.) # this keeps the mean volume per atom roughly constant. - -cutoff = 4. -if __name__ == '__main__': - setup_analysis_logger() - - if torch.cuda.is_available(): - device_type = 'GPU' - device = torch.device(type='cuda') - else: - device_type = 'CPU' - device = torch.device(type='cpu') - - for _ in range(2): - # A first pass for KeOps compilation - torch.manual_seed(1234) - list_natoms = [] - list_timing = [] - for it, power in enumerate(powers, 1): - natom = 2 ** power - list_natoms.append(natom) - logging.info(f"Doing {natom} atoms ...") - basis_vectors = create_basis_vectors(batch_size, min_dimension, max_dimension).to(device) - min_dimension = dimension_increasing_factor * min_dimension - max_dimension = dimension_increasing_factor * max_dimension - - relative_coordinates = torch.rand(batch_size, natom, 3).to(device) - - t1 = time.time() - _ = get_periodic_adjacency_information(relative_coordinates, basis_vectors, cutoff) - t2 = time.time() - dt = t2 - t1 - list_timing.append(t2 - t1) - logging.info(f" - It took {dt: 8.4e} seconds to generate A ({dt/batch_size: 8.4e} sec/structure)") - - list_natoms = np.array(list_natoms) - list_relative_timing = np.array(list_timing) / batch_size - - fig = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig.suptitle(f"Adjacency Computation Time on {device_type} per Structure\n" - f"Random Coordinates, Batch Size of {batch_size}") - ax = fig.add_subplot(111) - ax.set_xlabel("Number of Atoms") - ax.set_ylabel("Time") - ax.loglog(list_natoms, list_relative_timing, 'y-o', alpha=0.5, label='A Calculation Times') - ax.legend(loc=0) - fig.tight_layout() - fig.savefig(ANALYSIS_RESULTS_DIR.joinpath(f"timing_tests_{device_type}_adjacency_matrix.png")) From 30eb40c95b27d312cfaacd817bfdb9361edf9fc8 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Mon, 21 Oct 2024 20:49:08 -0400 Subject: [PATCH 18/29] Changing structure. --- examples/drawing_samples/draw_samples.py | 5 ++--- .../analytical_score_sampling_and_plotting.py | 8 ++++---- .../generate_sample_energies.py | 6 +++--- .../plot_sample_energies.py | 2 +- .../analytic_score/perfect_score_loss_analysis.py | 14 +++++++------- .../plot_repaint_analytical_score_trajectories.py | 6 +++--- .../repaint/repaint_with_analytic_score.py | 6 +++--- .../analytic_score/score_convergence_analysis.py | 6 +++--- .../analysis/diffusion_sample_position_analysis.py | 3 +-- .../analysis/exploding_variance_analysis.py | 7 +++---- .../analysis/perturbation_kernel_analysis.py | 3 +-- experiments/analysis/plot_repulsive_force.py | 5 ++--- .../plotting_gaussian_vs_wrapped_gaussian.py | 5 ++--- experiments/analysis/target_score_analysis.py | 3 +-- experiments/analysis_utils.py | 5 +++-- experiments/dataset_analysis/dataset_covariance.py | 3 +-- .../energy_consistency_analysis.py | 6 +++--- experiments/dataset_analysis/plot_si_phonon_DOS.py | 3 +-- .../dataset_analysis/si_diffusion_analysis.py | 4 ++-- .../dataset_analysis/si_diffusion_v1_analysis.py | 5 ++--- ..._hoc_experiments_with_various_score_networks.py | 13 +++++++------ .../analysis_callbacks.py | 9 +++++---- .../overfit_diffusion_mace.py | 12 ++++++------ .../generators/sde_generator_sanity_check.py | 8 ++++---- .../plot_repaint_score_trajectories.py | 5 ++--- .../sampling_sota_model/repaint_with_sota_score.py | 5 ++--- .../sota_score_sampling_and_plotting.py | 7 +++---- .../draw_samples_from_equilibrium.py | 6 +++--- .../plot_hessian_eigenvalues.py | 10 +++++----- .../score_stability_analysis/plot_score_norm.py | 8 ++++---- experiments/score_stability_analysis/util.py | 3 +-- .../__init__.py | 0 .../activelearning_dataclasses.py | 0 .../active_learning_loop/benchmark.py | 3 +-- .../active_learning_loop/oracle.py | 1 - .../active_learning_loop/utils.py | 0 .../analysis/README_ovito.md | 0 .../analysis/__init__.py | 0 .../analysis/generator_sample_analysis_utils.py | 5 ++--- .../analysis/ovito_visualisation.py | 0 .../analysis/plot_style.txt | 0 .../callbacks/__init__.py | 0 .../callbacks/callback_loader.py | 5 ++--- .../callbacks/loss_monitoring_callback.py | 5 ++--- .../callbacks/sampling_visualization_callback.py | 5 ++--- .../callbacks/standard_callbacks.py | 0 .../data/__init__.py | 0 .../data/diffusion/__init__.py | 0 .../data/diffusion/data_loader.py | 3 +-- .../data/diffusion/data_preprocess.py | 3 +-- .../data/parse_lammps_outputs.py | 0 .../data/utils.py | 0 .../generators/__init__.py | 0 .../generators/constrained_langevin_generator.py | 5 ++--- .../generators/instantiate_generator.py | 5 +++-- .../generators/langevin_generator.py | 5 ++--- .../generators/load_sampling_parameters.py | 3 ++- .../generators/ode_position_generator.py | 9 ++++----- .../generators/position_generator.py | 0 .../predictor_corrector_position_generator.py | 7 +++---- .../generators/sde_position_generator.py | 7 +++---- .../loggers/__init__.py | 0 .../loggers/logger_loader.py | 0 .../main_utils.py | 0 .../metrics/__init__.py | 0 .../metrics/kolmogorov_smirnov_metrics.py | 0 .../metrics/sampling_metrics_parameters.py | 0 .../mlip/mtp_train.py | 3 +-- .../mlip/mtp_utils.py | 0 .../models/__init__.py | 0 .../models/diffusion_mace.py | 11 +++++------ .../models/egnn.py | 3 +-- .../models/egnn_utils.py | 1 - .../models/graph_utils.py | 1 - .../models/instantiate_diffusion_model.py | 2 +- .../models/loss.py | 1 - .../models/mace_utils.py | 5 ++--- .../models/mlip/mtp.py | 7 +++---- .../models/normalized_score_fokker_planck_error.py | 5 ++--- .../models/optimizer.py | 3 +-- .../models/position_diffusion_lightning_model.py | 7 +++---- .../models/scheduler.py | 3 +-- .../models/score_networks/__init__.py | 0 .../score_networks/analytical_score_network.py | 1 - .../score_networks/diffusion_mace_score_network.py | 7 +++---- .../models/score_networks/egnn_score_network.py | 1 - .../force_field_augmented_score_network.py | 1 - .../models/score_networks/mace_score_network.py | 9 ++++----- .../models/score_networks/mlp_score_network.py | 3 +-- .../models/score_networks/score_network.py | 1 - .../models/score_networks/score_network_factory.py | 0 .../models/score_networks/score_prediction_head.py | 5 ++--- .../namespace.py | 0 .../oracle/energies.py | 1 - .../oracle/lammps.py | 3 +-- .../sample_diffusion.py | 8 ++++---- .../samplers/__init__.py | 0 .../samplers/exploding_variance.py | 3 +-- .../samplers/noisy_relative_coordinates_sampler.py | 1 - .../samplers/variance_sampler.py | 0 .../samples/__init__.py | 0 .../samples/diffusion_sampling_parameters.py | 5 +++-- .../samples/sampling.py | 5 ++--- .../score/__init__.py | 0 .../score/wrapped_gaussian_score.py | 0 .../train_diffusion.py | 1 - .../utils/__init__.py | 0 .../utils/basis_transformations.py | 0 .../utils/configuration_parsing.py | 0 .../utils/file_utils.py | 0 .../utils/hp_utils.py | 0 .../utils/logging_utils.py | 0 .../utils/neighbors.py | 3 +-- .../utils/ovito_utils.py | 0 .../utils/reproducibility_utils.py | 0 .../utils/sample_trajectory.py | 0 .../utils/structure_utils.py | 5 ++--- .../utils/tensor_utils.py | 0 tests/active_learning_loop/test_benchmark.py | 1 - tests/analysis/test_ovito_visualisation.py | 5 ++--- tests/data/diffusion/test_data_loader.py | 2 +- tests/data/diffusion/test_data_preprocess.py | 2 +- tests/data/test_parse_lammps_output.py | 4 ++-- tests/data/test_utils.py | 1 - tests/fake_data_utils.py | 1 - tests/generators/conftest.py | 1 - .../test_constrained_langevin_generator.py | 2 +- tests/generators/test_langevin_generator.py | 6 +++--- tests/generators/test_ode_position_generator.py | 4 ++-- .../test_predictor_corrector_position_generator.py | 2 +- tests/generators/test_sde_position_generator.py | 4 ++-- .../test_force_field_augmented_score_network.py | 1 - tests/models/score_network/test_score_network.py | 1 - .../score_network/test_score_prediction_head.py | 3 +-- tests/models/test_analytical_score_network.py | 1 - tests/models/test_diffusion_mace.py | 5 ++--- tests/models/test_egnn.py | 1 - tests/models/test_egnn_utils.py | 1 - tests/models/test_loss.py | 1 - tests/models/test_mace_utils.py | 10 +++++----- tests/models/test_mtp.py | 5 ++--- tests/models/test_optimizer.py | 1 - .../test_position_diffusion_lightning_model.py | 7 +++---- tests/models/test_scheduler.py | 1 - tests/models/test_score_fokker_planck_error.py | 7 +++---- tests/oracle/test_lammps.py | 1 - tests/samplers/test_exploding_variance.py | 3 +-- .../test_noisy_relative_coordinates_sampler.py | 1 - tests/samplers/test_variance_sampler.py | 3 +-- tests/samples_and_metrics/test_sampling.py | 7 +++---- tests/score/test_wrapped_gaussian_score.py | 1 - tests/test_hp_utils.py | 1 - tests/test_sample_diffusion.py | 3 +-- tests/test_train_diffusion.py | 2 +- tests/utils/test_basis_transformations.py | 1 - tests/utils/test_configuration_parsing.py | 1 - tests/utils/test_neighbors.py | 4 ++-- tests/utils/test_sample_trajectory.py | 1 - tests/utils/test_structure_utils.py | 1 - tests/utils/test_tensor_utils.py | 1 - 160 files changed, 204 insertions(+), 284 deletions(-) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/__init__.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/active_learning_loop/activelearning_dataclasses.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/active_learning_loop/benchmark.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/active_learning_loop/oracle.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/active_learning_loop/utils.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/analysis/README_ovito.md (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/analysis/__init__.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/analysis/generator_sample_analysis_utils.py (97%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/analysis/ovito_visualisation.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/analysis/plot_style.txt (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/callbacks/__init__.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/callbacks/callback_loader.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/callbacks/loss_monitoring_callback.py (98%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/callbacks/sampling_visualization_callback.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/callbacks/standard_callbacks.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/data/__init__.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/data/diffusion/__init__.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/data/diffusion/data_loader.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/data/diffusion/data_preprocess.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/data/parse_lammps_outputs.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/data/utils.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/generators/__init__.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/generators/constrained_langevin_generator.py (98%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/generators/instantiate_generator.py (91%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/generators/langevin_generator.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/generators/load_sampling_parameters.py (94%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/generators/ode_position_generator.py (98%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/generators/position_generator.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/generators/predictor_corrector_position_generator.py (98%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/generators/sde_position_generator.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/loggers/__init__.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/loggers/logger_loader.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/main_utils.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/metrics/__init__.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/metrics/kolmogorov_smirnov_metrics.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/metrics/sampling_metrics_parameters.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/mlip/mtp_train.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/mlip/mtp_utils.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/__init__.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/diffusion_mace.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/egnn.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/egnn_utils.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/graph_utils.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/instantiate_diffusion_model.py (96%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/loss.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/mace_utils.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/mlip/mtp.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/normalized_score_fokker_planck_error.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/optimizer.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/position_diffusion_lightning_model.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/scheduler.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/score_networks/__init__.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/score_networks/analytical_score_network.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/score_networks/diffusion_mace_score_network.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/score_networks/egnn_score_network.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/score_networks/force_field_augmented_score_network.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/score_networks/mace_score_network.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/score_networks/mlp_score_network.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/score_networks/score_network.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/score_networks/score_network_factory.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/models/score_networks/score_prediction_head.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/namespace.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/oracle/energies.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/oracle/lammps.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/sample_diffusion.py (96%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/samplers/__init__.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/samplers/exploding_variance.py (96%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/samplers/noisy_relative_coordinates_sampler.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/samplers/variance_sampler.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/samples/__init__.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/samples/diffusion_sampling_parameters.py (92%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/samples/sampling.py (97%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/score/__init__.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/score/wrapped_gaussian_score.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/train_diffusion.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/utils/__init__.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/utils/basis_transformations.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/utils/configuration_parsing.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/utils/file_utils.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/utils/hp_utils.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/utils/logging_utils.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/utils/neighbors.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/utils/ovito_utils.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/utils/reproducibility_utils.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/utils/sample_trajectory.py (100%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/utils/structure_utils.py (99%) rename {crystal_diffusion => src/diffusion_for_multi_scale_molecular_dynamics}/utils/tensor_utils.py (100%) diff --git a/examples/drawing_samples/draw_samples.py b/examples/drawing_samples/draw_samples.py index 20e712a8..ab0bf130 100644 --- a/examples/drawing_samples/draw_samples.py +++ b/examples/drawing_samples/draw_samples.py @@ -9,7 +9,6 @@ import numpy as np import torch - from crystal_diffusion.generators.instantiate_generator import \ instantiate_generator from crystal_diffusion.generators.predictor_corrector_position_generator import \ @@ -17,9 +16,9 @@ from crystal_diffusion.models.position_diffusion_lightning_model import \ PositionDiffusionLightningModel from crystal_diffusion.oracle.energies import compute_oracle_energies -from crystal_diffusion.samplers.variance_sampler import NoiseParameters -from crystal_diffusion.samples.sampling import create_batch_of_samples from crystal_diffusion.utils.logging_utils import setup_analysis_logger +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters +from src.crystal_diffusion.samples.sampling import create_batch_of_samples logger = logging.getLogger(__name__) setup_analysis_logger() diff --git a/experiments/analysis/analytic_score/analytical_score_sampling_and_plotting.py b/experiments/analysis/analytic_score/analytical_score_sampling_and_plotting.py index 0d34dae9..44f2b452 100644 --- a/experiments/analysis/analytic_score/analytical_score_sampling_and_plotting.py +++ b/experiments/analysis/analytic_score/analytical_score_sampling_and_plotting.py @@ -11,9 +11,6 @@ import matplotlib.pyplot as plt import numpy as np import torch -from einops import einops - -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH from crystal_diffusion.generators.langevin_generator import LangevinGenerator from crystal_diffusion.generators.ode_position_generator import ( ExplodingVarianceODEPositionGenerator, ODESamplingParameters) @@ -21,8 +18,11 @@ PredictorCorrectorSamplingParameters from crystal_diffusion.models.score_networks.analytical_score_network import ( AnalyticalScoreNetwork, AnalyticalScoreNetworkParameters) -from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.utils.logging_utils import setup_analysis_logger +from einops import einops +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters + from experiments.analysis.analytic_score import ANALYTIC_SCORE_RESULTS_DIR from experiments.analysis.analytic_score.utils import ( get_exact_samples, get_samples_harmonic_energy, get_silicon_supercell, diff --git a/experiments/analysis/analytic_score/exploring_langevin_generator/generate_sample_energies.py b/experiments/analysis/analytic_score/exploring_langevin_generator/generate_sample_energies.py index ac991d62..17ae45ed 100644 --- a/experiments/analysis/analytic_score/exploring_langevin_generator/generate_sample_energies.py +++ b/experiments/analysis/analytic_score/exploring_langevin_generator/generate_sample_energies.py @@ -5,8 +5,6 @@ import einops import numpy as np import torch -from tqdm import tqdm - from crystal_diffusion.callbacks.sampling_visualization_callback import logger from crystal_diffusion.generators.langevin_generator import LangevinGenerator from crystal_diffusion.generators.predictor_corrector_position_generator import \ @@ -14,9 +12,11 @@ from crystal_diffusion.models.score_networks.analytical_score_network import ( AnalyticalScoreNetworkParameters, TargetScoreBasedAnalyticalScoreNetwork) from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps -from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.utils.basis_transformations import ( get_positions_from_coordinates, map_relative_coordinates_to_unit_cell) +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters +from tqdm import tqdm + from experiments.analysis.analytic_score.exploring_langevin_generator import \ LANGEVIN_EXPLORATION_DIRECTORY from experiments.analysis.analytic_score.utils import (get_exact_samples, diff --git a/experiments/analysis/analytic_score/exploring_langevin_generator/plot_sample_energies.py b/experiments/analysis/analytic_score/exploring_langevin_generator/plot_sample_energies.py index 36def5d5..4d897836 100644 --- a/experiments/analysis/analytic_score/exploring_langevin_generator/plot_sample_energies.py +++ b/experiments/analysis/analytic_score/exploring_langevin_generator/plot_sample_energies.py @@ -2,8 +2,8 @@ import matplotlib.pyplot as plt import numpy as np +from src.crystal_diffusion.analysis import PLOT_STYLE_PATH -from crystal_diffusion.analysis import PLOT_STYLE_PATH from experiments.analysis.analytic_score.exploring_langevin_generator import \ LANGEVIN_EXPLORATION_DIRECTORY diff --git a/experiments/analysis/analytic_score/perfect_score_loss_analysis.py b/experiments/analysis/analytic_score/perfect_score_loss_analysis.py index 2b3af48c..d9cdf106 100644 --- a/experiments/analysis/analytic_score/perfect_score_loss_analysis.py +++ b/experiments/analysis/analytic_score/perfect_score_loss_analysis.py @@ -6,12 +6,7 @@ import numpy as np import pytorch_lightning as pl import torch -from pytorch_lightning.loggers import CSVLogger -from torch.utils.data import DataLoader -from tqdm import tqdm - from crystal_diffusion import ANALYSIS_RESULTS_DIR -from crystal_diffusion.analysis import PLOT_STYLE_PATH from crystal_diffusion.callbacks.loss_monitoring_callback import \ LossMonitoringCallback from crystal_diffusion.callbacks.sampling_visualization_callback import \ @@ -32,10 +27,15 @@ from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps from crystal_diffusion.samplers.noisy_relative_coordinates_sampler import \ NoisyRelativeCoordinatesSampler -from crystal_diffusion.samplers.variance_sampler import ( - ExplodingVarianceSampler, NoiseParameters) from crystal_diffusion.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell +from pytorch_lightning.loggers import CSVLogger +from src.crystal_diffusion.analysis import PLOT_STYLE_PATH +from src.crystal_diffusion.samplers.variance_sampler import ( + ExplodingVarianceSampler, NoiseParameters) +from torch.utils.data import DataLoader +from tqdm import tqdm + from experiments.analysis.analytic_score.utils import (get_exact_samples, get_silicon_supercell) diff --git a/experiments/analysis/analytic_score/repaint/plot_repaint_analytical_score_trajectories.py b/experiments/analysis/analytic_score/repaint/plot_repaint_analytical_score_trajectories.py index c7d58a8b..aa23103e 100644 --- a/experiments/analysis/analytic_score/repaint/plot_repaint_analytical_score_trajectories.py +++ b/experiments/analysis/analytic_score/repaint/plot_repaint_analytical_score_trajectories.py @@ -5,11 +5,11 @@ import matplotlib.pyplot as plt import numpy as np import torch -from einops import einops - from crystal_diffusion import ANALYSIS_RESULTS_DIR -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH from crystal_diffusion.utils.logging_utils import setup_analysis_logger +from einops import einops +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH + from experiments.analysis.analytic_score import (get_exact_samples, get_samples_harmonic_energy, get_silicon_supercell) diff --git a/experiments/analysis/analytic_score/repaint/repaint_with_analytic_score.py b/experiments/analysis/analytic_score/repaint/repaint_with_analytic_score.py index 65312ea1..c4667eb4 100644 --- a/experiments/analysis/analytic_score/repaint/repaint_with_analytic_score.py +++ b/experiments/analysis/analytic_score/repaint/repaint_with_analytic_score.py @@ -3,16 +3,16 @@ import matplotlib.pyplot as plt import numpy as np import torch - from crystal_diffusion import ANALYSIS_RESULTS_DIR -from crystal_diffusion.analysis import PLOT_STYLE_PATH from crystal_diffusion.generators.constrained_langevin_generator import ( ConstrainedLangevinGenerator, ConstrainedLangevinGeneratorParameters) from crystal_diffusion.models.score_networks.analytical_score_network import ( AnalyticalScoreNetwork, AnalyticalScoreNetworkParameters) -from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.utils.logging_utils import setup_analysis_logger from crystal_diffusion.utils.structure_utils import create_structure +from src.crystal_diffusion.analysis import PLOT_STYLE_PATH +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters + from experiments.analysis.analytic_score import (get_samples_harmonic_energy, get_silicon_supercell, get_unit_cells) diff --git a/experiments/analysis/analytic_score/score_convergence_analysis.py b/experiments/analysis/analytic_score/score_convergence_analysis.py index aad84e78..1e61ff45 100644 --- a/experiments/analysis/analytic_score/score_convergence_analysis.py +++ b/experiments/analysis/analytic_score/score_convergence_analysis.py @@ -6,13 +6,13 @@ import matplotlib.pyplot as plt import torch -from einops import einops - -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH from crystal_diffusion.models.score_networks.analytical_score_network import ( AnalyticalScoreNetwork, AnalyticalScoreNetworkParameters) from crystal_diffusion.namespace import (NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) +from einops import einops +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH + from experiments.analysis.analytic_score import ANALYTIC_SCORE_RESULTS_DIR from experiments.analysis.analytic_score.utils import get_unit_cells diff --git a/experiments/analysis/diffusion_sample_position_analysis.py b/experiments/analysis/diffusion_sample_position_analysis.py index 0afacbc6..1986ed48 100644 --- a/experiments/analysis/diffusion_sample_position_analysis.py +++ b/experiments/analysis/diffusion_sample_position_analysis.py @@ -6,11 +6,10 @@ import matplotlib.pyplot as plt import numpy as np import torch - from crystal_diffusion import TOP_DIR -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH from crystal_diffusion.utils.sample_trajectory import \ PredictorCorrectorSampleTrajectory +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH plt.style.use(PLOT_STYLE_PATH) diff --git a/experiments/analysis/exploding_variance_analysis.py b/experiments/analysis/exploding_variance_analysis.py index c7c02560..0520683b 100644 --- a/experiments/analysis/exploding_variance_analysis.py +++ b/experiments/analysis/exploding_variance_analysis.py @@ -5,13 +5,12 @@ """ import matplotlib.pyplot as plt import torch - from crystal_diffusion import ANALYSIS_RESULTS_DIR -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from crystal_diffusion.samplers.variance_sampler import ( - ExplodingVarianceSampler, NoiseParameters) from crystal_diffusion.score.wrapped_gaussian_score import \ get_sigma_normalized_score +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH +from src.crystal_diffusion.samplers.variance_sampler import ( + ExplodingVarianceSampler, NoiseParameters) plt.style.use(PLOT_STYLE_PATH) diff --git a/experiments/analysis/perturbation_kernel_analysis.py b/experiments/analysis/perturbation_kernel_analysis.py index ce453bb3..25d2de3a 100644 --- a/experiments/analysis/perturbation_kernel_analysis.py +++ b/experiments/analysis/perturbation_kernel_analysis.py @@ -5,11 +5,10 @@ """ import matplotlib.pyplot as plt import torch - from crystal_diffusion import ANALYSIS_RESULTS_DIR -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH from crystal_diffusion.score.wrapped_gaussian_score import \ get_sigma_normalized_score +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH plt.style.use(PLOT_STYLE_PATH) diff --git a/experiments/analysis/plot_repulsive_force.py b/experiments/analysis/plot_repulsive_force.py index f6e0a379..d5e50b97 100644 --- a/experiments/analysis/plot_repulsive_force.py +++ b/experiments/analysis/plot_repulsive_force.py @@ -1,10 +1,9 @@ import torch -from matplotlib import pyplot as plt - -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH from crystal_diffusion.models.score_networks.force_field_augmented_score_network import ( ForceFieldAugmentedScoreNetwork, ForceFieldParameters) from crystal_diffusion.namespace import NOISY_RELATIVE_COORDINATES, UNIT_CELL +from matplotlib import pyplot as plt +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH plt.style.use(PLOT_STYLE_PATH) diff --git a/experiments/analysis/plotting_gaussian_vs_wrapped_gaussian.py b/experiments/analysis/plotting_gaussian_vs_wrapped_gaussian.py index 0d073ccc..7b13971a 100644 --- a/experiments/analysis/plotting_gaussian_vs_wrapped_gaussian.py +++ b/experiments/analysis/plotting_gaussian_vs_wrapped_gaussian.py @@ -5,10 +5,9 @@ """ import matplotlib.pyplot as plt import numpy as np -from scipy.stats import norm - from crystal_diffusion import ANALYSIS_RESULTS_DIR -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH +from scipy.stats import norm +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH plt.style.use(PLOT_STYLE_PATH) diff --git a/experiments/analysis/target_score_analysis.py b/experiments/analysis/target_score_analysis.py index b4cfc30f..0bca3889 100644 --- a/experiments/analysis/target_score_analysis.py +++ b/experiments/analysis/target_score_analysis.py @@ -6,12 +6,11 @@ import matplotlib.pyplot as plt import numpy as np import torch - from crystal_diffusion import ANALYSIS_RESULTS_DIR -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH from crystal_diffusion.score.wrapped_gaussian_score import ( SIGMA_THRESHOLD, get_sigma_normalized_score, get_sigma_normalized_score_brute_force) +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH plt.style.use(PLOT_STYLE_PATH) diff --git a/experiments/analysis_utils.py b/experiments/analysis_utils.py index 156de546..c3452bee 100644 --- a/experiments/analysis_utils.py +++ b/experiments/analysis_utils.py @@ -4,9 +4,10 @@ from typing import Tuple import pandas as pd - from crystal_diffusion import DATA_DIR -from crystal_diffusion.data.parse_lammps_outputs import parse_lammps_thermo_log +from src.crystal_diffusion.data.parse_lammps_outputs import \ + parse_lammps_thermo_log + from experiments import EXPERIMENT_ANALYSIS_DIR logger = logging.getLogger(__name__) diff --git a/experiments/dataset_analysis/dataset_covariance.py b/experiments/dataset_analysis/dataset_covariance.py index eed61e7e..83b95dc6 100644 --- a/experiments/dataset_analysis/dataset_covariance.py +++ b/experiments/dataset_analysis/dataset_covariance.py @@ -8,14 +8,13 @@ import einops import torch -from tqdm import tqdm - from crystal_diffusion import ANALYSIS_RESULTS_DIR, DATA_DIR from crystal_diffusion.data.diffusion.data_loader import ( LammpsForDiffusionDataModule, LammpsLoaderParameters) from crystal_diffusion.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell from crystal_diffusion.utils.logging_utils import setup_analysis_logger +from tqdm import tqdm logger = logging.getLogger(__name__) dataset_name = 'si_diffusion_2x2x2' diff --git a/experiments/dataset_analysis/energy_consistency_analysis.py b/experiments/dataset_analysis/energy_consistency_analysis.py index 1c6113f0..4c32c45d 100644 --- a/experiments/dataset_analysis/energy_consistency_analysis.py +++ b/experiments/dataset_analysis/energy_consistency_analysis.py @@ -11,16 +11,16 @@ import matplotlib.pyplot as plt import numpy as np -from tqdm import tqdm - from crystal_diffusion import DATA_DIR -from crystal_diffusion.analysis import PLOT_STYLE_PATH from crystal_diffusion.callbacks.sampling_visualization_callback import ( LOGGER_FIGSIZE, SamplingVisualizationCallback) from crystal_diffusion.data.diffusion.data_loader import ( LammpsForDiffusionDataModule, LammpsLoaderParameters) from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps from crystal_diffusion.utils.logging_utils import setup_analysis_logger +from src.crystal_diffusion.analysis import PLOT_STYLE_PATH +from tqdm import tqdm + from experiments import EXPERIMENT_ANALYSIS_DIR from experiments.analysis_utils import get_thermo_dataset diff --git a/experiments/dataset_analysis/plot_si_phonon_DOS.py b/experiments/dataset_analysis/plot_si_phonon_DOS.py index 3d719b22..efda2862 100644 --- a/experiments/dataset_analysis/plot_si_phonon_DOS.py +++ b/experiments/dataset_analysis/plot_si_phonon_DOS.py @@ -6,9 +6,8 @@ """ import matplotlib.pyplot as plt import torch - from crystal_diffusion import ANALYSIS_RESULTS_DIR -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH plt.style.use(PLOT_STYLE_PATH) diff --git a/experiments/dataset_analysis/si_diffusion_analysis.py b/experiments/dataset_analysis/si_diffusion_analysis.py index d0de054b..3bb92a53 100644 --- a/experiments/dataset_analysis/si_diffusion_analysis.py +++ b/experiments/dataset_analysis/si_diffusion_analysis.py @@ -15,10 +15,10 @@ import numpy as np import pandas as pd import scipy - from crystal_diffusion import ANALYSIS_RESULTS_DIR, DATA_DIR -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH from crystal_diffusion.utils.logging_utils import setup_analysis_logger +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH + from experiments.analysis_utils import get_thermo_dataset plt.style.use(PLOT_STYLE_PATH) diff --git a/experiments/dataset_analysis/si_diffusion_v1_analysis.py b/experiments/dataset_analysis/si_diffusion_v1_analysis.py index ddce7b67..81c1e1f4 100644 --- a/experiments/dataset_analysis/si_diffusion_v1_analysis.py +++ b/experiments/dataset_analysis/si_diffusion_v1_analysis.py @@ -8,10 +8,9 @@ import matplotlib.pyplot as plt import numpy as np import pandas as pd - from crystal_diffusion import ANALYSIS_RESULTS_DIR, DATA_DIR -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from crystal_diffusion.data.parse_lammps_outputs import parse_lammps_output +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH +from src.crystal_diffusion.data.parse_lammps_outputs import parse_lammps_output DATASET_NAME = 'si_diffusion_v1' diff --git a/experiments/diffusion_mace_harmonic_data/ad_hoc_experiments_with_various_score_networks.py b/experiments/diffusion_mace_harmonic_data/ad_hoc_experiments_with_various_score_networks.py index 026045d2..209cd31e 100644 --- a/experiments/diffusion_mace_harmonic_data/ad_hoc_experiments_with_various_score_networks.py +++ b/experiments/diffusion_mace_harmonic_data/ad_hoc_experiments_with_various_score_networks.py @@ -4,12 +4,7 @@ import matplotlib.pyplot as plt import pytorch_lightning as pl import torch -from pytorch_lightning.loggers import TensorBoardLogger -from torch.utils.data import DataLoader - -from crystal_diffusion.analysis import PLOT_STYLE_PATH from crystal_diffusion.callbacks.callback_loader import create_all_callbacks -from crystal_diffusion.generators.position_generator import SamplingParameters from crystal_diffusion.models.optimizer import OptimizerParameters from crystal_diffusion.models.position_diffusion_lightning_model import ( PositionDiffusionLightningModel, PositionDiffusionParameters) @@ -24,7 +19,13 @@ from crystal_diffusion.models.score_networks.score_prediction_head import \ MaceEquivariantScorePredictionHeadParameters from crystal_diffusion.namespace import CARTESIAN_FORCES, RELATIVE_COORDINATES -from crystal_diffusion.samplers.variance_sampler import NoiseParameters +from pytorch_lightning.loggers import TensorBoardLogger +from src.crystal_diffusion.analysis import PLOT_STYLE_PATH +from src.crystal_diffusion.generators.position_generator import \ + SamplingParameters +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters +from torch.utils.data import DataLoader + from experiments.analysis.analytic_score import (get_exact_samples, get_relative_harmonic_energy) from experiments.diffusion_mace_harmonic_data.analysis_callbacks import \ diff --git a/experiments/diffusion_mace_harmonic_data/analysis_callbacks.py b/experiments/diffusion_mace_harmonic_data/analysis_callbacks.py index 28e417c3..4bdb5e24 100644 --- a/experiments/diffusion_mace_harmonic_data/analysis_callbacks.py +++ b/experiments/diffusion_mace_harmonic_data/analysis_callbacks.py @@ -8,12 +8,13 @@ import matplotlib.pyplot as plt import numpy as np import torch - -from crystal_diffusion.analysis import PLOT_STYLE_PATH from crystal_diffusion.callbacks.sampling_visualization_callback import \ SamplingVisualizationCallback -from crystal_diffusion.generators.position_generator import SamplingParameters -from crystal_diffusion.samplers.variance_sampler import NoiseParameters +from src.crystal_diffusion.analysis import PLOT_STYLE_PATH +from src.crystal_diffusion.generators.position_generator import \ + SamplingParameters +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters + from experiments.analysis.analytic_score import get_relative_harmonic_energy logger = logging.getLogger(__name__) diff --git a/experiments/diffusion_mace_harmonic_data/overfit_diffusion_mace.py b/experiments/diffusion_mace_harmonic_data/overfit_diffusion_mace.py index 11879e90..b9d5cbc4 100644 --- a/experiments/diffusion_mace_harmonic_data/overfit_diffusion_mace.py +++ b/experiments/diffusion_mace_harmonic_data/overfit_diffusion_mace.py @@ -7,10 +7,6 @@ import pytorch_lightning as pl import torch -from pytorch_lightning.loggers import TensorBoardLogger -from torch import optim -from torch.utils.data import DataLoader - from crystal_diffusion.callbacks.standard_callbacks import CustomProgressBar from crystal_diffusion.models.score_networks.analytical_score_network import ( AnalyticalScoreNetwork, AnalyticalScoreNetworkParameters) @@ -21,12 +17,16 @@ UNIT_CELL) from crystal_diffusion.samplers.noisy_relative_coordinates_sampler import \ NoisyRelativeCoordinatesSampler -from crystal_diffusion.samplers.variance_sampler import ( - ExplodingVarianceSampler, NoiseParameters) from crystal_diffusion.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell from crystal_diffusion.utils.tensor_utils import \ broadcast_batch_tensor_to_all_dimensions +from pytorch_lightning.loggers import TensorBoardLogger +from src.crystal_diffusion.samplers.variance_sampler import ( + ExplodingVarianceSampler, NoiseParameters) +from torch import optim +from torch.utils.data import DataLoader + from experiments.analysis.analytic_score import (get_exact_samples, get_unit_cells) diff --git a/experiments/generators/sde_generator_sanity_check.py b/experiments/generators/sde_generator_sanity_check.py index ef4e1554..075f441b 100644 --- a/experiments/generators/sde_generator_sanity_check.py +++ b/experiments/generators/sde_generator_sanity_check.py @@ -6,16 +6,16 @@ import einops import numpy as np import torch -from matplotlib import pyplot as plt - -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH from crystal_diffusion.generators.sde_position_generator import ( ExplodingVarianceSDEPositionGenerator, SDESamplingParameters) from crystal_diffusion.models.score_networks.analytical_score_network import ( AnalyticalScoreNetworkParameters, TargetScoreBasedAnalyticalScoreNetwork) -from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell +from matplotlib import pyplot as plt +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters + from experiments.analysis.analytic_score.utils import get_exact_samples from experiments.generators import GENERATOR_SANITY_CHECK_DIRECTORY diff --git a/experiments/sampling_sota_model/plot_repaint_score_trajectories.py b/experiments/sampling_sota_model/plot_repaint_score_trajectories.py index c9071386..c9f6252a 100644 --- a/experiments/sampling_sota_model/plot_repaint_score_trajectories.py +++ b/experiments/sampling_sota_model/plot_repaint_score_trajectories.py @@ -6,10 +6,9 @@ import matplotlib.pyplot as plt import numpy as np import torch -from einops import einops - -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH from crystal_diffusion.utils.logging_utils import setup_analysis_logger +from einops import einops +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH logger = logging.getLogger(__name__) setup_analysis_logger() diff --git a/experiments/sampling_sota_model/repaint_with_sota_score.py b/experiments/sampling_sota_model/repaint_with_sota_score.py index 3eb0c564..9a96d80d 100644 --- a/experiments/sampling_sota_model/repaint_with_sota_score.py +++ b/experiments/sampling_sota_model/repaint_with_sota_score.py @@ -6,16 +6,15 @@ import numpy as np import torch import yaml - from crystal_diffusion import DATA_DIR -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH from crystal_diffusion.generators.constrained_langevin_generator import ( ConstrainedLangevinGenerator, ConstrainedLangevinGeneratorParameters) from crystal_diffusion.models.instantiate_diffusion_model import \ load_diffusion_model from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps -from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.utils.logging_utils import setup_analysis_logger +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters logger = logging.getLogger(__name__) setup_analysis_logger() diff --git a/experiments/sampling_sota_model/sota_score_sampling_and_plotting.py b/experiments/sampling_sota_model/sota_score_sampling_and_plotting.py index 0039ece7..2867eb28 100644 --- a/experiments/sampling_sota_model/sota_score_sampling_and_plotting.py +++ b/experiments/sampling_sota_model/sota_score_sampling_and_plotting.py @@ -8,10 +8,7 @@ import numpy as np import torch import yaml -from einops import einops - from crystal_diffusion import DATA_DIR -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH from crystal_diffusion.generators.langevin_generator import LangevinGenerator from crystal_diffusion.generators.ode_position_generator import ( ExplodingVarianceODEPositionGenerator, ODESamplingParameters) @@ -20,8 +17,10 @@ from crystal_diffusion.models.instantiate_diffusion_model import \ load_diffusion_model from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps -from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.utils.logging_utils import setup_analysis_logger +from einops import einops +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters logger = logging.getLogger(__name__) setup_analysis_logger() diff --git a/experiments/score_stability_analysis/draw_samples_from_equilibrium.py b/experiments/score_stability_analysis/draw_samples_from_equilibrium.py index 0dc28164..303f92fb 100644 --- a/experiments/score_stability_analysis/draw_samples_from_equilibrium.py +++ b/experiments/score_stability_analysis/draw_samples_from_equilibrium.py @@ -4,8 +4,6 @@ import matplotlib.pyplot as plt import numpy as np import torch - -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH from crystal_diffusion.generators.langevin_generator import LangevinGenerator from crystal_diffusion.generators.ode_position_generator import ( ExplodingVarianceODEPositionGenerator, ODESamplingParameters) @@ -16,8 +14,10 @@ from crystal_diffusion.models.position_diffusion_lightning_model import \ PositionDiffusionLightningModel from crystal_diffusion.models.score_networks import ScoreNetwork -from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.utils.logging_utils import setup_analysis_logger +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters + from experiments.analysis.analytic_score.exploring_langevin_generator.generate_sample_energies import \ EnergyCalculator from experiments.analysis.analytic_score.utils import get_silicon_supercell diff --git a/experiments/score_stability_analysis/plot_hessian_eigenvalues.py b/experiments/score_stability_analysis/plot_hessian_eigenvalues.py index dcfbe27c..b9508d33 100644 --- a/experiments/score_stability_analysis/plot_hessian_eigenvalues.py +++ b/experiments/score_stability_analysis/plot_hessian_eigenvalues.py @@ -5,15 +5,15 @@ import matplotlib.pyplot as plt import numpy as np import torch -from torch.func import jacrev -from tqdm import tqdm - -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH from crystal_diffusion.models.position_diffusion_lightning_model import \ PositionDiffusionLightningModel from crystal_diffusion.samplers.exploding_variance import ExplodingVariance -from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.utils.logging_utils import setup_analysis_logger +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters +from torch.func import jacrev +from tqdm import tqdm + from experiments import get_normalized_score_function from experiments.analysis.analytic_score.utils import get_silicon_supercell diff --git a/experiments/score_stability_analysis/plot_score_norm.py b/experiments/score_stability_analysis/plot_score_norm.py index 57f050df..1cfd8399 100644 --- a/experiments/score_stability_analysis/plot_score_norm.py +++ b/experiments/score_stability_analysis/plot_score_norm.py @@ -4,16 +4,16 @@ import matplotlib.pyplot as plt import numpy as np import torch -from tqdm import tqdm - -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH from crystal_diffusion.models.position_diffusion_lightning_model import \ PositionDiffusionLightningModel from crystal_diffusion.samplers.exploding_variance import ExplodingVariance -from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell from crystal_diffusion.utils.logging_utils import setup_analysis_logger +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters +from tqdm import tqdm + from experiments import create_fixed_time_normalized_score_function from experiments.analysis.analytic_score.utils import get_silicon_supercell diff --git a/experiments/score_stability_analysis/util.py b/experiments/score_stability_analysis/util.py index 6e272894..842107de 100644 --- a/experiments/score_stability_analysis/util.py +++ b/experiments/score_stability_analysis/util.py @@ -3,13 +3,12 @@ import einops import torch - from crystal_diffusion.models.score_networks import ScoreNetwork from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) from crystal_diffusion.samplers.exploding_variance import ExplodingVariance -from crystal_diffusion.samplers.variance_sampler import NoiseParameters +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters def get_normalized_score_function( diff --git a/crystal_diffusion/__init__.py b/src/diffusion_for_multi_scale_molecular_dynamics/__init__.py similarity index 100% rename from crystal_diffusion/__init__.py rename to src/diffusion_for_multi_scale_molecular_dynamics/__init__.py diff --git a/crystal_diffusion/active_learning_loop/activelearning_dataclasses.py b/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/activelearning_dataclasses.py similarity index 100% rename from crystal_diffusion/active_learning_loop/activelearning_dataclasses.py rename to src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/activelearning_dataclasses.py diff --git a/crystal_diffusion/active_learning_loop/benchmark.py b/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/benchmark.py similarity index 99% rename from crystal_diffusion/active_learning_loop/benchmark.py rename to src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/benchmark.py index 7b231d3a..de3773af 100644 --- a/crystal_diffusion/active_learning_loop/benchmark.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/benchmark.py @@ -5,11 +5,10 @@ import numpy as np import pandas as pd import yaml -from hydra.utils import instantiate - from crystal_diffusion.active_learning_loop.utils import ( extract_target_region, get_structures_for_retraining) from crystal_diffusion.models.mlip.mtp import MTPWithMLIP3 +from hydra.utils import instantiate class ActiveLearningLoop: diff --git a/crystal_diffusion/active_learning_loop/oracle.py b/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/oracle.py similarity index 99% rename from crystal_diffusion/active_learning_loop/oracle.py rename to src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/oracle.py index a1893f5c..f54f04e9 100644 --- a/crystal_diffusion/active_learning_loop/oracle.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/oracle.py @@ -2,7 +2,6 @@ from typing import Dict, Tuple import numpy as np - from crystal_diffusion import DATA_DIR from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps diff --git a/crystal_diffusion/active_learning_loop/utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/utils.py similarity index 100% rename from crystal_diffusion/active_learning_loop/utils.py rename to src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/utils.py diff --git a/crystal_diffusion/analysis/README_ovito.md b/src/diffusion_for_multi_scale_molecular_dynamics/analysis/README_ovito.md similarity index 100% rename from crystal_diffusion/analysis/README_ovito.md rename to src/diffusion_for_multi_scale_molecular_dynamics/analysis/README_ovito.md diff --git a/crystal_diffusion/analysis/__init__.py b/src/diffusion_for_multi_scale_molecular_dynamics/analysis/__init__.py similarity index 100% rename from crystal_diffusion/analysis/__init__.py rename to src/diffusion_for_multi_scale_molecular_dynamics/analysis/__init__.py diff --git a/crystal_diffusion/analysis/generator_sample_analysis_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/analysis/generator_sample_analysis_utils.py similarity index 97% rename from crystal_diffusion/analysis/generator_sample_analysis_utils.py rename to src/diffusion_for_multi_scale_molecular_dynamics/analysis/generator_sample_analysis_utils.py index 76d1da3a..4543c330 100644 --- a/crystal_diffusion/analysis/generator_sample_analysis_utils.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/analysis/generator_sample_analysis_utils.py @@ -1,11 +1,10 @@ import torch -from einops import einops - from crystal_diffusion.generators.ode_position_generator import ( ExplodingVarianceODEPositionGenerator, ODESamplingParameters) from crystal_diffusion.models.mace_utils import get_adj_matrix from crystal_diffusion.models.score_networks.score_network import ScoreNetwork -from crystal_diffusion.samplers.variance_sampler import NoiseParameters +from einops import einops +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters class PartialODEPositionGenerator(ExplodingVarianceODEPositionGenerator): diff --git a/crystal_diffusion/analysis/ovito_visualisation.py b/src/diffusion_for_multi_scale_molecular_dynamics/analysis/ovito_visualisation.py similarity index 100% rename from crystal_diffusion/analysis/ovito_visualisation.py rename to src/diffusion_for_multi_scale_molecular_dynamics/analysis/ovito_visualisation.py diff --git a/crystal_diffusion/analysis/plot_style.txt b/src/diffusion_for_multi_scale_molecular_dynamics/analysis/plot_style.txt similarity index 100% rename from crystal_diffusion/analysis/plot_style.txt rename to src/diffusion_for_multi_scale_molecular_dynamics/analysis/plot_style.txt diff --git a/crystal_diffusion/callbacks/__init__.py b/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/__init__.py similarity index 100% rename from crystal_diffusion/callbacks/__init__.py rename to src/diffusion_for_multi_scale_molecular_dynamics/callbacks/__init__.py diff --git a/crystal_diffusion/callbacks/callback_loader.py b/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/callback_loader.py similarity index 99% rename from crystal_diffusion/callbacks/callback_loader.py rename to src/diffusion_for_multi_scale_molecular_dynamics/callbacks/callback_loader.py index 977e26b5..62c77b2c 100644 --- a/crystal_diffusion/callbacks/callback_loader.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/callback_loader.py @@ -1,8 +1,5 @@ from typing import Any, AnyStr, Dict -from pytorch_lightning import Callback -from pytorch_lightning.callbacks import LearningRateMonitor - from crystal_diffusion.callbacks.loss_monitoring_callback import \ instantiate_loss_monitoring_callback from crystal_diffusion.callbacks.sampling_visualization_callback import \ @@ -10,6 +7,8 @@ from crystal_diffusion.callbacks.standard_callbacks import ( CustomProgressBar, instantiate_early_stopping_callback, instantiate_model_checkpoint_callbacks) +from pytorch_lightning import Callback +from pytorch_lightning.callbacks import LearningRateMonitor OPTIONAL_CALLBACK_DICTIONARY = dict(early_stopping=instantiate_early_stopping_callback, model_checkpoint=instantiate_model_checkpoint_callbacks, diff --git a/crystal_diffusion/callbacks/loss_monitoring_callback.py b/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/loss_monitoring_callback.py similarity index 98% rename from crystal_diffusion/callbacks/loss_monitoring_callback.py rename to src/diffusion_for_multi_scale_molecular_dynamics/callbacks/loss_monitoring_callback.py index 8640e151..5e32892d 100644 --- a/crystal_diffusion/callbacks/loss_monitoring_callback.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/loss_monitoring_callback.py @@ -2,11 +2,10 @@ import numpy as np import torch +from crystal_diffusion.loggers.logger_loader import log_figure from matplotlib import pyplot as plt from pytorch_lightning import Callback - -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from crystal_diffusion.loggers.logger_loader import log_figure +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH plt.style.use(PLOT_STYLE_PATH) diff --git a/crystal_diffusion/callbacks/sampling_visualization_callback.py b/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/sampling_visualization_callback.py similarity index 99% rename from crystal_diffusion/callbacks/sampling_visualization_callback.py rename to src/diffusion_for_multi_scale_molecular_dynamics/callbacks/sampling_visualization_callback.py index ed7a0472..025a10bd 100644 --- a/crystal_diffusion/callbacks/sampling_visualization_callback.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/sampling_visualization_callback.py @@ -6,11 +6,10 @@ import numpy as np import torch +from crystal_diffusion.loggers.logger_loader import log_figure from matplotlib import pyplot as plt from pytorch_lightning import Callback, LightningModule, Trainer - -from crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from crystal_diffusion.loggers.logger_loader import log_figure +from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH logger = logging.getLogger(__name__) diff --git a/crystal_diffusion/callbacks/standard_callbacks.py b/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/standard_callbacks.py similarity index 100% rename from crystal_diffusion/callbacks/standard_callbacks.py rename to src/diffusion_for_multi_scale_molecular_dynamics/callbacks/standard_callbacks.py diff --git a/crystal_diffusion/data/__init__.py b/src/diffusion_for_multi_scale_molecular_dynamics/data/__init__.py similarity index 100% rename from crystal_diffusion/data/__init__.py rename to src/diffusion_for_multi_scale_molecular_dynamics/data/__init__.py diff --git a/crystal_diffusion/data/diffusion/__init__.py b/src/diffusion_for_multi_scale_molecular_dynamics/data/diffusion/__init__.py similarity index 100% rename from crystal_diffusion/data/diffusion/__init__.py rename to src/diffusion_for_multi_scale_molecular_dynamics/data/diffusion/__init__.py diff --git a/crystal_diffusion/data/diffusion/data_loader.py b/src/diffusion_for_multi_scale_molecular_dynamics/data/diffusion/data_loader.py similarity index 99% rename from crystal_diffusion/data/diffusion/data_loader.py rename to src/diffusion_for_multi_scale_molecular_dynamics/data/diffusion/data_loader.py index 9cda99b3..0f81b5bb 100644 --- a/crystal_diffusion/data/diffusion/data_loader.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/data/diffusion/data_loader.py @@ -9,12 +9,11 @@ import pytorch_lightning as pl import torch import torch.nn.functional as F -from torch.utils.data import DataLoader - from crystal_diffusion.data.diffusion.data_preprocess import \ LammpsProcessorForDiffusion from crystal_diffusion.namespace import (CARTESIAN_FORCES, CARTESIAN_POSITIONS, RELATIVE_COORDINATES) +from torch.utils.data import DataLoader logger = logging.getLogger(__name__) diff --git a/crystal_diffusion/data/diffusion/data_preprocess.py b/src/diffusion_for_multi_scale_molecular_dynamics/data/diffusion/data_preprocess.py similarity index 99% rename from crystal_diffusion/data/diffusion/data_preprocess.py rename to src/diffusion_for_multi_scale_molecular_dynamics/data/diffusion/data_preprocess.py index ce7f87cb..efa00ed8 100644 --- a/crystal_diffusion/data/diffusion/data_preprocess.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/data/diffusion/data_preprocess.py @@ -7,10 +7,9 @@ from typing import List, Optional, Tuple, Union import pandas as pd - -from crystal_diffusion.data.parse_lammps_outputs import parse_lammps_output from crystal_diffusion.namespace import (CARTESIAN_FORCES, CARTESIAN_POSITIONS, RELATIVE_COORDINATES) +from src.crystal_diffusion.data.parse_lammps_outputs import parse_lammps_output logger = logging.getLogger(__name__) diff --git a/crystal_diffusion/data/parse_lammps_outputs.py b/src/diffusion_for_multi_scale_molecular_dynamics/data/parse_lammps_outputs.py similarity index 100% rename from crystal_diffusion/data/parse_lammps_outputs.py rename to src/diffusion_for_multi_scale_molecular_dynamics/data/parse_lammps_outputs.py diff --git a/crystal_diffusion/data/utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/data/utils.py similarity index 100% rename from crystal_diffusion/data/utils.py rename to src/diffusion_for_multi_scale_molecular_dynamics/data/utils.py diff --git a/crystal_diffusion/generators/__init__.py b/src/diffusion_for_multi_scale_molecular_dynamics/generators/__init__.py similarity index 100% rename from crystal_diffusion/generators/__init__.py rename to src/diffusion_for_multi_scale_molecular_dynamics/generators/__init__.py diff --git a/crystal_diffusion/generators/constrained_langevin_generator.py b/src/diffusion_for_multi_scale_molecular_dynamics/generators/constrained_langevin_generator.py similarity index 98% rename from crystal_diffusion/generators/constrained_langevin_generator.py rename to src/diffusion_for_multi_scale_molecular_dynamics/generators/constrained_langevin_generator.py index 96cda59c..9cbed410 100644 --- a/crystal_diffusion/generators/constrained_langevin_generator.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/generators/constrained_langevin_generator.py @@ -2,17 +2,16 @@ import numpy as np import torch -from tqdm import tqdm - from crystal_diffusion.generators.langevin_generator import LangevinGenerator from crystal_diffusion.generators.predictor_corrector_position_generator import \ PredictorCorrectorSamplingParameters from crystal_diffusion.models.score_networks.score_network import ScoreNetwork from crystal_diffusion.samplers.noisy_relative_coordinates_sampler import \ NoisyRelativeCoordinatesSampler -from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters +from tqdm import tqdm @dataclass(kw_only=True) diff --git a/crystal_diffusion/generators/instantiate_generator.py b/src/diffusion_for_multi_scale_molecular_dynamics/generators/instantiate_generator.py similarity index 91% rename from crystal_diffusion/generators/instantiate_generator.py rename to src/diffusion_for_multi_scale_molecular_dynamics/generators/instantiate_generator.py index ac6277bb..d7d15501 100644 --- a/crystal_diffusion/generators/instantiate_generator.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/generators/instantiate_generator.py @@ -1,11 +1,12 @@ from crystal_diffusion.generators.langevin_generator import LangevinGenerator from crystal_diffusion.generators.ode_position_generator import \ ExplodingVarianceODEPositionGenerator -from crystal_diffusion.generators.position_generator import SamplingParameters from crystal_diffusion.generators.sde_position_generator import \ ExplodingVarianceSDEPositionGenerator from crystal_diffusion.models.score_networks import ScoreNetwork -from crystal_diffusion.samplers.variance_sampler import NoiseParameters +from src.crystal_diffusion.generators.position_generator import \ + SamplingParameters +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters def instantiate_generator(sampling_parameters: SamplingParameters, diff --git a/crystal_diffusion/generators/langevin_generator.py b/src/diffusion_for_multi_scale_molecular_dynamics/generators/langevin_generator.py similarity index 99% rename from crystal_diffusion/generators/langevin_generator.py rename to src/diffusion_for_multi_scale_molecular_dynamics/generators/langevin_generator.py index e85db9ab..ed7d9888 100644 --- a/crystal_diffusion/generators/langevin_generator.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/generators/langevin_generator.py @@ -1,15 +1,14 @@ import torch - from crystal_diffusion.generators.predictor_corrector_position_generator import ( PredictorCorrectorPositionGenerator, PredictorCorrectorSamplingParameters) from crystal_diffusion.models.score_networks.score_network import ScoreNetwork from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) -from crystal_diffusion.samplers.variance_sampler import ( - ExplodingVarianceSampler, NoiseParameters) from crystal_diffusion.utils.sample_trajectory import ( NoOpPredictorCorrectorSampleTrajectory, PredictorCorrectorSampleTrajectory) +from src.crystal_diffusion.samplers.variance_sampler import ( + ExplodingVarianceSampler, NoiseParameters) class LangevinGenerator(PredictorCorrectorPositionGenerator): diff --git a/crystal_diffusion/generators/load_sampling_parameters.py b/src/diffusion_for_multi_scale_molecular_dynamics/generators/load_sampling_parameters.py similarity index 94% rename from crystal_diffusion/generators/load_sampling_parameters.py rename to src/diffusion_for_multi_scale_molecular_dynamics/generators/load_sampling_parameters.py index 21ce3c21..ef633bf8 100644 --- a/crystal_diffusion/generators/load_sampling_parameters.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/generators/load_sampling_parameters.py @@ -2,11 +2,12 @@ from crystal_diffusion.generators.ode_position_generator import \ ODESamplingParameters -from crystal_diffusion.generators.position_generator import SamplingParameters from crystal_diffusion.generators.predictor_corrector_position_generator import \ PredictorCorrectorSamplingParameters from crystal_diffusion.generators.sde_position_generator import \ SDESamplingParameters +from src.crystal_diffusion.generators.position_generator import \ + SamplingParameters def load_sampling_parameters(sampling_parameter_dictionary: Dict[AnyStr, Any]) -> SamplingParameters: diff --git a/crystal_diffusion/generators/ode_position_generator.py b/src/diffusion_for_multi_scale_molecular_dynamics/generators/ode_position_generator.py similarity index 98% rename from crystal_diffusion/generators/ode_position_generator.py rename to src/diffusion_for_multi_scale_molecular_dynamics/generators/ode_position_generator.py index ca61962f..49f53ebf 100644 --- a/crystal_diffusion/generators/ode_position_generator.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/generators/ode_position_generator.py @@ -5,19 +5,18 @@ import einops import torch import torchode as to -from torchode import Solution - -from crystal_diffusion.generators.position_generator import ( - PositionGenerator, SamplingParameters) from crystal_diffusion.models.score_networks.score_network import ScoreNetwork from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) -from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell from crystal_diffusion.utils.sample_trajectory import (NoOpODESampleTrajectory, ODESampleTrajectory) +from src.crystal_diffusion.generators.position_generator import ( + PositionGenerator, SamplingParameters) +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters +from torchode import Solution logger = logging.getLogger(__name__) diff --git a/crystal_diffusion/generators/position_generator.py b/src/diffusion_for_multi_scale_molecular_dynamics/generators/position_generator.py similarity index 100% rename from crystal_diffusion/generators/position_generator.py rename to src/diffusion_for_multi_scale_molecular_dynamics/generators/position_generator.py diff --git a/crystal_diffusion/generators/predictor_corrector_position_generator.py b/src/diffusion_for_multi_scale_molecular_dynamics/generators/predictor_corrector_position_generator.py similarity index 98% rename from crystal_diffusion/generators/predictor_corrector_position_generator.py rename to src/diffusion_for_multi_scale_molecular_dynamics/generators/predictor_corrector_position_generator.py index 459246ba..ba7178cc 100644 --- a/crystal_diffusion/generators/predictor_corrector_position_generator.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/generators/predictor_corrector_position_generator.py @@ -3,12 +3,11 @@ from dataclasses import dataclass import torch -from tqdm import tqdm - -from crystal_diffusion.generators.position_generator import ( - PositionGenerator, SamplingParameters) from crystal_diffusion.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell +from src.crystal_diffusion.generators.position_generator import ( + PositionGenerator, SamplingParameters) +from tqdm import tqdm logger = logging.getLogger(__name__) diff --git a/crystal_diffusion/generators/sde_position_generator.py b/src/diffusion_for_multi_scale_molecular_dynamics/generators/sde_position_generator.py similarity index 99% rename from crystal_diffusion/generators/sde_position_generator.py rename to src/diffusion_for_multi_scale_molecular_dynamics/generators/sde_position_generator.py index 3a233850..f2432942 100644 --- a/crystal_diffusion/generators/sde_position_generator.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/generators/sde_position_generator.py @@ -4,17 +4,16 @@ import einops import torch import torchsde - -from crystal_diffusion.generators.position_generator import ( - PositionGenerator, SamplingParameters) from crystal_diffusion.models.score_networks import ScoreNetwork from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) -from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell from crystal_diffusion.utils.sample_trajectory import SDESampleTrajectory +from src.crystal_diffusion.generators.position_generator import ( + PositionGenerator, SamplingParameters) +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters logger = logging.getLogger(__name__) diff --git a/crystal_diffusion/loggers/__init__.py b/src/diffusion_for_multi_scale_molecular_dynamics/loggers/__init__.py similarity index 100% rename from crystal_diffusion/loggers/__init__.py rename to src/diffusion_for_multi_scale_molecular_dynamics/loggers/__init__.py diff --git a/crystal_diffusion/loggers/logger_loader.py b/src/diffusion_for_multi_scale_molecular_dynamics/loggers/logger_loader.py similarity index 100% rename from crystal_diffusion/loggers/logger_loader.py rename to src/diffusion_for_multi_scale_molecular_dynamics/loggers/logger_loader.py diff --git a/crystal_diffusion/main_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/main_utils.py similarity index 100% rename from crystal_diffusion/main_utils.py rename to src/diffusion_for_multi_scale_molecular_dynamics/main_utils.py diff --git a/crystal_diffusion/metrics/__init__.py b/src/diffusion_for_multi_scale_molecular_dynamics/metrics/__init__.py similarity index 100% rename from crystal_diffusion/metrics/__init__.py rename to src/diffusion_for_multi_scale_molecular_dynamics/metrics/__init__.py diff --git a/crystal_diffusion/metrics/kolmogorov_smirnov_metrics.py b/src/diffusion_for_multi_scale_molecular_dynamics/metrics/kolmogorov_smirnov_metrics.py similarity index 100% rename from crystal_diffusion/metrics/kolmogorov_smirnov_metrics.py rename to src/diffusion_for_multi_scale_molecular_dynamics/metrics/kolmogorov_smirnov_metrics.py diff --git a/crystal_diffusion/metrics/sampling_metrics_parameters.py b/src/diffusion_for_multi_scale_molecular_dynamics/metrics/sampling_metrics_parameters.py similarity index 100% rename from crystal_diffusion/metrics/sampling_metrics_parameters.py rename to src/diffusion_for_multi_scale_molecular_dynamics/metrics/sampling_metrics_parameters.py diff --git a/crystal_diffusion/mlip/mtp_train.py b/src/diffusion_for_multi_scale_molecular_dynamics/mlip/mtp_train.py similarity index 99% rename from crystal_diffusion/mlip/mtp_train.py rename to src/diffusion_for_multi_scale_molecular_dynamics/mlip/mtp_train.py index bcf527ed..93312518 100644 --- a/crystal_diffusion/mlip/mtp_train.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/mlip/mtp_train.py @@ -6,12 +6,11 @@ from typing import Dict, Tuple import pandas as pd -from sklearn.metrics import mean_absolute_error - from crystal_diffusion.mlip.mtp_utils import (MTPInputs, crawl_lammps_directory, prepare_mtp_inputs_from_lammps) from crystal_diffusion.models.mlip.mtp import MTPWithMLIP3 +from sklearn.metrics import mean_absolute_error atom_dict = {1: 'Si'} diff --git a/crystal_diffusion/mlip/mtp_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/mlip/mtp_utils.py similarity index 100% rename from crystal_diffusion/mlip/mtp_utils.py rename to src/diffusion_for_multi_scale_molecular_dynamics/mlip/mtp_utils.py diff --git a/crystal_diffusion/models/__init__.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/__init__.py similarity index 100% rename from crystal_diffusion/models/__init__.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/__init__.py diff --git a/crystal_diffusion/models/diffusion_mace.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/diffusion_mace.py similarity index 99% rename from crystal_diffusion/models/diffusion_mace.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/diffusion_mace.py index 6b1f732e..b4919e12 100644 --- a/crystal_diffusion/models/diffusion_mace.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/diffusion_mace.py @@ -1,6 +1,11 @@ from typing import AnyStr, Callable, Dict, List, Optional, Type, Union import torch +from crystal_diffusion.models.mace_utils import (get_adj_matrix, + reshape_from_e3nn_to_mace, + reshape_from_mace_to_e3nn) +from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, + NOISY_CARTESIAN_POSITIONS, UNIT_CELL) from e3nn import o3 from e3nn.nn import Activation, BatchNorm, NormActivation from mace.modules import (EquivariantProductBasisBlock, InteractionBlock, @@ -8,12 +13,6 @@ from mace.modules.utils import get_edge_vectors_and_lengths from torch_geometric.data import Data -from crystal_diffusion.models.mace_utils import (get_adj_matrix, - reshape_from_e3nn_to_mace, - reshape_from_mace_to_e3nn) -from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, - NOISY_CARTESIAN_POSITIONS, UNIT_CELL) - class LinearVectorReadoutBlock(torch.nn.Module): """Linear readout block for vector representation.""" diff --git a/crystal_diffusion/models/egnn.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/egnn.py similarity index 99% rename from crystal_diffusion/models/egnn.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/egnn.py index 5170d2d8..6aa34c40 100644 --- a/crystal_diffusion/models/egnn.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/egnn.py @@ -10,10 +10,9 @@ from typing import Callable, Tuple import torch -from torch import nn - from crystal_diffusion.models.egnn_utils import (unsorted_segment_mean, unsorted_segment_sum) +from torch import nn class E_GCL(nn.Module): diff --git a/crystal_diffusion/models/egnn_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/egnn_utils.py similarity index 99% rename from crystal_diffusion/models/egnn_utils.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/egnn_utils.py index d7ad60a5..d1b03359 100644 --- a/crystal_diffusion/models/egnn_utils.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/egnn_utils.py @@ -1,7 +1,6 @@ from typing import List import torch - from crystal_diffusion.models.mace_utils import get_adj_matrix from crystal_diffusion.utils.basis_transformations import \ get_positions_from_coordinates diff --git a/crystal_diffusion/models/graph_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/graph_utils.py similarity index 99% rename from crystal_diffusion/models/graph_utils.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/graph_utils.py index 602e09ad..5750c4e0 100644 --- a/crystal_diffusion/models/graph_utils.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/graph_utils.py @@ -1,7 +1,6 @@ from typing import Tuple import torch - from crystal_diffusion.utils.neighbors import ( get_periodic_adjacency_information, shift_adjacency_matrix_indices_for_graph_batching) diff --git a/crystal_diffusion/models/instantiate_diffusion_model.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/instantiate_diffusion_model.py similarity index 96% rename from crystal_diffusion/models/instantiate_diffusion_model.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/instantiate_diffusion_model.py index 511fce7f..3856e095 100644 --- a/crystal_diffusion/models/instantiate_diffusion_model.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/instantiate_diffusion_model.py @@ -9,9 +9,9 @@ from crystal_diffusion.models.scheduler import create_scheduler_parameters from crystal_diffusion.models.score_networks.score_network_factory import \ create_score_network_parameters -from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.samples.diffusion_sampling_parameters import \ load_diffusion_sampling_parameters +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters logger = logging.getLogger(__name__) diff --git a/crystal_diffusion/models/loss.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/loss.py similarity index 99% rename from crystal_diffusion/models/loss.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/loss.py index f1b8599f..8af9aa47 100644 --- a/crystal_diffusion/models/loss.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/loss.py @@ -2,7 +2,6 @@ from typing import Any, Dict import torch - from crystal_diffusion.utils.configuration_parsing import \ create_parameters_from_configuration_dictionary diff --git a/crystal_diffusion/models/mace_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/mace_utils.py similarity index 99% rename from crystal_diffusion/models/mace_utils.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/mace_utils.py index 3b45ef4d..fde3cf5b 100644 --- a/crystal_diffusion/models/mace_utils.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/mace_utils.py @@ -3,11 +3,10 @@ from typing import AnyStr, Dict, Tuple import torch -from e3nn import o3 -from torch_geometric.data import Data - from crystal_diffusion.models.graph_utils import get_adj_matrix from crystal_diffusion.namespace import NOISY_CARTESIAN_POSITIONS, UNIT_CELL +from e3nn import o3 +from torch_geometric.data import Data def input_to_mace(x: Dict[AnyStr, torch.Tensor], radial_cutoff: float) -> Data: diff --git a/crystal_diffusion/models/mlip/mtp.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/mlip/mtp.py similarity index 99% rename from crystal_diffusion/models/mlip/mtp.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/mlip/mtp.py index bc822cf6..b6bc20ea 100644 --- a/crystal_diffusion/models/mlip/mtp.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/mlip/mtp.py @@ -15,16 +15,15 @@ import numpy as np import pandas as pd +from crystal_diffusion.mlip.mtp_utils import (MTPInputs, concat_mtp_inputs, + crawl_lammps_directory, + prepare_mtp_inputs_from_lammps) from maml.apps.pes import MTPotential from maml.utils import check_structures_forces_stresses, pool_from from monty.io import zopen from monty.tempfile import ScratchDir from pymatgen.core import Structure -from crystal_diffusion.mlip.mtp_utils import (MTPInputs, concat_mtp_inputs, - crawl_lammps_directory, - prepare_mtp_inputs_from_lammps) - @dataclass(kw_only=True) class MTPArguments: diff --git a/crystal_diffusion/models/normalized_score_fokker_planck_error.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/normalized_score_fokker_planck_error.py similarity index 99% rename from crystal_diffusion/models/normalized_score_fokker_planck_error.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/normalized_score_fokker_planck_error.py index b0e939ec..2d57d9e1 100644 --- a/crystal_diffusion/models/normalized_score_fokker_planck_error.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/normalized_score_fokker_planck_error.py @@ -2,14 +2,13 @@ import einops import torch -from torch.func import jacrev - from crystal_diffusion.models.score_networks import ScoreNetwork from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) from crystal_diffusion.samplers.exploding_variance import ExplodingVariance -from crystal_diffusion.samplers.variance_sampler import NoiseParameters +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters +from torch.func import jacrev class NormalizedScoreFokkerPlanckError(torch.nn.Module): diff --git a/crystal_diffusion/models/optimizer.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/optimizer.py similarity index 99% rename from crystal_diffusion/models/optimizer.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/optimizer.py index 1f462724..97e8cc7b 100644 --- a/crystal_diffusion/models/optimizer.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/optimizer.py @@ -3,10 +3,9 @@ from typing import Any, Dict import torch -from torch import optim - from crystal_diffusion.utils.configuration_parsing import \ create_parameters_from_configuration_dictionary +from torch import optim logger = logging.getLogger(__name__) diff --git a/crystal_diffusion/models/position_diffusion_lightning_model.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/position_diffusion_lightning_model.py similarity index 99% rename from crystal_diffusion/models/position_diffusion_lightning_model.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/position_diffusion_lightning_model.py index 2fc5e6e1..9d7bf3b7 100644 --- a/crystal_diffusion/models/position_diffusion_lightning_model.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/position_diffusion_lightning_model.py @@ -4,7 +4,6 @@ import pytorch_lightning as pl import torch - from crystal_diffusion.generators.instantiate_generator import \ instantiate_generator from crystal_diffusion.metrics.kolmogorov_smirnov_metrics import \ @@ -25,11 +24,8 @@ from crystal_diffusion.oracle.energies import compute_oracle_energies from crystal_diffusion.samplers.noisy_relative_coordinates_sampler import \ NoisyRelativeCoordinatesSampler -from crystal_diffusion.samplers.variance_sampler import ( - ExplodingVarianceSampler, NoiseParameters) from crystal_diffusion.samples.diffusion_sampling_parameters import \ DiffusionSamplingParameters -from crystal_diffusion.samples.sampling import create_batch_of_samples from crystal_diffusion.score.wrapped_gaussian_score import \ get_sigma_normalized_score from crystal_diffusion.utils.basis_transformations import ( @@ -37,6 +33,9 @@ from crystal_diffusion.utils.structure_utils import compute_distances_in_batch from crystal_diffusion.utils.tensor_utils import \ broadcast_batch_tensor_to_all_dimensions +from src.crystal_diffusion.samplers.variance_sampler import ( + ExplodingVarianceSampler, NoiseParameters) +from src.crystal_diffusion.samples.sampling import create_batch_of_samples logger = logging.getLogger(__name__) diff --git a/crystal_diffusion/models/scheduler.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/scheduler.py similarity index 99% rename from crystal_diffusion/models/scheduler.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/scheduler.py index b07642eb..1adc4558 100644 --- a/crystal_diffusion/models/scheduler.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/scheduler.py @@ -1,10 +1,9 @@ from dataclasses import asdict, dataclass from typing import Any, AnyStr, Dict, Union -from torch import optim - from crystal_diffusion.utils.configuration_parsing import \ create_parameters_from_configuration_dictionary +from torch import optim @dataclass(kw_only=True) diff --git a/crystal_diffusion/models/score_networks/__init__.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/__init__.py similarity index 100% rename from crystal_diffusion/models/score_networks/__init__.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/__init__.py diff --git a/crystal_diffusion/models/score_networks/analytical_score_network.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/analytical_score_network.py similarity index 99% rename from crystal_diffusion/models/score_networks/analytical_score_network.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/analytical_score_network.py index d7bebd34..f1cbf3b0 100644 --- a/crystal_diffusion/models/score_networks/analytical_score_network.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/analytical_score_network.py @@ -16,7 +16,6 @@ import einops import torch - from crystal_diffusion.models.score_networks.score_network import ( ScoreNetwork, ScoreNetworkParameters) from crystal_diffusion.namespace import NOISE, NOISY_RELATIVE_COORDINATES diff --git a/crystal_diffusion/models/score_networks/diffusion_mace_score_network.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/diffusion_mace_score_network.py similarity index 99% rename from crystal_diffusion/models/score_networks/diffusion_mace_score_network.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/diffusion_mace_score_network.py index defcbacd..31269025 100644 --- a/crystal_diffusion/models/score_networks/diffusion_mace_score_network.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/diffusion_mace_score_network.py @@ -2,10 +2,6 @@ from typing import AnyStr, Dict, List import torch -from e3nn import o3 -from mace.modules import gate_dict, interaction_classes -from mace.tools.torch_geometric.dataloader import Collater - from crystal_diffusion.models.diffusion_mace import (DiffusionMACE, input_to_diffusion_mace) from crystal_diffusion.models.score_networks.score_network import ( @@ -14,6 +10,9 @@ NOISY_RELATIVE_COORDINATES, UNIT_CELL) from crystal_diffusion.utils.basis_transformations import ( get_positions_from_coordinates, get_reciprocal_basis_vectors) +from e3nn import o3 +from mace.modules import gate_dict, interaction_classes +from mace.tools.torch_geometric.dataloader import Collater @dataclass(kw_only=True) diff --git a/crystal_diffusion/models/score_networks/egnn_score_network.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/egnn_score_network.py similarity index 99% rename from crystal_diffusion/models/score_networks/egnn_score_network.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/egnn_score_network.py index 17f6d39e..d90987a2 100644 --- a/crystal_diffusion/models/score_networks/egnn_score_network.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/egnn_score_network.py @@ -3,7 +3,6 @@ import einops import torch - from crystal_diffusion.models.egnn import EGNN from crystal_diffusion.models.egnn_utils import (get_edges_batch, get_edges_with_radial_cutoff) diff --git a/crystal_diffusion/models/score_networks/force_field_augmented_score_network.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/force_field_augmented_score_network.py similarity index 99% rename from crystal_diffusion/models/score_networks/force_field_augmented_score_network.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/force_field_augmented_score_network.py index 5532f595..59ef4f5c 100644 --- a/crystal_diffusion/models/score_networks/force_field_augmented_score_network.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/force_field_augmented_score_network.py @@ -3,7 +3,6 @@ import einops import torch - from crystal_diffusion.models.score_networks import ScoreNetwork from crystal_diffusion.namespace import NOISY_RELATIVE_COORDINATES, UNIT_CELL from crystal_diffusion.utils.basis_transformations import ( diff --git a/crystal_diffusion/models/score_networks/mace_score_network.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/mace_score_network.py similarity index 99% rename from crystal_diffusion/models/score_networks/mace_score_network.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/mace_score_network.py index f1c4dbbf..b0c5b838 100644 --- a/crystal_diffusion/models/score_networks/mace_score_network.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/mace_score_network.py @@ -3,11 +3,6 @@ import numpy as np import torch -from e3nn import o3 -from mace.modules import MACE, gate_dict, interaction_classes -from mace.tools import get_atomic_number_table_from_zs -from mace.tools.torch_geometric.dataloader import Collater - from crystal_diffusion.models.mace_utils import ( build_mace_output_nodes_irreducible_representation, get_pretrained_mace, get_pretrained_mace_output_node_features_irreps, input_to_mace) @@ -18,6 +13,10 @@ from crystal_diffusion.namespace import (NOISY_CARTESIAN_POSITIONS, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) +from e3nn import o3 +from mace.modules import MACE, gate_dict, interaction_classes +from mace.tools import get_atomic_number_table_from_zs +from mace.tools.torch_geometric.dataloader import Collater @dataclass(kw_only=True) diff --git a/crystal_diffusion/models/score_networks/mlp_score_network.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/mlp_score_network.py similarity index 99% rename from crystal_diffusion/models/score_networks/mlp_score_network.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/mlp_score_network.py index a99f5c0d..c5e989cc 100644 --- a/crystal_diffusion/models/score_networks/mlp_score_network.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/mlp_score_network.py @@ -2,12 +2,11 @@ from typing import AnyStr, Dict import torch -from torch import nn - from crystal_diffusion.models.score_networks.score_network import ( ScoreNetwork, ScoreNetworkParameters) from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, NOISY_RELATIVE_COORDINATES) +from torch import nn @dataclass(kw_only=True) diff --git a/crystal_diffusion/models/score_networks/score_network.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/score_network.py similarity index 99% rename from crystal_diffusion/models/score_networks/score_network.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/score_network.py index 39d0e38a..2701e654 100644 --- a/crystal_diffusion/models/score_networks/score_network.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/score_network.py @@ -9,7 +9,6 @@ from typing import AnyStr, Dict, Optional import torch - from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) diff --git a/crystal_diffusion/models/score_networks/score_network_factory.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/score_network_factory.py similarity index 100% rename from crystal_diffusion/models/score_networks/score_network_factory.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/score_network_factory.py diff --git a/crystal_diffusion/models/score_networks/score_prediction_head.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/score_prediction_head.py similarity index 99% rename from crystal_diffusion/models/score_networks/score_prediction_head.py rename to src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/score_prediction_head.py index 4136dc08..47c680cf 100644 --- a/crystal_diffusion/models/score_networks/score_prediction_head.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/score_prediction_head.py @@ -2,14 +2,13 @@ import e3nn import torch +from crystal_diffusion.models.mace_utils import \ + get_normalized_irreps_permutation_indices from e3nn import o3 from e3nn.nn import Activation from mace.modules import LinearNodeEmbeddingBlock, gate_dict from torch import nn -from crystal_diffusion.models.mace_utils import \ - get_normalized_irreps_permutation_indices - @dataclass(kw_only=True) class MaceScorePredictionHeadParameters: diff --git a/crystal_diffusion/namespace.py b/src/diffusion_for_multi_scale_molecular_dynamics/namespace.py similarity index 100% rename from crystal_diffusion/namespace.py rename to src/diffusion_for_multi_scale_molecular_dynamics/namespace.py diff --git a/crystal_diffusion/oracle/energies.py b/src/diffusion_for_multi_scale_molecular_dynamics/oracle/energies.py similarity index 99% rename from crystal_diffusion/oracle/energies.py rename to src/diffusion_for_multi_scale_molecular_dynamics/oracle/energies.py index 29eccc7d..ccc69659 100644 --- a/crystal_diffusion/oracle/energies.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/oracle/energies.py @@ -4,7 +4,6 @@ import numpy as np import torch - from crystal_diffusion.namespace import CARTESIAN_POSITIONS, UNIT_CELL from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps diff --git a/crystal_diffusion/oracle/lammps.py b/src/diffusion_for_multi_scale_molecular_dynamics/oracle/lammps.py similarity index 99% rename from crystal_diffusion/oracle/lammps.py rename to src/diffusion_for_multi_scale_molecular_dynamics/oracle/lammps.py index 0107aa3f..cd0bd43c 100644 --- a/crystal_diffusion/oracle/lammps.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/oracle/lammps.py @@ -7,9 +7,8 @@ import numpy as np import pandas as pd import yaml -from pymatgen.core import Element - from crystal_diffusion import DATA_DIR +from pymatgen.core import Element def get_energy_and_forces_from_lammps(cartesian_positions: np.ndarray, diff --git a/crystal_diffusion/sample_diffusion.py b/src/diffusion_for_multi_scale_molecular_dynamics/sample_diffusion.py similarity index 96% rename from crystal_diffusion/sample_diffusion.py rename to src/diffusion_for_multi_scale_molecular_dynamics/sample_diffusion.py index ea598c63..08fd58c7 100644 --- a/crystal_diffusion/sample_diffusion.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/sample_diffusion.py @@ -10,21 +10,21 @@ from typing import Any, AnyStr, Dict, Optional, Union import torch - from crystal_diffusion.generators.instantiate_generator import \ instantiate_generator from crystal_diffusion.generators.load_sampling_parameters import \ load_sampling_parameters -from crystal_diffusion.generators.position_generator import SamplingParameters from crystal_diffusion.main_utils import load_and_backup_hyperparameters from crystal_diffusion.models.position_diffusion_lightning_model import \ PositionDiffusionLightningModel from crystal_diffusion.models.score_networks import ScoreNetwork from crystal_diffusion.oracle.energies import compute_oracle_energies -from crystal_diffusion.samplers.variance_sampler import NoiseParameters -from crystal_diffusion.samples.sampling import create_batch_of_samples from crystal_diffusion.utils.logging_utils import (get_git_hash, setup_console_logger) +from src.crystal_diffusion.generators.position_generator import \ + SamplingParameters +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters +from src.crystal_diffusion.samples.sampling import create_batch_of_samples logger = logging.getLogger(__name__) diff --git a/crystal_diffusion/samplers/__init__.py b/src/diffusion_for_multi_scale_molecular_dynamics/samplers/__init__.py similarity index 100% rename from crystal_diffusion/samplers/__init__.py rename to src/diffusion_for_multi_scale_molecular_dynamics/samplers/__init__.py diff --git a/crystal_diffusion/samplers/exploding_variance.py b/src/diffusion_for_multi_scale_molecular_dynamics/samplers/exploding_variance.py similarity index 96% rename from crystal_diffusion/samplers/exploding_variance.py rename to src/diffusion_for_multi_scale_molecular_dynamics/samplers/exploding_variance.py index 0f84e26d..45dddb80 100644 --- a/crystal_diffusion/samplers/exploding_variance.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/samplers/exploding_variance.py @@ -1,6 +1,5 @@ import torch - -from crystal_diffusion.samplers.variance_sampler import NoiseParameters +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters class ExplodingVariance(torch.nn.Module): diff --git a/crystal_diffusion/samplers/noisy_relative_coordinates_sampler.py b/src/diffusion_for_multi_scale_molecular_dynamics/samplers/noisy_relative_coordinates_sampler.py similarity index 99% rename from crystal_diffusion/samplers/noisy_relative_coordinates_sampler.py rename to src/diffusion_for_multi_scale_molecular_dynamics/samplers/noisy_relative_coordinates_sampler.py index 7b19f7c7..f1bd093b 100644 --- a/crystal_diffusion/samplers/noisy_relative_coordinates_sampler.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/samplers/noisy_relative_coordinates_sampler.py @@ -5,7 +5,6 @@ from typing import Tuple import torch - from crystal_diffusion.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell diff --git a/crystal_diffusion/samplers/variance_sampler.py b/src/diffusion_for_multi_scale_molecular_dynamics/samplers/variance_sampler.py similarity index 100% rename from crystal_diffusion/samplers/variance_sampler.py rename to src/diffusion_for_multi_scale_molecular_dynamics/samplers/variance_sampler.py diff --git a/crystal_diffusion/samples/__init__.py b/src/diffusion_for_multi_scale_molecular_dynamics/samples/__init__.py similarity index 100% rename from crystal_diffusion/samples/__init__.py rename to src/diffusion_for_multi_scale_molecular_dynamics/samples/__init__.py diff --git a/crystal_diffusion/samples/diffusion_sampling_parameters.py b/src/diffusion_for_multi_scale_molecular_dynamics/samples/diffusion_sampling_parameters.py similarity index 92% rename from crystal_diffusion/samples/diffusion_sampling_parameters.py rename to src/diffusion_for_multi_scale_molecular_dynamics/samples/diffusion_sampling_parameters.py index 50aec3b2..2d4682a2 100644 --- a/crystal_diffusion/samples/diffusion_sampling_parameters.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/samples/diffusion_sampling_parameters.py @@ -3,10 +3,11 @@ from crystal_diffusion.generators.load_sampling_parameters import \ load_sampling_parameters -from crystal_diffusion.generators.position_generator import SamplingParameters from crystal_diffusion.metrics.sampling_metrics_parameters import \ SamplingMetricsParameters -from crystal_diffusion.samplers.variance_sampler import NoiseParameters +from src.crystal_diffusion.generators.position_generator import \ + SamplingParameters +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters @dataclass(kw_only=True) diff --git a/crystal_diffusion/samples/sampling.py b/src/diffusion_for_multi_scale_molecular_dynamics/samples/sampling.py similarity index 97% rename from crystal_diffusion/samples/sampling.py rename to src/diffusion_for_multi_scale_molecular_dynamics/samples/sampling.py index 1c3e357b..dc3545b7 100644 --- a/crystal_diffusion/samples/sampling.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/samples/sampling.py @@ -1,15 +1,14 @@ import logging import torch - -from crystal_diffusion.generators.position_generator import ( - PositionGenerator, SamplingParameters) from crystal_diffusion.namespace import (CARTESIAN_POSITIONS, RELATIVE_COORDINATES, UNIT_CELL) from crystal_diffusion.utils.basis_transformations import \ get_positions_from_coordinates from crystal_diffusion.utils.structure_utils import \ get_orthogonal_basis_vectors +from src.crystal_diffusion.generators.position_generator import ( + PositionGenerator, SamplingParameters) logger = logging.getLogger(__name__) diff --git a/crystal_diffusion/score/__init__.py b/src/diffusion_for_multi_scale_molecular_dynamics/score/__init__.py similarity index 100% rename from crystal_diffusion/score/__init__.py rename to src/diffusion_for_multi_scale_molecular_dynamics/score/__init__.py diff --git a/crystal_diffusion/score/wrapped_gaussian_score.py b/src/diffusion_for_multi_scale_molecular_dynamics/score/wrapped_gaussian_score.py similarity index 100% rename from crystal_diffusion/score/wrapped_gaussian_score.py rename to src/diffusion_for_multi_scale_molecular_dynamics/score/wrapped_gaussian_score.py diff --git a/crystal_diffusion/train_diffusion.py b/src/diffusion_for_multi_scale_molecular_dynamics/train_diffusion.py similarity index 99% rename from crystal_diffusion/train_diffusion.py rename to src/diffusion_for_multi_scale_molecular_dynamics/train_diffusion.py index bd66163d..cc301218 100644 --- a/crystal_diffusion/train_diffusion.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/train_diffusion.py @@ -8,7 +8,6 @@ import pytorch_lightning import pytorch_lightning as pl import yaml - from crystal_diffusion.callbacks.callback_loader import create_all_callbacks from crystal_diffusion.data.diffusion.data_loader import ( LammpsForDiffusionDataModule, LammpsLoaderParameters) diff --git a/crystal_diffusion/utils/__init__.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/__init__.py similarity index 100% rename from crystal_diffusion/utils/__init__.py rename to src/diffusion_for_multi_scale_molecular_dynamics/utils/__init__.py diff --git a/crystal_diffusion/utils/basis_transformations.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/basis_transformations.py similarity index 100% rename from crystal_diffusion/utils/basis_transformations.py rename to src/diffusion_for_multi_scale_molecular_dynamics/utils/basis_transformations.py diff --git a/crystal_diffusion/utils/configuration_parsing.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/configuration_parsing.py similarity index 100% rename from crystal_diffusion/utils/configuration_parsing.py rename to src/diffusion_for_multi_scale_molecular_dynamics/utils/configuration_parsing.py diff --git a/crystal_diffusion/utils/file_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/file_utils.py similarity index 100% rename from crystal_diffusion/utils/file_utils.py rename to src/diffusion_for_multi_scale_molecular_dynamics/utils/file_utils.py diff --git a/crystal_diffusion/utils/hp_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/hp_utils.py similarity index 100% rename from crystal_diffusion/utils/hp_utils.py rename to src/diffusion_for_multi_scale_molecular_dynamics/utils/hp_utils.py diff --git a/crystal_diffusion/utils/logging_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/logging_utils.py similarity index 100% rename from crystal_diffusion/utils/logging_utils.py rename to src/diffusion_for_multi_scale_molecular_dynamics/utils/logging_utils.py diff --git a/crystal_diffusion/utils/neighbors.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/neighbors.py similarity index 99% rename from crystal_diffusion/utils/neighbors.py rename to src/diffusion_for_multi_scale_molecular_dynamics/utils/neighbors.py index 0bf28f0c..7fdd16bc 100644 --- a/crystal_diffusion/utils/neighbors.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/utils/neighbors.py @@ -9,10 +9,9 @@ import numpy as np import torch -from pykeops.torch import LazyTensor - from crystal_diffusion.utils.basis_transformations import \ get_positions_from_coordinates +from pykeops.torch import LazyTensor INDEX_PADDING_VALUE = -1 POSITION_PADDING_VALUE = np.NaN diff --git a/crystal_diffusion/utils/ovito_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/ovito_utils.py similarity index 100% rename from crystal_diffusion/utils/ovito_utils.py rename to src/diffusion_for_multi_scale_molecular_dynamics/utils/ovito_utils.py diff --git a/crystal_diffusion/utils/reproducibility_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/reproducibility_utils.py similarity index 100% rename from crystal_diffusion/utils/reproducibility_utils.py rename to src/diffusion_for_multi_scale_molecular_dynamics/utils/reproducibility_utils.py diff --git a/crystal_diffusion/utils/sample_trajectory.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/sample_trajectory.py similarity index 100% rename from crystal_diffusion/utils/sample_trajectory.py rename to src/diffusion_for_multi_scale_molecular_dynamics/utils/sample_trajectory.py diff --git a/crystal_diffusion/utils/structure_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/structure_utils.py similarity index 99% rename from crystal_diffusion/utils/structure_utils.py rename to src/diffusion_for_multi_scale_molecular_dynamics/utils/structure_utils.py index 125d6f9c..7426bff3 100644 --- a/crystal_diffusion/utils/structure_utils.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/utils/structure_utils.py @@ -2,12 +2,11 @@ import numpy as np import torch -from pykeops.torch import LazyTensor -from pymatgen.core import Lattice, Structure - from crystal_diffusion.utils.neighbors import ( _get_relative_coordinates_lattice_vectors, _get_shifted_positions, get_periodic_adjacency_information, get_positions_from_coordinates) +from pykeops.torch import LazyTensor +from pymatgen.core import Lattice, Structure def create_structure(basis_vectors: np.ndarray, relative_coordinates: np.ndarray, species: List[str]) -> Structure: diff --git a/crystal_diffusion/utils/tensor_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/tensor_utils.py similarity index 100% rename from crystal_diffusion/utils/tensor_utils.py rename to src/diffusion_for_multi_scale_molecular_dynamics/utils/tensor_utils.py diff --git a/tests/active_learning_loop/test_benchmark.py b/tests/active_learning_loop/test_benchmark.py index ac712970..b23579cc 100644 --- a/tests/active_learning_loop/test_benchmark.py +++ b/tests/active_learning_loop/test_benchmark.py @@ -3,7 +3,6 @@ import pandas as pd import pytest - from crystal_diffusion.active_learning_loop.benchmark import ActiveLearningLoop diff --git a/tests/analysis/test_ovito_visualisation.py b/tests/analysis/test_ovito_visualisation.py index 20377056..5be2d2de 100644 --- a/tests/analysis/test_ovito_visualisation.py +++ b/tests/analysis/test_ovito_visualisation.py @@ -4,9 +4,8 @@ import numpy as np import pandas as pd import pytest - -from crystal_diffusion.analysis.ovito_visualisation import ( - get_lattice_from_lammps, mtp_predictions_to_ovito) +from src.crystal_diffusion.analysis import (get_lattice_from_lammps, + mtp_predictions_to_ovito) class TestMTP2Ovito: diff --git a/tests/data/diffusion/test_data_loader.py b/tests/data/diffusion/test_data_loader.py index 893cf1a8..0297d3a2 100644 --- a/tests/data/diffusion/test_data_loader.py +++ b/tests/data/diffusion/test_data_loader.py @@ -3,11 +3,11 @@ import pytest import torch - from crystal_diffusion.data.diffusion.data_loader import ( LammpsForDiffusionDataModule, LammpsLoaderParameters) from crystal_diffusion.namespace import (CARTESIAN_FORCES, CARTESIAN_POSITIONS, RELATIVE_COORDINATES) + from tests.conftest import TestDiffusionDataBase from tests.fake_data_utils import Configuration, find_aligning_permutation diff --git a/tests/data/diffusion/test_data_preprocess.py b/tests/data/diffusion/test_data_preprocess.py index 0ca65cb0..b11f8830 100644 --- a/tests/data/diffusion/test_data_preprocess.py +++ b/tests/data/diffusion/test_data_preprocess.py @@ -3,11 +3,11 @@ import numpy as np import pandas as pd import pytest - from crystal_diffusion.data.diffusion.data_preprocess import \ LammpsProcessorForDiffusion from crystal_diffusion.namespace import (CARTESIAN_FORCES, CARTESIAN_POSITIONS, RELATIVE_COORDINATES) + from tests.conftest import TestDiffusionDataBase from tests.fake_data_utils import generate_parquet_dataframe diff --git a/tests/data/test_parse_lammps_output.py b/tests/data/test_parse_lammps_output.py index 5cabbaa0..605ba613 100644 --- a/tests/data/test_parse_lammps_output.py +++ b/tests/data/test_parse_lammps_output.py @@ -4,9 +4,9 @@ import pandas as pd import pytest import yaml - -from crystal_diffusion.data.parse_lammps_outputs import ( +from src.crystal_diffusion.data.parse_lammps_outputs import ( parse_lammps_dump, parse_lammps_output, parse_lammps_thermo_log) + from tests.fake_data_utils import (create_dump_yaml_documents, generate_fake_configuration, generate_parse_dump_output_dataframe, diff --git a/tests/data/test_utils.py b/tests/data/test_utils.py index c7fdf7d7..03668987 100644 --- a/tests/data/test_utils.py +++ b/tests/data/test_utils.py @@ -1,6 +1,5 @@ import pytest import yaml - from crystal_diffusion.data.utils import crop_lammps_yaml # Sample data for dump and thermo YAML files diff --git a/tests/fake_data_utils.py b/tests/fake_data_utils.py index 0a560f3e..390d6a96 100644 --- a/tests/fake_data_utils.py +++ b/tests/fake_data_utils.py @@ -5,7 +5,6 @@ import pandas as pd import torch import yaml - from crystal_diffusion.namespace import (CARTESIAN_FORCES, CARTESIAN_POSITIONS, RELATIVE_COORDINATES) diff --git a/tests/generators/conftest.py b/tests/generators/conftest.py index 051894be..bed0b402 100644 --- a/tests/generators/conftest.py +++ b/tests/generators/conftest.py @@ -2,7 +2,6 @@ import pytest import torch - from crystal_diffusion.models.score_networks import (ScoreNetwork, ScoreNetworkParameters) from crystal_diffusion.namespace import NOISY_RELATIVE_COORDINATES diff --git a/tests/generators/test_constrained_langevin_generator.py b/tests/generators/test_constrained_langevin_generator.py index 725dd09b..035634f4 100644 --- a/tests/generators/test_constrained_langevin_generator.py +++ b/tests/generators/test_constrained_langevin_generator.py @@ -1,9 +1,9 @@ import einops import pytest import torch - from crystal_diffusion.generators.constrained_langevin_generator import ( ConstrainedLangevinGenerator, ConstrainedLangevinGeneratorParameters) + from tests.generators.test_langevin_generator import TestLangevinGenerator diff --git a/tests/generators/test_langevin_generator.py b/tests/generators/test_langevin_generator.py index c5b5245c..18e2190f 100644 --- a/tests/generators/test_langevin_generator.py +++ b/tests/generators/test_langevin_generator.py @@ -1,13 +1,13 @@ import pytest import torch - from crystal_diffusion.generators.langevin_generator import LangevinGenerator from crystal_diffusion.generators.predictor_corrector_position_generator import \ PredictorCorrectorSamplingParameters -from crystal_diffusion.samplers.variance_sampler import ( - ExplodingVarianceSampler, NoiseParameters) from crystal_diffusion.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell +from src.crystal_diffusion.samplers.variance_sampler import ( + ExplodingVarianceSampler, NoiseParameters) + from tests.generators.conftest import BaseTestGenerator diff --git a/tests/generators/test_ode_position_generator.py b/tests/generators/test_ode_position_generator.py index c9a13f4e..1564aba7 100644 --- a/tests/generators/test_ode_position_generator.py +++ b/tests/generators/test_ode_position_generator.py @@ -1,10 +1,10 @@ import pytest import torch - from crystal_diffusion.generators.ode_position_generator import ( ExplodingVarianceODEPositionGenerator, ODESamplingParameters) -from crystal_diffusion.samplers.variance_sampler import ( +from src.crystal_diffusion.samplers.variance_sampler import ( ExplodingVarianceSampler, NoiseParameters) + from tests.generators.conftest import BaseTestGenerator diff --git a/tests/generators/test_predictor_corrector_position_generator.py b/tests/generators/test_predictor_corrector_position_generator.py index 17eb789b..b8ada474 100644 --- a/tests/generators/test_predictor_corrector_position_generator.py +++ b/tests/generators/test_predictor_corrector_position_generator.py @@ -1,10 +1,10 @@ import pytest import torch - from crystal_diffusion.generators.predictor_corrector_position_generator import \ PredictorCorrectorPositionGenerator from crystal_diffusion.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell + from tests.generators.conftest import BaseTestGenerator diff --git a/tests/generators/test_sde_position_generator.py b/tests/generators/test_sde_position_generator.py index 03b78142..7950e90d 100644 --- a/tests/generators/test_sde_position_generator.py +++ b/tests/generators/test_sde_position_generator.py @@ -1,10 +1,10 @@ import pytest import torch - from crystal_diffusion.generators.sde_position_generator import ( SDE, ExplodingVarianceSDEPositionGenerator, SDESamplingParameters) -from crystal_diffusion.samplers.variance_sampler import ( +from src.crystal_diffusion.samplers.variance_sampler import ( ExplodingVarianceSampler, NoiseParameters) + from tests.generators.conftest import BaseTestGenerator diff --git a/tests/models/score_network/test_force_field_augmented_score_network.py b/tests/models/score_network/test_force_field_augmented_score_network.py index da973530..498b039b 100644 --- a/tests/models/score_network/test_force_field_augmented_score_network.py +++ b/tests/models/score_network/test_force_field_augmented_score_network.py @@ -1,6 +1,5 @@ import pytest import torch - from crystal_diffusion.models.score_networks.force_field_augmented_score_network import ( ForceFieldAugmentedScoreNetwork, ForceFieldParameters) from crystal_diffusion.models.score_networks.mlp_score_network import ( diff --git a/tests/models/score_network/test_score_network.py b/tests/models/score_network/test_score_network.py index fcb54d0c..d9a60e94 100644 --- a/tests/models/score_network/test_score_network.py +++ b/tests/models/score_network/test_score_network.py @@ -6,7 +6,6 @@ import numpy as np import pytest import torch - from crystal_diffusion.models.score_networks.diffusion_mace_score_network import ( DiffusionMACEScoreNetwork, DiffusionMACEScoreNetworkParameters) from crystal_diffusion.models.score_networks.egnn_score_network import ( diff --git a/tests/models/score_network/test_score_prediction_head.py b/tests/models/score_network/test_score_prediction_head.py index 6ad54083..447cf336 100644 --- a/tests/models/score_network/test_score_prediction_head.py +++ b/tests/models/score_network/test_score_prediction_head.py @@ -1,12 +1,11 @@ import pytest import torch -from e3nn import o3 - from crystal_diffusion.models.mace_utils import \ build_mace_output_nodes_irreducible_representation from crystal_diffusion.models.score_networks.score_prediction_head import ( MaceEquivariantScorePredictionHead, MaceEquivariantScorePredictionHeadParameters) +from e3nn import o3 class TestMaceEquivariantScorePredictionHead: diff --git a/tests/models/test_analytical_score_network.py b/tests/models/test_analytical_score_network.py index 7a5cdf45..7a14d3e4 100644 --- a/tests/models/test_analytical_score_network.py +++ b/tests/models/test_analytical_score_network.py @@ -2,7 +2,6 @@ import pytest import torch - from crystal_diffusion.models.score_networks.analytical_score_network import ( AnalyticalScoreNetwork, AnalyticalScoreNetworkParameters, TargetScoreBasedAnalyticalScoreNetwork) diff --git a/tests/models/test_diffusion_mace.py b/tests/models/test_diffusion_mace.py index b57d8394..db03e61b 100644 --- a/tests/models/test_diffusion_mace.py +++ b/tests/models/test_diffusion_mace.py @@ -1,8 +1,5 @@ import pytest import torch -from e3nn import o3 -from mace.modules import gate_dict, interaction_classes - from crystal_diffusion.models.diffusion_mace import (DiffusionMACE, LinearVectorReadoutBlock, input_to_diffusion_mace) @@ -14,6 +11,8 @@ get_positions_from_coordinates, get_reciprocal_basis_vectors, get_relative_coordinates_from_cartesian_positions, map_relative_coordinates_to_unit_cell) +from e3nn import o3 +from mace.modules import gate_dict, interaction_classes def test_linear_vector_readout_block(): diff --git a/tests/models/test_egnn.py b/tests/models/test_egnn.py index 98d615ea..c7e11a67 100644 --- a/tests/models/test_egnn.py +++ b/tests/models/test_egnn.py @@ -3,7 +3,6 @@ import pytest import torch - from crystal_diffusion.models.egnn import E_GCL, EGNN diff --git a/tests/models/test_egnn_utils.py b/tests/models/test_egnn_utils.py index 81f58776..8dc211ff 100644 --- a/tests/models/test_egnn_utils.py +++ b/tests/models/test_egnn_utils.py @@ -1,6 +1,5 @@ import pytest import torch - from crystal_diffusion.models.egnn_utils import (unsorted_segment_mean, unsorted_segment_sum) diff --git a/tests/models/test_loss.py b/tests/models/test_loss.py index 42ea6063..674ee48e 100644 --- a/tests/models/test_loss.py +++ b/tests/models/test_loss.py @@ -1,6 +1,5 @@ import pytest import torch - from crystal_diffusion.models.loss import (MSELossParameters, WeightedMSELossParameters, create_loss_calculator) diff --git a/tests/models/test_mace_utils.py b/tests/models/test_mace_utils.py index ecf5613a..71c50751 100644 --- a/tests/models/test_mace_utils.py +++ b/tests/models/test_mace_utils.py @@ -4,17 +4,17 @@ import numpy as np import pytest import torch -from e3nn import o3 -from mace.data import AtomicData, Configuration -from mace.tools import get_atomic_number_table_from_zs -from mace.tools.torch_geometric.dataloader import Collater - from crystal_diffusion.models.mace_utils import ( get_normalized_irreps_permutation_indices, get_pretrained_mace, input_to_mace, reshape_from_e3nn_to_mace, reshape_from_mace_to_e3nn) from crystal_diffusion.namespace import NOISY_CARTESIAN_POSITIONS, UNIT_CELL from crystal_diffusion.utils.basis_transformations import \ get_positions_from_coordinates +from e3nn import o3 +from mace.data import AtomicData, Configuration +from mace.tools import get_atomic_number_table_from_zs +from mace.tools.torch_geometric.dataloader import Collater + from tests.fake_data_utils import find_aligning_permutation diff --git a/tests/models/test_mtp.py b/tests/models/test_mtp.py index 13a661ae..0b31c655 100644 --- a/tests/models/test_mtp.py +++ b/tests/models/test_mtp.py @@ -5,14 +5,13 @@ import pandas as pd import pytest import yaml -from pymatgen.core import Structure -from sklearn.metrics import mean_absolute_error - from crystal_diffusion.mlip.mtp_utils import ( MTPInputs, extract_energy_from_thermo_log, extract_structure_and_forces_from_file, get_metrics_from_pred, prepare_mtp_inputs_from_lammps) from crystal_diffusion.models.mlip.mtp import MTPArguments, MTPWithMLIP3 +from pymatgen.core import Structure +from sklearn.metrics import mean_absolute_error class FakeStructure: diff --git a/tests/models/test_optimizer.py b/tests/models/test_optimizer.py index 3f3ec34c..11e3e98b 100644 --- a/tests/models/test_optimizer.py +++ b/tests/models/test_optimizer.py @@ -1,6 +1,5 @@ import pytest import torch - from crystal_diffusion.models.optimizer import (OptimizerParameters, load_optimizer) diff --git a/tests/models/test_position_diffusion_lightning_model.py b/tests/models/test_position_diffusion_lightning_model.py index 2700ee34..6d3bd49c 100644 --- a/tests/models/test_position_diffusion_lightning_model.py +++ b/tests/models/test_position_diffusion_lightning_model.py @@ -1,8 +1,5 @@ import pytest import torch -from pytorch_lightning import LightningDataModule, Trainer -from torch.utils.data import DataLoader, random_split - from crystal_diffusion.generators.predictor_corrector_position_generator import \ PredictorCorrectorSamplingParameters from crystal_diffusion.metrics.sampling_metrics_parameters import \ @@ -16,13 +13,15 @@ from crystal_diffusion.models.score_networks.mlp_score_network import \ MLPScoreNetworkParameters from crystal_diffusion.namespace import CARTESIAN_FORCES, RELATIVE_COORDINATES -from crystal_diffusion.samplers.variance_sampler import NoiseParameters from crystal_diffusion.samples.diffusion_sampling_parameters import \ DiffusionSamplingParameters from crystal_diffusion.score.wrapped_gaussian_score import \ get_sigma_normalized_score_brute_force from crystal_diffusion.utils.tensor_utils import \ broadcast_batch_tensor_to_all_dimensions +from pytorch_lightning import LightningDataModule, Trainer +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters +from torch.utils.data import DataLoader, random_split class FakePositionsDataModule(LightningDataModule): diff --git a/tests/models/test_scheduler.py b/tests/models/test_scheduler.py index 791ec753..57b141eb 100644 --- a/tests/models/test_scheduler.py +++ b/tests/models/test_scheduler.py @@ -1,6 +1,5 @@ import pytest import torch - from crystal_diffusion.models.optimizer import (OptimizerParameters, load_optimizer) from crystal_diffusion.models.scheduler import ( diff --git a/tests/models/test_score_fokker_planck_error.py b/tests/models/test_score_fokker_planck_error.py index cca17b8e..672d5a90 100644 --- a/tests/models/test_score_fokker_planck_error.py +++ b/tests/models/test_score_fokker_planck_error.py @@ -3,9 +3,6 @@ import einops import pytest import torch - -from crystal_diffusion.models.normalized_score_fokker_planck_error import \ - NormalizedScoreFokkerPlanckError from crystal_diffusion.models.score_networks.egnn_score_network import \ EGNNScoreNetworkParameters from crystal_diffusion.models.score_networks.score_network_factory import \ @@ -13,7 +10,9 @@ from crystal_diffusion.namespace import (NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) from crystal_diffusion.samplers.exploding_variance import ExplodingVariance -from crystal_diffusion.samplers.variance_sampler import NoiseParameters +from src.crystal_diffusion.models.normalized_score_fokker_planck_error import \ + NormalizedScoreFokkerPlanckError +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters def get_finite_difference_time_derivative( diff --git a/tests/oracle/test_lammps.py b/tests/oracle/test_lammps.py index fb29bebe..cb342d90 100644 --- a/tests/oracle/test_lammps.py +++ b/tests/oracle/test_lammps.py @@ -1,6 +1,5 @@ import numpy as np import pytest - from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps diff --git a/tests/samplers/test_exploding_variance.py b/tests/samplers/test_exploding_variance.py index 3b317304..551471fb 100644 --- a/tests/samplers/test_exploding_variance.py +++ b/tests/samplers/test_exploding_variance.py @@ -1,8 +1,7 @@ import pytest import torch - from crystal_diffusion.samplers.exploding_variance import ExplodingVariance -from crystal_diffusion.samplers.variance_sampler import NoiseParameters +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters class TestExplodingVariance: diff --git a/tests/samplers/test_noisy_relative_coordinates_sampler.py b/tests/samplers/test_noisy_relative_coordinates_sampler.py index 48e9f7c0..a087b8c6 100644 --- a/tests/samplers/test_noisy_relative_coordinates_sampler.py +++ b/tests/samplers/test_noisy_relative_coordinates_sampler.py @@ -1,7 +1,6 @@ import numpy as np import pytest import torch - from crystal_diffusion.samplers.noisy_relative_coordinates_sampler import \ NoisyRelativeCoordinatesSampler diff --git a/tests/samplers/test_variance_sampler.py b/tests/samplers/test_variance_sampler.py index 4aaa048f..50779272 100644 --- a/tests/samplers/test_variance_sampler.py +++ b/tests/samplers/test_variance_sampler.py @@ -1,7 +1,6 @@ import pytest import torch - -from crystal_diffusion.samplers.variance_sampler import ( +from src.crystal_diffusion.samplers.variance_sampler import ( ExplodingVarianceSampler, NoiseParameters) diff --git a/tests/samples_and_metrics/test_sampling.py b/tests/samples_and_metrics/test_sampling.py index 02260924..fb939520 100644 --- a/tests/samples_and_metrics/test_sampling.py +++ b/tests/samples_and_metrics/test_sampling.py @@ -1,14 +1,13 @@ import einops import pytest import torch - -from crystal_diffusion.generators.position_generator import ( - PositionGenerator, SamplingParameters) from crystal_diffusion.namespace import (CARTESIAN_POSITIONS, RELATIVE_COORDINATES, UNIT_CELL) -from crystal_diffusion.samples.sampling import create_batch_of_samples from crystal_diffusion.utils.basis_transformations import \ get_positions_from_coordinates +from src.crystal_diffusion.generators.position_generator import ( + PositionGenerator, SamplingParameters) +from src.crystal_diffusion.samples.sampling import create_batch_of_samples class DummyGenerator(PositionGenerator): diff --git a/tests/score/test_wrapped_gaussian_score.py b/tests/score/test_wrapped_gaussian_score.py index e2a19dc5..0787bede 100644 --- a/tests/score/test_wrapped_gaussian_score.py +++ b/tests/score/test_wrapped_gaussian_score.py @@ -1,7 +1,6 @@ import numpy as np import pytest import torch - from crystal_diffusion.score.wrapped_gaussian_score import ( SIGMA_THRESHOLD, _get_large_sigma_mask, _get_s1a_exponential, _get_s1b_exponential, _get_sigma_normalized_s2, diff --git a/tests/test_hp_utils.py b/tests/test_hp_utils.py index e964d3e0..d35c8b71 100644 --- a/tests/test_hp_utils.py +++ b/tests/test_hp_utils.py @@ -1,5 +1,4 @@ import pytest - from crystal_diffusion.utils.hp_utils import check_hp diff --git a/tests/test_sample_diffusion.py b/tests/test_sample_diffusion.py index aa652e40..56c6e07a 100644 --- a/tests/test_sample_diffusion.py +++ b/tests/test_sample_diffusion.py @@ -3,7 +3,6 @@ import pytest import torch import yaml - from crystal_diffusion import sample_diffusion from crystal_diffusion.generators.predictor_corrector_position_generator import \ PredictorCorrectorSamplingParameters @@ -14,7 +13,7 @@ from crystal_diffusion.models.score_networks.mlp_score_network import \ MLPScoreNetworkParameters from crystal_diffusion.namespace import RELATIVE_COORDINATES -from crystal_diffusion.samplers.variance_sampler import NoiseParameters +from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters @pytest.fixture() diff --git a/tests/test_train_diffusion.py b/tests/test_train_diffusion.py index 0df62750..e7866b6f 100644 --- a/tests/test_train_diffusion.py +++ b/tests/test_train_diffusion.py @@ -12,10 +12,10 @@ import numpy as np import pytest import yaml - from crystal_diffusion import train_diffusion from crystal_diffusion.callbacks.standard_callbacks import (BEST_MODEL_NAME, LAST_MODEL_NAME) + from tests.conftest import TestDiffusionDataBase best_model_regex = re.compile(r"best_model-epoch=(?P(\d+)).*.ckpt") diff --git a/tests/utils/test_basis_transformations.py b/tests/utils/test_basis_transformations.py index 3b84199c..62541103 100644 --- a/tests/utils/test_basis_transformations.py +++ b/tests/utils/test_basis_transformations.py @@ -1,6 +1,5 @@ import pytest import torch - from crystal_diffusion.utils.basis_transformations import ( get_positions_from_coordinates, get_reciprocal_basis_vectors, get_relative_coordinates_from_cartesian_positions, diff --git a/tests/utils/test_configuration_parsing.py b/tests/utils/test_configuration_parsing.py index aea8fc71..c72fc92c 100644 --- a/tests/utils/test_configuration_parsing.py +++ b/tests/utils/test_configuration_parsing.py @@ -1,7 +1,6 @@ from dataclasses import asdict, dataclass import pytest - from crystal_diffusion.utils.configuration_parsing import \ create_parameters_from_configuration_dictionary diff --git a/tests/utils/test_neighbors.py b/tests/utils/test_neighbors.py index 10529c72..aee6dc0e 100644 --- a/tests/utils/test_neighbors.py +++ b/tests/utils/test_neighbors.py @@ -3,8 +3,6 @@ import numpy as np import pytest import torch -from pymatgen.core import Lattice, Structure - from crystal_diffusion.utils.basis_transformations import \ get_positions_from_coordinates from crystal_diffusion.utils.neighbors import ( @@ -12,6 +10,8 @@ _get_shifted_positions, _get_shortest_distance_that_crosses_unit_cell, _get_vectors_from_multiple_indices, get_periodic_adjacency_information, shift_adjacency_matrix_indices_for_graph_batching) +from pymatgen.core import Lattice, Structure + from tests.fake_data_utils import find_aligning_permutation Neighbors = namedtuple("Neighbors", ["source_index", "destination_index", "displacement", "shift"]) diff --git a/tests/utils/test_sample_trajectory.py b/tests/utils/test_sample_trajectory.py index ab6e7a97..bef3e266 100644 --- a/tests/utils/test_sample_trajectory.py +++ b/tests/utils/test_sample_trajectory.py @@ -3,7 +3,6 @@ import einops import pytest import torch - from crystal_diffusion.utils.sample_trajectory import \ PredictorCorrectorSampleTrajectory diff --git a/tests/utils/test_structure_utils.py b/tests/utils/test_structure_utils.py index 1f366968..d73cab76 100644 --- a/tests/utils/test_structure_utils.py +++ b/tests/utils/test_structure_utils.py @@ -1,6 +1,5 @@ import pytest import torch - from crystal_diffusion.utils.basis_transformations import \ get_positions_from_coordinates from crystal_diffusion.utils.structure_utils import ( diff --git a/tests/utils/test_tensor_utils.py b/tests/utils/test_tensor_utils.py index 5a07b452..dee8184c 100644 --- a/tests/utils/test_tensor_utils.py +++ b/tests/utils/test_tensor_utils.py @@ -1,6 +1,5 @@ import pytest import torch - from crystal_diffusion.utils.tensor_utils import \ broadcast_batch_tensor_to_all_dimensions From 852ed01a8b789ce93a82e40de5bc75a846e5f8d4 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Mon, 21 Oct 2024 21:43:41 -0400 Subject: [PATCH 19/29] Cleaned up imports --- experiments/analysis_utils.py | 6 +- .../__init__.py | 2 +- .../activelearning_dataclasses.py | 7 +- .../active_learning_loop/benchmark.py | 182 ++++++---- .../active_learning_loop/oracle.py | 39 ++- .../active_learning_loop/utils.py | 56 ++-- .../generator_sample_analysis_utils.py | 14 +- .../analysis/ovito_visualisation.py | 53 ++- .../callbacks/callback_loader.py | 31 +- .../callbacks/loss_monitoring_callback.py | 116 ++++--- .../sampling_visualization_callback.py | 8 +- .../callbacks/standard_callbacks.py | 32 +- .../data/diffusion/data_loader.py | 131 +++++--- .../data/diffusion/data_preprocess.py | 109 ++++-- .../data/parse_lammps_outputs.py | 64 ++-- .../data/utils.py | 24 +- .../constrained_langevin_generator.py | 108 +++--- .../generators/instantiate_generator.py | 56 ++-- .../generators/langevin_generator.py | 135 +++++--- .../generators/load_sampling_parameters.py | 37 ++- .../generators/ode_position_generator.py | 190 +++++++---- .../generators/position_generator.py | 17 +- .../predictor_corrector_position_generator.py | 72 ++-- .../generators/sde_position_generator.py | 248 +++++++++----- .../loggers/logger_loader.py | 84 +++-- .../main_utils.py | 92 +++-- .../metrics/kolmogorov_smirnov_metrics.py | 9 +- .../metrics/sampling_metrics_parameters.py | 5 +- .../mlip/mtp_train.py | 80 +++-- .../mlip/mtp_utils.py | 127 ++++--- .../models/diffusion_mace.py | 204 ++++++++---- .../models/egnn.py | 84 +++-- .../models/egnn_utils.py | 46 ++- .../models/graph_utils.py | 19 +- .../models/instantiate_diffusion_model.py | 43 ++- .../models/loss.py | 88 +++-- .../models/mace_utils.py | 106 ++++-- .../models/mlip/mtp.py | 163 +++++---- .../normalized_score_fokker_planck_error.py | 21 +- .../models/optimizer.py | 32 +- .../position_diffusion_lightning_model.py | 50 +-- .../models/scheduler.py | 5 +- .../models/score_networks/__init__.py | 2 +- .../analytical_score_network.py | 156 ++++++--- .../diffusion_mace_score_network.py | 74 +++-- .../score_networks/egnn_score_network.py | 84 +++-- .../force_field_augmented_score_network.py | 16 +- .../score_networks/mace_score_network.py | 96 ++++-- .../score_networks/mlp_score_network.py | 55 ++- .../models/score_networks/score_network.py | 95 ++++-- .../score_networks/score_network_factory.py | 99 +++--- .../score_networks/score_prediction_head.py | 97 ++++-- .../namespace.py | 12 +- .../oracle/__init__.py | 0 .../oracle/energies.py | 24 +- .../oracle/lammps.py | 64 ++-- .../sample_diffusion.py | 31 +- .../samplers/exploding_variance.py | 22 +- .../noisy_relative_coordinates_sampler.py | 23 +- .../samplers/variance_sampler.py | 71 ++-- .../samples/diffusion_sampling_parameters.py | 58 ++-- .../samples/sampling.py | 47 ++- .../score/wrapped_gaussian_score.py | 57 +++- .../train_diffusion.py | 150 +++++---- .../utils/basis_transformations.py | 15 +- .../utils/configuration_parsing.py | 16 +- .../utils/file_utils.py | 2 +- .../utils/hp_utils.py | 8 +- .../utils/logging_utils.py | 32 +- .../utils/neighbors.py | 158 ++++++--- .../utils/ovito_utils.py | 1 + .../utils/sample_trajectory.py | 199 +++++++---- .../utils/structure_utils.py | 84 +++-- .../utils/tensor_utils.py | 12 +- tests/active_learning_loop/test_benchmark.py | 57 ++-- tests/analysis/test_ovito_visualisation.py | 42 +-- tests/conftest.py | 47 ++- tests/data/diffusion/test_data_loader.py | 176 ++++++---- tests/data/diffusion/test_data_preprocess.py | 114 +++++-- tests/data/test_parse_lammps_output.py | 80 +++-- tests/data/test_utils.py | 6 +- tests/fake_data_utils.py | 197 +++++++---- tests/generators/conftest.py | 22 +- .../test_constrained_langevin_generator.py | 45 ++- tests/generators/test_langevin_generator.py | 115 +++++-- .../generators/test_ode_position_generator.py | 77 +++-- ..._predictor_corrector_position_generator.py | 57 +++- .../generators/test_sde_position_generator.py | 133 +++++--- ...est_force_field_augmented_score_network.py | 13 +- .../score_network/test_score_network.py | 313 ++++++++++++------ .../test_score_prediction_head.py | 34 +- tests/models/test_analytical_score_network.py | 114 +++++-- tests/models/test_diffusion_mace.py | 291 ++++++++++------ tests/models/test_egnn.py | 191 +++++++---- tests/models/test_egnn_utils.py | 17 +- tests/models/test_loss.py | 61 ++-- tests/models/test_mace_utils.py | 212 ++++++++---- tests/models/test_mtp.py | 191 +++++++---- tests/models/test_optimizer.py | 12 +- ...test_position_diffusion_lightning_model.py | 133 +++++--- tests/models/test_scheduler.py | 30 +- .../models/test_score_fokker_planck_error.py | 17 +- tests/oracle/test_lammps.py | 22 +- tests/samplers/test_exploding_variance.py | 19 +- ...test_noisy_relative_coordinates_sampler.py | 9 +- tests/samplers/test_variance_sampler.py | 44 ++- tests/samples_and_metrics/test_sampling.py | 16 +- tests/score/test_wrapped_gaussian_score.py | 23 +- tests/test_hp_utils.py | 20 +- tests/test_sample_diffusion.py | 23 +- tests/test_train_diffusion.py | 291 +++++++++------- tests/utils/test_basis_transformations.py | 52 ++- tests/utils/test_configuration_parsing.py | 28 +- tests/utils/test_neighbors.py | 204 ++++++++---- tests/utils/test_sample_trajectory.py | 235 ++++++++----- tests/utils/test_structure_utils.py | 5 +- tests/utils/test_tensor_utils.py | 11 +- 117 files changed, 5772 insertions(+), 2882 deletions(-) create mode 100644 src/diffusion_for_multi_scale_molecular_dynamics/oracle/__init__.py diff --git a/experiments/analysis_utils.py b/experiments/analysis_utils.py index c3452bee..3f92569f 100644 --- a/experiments/analysis_utils.py +++ b/experiments/analysis_utils.py @@ -4,10 +4,10 @@ from typing import Tuple import pandas as pd -from crystal_diffusion import DATA_DIR -from src.crystal_diffusion.data.parse_lammps_outputs import \ - parse_lammps_thermo_log +from diffusion_for_multi_scale_molecular_dynamics import DATA_DIR +from diffusion_for_multi_scale_molecular_dynamics.data.parse_lammps_outputs import \ + parse_lammps_thermo_log from experiments import EXPERIMENT_ANALYSIS_DIR logger = logging.getLogger(__name__) diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/__init__.py b/src/diffusion_for_multi_scale_molecular_dynamics/__init__.py index 7c38fcbb..babbfa2c 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/__init__.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/__init__.py @@ -1,6 +1,6 @@ from pathlib import Path -ROOT_DIR = Path(__file__).parent +ROOT_DIR = Path(__file__).parent.parent TOP_DIR = ROOT_DIR.parent ANALYSIS_RESULTS_DIR = TOP_DIR.joinpath("analysis_results/") ANALYSIS_RESULTS_DIR.mkdir(exist_ok=True) diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/activelearning_dataclasses.py b/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/activelearning_dataclasses.py index c5333d3c..cdb4e34b 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/activelearning_dataclasses.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/activelearning_dataclasses.py @@ -4,6 +4,7 @@ @dataclass(kw_only=True) class ActiveLearningDataArguments: """Paths to the training, validaition datasets and output directory.""" + training_data_dir: str # training data directory evaluation_data_dir: str # evaluation data directory output_dir: str # directory where to save the results @@ -12,7 +13,8 @@ class ActiveLearningDataArguments: @dataclass(kw_only=True) class StructureEvaluationArguments: """Parameters related to the MLIP evaluation.""" - evaluation_criteria: str = 'nbh_grades' + + evaluation_criteria: str = "nbh_grades" criteria_threshold: float = 10 number_of_structures: int = None extraction_radius: float = 3 @@ -21,4 +23,5 @@ class StructureEvaluationArguments: @dataclass(kw_only=True) class RepaintingArguments: """Parameters related to the structure generation model.""" - model: str = 'dev_dummy' + + model: str = "dev_dummy" diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/benchmark.py b/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/benchmark.py index de3773af..1ae2ee03 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/benchmark.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/benchmark.py @@ -5,17 +5,21 @@ import numpy as np import pandas as pd import yaml -from crystal_diffusion.active_learning_loop.utils import ( - extract_target_region, get_structures_for_retraining) -from crystal_diffusion.models.mlip.mtp import MTPWithMLIP3 from hydra.utils import instantiate +from diffusion_for_multi_scale_molecular_dynamics.active_learning_loop.utils import ( + extract_target_region, get_structures_for_retraining) +from diffusion_for_multi_scale_molecular_dynamics.models.mlip.mtp import \ + MTPWithMLIP3 + class ActiveLearningLoop: """Method to train, evaluate and fine-tune a MLIP.""" - def __init__(self, - meta_config: str, - ): + + def __init__( + self, + meta_config: str, + ): """Active learning benchmark. Includes methods to train & evaluate a MLIP, isolate bad sub-structures, repaint new structures and retrain @@ -24,14 +28,23 @@ def __init__(self, Args: meta_config: path to a yaml configuration with the parameters for the modules in the class """ - assert os.path.exists(meta_config), "configuration file for active learning loop does not exist." + assert os.path.exists( + meta_config + ), "configuration file for active learning loop does not exist." # define the modules in the __init__ function - self.data_paths, self.mlip_model, self.eval_config, self.structure_generation = None, None, None, None + ( + self.data_paths, + self.mlip_model, + self.eval_config, + self.structure_generation, + ) = (None, None, None, None) self.oracle = None # use hydra to convert the yaml into modules and other data classes self.parse_config(meta_config) self.atom_dict = {1: "Si"} # TODO this should be define somewhere smart - self.trained_mlips = [] # history of trained MLIPs (optional - not sure if we should keep this) + self.trained_mlips = ( + [] + ) # history of trained MLIPs (optional - not sure if we should keep this) self.training_sets = [] # history of training sets def parse_config(self, meta_config: str): @@ -45,18 +58,18 @@ def parse_config(self, meta_config: str): Args: meta_config: path to configuration yaml file """ - with open(meta_config, 'r') as stream: + with open(meta_config, "r") as stream: meta_config = yaml.load(stream, Loader=yaml.FullLoader) # paths to the training & evaluation datasets - self.data_paths = instantiate(meta_config['active_learning_data']) + self.data_paths = instantiate(meta_config["active_learning_data"]) # MLIP model - for example MTP - self.mlip_model = instantiate(meta_config['mlip']) + self.mlip_model = instantiate(meta_config["mlip"]) # parameters to find and isolate the problematic regions in the evaluation dataset - self.eval_config = instantiate(meta_config['structure_evaluation']) + self.eval_config = instantiate(meta_config["structure_evaluation"]) # structure generation module - self.structure_generation = instantiate(meta_config['repainting_model']) + self.structure_generation = instantiate(meta_config["repainting_model"]) # force labeling module - self.oracle = instantiate(meta_config['oracle']) + self.oracle = instantiate(meta_config["oracle"]) def train_mlip(self, round: int = 1, training_set: Optional[Any] = None) -> str: """Train a MLIP using the parameters specified in the configuration file. @@ -72,19 +85,29 @@ def train_mlip(self, round: int = 1, training_set: Optional[Any] = None) -> str: """ if training_set is None: if len(self.training_sets) == 0: - self.training_sets = [self.mlip_model.prepare_dataset_from_lammps( - root_data_dir=self.data_paths.training_data_dir, - atom_dict=self.atom_dict, - mode="train" - )] + self.training_sets = [ + self.mlip_model.prepare_dataset_from_lammps( + root_data_dir=self.data_paths.training_data_dir, + atom_dict=self.atom_dict, + mode="train", + ) + ] training_set = self.mlip_model.merge_inputs(self.training_sets) - trained_mtp = self.mlip_model.train(training_set, mlip_name=f'mlip_round_{round}') - self.trained_mlips.append(trained_mtp) # history of trained MLIPs ... not sure if useful + trained_mtp = self.mlip_model.train( + training_set, mlip_name=f"mlip_round_{round}" + ) + self.trained_mlips.append( + trained_mtp + ) # history of trained MLIPs ... not sure if useful return trained_mtp - def evaluate_mlip(self, round: int = 1, mlip_name: Optional[str] = None, forces_available: bool = True - ) -> pd.DataFrame: + def evaluate_mlip( + self, + round: int = 1, + mlip_name: Optional[str] = None, + forces_available: bool = True, + ) -> pd.DataFrame: """Evaluate a MLIP using the parameters specified in the configuration file. Args: @@ -99,13 +122,17 @@ def evaluate_mlip(self, round: int = 1, mlip_name: Optional[str] = None, forces_ root_data_dir=self.data_paths.evaluation_data_dir, atom_dict=self.atom_dict, mode="evaluation", - get_forces=forces_available + get_forces=forces_available, ) # first returned element is the ground truth DF # TODO make sure this works even if the GT is not available... if mlip_name is None: - mlip_name = os.path.join(self.mlip_model.savedir, f'mlip_round_{round}.almtp') - _, prediction_df = self.mlip_model.evaluate(evaluation_dataset, mlip_name=mlip_name) + mlip_name = os.path.join( + self.mlip_model.savedir, f"mlip_round_{round}.almtp" + ) + _, prediction_df = self.mlip_model.evaluate( + evaluation_dataset, mlip_name=mlip_name + ) return prediction_df @@ -120,13 +147,17 @@ def get_bad_structures(self, prediction_df: pd.DataFrame) -> List[pd.DataFrame]: list of structures with a high uncertainty criteria. """ num_structures = self.eval_config.number_of_structures - structures_to_retrain = get_structures_for_retraining(prediction_df, - criteria_threshold=self.eval_config.criteria_threshold, - number_of_structures=num_structures, - evaluation_criteria=self.eval_config.evaluation_criteria) + structures_to_retrain = get_structures_for_retraining( + prediction_df, + criteria_threshold=self.eval_config.criteria_threshold, + number_of_structures=num_structures, + evaluation_criteria=self.eval_config.evaluation_criteria, + ) return structures_to_retrain - def excise_worst_atom(self, structures_to_retrain: List[pd.DataFrame]) -> List[pd.DataFrame]: + def excise_worst_atom( + self, structures_to_retrain: List[pd.DataFrame] + ) -> List[pd.DataFrame]: """For a given structure, isolate the atom with the highest uncertainty criteria. Args: @@ -138,16 +169,19 @@ def excise_worst_atom(self, structures_to_retrain: List[pd.DataFrame]) -> List[p # we assume the extraction region to be a sphere of radius extraction_radius around the worst atoms # if more than 1 atom are bad in a structure, we only extract the worst # TODO implement other extraction methods - bad_regions = [extract_target_region(s, - extraction_radius=self.eval_config.extraction_radius, - evaluation_criteria=self.eval_config.evaluation_criteria) - for s in structures_to_retrain] + bad_regions = [ + extract_target_region( + s, + extraction_radius=self.eval_config.extraction_radius, + evaluation_criteria=self.eval_config.evaluation_criteria, + ) + for s in structures_to_retrain + ] return bad_regions - def get_structure_candidate_from_generative_model(self, - fixed_atoms: pd.DataFrame, - number_of_candidates: int = 1 - ) -> pd.DataFrame: + def get_structure_candidate_from_generative_model( + self, fixed_atoms: pd.DataFrame, number_of_candidates: int = 1 + ) -> pd.DataFrame: """Generate new structures around the specified fixed atoms. Args: @@ -159,11 +193,13 @@ def get_structure_candidate_from_generative_model(self, """ # TODO: call the diffusion model and get number_of_candidates samples with repaint using the fixed_atoms - if self.structure_generation.model == 'dev_dummy': # replace with a wrapper around the diffusion model + if ( + self.structure_generation.model == "dev_dummy" + ): # replace with a wrapper around the diffusion model # and hydra instantiate return fixed_atoms else: - raise NotImplementedError('Only dev_dummy is supported at the moment.') + raise NotImplementedError("Only dev_dummy is supported at the moment.") def new_structure_to_csv(self, new_structures: List[pd.DataFrame], round: int = 1): """Save the generated structures in a csv format in the output dir. @@ -172,10 +208,14 @@ def new_structure_to_csv(self, new_structures: List[pd.DataFrame], round: int = new_structures: structures proposed by the generative model round: current round of training. Defaults to 1. """ - root_data_dir = os.path.join(self.data_paths.output_dir, f'new_structures_round_{round}') + root_data_dir = os.path.join( + self.data_paths.output_dir, f"new_structures_round_{round}" + ) os.makedirs(root_data_dir, exist_ok=True) for i, new_struc in enumerate(new_structures): - new_struc.to_csv(os.path.join(root_data_dir, f'structure_{i}.csv'), index=False) + new_struc.to_csv( + os.path.join(root_data_dir, f"structure_{i}.csv"), index=False + ) def get_labels_from_oracle(self, round: int = 1) -> Any: """Compute energy and forces from an oracle such as LAMMPS for the new candidates generated in a round of AL. @@ -187,11 +227,19 @@ def get_labels_from_oracle(self, round: int = 1) -> Any: mlip data input (for example, MTPInputs) """ new_labeled_samples = [] - for file in os.listdir(os.path.join(self.data_paths.output_dir, f'new_structures_round_{round}')): - if file.endswith('.csv'): - new_labeled_samples.append(self.call_oracle( - os.path.join(self.data_paths.output_dir, f'new_structures_round_{round}', file) - )) + for file in os.listdir( + os.path.join(self.data_paths.output_dir, f"new_structures_round_{round}") + ): + if file.endswith(".csv"): + new_labeled_samples.append( + self.call_oracle( + os.path.join( + self.data_paths.output_dir, + f"new_structures_round_{round}", + file, + ) + ) + ) new_labeled_samples = self.mlip_model.merge_inputs(new_labeled_samples) return new_labeled_samples @@ -205,9 +253,11 @@ def call_oracle(self, path_to_file: str) -> Any: mlip data inputs (for example, MTPInputs) """ data = pd.read_csv(path_to_file) - cartesian_positions = data[['x', 'y', 'z']].to_numpy() + cartesian_positions = data[["x", "y", "z"]].to_numpy() box = np.eye(3, 3) * 5.43 # TODO this is bad - fix this - atom_type = np.ones(cartesian_positions.shape[0], dtype=np.integer) # TODO also bad + atom_type = np.ones( + cartesian_positions.shape[0], dtype=np.integer + ) # TODO also bad energy, forces = self.oracle(cartesian_positions, box, atom_type) labels_as_mtp = self.mlip_model.prepare_dataset_from_numpy( cartesian_positions, @@ -218,8 +268,9 @@ def call_oracle(self, path_to_file: str) -> Any: ) return labels_as_mtp - def round_of_active_learning_loop(self, trained_mlip: Optional[MTPWithMLIP3] = None - ) -> Tuple[pd.DataFrame, pd.DataFrame]: + def round_of_active_learning_loop( + self, trained_mlip: Optional[MTPWithMLIP3] = None + ) -> Tuple[pd.DataFrame, pd.DataFrame]: """Do a full loop of activate learning. The following steps are done in sequence: @@ -248,16 +299,22 @@ def round_of_active_learning_loop(self, trained_mlip: Optional[MTPWithMLIP3] = N pred_df = self.evaluate_mlip(mlip_name=trained_mlip) bad_structures = self.get_bad_structures(pred_df) bad_regions = self.excise_worst_atom(bad_structures) - new_candidates = [self.get_structure_candidate_from_generative_model(x) for x in bad_regions] + new_candidates = [ + self.get_structure_candidate_from_generative_model(x) for x in bad_regions + ] self.new_structure_to_csv(new_candidates) new_labeled_candidates = self.get_labels_from_oracle() - new_training_set = self.mlip_model.merge_inputs([self.training_sets[-1], new_labeled_candidates]) + new_training_set = self.mlip_model.merge_inputs( + [self.training_sets[-1], new_labeled_candidates] + ) self.training_sets.append(new_training_set) new_mtp = self.train_mlip() new_pred_df = self.evaluate_mlip(mlip_name=new_mtp) return pred_df, new_pred_df - def evaluate_mtp_update(self, original_predictions: pd.DataFrame, updated_predictions) -> Tuple[float, float]: + def evaluate_mtp_update( + self, original_predictions: pd.DataFrame, updated_predictions + ) -> Tuple[float, float]: """Find the evaluation criteria in the original predictions and the corresponding value after retraining. Args: @@ -272,10 +329,13 @@ def evaluate_mtp_update(self, original_predictions: pd.DataFrame, updated_predic # TODO we assume a max - but it could be a min i criteria = self.eval_config.evaluation_criteria atom_index, structure_index, original_value = original_predictions.iloc[ - original_predictions[criteria].argmax()][['atom_index', 'structure_index', criteria]] + original_predictions[criteria].argmax() + ][["atom_index", "structure_index", criteria]] updated_value = updated_predictions.loc[ - (updated_predictions['atom_index'] == atom_index) - & (updated_predictions['structure_index'] == structure_index), criteria].values.item() + (updated_predictions["atom_index"] == atom_index) + & (updated_predictions["structure_index"] == structure_index), + criteria, + ].values.item() return original_value, updated_value @@ -286,7 +346,7 @@ def get_arguments() -> argparse.Namespace: args: arguments """ parser = argparse.ArgumentParser() - parser.add_argument('--config', help='path to data directory', required=True) + parser.add_argument("--config", help="path to data directory", required=True) args = parser.parse_args() return args @@ -301,5 +361,5 @@ def main(): al_loop.evaluate_mtp_update(initial_df, new_df) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/oracle.py b/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/oracle.py index f54f04e9..eec72e20 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/oracle.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/oracle.py @@ -2,23 +2,28 @@ from typing import Dict, Tuple import numpy as np -from crystal_diffusion import DATA_DIR -from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps + +from diffusion_for_multi_scale_molecular_dynamics import DATA_DIR +from diffusion_for_multi_scale_molecular_dynamics.oracle.lammps import \ + get_energy_and_forces_from_lammps class LAMMPS_for_active_learning: """Oracle using LAMMPS to get the energy and forces on atoms.""" + def __init__(self): """Initialize the class.""" pass - def __call__(self, - cartesian_positions: np.ndarray, - box: np.ndarray, - atom_types: np.ndarray, - atom_type_map: Dict[int, str] = {1: 'Si'}, - tmp_work_dir: str = './', - pair_coeff_dir: Path = DATA_DIR) -> Tuple[float, np.ndarray]: + def __call__( + self, + cartesian_positions: np.ndarray, + box: np.ndarray, + atom_types: np.ndarray, + atom_type_map: Dict[int, str] = {1: "Si"}, + tmp_work_dir: str = "./", + pair_coeff_dir: Path = DATA_DIR, + ) -> Tuple[float, np.ndarray]: """Call LAMMPS to get energy and forces for a given set of atoms. Args: @@ -33,11 +38,19 @@ def __call__(self, energy and forces on each atom (n_atom x 3) """ shifted_positions = self.shift_positions(cartesian_positions, box) - energy, forces = get_energy_and_forces_from_lammps(shifted_positions, box, atom_types, atom_type_map, - tmp_work_dir, pair_coeff_dir) - return energy, forces[['fx', 'fy', 'fz']].to_numpy() + energy, forces = get_energy_and_forces_from_lammps( + shifted_positions, + box, + atom_types, + atom_type_map, + tmp_work_dir, + pair_coeff_dir, + ) + return energy, forces[["fx", "fy", "fz"]].to_numpy() - def shift_positions(self, cartesian_positions: np.ndarray, box: np.ndarray) -> np.ndarray: + def shift_positions( + self, cartesian_positions: np.ndarray, box: np.ndarray + ) -> np.ndarray: """Shift the positions of the atoms so all coordinates are positives. This is because LAMMPS will ignore atoms with coordinates outside the [0, a] range (a = size of the unit cell). diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/utils.py index 02833f89..bf5a466e 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/utils.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/utils.py @@ -3,12 +3,13 @@ import pandas as pd -def get_structures_for_retraining(prediction_df: pd.DataFrame, - criteria_threshold: Optional[float] = None, - number_of_structures: Optional[int] = None, - evaluation_criteria: str = 'nbh_grades', - structure_index: str = 'structure_index' - ) -> List[pd.DataFrame]: +def get_structures_for_retraining( + prediction_df: pd.DataFrame, + criteria_threshold: Optional[float] = None, + number_of_structures: Optional[int] = None, + evaluation_criteria: str = "nbh_grades", + structure_index: str = "structure_index", +) -> List[pd.DataFrame]: """Find the structures with the worst value of the evaluation criteria. Args: @@ -27,26 +28,39 @@ def get_structures_for_retraining(prediction_df: pd.DataFrame, list of the structures with a bad evaluation criteria. Length of the list depends on criteria_threhold and number_of_structures. """ - assert criteria_threshold is not None or number_of_structures is not None, \ - "criteria_threshold or number_of_structures should be set." + assert ( + criteria_threshold is not None or number_of_structures is not None + ), "criteria_threshold or number_of_structures should be set." # get the highest evaluation_criteria for each structure i.e. only the worst atom counts for structure selection - criteria_by_structure = prediction_df[[evaluation_criteria, structure_index]].groupby(structure_index).max() + criteria_by_structure = ( + prediction_df[[evaluation_criteria, structure_index]] + .groupby(structure_index) + .max() + ) # find the top number_of_structures - structures_indices = criteria_by_structure.sort_values(by=evaluation_criteria, ascending=False) + structures_indices = criteria_by_structure.sort_values( + by=evaluation_criteria, ascending=False + ) if criteria_threshold is not None: - structures_indices = structures_indices[structures_indices[evaluation_criteria] >= criteria_threshold] + structures_indices = structures_indices[ + structures_indices[evaluation_criteria] >= criteria_threshold + ] structures_indices = structures_indices.index.to_list() if number_of_structures is not None: structures_indices = structures_indices[:number_of_structures] structures_to_retrain = [] for idx in structures_indices: - structures_to_retrain.append(prediction_df[prediction_df[structure_index] == idx]) + structures_to_retrain.append( + prediction_df[prediction_df[structure_index] == idx] + ) return structures_to_retrain -def extract_target_region(structure_df: pd.DataFrame, - extraction_radius: float, - evaluation_criteria: str = 'nbh_grades') -> pd.DataFrame: +def extract_target_region( + structure_df: pd.DataFrame, + extraction_radius: float, + evaluation_criteria: str = "nbh_grades", +) -> pd.DataFrame: """Extract the atom with the worst evaluation criteria and all the atoms within a distance extraction_radious. Args: @@ -60,9 +74,13 @@ def extract_target_region(structure_df: pd.DataFrame, # extract the worst ato and a region around of radius extraction_radius # TODO better method to determine radius: number of atoms ? target_atom = structure_df[evaluation_criteria].idxmax() - target_position = structure_df.loc[target_atom][['x', 'y', 'z']] + target_position = structure_df.loc[target_atom][["x", "y", "z"]] # TODO periodicity... - structure_df.loc[:, 'distance_squared'] = structure_df.apply( - lambda x: sum([(x[i] - target_position[i]) ** 2 for i in ['x', 'y', 'z']]), axis=1) - atom_positions = structure_df.loc[structure_df['distance_squared'] <= extraction_radius ** 2, ['x', 'y', 'z']] + structure_df.loc[:, "distance_squared"] = structure_df.apply( + lambda x: sum([(x[i] - target_position[i]) ** 2 for i in ["x", "y", "z"]]), + axis=1, + ) + atom_positions = structure_df.loc[ + structure_df["distance_squared"] <= extraction_radius**2, ["x", "y", "z"] + ] return atom_positions diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/analysis/generator_sample_analysis_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/analysis/generator_sample_analysis_utils.py index 4543c330..14c98513 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/analysis/generator_sample_analysis_utils.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/analysis/generator_sample_analysis_utils.py @@ -1,10 +1,14 @@ import torch -from crystal_diffusion.generators.ode_position_generator import ( - ExplodingVarianceODEPositionGenerator, ODESamplingParameters) -from crystal_diffusion.models.mace_utils import get_adj_matrix -from crystal_diffusion.models.score_networks.score_network import ScoreNetwork from einops import einops -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters + +from diffusion_for_multi_scale_molecular_dynamics.generators.ode_position_generator import ( + ExplodingVarianceODEPositionGenerator, ODESamplingParameters) +from diffusion_for_multi_scale_molecular_dynamics.models.graph_utils import \ + get_adj_matrix +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_network import \ + ScoreNetwork +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters class PartialODEPositionGenerator(ExplodingVarianceODEPositionGenerator): diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/analysis/ovito_visualisation.py b/src/diffusion_for_multi_scale_molecular_dynamics/analysis/ovito_visualisation.py index c88460bb..8401051b 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/analysis/ovito_visualisation.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/analysis/ovito_visualisation.py @@ -3,6 +3,7 @@ Draw the MD simulation with the MaxVol values. Some tweaking is required in the OVITO UI. See README_OVITO.md for more information. """ + import argparse import os @@ -14,16 +15,28 @@ def main(): """Read MTP output files and convert to xyz format readable by OVITO.""" parser = argparse.ArgumentParser() - parser.add_argument("--prediction_file", help="MTP prediction files. Should contain position and MaxVol gamma.") - parser.add_argument("--lammps_output", help="LAMMPS output file. Should contain the bounding box information.") - parser.add_argument("--output_name", help="Name of the output file that can be loaded by OVITO. ") + parser.add_argument( + "--prediction_file", + help="MTP prediction files. Should contain position and MaxVol gamma.", + ) + parser.add_argument( + "--lammps_output", + help="LAMMPS output file. Should contain the bounding box information.", + ) + parser.add_argument( + "--output_name", help="Name of the output file that can be loaded by OVITO. " + ) args = parser.parse_args() - assert os.path.exists(args.lammps_output), f"LAMMPS out file {args.lammps_output} does not exist." + assert os.path.exists( + args.lammps_output + ), f"LAMMPS out file {args.lammps_output} does not exist." lattice = get_lattice_from_lammps(args.lammps_output) - assert os.path.exists(args.prediction_file), f"Provided prediction file {args.prediction_file} does not exist." + assert os.path.exists( + args.prediction_file + ), f"Provided prediction file {args.prediction_file} does not exist." mtp_predictions_to_ovito(args.prediction_file, lattice, args.output_name) @@ -37,13 +50,17 @@ def get_lattice_from_lammps(lammps_output_file: str) -> np.ndarray: Returns: lattice: 3x3 array with lattice coordinates """ - with (open(lammps_output_file, 'r') as f): + with open(lammps_output_file, "r") as f: l_yaml = yaml.safe_load_all(f) - for d in l_yaml: # loop over LAMMPS outputs to get the MD box - we only need the first step + for ( + d + ) in ( + l_yaml + ): # loop over LAMMPS outputs to get the MD box - we only need the first step # lattice in yaml is 3 x 2 [0, x_lim] # we assume a rectangular lattice for now with the 2nd coordinates as the lattice vectors lattice = np.zeros((3, 3)) - for i, x in enumerate(d['box']): + for i, x in enumerate(d["box"]): lattice[i, i] = x[1] break return lattice @@ -57,16 +74,20 @@ def mtp_predictions_to_ovito(pred_file: str, lattice: np.ndarray, output_name: s lattice: lattice parameters in a 3x3 numpy array output_name: name of resulting file. An .xyz extension is added if not already in the name. """ - lattice = list(map(str, lattice.flatten())) # flatten and convert to string for formatting - lattice_str = 'Lattice=\"' + " ".join(lattice) + '\" Origin=\"0 0 0\" pbc=\"T T T\"' + lattice = list( + map(str, lattice.flatten()) + ) # flatten and convert to string for formatting + lattice_str = 'Lattice="' + " ".join(lattice) + '" Origin="0 0 0" pbc="T T T"' df = pd.read_csv(pred_file) # read the predictions xyz_file = "" # output will be written in file - for struct in sorted(df['structure_index'].unique()): # iterate over LAMMPS steps - xyz_values = df.loc[df['structure_index'] == struct, ['x', 'y', 'z']].to_numpy() - gamma_values = df.loc[df['structure_index'] == struct, 'nbh_grades'].to_numpy() # nbh_grade + for struct in sorted(df["structure_index"].unique()): # iterate over LAMMPS steps + xyz_values = df.loc[df["structure_index"] == struct, ["x", "y", "z"]].to_numpy() + gamma_values = df.loc[ + df["structure_index"] == struct, "nbh_grades" + ].to_numpy() # nbh_grade n_atom = xyz_values.shape[0] frame_txt = f"{n_atom}\n" - frame_txt += (lattice_str + ' Properties=pos:R:3:MaxVolGamma:R:1\n') + frame_txt += lattice_str + " Properties=pos:R:3:MaxVolGamma:R:1\n" # here, we can add properties to filter out atoms or identify some of them for i in range(n_atom): frame_txt += f"{' '.join(map(str, xyz_values[i, :]))} {gamma_values[i]}\n" @@ -75,9 +96,9 @@ def mtp_predictions_to_ovito(pred_file: str, lattice: np.ndarray, output_name: s if not output_name.endswith(".xyz"): output_name += ".xyz" - with open(output_name, 'w') as f: + with open(output_name, "w") as f: f.write(xyz_file) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/callback_loader.py b/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/callback_loader.py index 62c77b2c..934b3fd7 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/callback_loader.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/callback_loader.py @@ -1,22 +1,27 @@ from typing import Any, AnyStr, Dict -from crystal_diffusion.callbacks.loss_monitoring_callback import \ +from pytorch_lightning import Callback +from pytorch_lightning.callbacks import LearningRateMonitor + +from diffusion_for_multi_scale_molecular_dynamics.callbacks.loss_monitoring_callback import \ instantiate_loss_monitoring_callback -from crystal_diffusion.callbacks.sampling_visualization_callback import \ +from diffusion_for_multi_scale_molecular_dynamics.callbacks.sampling_visualization_callback import \ instantiate_sampling_visualization_callback -from crystal_diffusion.callbacks.standard_callbacks import ( +from diffusion_for_multi_scale_molecular_dynamics.callbacks.standard_callbacks import ( CustomProgressBar, instantiate_early_stopping_callback, instantiate_model_checkpoint_callbacks) -from pytorch_lightning import Callback -from pytorch_lightning.callbacks import LearningRateMonitor -OPTIONAL_CALLBACK_DICTIONARY = dict(early_stopping=instantiate_early_stopping_callback, - model_checkpoint=instantiate_model_checkpoint_callbacks, - sampling_visualization=instantiate_sampling_visualization_callback, - loss_monitoring=instantiate_loss_monitoring_callback) +OPTIONAL_CALLBACK_DICTIONARY = dict( + early_stopping=instantiate_early_stopping_callback, + model_checkpoint=instantiate_model_checkpoint_callbacks, + sampling_visualization=instantiate_sampling_visualization_callback, + loss_monitoring=instantiate_loss_monitoring_callback, +) -def create_all_callbacks(hyper_params: Dict[AnyStr, Any], output_directory: str, verbose: bool) -> Dict[str, Callback]: +def create_all_callbacks( + hyper_params: Dict[AnyStr, Any], output_directory: str, verbose: bool +) -> Dict[str, Callback]: """Create all callbacks. This method leverages the global dictionary OPTIONAL_CALLBACK_DICTIONARY which should be used to @@ -33,8 +38,10 @@ def create_all_callbacks(hyper_params: Dict[AnyStr, Any], output_directory: str, all_callbacks_dict: a dictionary of instantiated callbacks with relevant names as keys. """ # We always need a progress bar. We always want to know the learning rate. - all_callbacks_dict = dict(progress_bar=CustomProgressBar(), - learning_rate=LearningRateMonitor(logging_interval='epoch')) + all_callbacks_dict = dict( + progress_bar=CustomProgressBar(), + learning_rate=LearningRateMonitor(logging_interval="epoch"), + ) for callback_name, instantiate_callback in OPTIONAL_CALLBACK_DICTIONARY.items(): if callback_name not in hyper_params: diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/loss_monitoring_callback.py b/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/loss_monitoring_callback.py index 5e32892d..897b2fa2 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/loss_monitoring_callback.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/loss_monitoring_callback.py @@ -2,30 +2,41 @@ import numpy as np import torch -from crystal_diffusion.loggers.logger_loader import log_figure from matplotlib import pyplot as plt from pytorch_lightning import Callback -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH + +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) +from diffusion_for_multi_scale_molecular_dynamics.loggers.logger_loader import \ + log_figure plt.style.use(PLOT_STYLE_PATH) -def instantiate_loss_monitoring_callback(callback_params: Dict[AnyStr, Any], - output_directory: str, verbose: bool) -> Dict[str, Callback]: +def instantiate_loss_monitoring_callback( + callback_params: Dict[AnyStr, Any], output_directory: str, verbose: bool +) -> Dict[str, Callback]: """Instantiate the Loss monitoring callback.""" - number_of_bins = callback_params['number_of_bins'] - sample_every_n_epochs = callback_params['sample_every_n_epochs'] - spatial_dimension = callback_params.get('spatial_dimension', 3) - loss_monitoring_callback = LossMonitoringCallback(number_of_bins=number_of_bins, - sample_every_n_epochs=sample_every_n_epochs, - spatial_dimension=spatial_dimension) + number_of_bins = callback_params["number_of_bins"] + sample_every_n_epochs = callback_params["sample_every_n_epochs"] + spatial_dimension = callback_params.get("spatial_dimension", 3) + loss_monitoring_callback = LossMonitoringCallback( + number_of_bins=number_of_bins, + sample_every_n_epochs=sample_every_n_epochs, + spatial_dimension=spatial_dimension, + ) return dict(loss_monitoring_callback=loss_monitoring_callback) class LossMonitoringCallback(Callback): """Callback class to monitor the loss vs. time (or sigma) relationship.""" - def __init__(self, number_of_bins: int, sample_every_n_epochs: int, spatial_dimension: int = 3): + def __init__( + self, + number_of_bins: int, + sample_every_n_epochs: int, + spatial_dimension: int = 3, + ): """Init method.""" self.number_of_bins = number_of_bins self.sample_every_n_epochs = sample_every_n_epochs @@ -41,21 +52,32 @@ def _compute_results_at_this_epoch(self, current_epoch: int) -> bool: else: return False - def on_validation_batch_end(self, trainer, pl_module, outputs, batch, batch_idx, dataloader_idx=0): + def on_validation_batch_end( + self, trainer, pl_module, outputs, batch, batch_idx, dataloader_idx=0 + ): """Action to perform at the end of a validation batch.""" if not self._compute_results_at_this_epoch(trainer.current_epoch): return - batch_sigmas = outputs['sigmas'][:, :, 0] # the sigmas are the same for all atoms and space directions + batch_sigmas = outputs["sigmas"][ + :, :, 0 + ] # the sigmas are the same for all atoms and space directions self.all_sigmas.append(batch_sigmas.flatten()) # Compute the square errors per atoms - batched_squared_errors = ((outputs['predicted_normalized_scores'] - - outputs['target_normalized_conditional_scores'])**2).sum(dim=-1) + batched_squared_errors = ( + ( + outputs["predicted_normalized_scores"] + - outputs["target_normalized_conditional_scores"] + ) + ** 2 + ).sum(dim=-1) self.all_squared_errors.append(batched_squared_errors.flatten()) # Average over space dimensions, where the sigmas are the same. - self.all_weighted_losses.append(outputs["unreduced_loss"].mean(dim=-1).flatten()) + self.all_weighted_losses.append( + outputs["unreduced_loss"].mean(dim=-1).flatten() + ) def on_validation_epoch_end(self, trainer, pl_module): """Action to perform at the end of a training epoch.""" @@ -66,23 +88,29 @@ def on_validation_epoch_end(self, trainer, pl_module): squared_errors = torch.cat(self.all_squared_errors).detach().cpu().numpy() weighted_losses = torch.cat(self.all_weighted_losses).detach().cpu().numpy() - fig_squared_errors = self._plot_loss_scatter(sigmas, squared_errors, - trainer.current_epoch, 'Squared Error') - fig_weighted_loss = self._plot_loss_scatter(sigmas, weighted_losses, - trainer.current_epoch, 'Weighted Loss') + fig_squared_errors = self._plot_loss_scatter( + sigmas, squared_errors, trainer.current_epoch, "Squared Error" + ) + fig_weighted_loss = self._plot_loss_scatter( + sigmas, weighted_losses, trainer.current_epoch, "Weighted Loss" + ) for pl_logger in trainer.loggers: - log_figure(figure=fig_squared_errors, - global_step=trainer.global_step, - pl_logger=pl_logger, - dataset="validation", - name="squared_error") - - log_figure(figure=fig_weighted_loss, - global_step=trainer.global_step, - pl_logger=pl_logger, - dataset="validation", - name="weighted_loss") + log_figure( + figure=fig_squared_errors, + global_step=trainer.global_step, + pl_logger=pl_logger, + dataset="validation", + name="squared_error", + ) + + log_figure( + figure=fig_weighted_loss, + global_step=trainer.global_step, + pl_logger=pl_logger, + dataset="validation", + name="weighted_loss", + ) plt.close(fig_squared_errors) plt.close(fig_weighted_loss) @@ -91,13 +119,15 @@ def on_validation_epoch_end(self, trainer, pl_module): self.all_squared_errors.clear() self.all_weighted_losses.clear() - def _plot_loss_scatter(self, sigmas: np.ndarray, values: np.array, epoch: int, ylabel: str) -> plt.figure: + def _plot_loss_scatter( + self, sigmas: np.ndarray, values: np.array, epoch: int, ylabel: str + ) -> plt.figure: """Generate a scatter plot vs. the values of noise.""" total = np.mean(values) fig = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig.suptitle(f'{ylabel} vs. Sigma\nEpoch {epoch}, Total = {total:5.3e}') + fig.suptitle(f"{ylabel} vs. Sigma\nEpoch {epoch}, Total = {total:5.3e}") - bins = np.linspace(0., np.max(sigmas), self.number_of_bins) + bins = np.linspace(0.0, np.max(sigmas), self.number_of_bins) bin_indices = np.digitize(sigmas, bins) list_mean_sigmas = [] @@ -121,15 +151,19 @@ def _plot_loss_scatter(self, sigmas: np.ndarray, values: np.array, epoch: int, y ax1 = fig.add_subplot(111) - ax1.semilogy(list_mean_sigmas, list_mean_values, 'g-o', label='Binned Mean') - ax1.fill_between(list_mean_sigmas, - list_min_values, - list_max_values, - color='g', alpha=0.25, label="5% to 95% Quantile") + ax1.semilogy(list_mean_sigmas, list_mean_values, "g-o", label="Binned Mean") + ax1.fill_between( + list_mean_sigmas, + list_min_values, + list_max_values, + color="g", + alpha=0.25, + label="5% to 95% Quantile", + ) ax1.set_xlim([-0.01, np.max(sigmas) + 0.01]) - ax1.legend(loc='best') - ax1.set_xlabel('$\\sigma$') + ax1.legend(loc="best") + ax1.set_xlabel("$\\sigma$") ax1.set_ylabel(ylabel) fig.tight_layout() return fig diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/sampling_visualization_callback.py b/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/sampling_visualization_callback.py index 025a10bd..49cb5cda 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/sampling_visualization_callback.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/sampling_visualization_callback.py @@ -6,10 +6,13 @@ import numpy as np import torch -from crystal_diffusion.loggers.logger_loader import log_figure from matplotlib import pyplot as plt from pytorch_lightning import Callback, LightningModule, Trainer -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH + +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) +from diffusion_for_multi_scale_molecular_dynamics.loggers.logger_loader import \ + log_figure logger = logging.getLogger(__name__) @@ -19,6 +22,7 @@ @dataclass(kw_only=True) class SamplingVisualizationParameters: """Parameters to decide what to plot and write to disk.""" + record_every_n_epochs: int = 1 first_record_epoch: int = 1 record_trajectories: bool = True diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/standard_callbacks.py b/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/standard_callbacks.py index 937f1640..511f4604 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/standard_callbacks.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/callbacks/standard_callbacks.py @@ -8,24 +8,26 @@ logger = logging.getLogger(__name__) -BEST_MODEL_NAME = 'best_model' -LAST_MODEL_NAME = 'last_model' +BEST_MODEL_NAME = "best_model" +LAST_MODEL_NAME = "last_model" -def instantiate_early_stopping_callback(callback_params: Dict[AnyStr, Any], - output_directory: str, - verbose: bool) -> Dict[str, Callback]: +def instantiate_early_stopping_callback( + callback_params: Dict[AnyStr, Any], output_directory: str, verbose: bool +) -> Dict[str, Callback]: """Instantiate early stopping callback.""" - early_stopping = EarlyStopping(callback_params['metric'], - mode=callback_params['mode'], - patience=callback_params['patience'], - verbose=verbose) + early_stopping = EarlyStopping( + callback_params["metric"], + mode=callback_params["mode"], + patience=callback_params["patience"], + verbose=verbose, + ) return dict(early_stopping=early_stopping) -def instantiate_model_checkpoint_callbacks(callback_params: Dict[AnyStr, Any], - output_directory: str, - verbose: bool) -> Dict[str, Callback]: +def instantiate_model_checkpoint_callbacks( + callback_params: Dict[AnyStr, Any], output_directory: str, verbose: bool +) -> Dict[str, Callback]: """Instantiate best and last checkpoint callbacks.""" best_model_path = os.path.join(output_directory, BEST_MODEL_NAME) best_checkpoint_callback = ModelCheckpoint( @@ -45,11 +47,15 @@ def instantiate_model_checkpoint_callbacks(callback_params: Dict[AnyStr, Any], verbose=verbose, every_n_epochs=1, ) - return dict(best_checkpoint=best_checkpoint_callback, last_checkpoint=last_checkpoint_callback) + return dict( + best_checkpoint=best_checkpoint_callback, + last_checkpoint=last_checkpoint_callback, + ) class CustomProgressBar(RichProgressBar): """A custom progress bar based on Rich that doesn't log the v_num stuff.""" + def get_metrics(self, *args, **kwargs): """Get metrics.""" # don't show the version number diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/data/diffusion/data_loader.py b/src/diffusion_for_multi_scale_molecular_dynamics/data/diffusion/data_loader.py index 0f81b5bb..df6784c4 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/data/diffusion/data_loader.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/data/diffusion/data_loader.py @@ -1,4 +1,5 @@ """DataLoader from LAMMPS outputs for a diffusion model.""" + import logging import typing from dataclasses import dataclass @@ -9,18 +10,20 @@ import pytorch_lightning as pl import torch import torch.nn.functional as F -from crystal_diffusion.data.diffusion.data_preprocess import \ - LammpsProcessorForDiffusion -from crystal_diffusion.namespace import (CARTESIAN_FORCES, CARTESIAN_POSITIONS, - RELATIVE_COORDINATES) from torch.utils.data import DataLoader +from diffusion_for_multi_scale_molecular_dynamics.data.diffusion.data_preprocess import \ + LammpsProcessorForDiffusion +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, CARTESIAN_POSITIONS, RELATIVE_COORDINATES) + logger = logging.getLogger(__name__) @dataclass(kw_only=True) class LammpsLoaderParameters: """Base Hyper-parameters for score networks.""" + # Either batch_size XOR train_batch_size and valid_batch_size should be specified. batch_size: Optional[int] = None train_batch_size: Optional[int] = None @@ -34,11 +37,11 @@ class LammpsForDiffusionDataModule(pl.LightningDataModule): """Data module class that prepares dataset parsers and instantiates data loaders.""" def __init__( - self, - lammps_run_dir: str, - processed_dataset_dir: str, - hyper_params: LammpsLoaderParameters, - working_cache_dir: Optional[str] = None + self, + lammps_run_dir: str, + processed_dataset_dir: str, + hyper_params: LammpsLoaderParameters, + working_cache_dir: Optional[str] = None, ): """Initialize a dataset of LAMMPS structures for training a diffusion model. @@ -61,24 +64,30 @@ def __init__( self.spatial_dim = hyper_params.spatial_dimension if hyper_params.batch_size is None: - assert hyper_params.valid_batch_size is not None, \ - "If batch_size is None, valid_batch_size must be specified." - assert hyper_params.train_batch_size is not None, \ - "If batch_size is None, train_batch_size must be specified." + assert ( + hyper_params.valid_batch_size is not None + ), "If batch_size is None, valid_batch_size must be specified." + assert ( + hyper_params.train_batch_size is not None + ), "If batch_size is None, train_batch_size must be specified." self.train_batch_size = hyper_params.train_batch_size self.valid_batch_size = hyper_params.valid_batch_size else: - assert hyper_params.valid_batch_size is None, \ - "If batch_size is specified, valid_batch_size must be None." - assert hyper_params.train_batch_size is None, \ - "If batch_size is specified, train_batch_size must be None." + assert ( + hyper_params.valid_batch_size is None + ), "If batch_size is specified, valid_batch_size must be None." + assert ( + hyper_params.train_batch_size is None + ), "If batch_size is specified, train_batch_size must be None." self.train_batch_size = hyper_params.batch_size self.valid_batch_size = hyper_params.batch_size @staticmethod - def dataset_transform(x: Dict[typing.AnyStr, typing.Any], spatial_dim: int = 3) -> Dict[str, torch.Tensor]: + def dataset_transform( + x: Dict[typing.AnyStr, typing.Any], spatial_dim: int = 3 + ) -> Dict[str, torch.Tensor]: """Format the tensors for the Datasets library. This function is applied right after returning the objects in __getitem__ in a torch DataLoader. Everything is @@ -93,18 +102,28 @@ def dataset_transform(x: Dict[typing.AnyStr, typing.Any], spatial_dim: int = 3) transformed_x: formatted values as tensors """ transformed_x = {} - transformed_x['natom'] = torch.as_tensor(x['natom']).long() # resulting tensor size: (batchsize, ) - bsize = transformed_x['natom'].size(0) - transformed_x['box'] = torch.as_tensor(x['box']) # size: (batchsize, spatial dimension) + transformed_x["natom"] = torch.as_tensor( + x["natom"] + ).long() # resulting tensor size: (batchsize, ) + bsize = transformed_x["natom"].size(0) + transformed_x["box"] = torch.as_tensor( + x["box"] + ) # size: (batchsize, spatial dimension) for pos in [CARTESIAN_POSITIONS, RELATIVE_COORDINATES, CARTESIAN_FORCES]: transformed_x[pos] = torch.as_tensor(x[pos]).view(bsize, -1, spatial_dim) - transformed_x['type'] = torch.as_tensor(x['type']).long() # size: (batchsize, max atom) - transformed_x['potential_energy'] = torch.as_tensor(x['potential_energy']) # size: (batchsize, ) + transformed_x["type"] = torch.as_tensor( + x["type"] + ).long() # size: (batchsize, max atom) + transformed_x["potential_energy"] = torch.as_tensor( + x["potential_energy"] + ) # size: (batchsize, ) return transformed_x @staticmethod - def pad_samples(x: Dict[typing.AnyStr, typing.Any], max_atom: int, spatial_dim: int = 3) -> Dict[str, torch.Tensor]: + def pad_samples( + x: Dict[typing.AnyStr, typing.Any], max_atom: int, spatial_dim: int = 3 + ) -> Dict[str, torch.Tensor]: """Pad a sample for batching. Args: @@ -115,25 +134,37 @@ def pad_samples(x: Dict[typing.AnyStr, typing.Any], max_atom: int, spatial_dim: Returns: x: sample with padded type and position """ - natom = x['natom'] + natom = x["natom"] if natom > max_atom: - raise ValueError(f"Hyper-parameter max_atom is smaller than an example in the dataset with {natom} atoms.") - x['type'] = F.pad(torch.as_tensor(x['type']).long(), (0, max_atom - natom), 'constant', -1) + raise ValueError( + f"Hyper-parameter max_atom is smaller than an example in the dataset with {natom} atoms." + ) + x["type"] = F.pad( + torch.as_tensor(x["type"]).long(), (0, max_atom - natom), "constant", -1 + ) for pos in [CARTESIAN_POSITIONS, RELATIVE_COORDINATES, CARTESIAN_FORCES]: - x[pos] = F.pad(torch.as_tensor(x[pos]).float(), (0, spatial_dim * (max_atom - natom)), 'constant', - torch.nan) + x[pos] = F.pad( + torch.as_tensor(x[pos]).float(), + (0, spatial_dim * (max_atom - natom)), + "constant", + torch.nan, + ) return x def setup(self, stage: Optional[str] = None): """Parse and split all samples across the train/valid/test parsers.""" # here, we will actually assign train/val datasets for use in dataloaders - processed_data = LammpsProcessorForDiffusion(self.lammps_run_dir, self.processed_dataset_dir) + processed_data = LammpsProcessorForDiffusion( + self.lammps_run_dir, self.processed_dataset_dir + ) if stage == "fit" or stage is None: - self.train_dataset = datasets.Dataset.from_parquet(processed_data.train_files, - cache_dir=self.working_cache_dir) - self.valid_dataset = datasets.Dataset.from_parquet(processed_data.valid_files, - cache_dir=self.working_cache_dir) + self.train_dataset = datasets.Dataset.from_parquet( + processed_data.train_files, cache_dir=self.working_cache_dir + ) + self.valid_dataset = datasets.Dataset.from_parquet( + processed_data.valid_files, cache_dir=self.working_cache_dir + ) # TODO QoL valid dataset is labelled as train split by Datasets. Find a way to rename. else: raise NotImplementedError("Test mode needs to be implemented.") @@ -142,19 +173,33 @@ def setup(self, stage: Optional[str] = None): # or a .select(list[int]) with a list of indices to keep a subset. This is much faster than .filter # padding needs to be done here OR in the preprocessor # check if the max number of atoms matches at least the max in the training set - if max(self.train_dataset['natom']) > self.max_atom: - raise ValueError(f"Hyper-parameter max_atom {self.max_atom} is smaller than the largest structure in the" - + f"dataset which has {max(self.train_dataset['natom'])} atoms.") + if max(self.train_dataset["natom"]) > self.max_atom: + raise ValueError( + f"Hyper-parameter max_atom {self.max_atom} is smaller than the largest structure in the" + + f"dataset which has {max(self.train_dataset['natom'])} atoms." + ) # map() are applied once, not in-place. # The keyword argument "batched" can accelerate by working with batches, not useful for padding - self.train_dataset = self.train_dataset.map(partial(self.pad_samples, max_atom=self.max_atom, - spatial_dim=self.spatial_dim), batched=False) - self.valid_dataset = self.valid_dataset.map(partial(self.pad_samples, max_atom=self.max_atom, - spatial_dim=self.spatial_dim), batched=False) + self.train_dataset = self.train_dataset.map( + partial( + self.pad_samples, max_atom=self.max_atom, spatial_dim=self.spatial_dim + ), + batched=False, + ) + self.valid_dataset = self.valid_dataset.map( + partial( + self.pad_samples, max_atom=self.max_atom, spatial_dim=self.spatial_dim + ), + batched=False, + ) # set_transform is applied on-the-fly and is less costly upfront. Works with batches, so we can't use it for # padding - self.train_dataset.set_transform(partial(self.dataset_transform, spatial_dim=self.spatial_dim)) - self.valid_dataset.set_transform(partial(self.dataset_transform, spatial_dim=self.spatial_dim)) + self.train_dataset.set_transform( + partial(self.dataset_transform, spatial_dim=self.spatial_dim) + ) + self.valid_dataset.set_transform( + partial(self.dataset_transform, spatial_dim=self.spatial_dim) + ) def train_dataloader(self) -> DataLoader: """Create the training dataloader using the training data parser.""" diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/data/diffusion/data_preprocess.py b/src/diffusion_for_multi_scale_molecular_dynamics/data/diffusion/data_preprocess.py index efa00ed8..6db8195a 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/data/diffusion/data_preprocess.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/data/diffusion/data_preprocess.py @@ -1,4 +1,5 @@ """Convert results of LAMMPS simulation into dataloader friendly format.""" + import itertools import logging import os @@ -7,9 +8,11 @@ from typing import List, Optional, Tuple, Union import pandas as pd -from crystal_diffusion.namespace import (CARTESIAN_FORCES, CARTESIAN_POSITIONS, - RELATIVE_COORDINATES) -from src.crystal_diffusion.data.parse_lammps_outputs import parse_lammps_output + +from diffusion_for_multi_scale_molecular_dynamics.data.parse_lammps_outputs import \ + parse_lammps_output +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, CARTESIAN_POSITIONS, RELATIVE_COORDINATES) logger = logging.getLogger(__name__) @@ -28,10 +31,10 @@ def __init__(self, raw_data_dir: str, processed_data_dir: str): os.makedirs(processed_data_dir) # create the dir if it doesn't exist self.data_dir = processed_data_dir # TODO revisit data splits - self.train_files = self.prepare_data(raw_data_dir, mode='train') - self.valid_files = self.prepare_data(raw_data_dir, mode='valid') + self.train_files = self.prepare_data(raw_data_dir, mode="train") + self.valid_files = self.prepare_data(raw_data_dir, mode="valid") - def prepare_data(self, raw_data_dir: str, mode: str = 'train') -> List[str]: + def prepare_data(self, raw_data_dir: str, mode: str = "train") -> List[str]: """Read data in raw_data_dir and write to a parquet file for Datasets. Args: @@ -44,18 +47,32 @@ def prepare_data(self, raw_data_dir: str, mode: str = 'train') -> List[str]: # TODO split is assumed from data generation. We might revisit this. # we assume that raw_data_dir contains subdirectories named train_run_N for N>=1 # get the list of runs to parse - assert mode in ['train', 'valid', 'test'], f"Mode should be train, valid or test. Got {mode}." - list_runs = [d for d in os.listdir(raw_data_dir) if os.path.isdir(os.path.join(raw_data_dir, d)) - and d.startswith(f"{mode}_run")] + assert mode in [ + "train", + "valid", + "test", + ], f"Mode should be train, valid or test. Got {mode}." + list_runs = [ + d + for d in os.listdir(raw_data_dir) + if os.path.isdir(os.path.join(raw_data_dir, d)) + and d.startswith(f"{mode}_run") + ] list_files = [] for count, d in enumerate(list_runs, 1): - logging.info(f"Processing run directory {d} ({count} of {len(list_runs)})...") + logging.info( + f"Processing run directory {d} ({count} of {len(list_runs)})..." + ) if f"{d}.parquet" not in os.listdir(self.data_dir): logging.info(" * parquet file is absent. Generating...") df = self.parse_lammps_run(os.path.join(raw_data_dir, d)) if df is not None: logging.info(" * writing parquet file to disk...") - df.to_parquet(os.path.join(self.data_dir, f"{d}.parquet"), engine='pyarrow', index=False) + df.to_parquet( + os.path.join(self.data_dir, f"{d}.parquet"), + engine="pyarrow", + index=False, + ) if f"{d}.parquet" in os.listdir(self.data_dir): list_files.append(os.path.join(self.data_dir, f"{d}.parquet")) return list_files @@ -70,10 +87,17 @@ def _convert_coords_to_relative(row: pd.Series) -> List[float]: Returns: x, y and z in relative (reduced) coordinates """ - x_lim, y_lim, z_lim = row['box'] + x_lim, y_lim, z_lim = row["box"] # Cast the coordinates to float in case they are read in as strings - coord_red = [coord for triple in zip(row['x'], row['y'], row['z']) for coord in - ((float(triple[0]) / x_lim) % 1, (float(triple[1]) / y_lim) % 1, (float(triple[2]) / z_lim) % 1)] + coord_red = [ + coord + for triple in zip(row["x"], row["y"], row["z"]) + for coord in ( + (float(triple[0]) / x_lim) % 1, + (float(triple[1]) / y_lim) % 1, + (float(triple[2]) / z_lim) % 1, + ) + ] return coord_red def get_x_relative(self, df: pd.DataFrame) -> pd.DataFrame: @@ -85,11 +109,15 @@ def get_x_relative(self, df: pd.DataFrame) -> pd.DataFrame: Returns: dataframe with added column of relative positions [x1, y1, z1, x2, y2, ...] """ - df[RELATIVE_COORDINATES] = df.apply(lambda x: self._convert_coords_to_relative(x), axis=1) + df[RELATIVE_COORDINATES] = df.apply( + lambda x: self._convert_coords_to_relative(x), axis=1 + ) return df @staticmethod - def get_dump_and_thermo_files(run_dir: str) -> Tuple[Union[str, None], Union[str, None]]: + def get_dump_and_thermo_files( + run_dir: str, + ) -> Tuple[Union[str, None], Union[str, None]]: """Get dump and thermo files. Args: @@ -100,20 +128,26 @@ def get_dump_and_thermo_files(run_dir: str) -> Tuple[Union[str, None], Union[str one data file for each of (dump, thermo). """ # find the LAMMPS dump file and thermo file - dump_file = [d for d in os.listdir(run_dir) if 'dump' in d] + dump_file = [d for d in os.listdir(run_dir) if "dump" in d] if len(dump_file) == 1: dump_file_path = os.path.join(run_dir, dump_file[0]) else: - warnings.warn(f"Found {len(dump_file)} files with dump in the name in {run_dir}. " - f"Expected exactly one.", UserWarning) + warnings.warn( + f"Found {len(dump_file)} files with dump in the name in {run_dir}. " + f"Expected exactly one.", + UserWarning, + ) dump_file_path = None - thermo_file = [d for d in os.listdir(run_dir) if 'thermo' in d] + thermo_file = [d for d in os.listdir(run_dir) if "thermo" in d] if len(thermo_file) == 1: thermo_file_path = os.path.join(run_dir, thermo_file[0]) else: - warnings.warn(f"Found {len(thermo_file)} files with thermo in the name in {run_dir}. " - f"Expected exactly one.", UserWarning) + warnings.warn( + f"Found {len(thermo_file)} files with thermo in the name in {run_dir}. " + f"Expected exactly one.", + UserWarning, + ) thermo_file_path = None return dump_file_path, thermo_file_path @@ -155,19 +189,32 @@ def parse_lammps_run(self, run_dir: str) -> Optional[pd.DataFrame]: # Each row is a different MD step / usable example for diffusion model # TODO consider filtering out samples with large forces and MD steps that are too similar # TODO large force and similar are to be defined - df = df[['type', 'x', 'y', 'z', 'box', 'potential_energy', 'fx', 'fy', 'fz']] + df = df[["type", "x", "y", "z", "box", "potential_energy", "fx", "fy", "fz"]] df = self.get_x_relative(df) # add relative coordinates - df['natom'] = df['type'].apply(lambda x: len(x)) # count number of atoms in a structure + df["natom"] = df["type"].apply( + lambda x: len(x) + ) # count number of atoms in a structure # Parquet cannot handle a list of list; flattening positions. df[CARTESIAN_POSITIONS] = df.apply(self._flatten_positions_in_row, axis=1) # position is natom * 3 array - df[CARTESIAN_FORCES] = df.apply(partial(self._flatten_positions_in_row, keys=['fx', 'fy', 'fz']), axis=1) - return df[['natom', 'box', 'type', 'potential_energy', CARTESIAN_POSITIONS, RELATIVE_COORDINATES, - CARTESIAN_FORCES]] + df[CARTESIAN_FORCES] = df.apply( + partial(self._flatten_positions_in_row, keys=["fx", "fy", "fz"]), axis=1 + ) + return df[ + [ + "natom", + "box", + "type", + "potential_energy", + CARTESIAN_POSITIONS, + RELATIVE_COORDINATES, + CARTESIAN_FORCES, + ] + ] @staticmethod - def _flatten_positions_in_row(row: pd.Series, keys=['x', 'y', 'z']) -> List[float]: + def _flatten_positions_in_row(row: pd.Series, keys=["x", "y", "z"]) -> List[float]: """Function to flatten the positions in a dataframe row. Args: @@ -180,6 +227,10 @@ def _flatten_positions_in_row(row: pd.Series, keys=['x', 'y', 'z']) -> List[floa list_y = row[keys[1]] list_z = row[keys[2]] - flat_positions = list(itertools.chain.from_iterable([[x, y, z] for x, y, z in zip(list_x, list_y, list_z)])) + flat_positions = list( + itertools.chain.from_iterable( + [[x, y, z] for x, y, z in zip(list_x, list_y, list_z)] + ) + ) return flat_positions diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/data/parse_lammps_outputs.py b/src/diffusion_for_multi_scale_molecular_dynamics/data/parse_lammps_outputs.py index 4783a1a3..a7f44196 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/data/parse_lammps_outputs.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/data/parse_lammps_outputs.py @@ -9,7 +9,9 @@ from yaml import CLoader -def parse_lammps_output(lammps_dump: str, lammps_thermo_log: str, output_name: Optional[str] = None) -> pd.DataFrame: +def parse_lammps_output( + lammps_dump: str, lammps_thermo_log: str, output_name: Optional[str] = None +) -> pd.DataFrame: """Parse a LAMMPS output file and save in a .csv format. Args: @@ -22,10 +24,14 @@ def parse_lammps_output(lammps_dump: str, lammps_thermo_log: str, output_name: O data in a dataframe """ if not os.path.exists(lammps_dump): - raise ValueError(f'{lammps_dump} does not exist. Please provide a valid LAMMPS dump file as yaml.') + raise ValueError( + f"{lammps_dump} does not exist. Please provide a valid LAMMPS dump file as yaml." + ) if not os.path.exists(lammps_thermo_log): - raise ValueError(f'{lammps_thermo_log} does not exist. Please provide a valid LAMMPS thermo log file as yaml.') + raise ValueError( + f"{lammps_thermo_log} does not exist. Please provide a valid LAMMPS thermo log file as yaml." + ) # get the atom information (positions and forces) from the LAMMPS 'dump' file pd_data = parse_lammps_dump(lammps_dump) @@ -34,13 +40,13 @@ def parse_lammps_output(lammps_dump: str, lammps_thermo_log: str, output_name: O thermo_log_data_dictionary = parse_lammps_thermo_log(lammps_thermo_log) pd_data.update(thermo_log_data_dictionary) - if output_name is not None and not output_name.endswith('.parquet'): - output_name += '.parquet' + if output_name is not None and not output_name.endswith(".parquet"): + output_name += ".parquet" df = pd.DataFrame(pd_data) if output_name is not None: - df.to_parquet(output_name, engine='pyarrow', index=False) + df.to_parquet(output_name, engine="pyarrow", index=False) return df @@ -56,18 +62,20 @@ def parse_lammps_dump(lammps_dump: str) -> Dict[str, Any]: Returns: data: a dictionary with all the relevant data. """ - expected_keywords = ['id', 'type', 'x', 'y', 'z', 'fx', 'fy', 'fz'] + expected_keywords = ["id", "type", "x", "y", "z", "fx", "fy", "fz"] datatypes = 2 * [np.int64] + 6 * [np.float64] pd_data = defaultdict(list) - with open(lammps_dump, 'r') as stream: + with open(lammps_dump, "r") as stream: dump_yaml = yaml.load_all(stream, Loader=CLoader) for doc in dump_yaml: # loop over MD steps - pd_data['box'].append(np.array(doc['box'])[:, 1]) + pd_data["box"].append(np.array(doc["box"])[:, 1]) - assert doc['keywords'] == expected_keywords - data = np.array(doc['data']).transpose() # convert to numpy so that we can easily slice + assert doc["keywords"] == expected_keywords + data = np.array( + doc["data"] + ).transpose() # convert to numpy so that we can easily slice for keyword, datatype, data_row in zip(expected_keywords, datatypes, data): pd_data[keyword].append(data_row.astype(datatype)) return pd_data @@ -83,26 +91,26 @@ def parse_lammps_thermo_log(lammps_thermo_log: str) -> Dict[str, List[float]]: parsed_data: the data from the log, parsed in a dictionary. """ data_dict = defaultdict(list) - optional_keywords = {'Press': 'pressure', 'Temp': 'temperature'} + optional_keywords = {"Press": "pressure", "Temp": "temperature"} optional_indices = dict() - with open(lammps_thermo_log, 'r') as f: + with open(lammps_thermo_log, "r") as f: log_yaml = yaml.safe_load(f) - kin_idx = log_yaml['keywords'].index('KinEng') - pot_idx = log_yaml['keywords'].index('PotEng') + kin_idx = log_yaml["keywords"].index("KinEng") + pot_idx = log_yaml["keywords"].index("PotEng") # For optional keys, impose better names than the LAMMPS keys. for yaml_key, long_name in optional_keywords.items(): - if yaml_key in log_yaml['keywords']: - idx = log_yaml['keywords'].index(yaml_key) + if yaml_key in log_yaml["keywords"]: + idx = log_yaml["keywords"].index(yaml_key) optional_indices[long_name] = idx - for record in log_yaml['data']: + for record in log_yaml["data"]: potential_energy = record[pot_idx] kinetic_energy = record[kin_idx] - data_dict['potential_energy'].append(potential_energy) - data_dict['kinetic_energy'].append(kinetic_energy) - data_dict['energy'].append(potential_energy + kinetic_energy) + data_dict["potential_energy"].append(potential_energy) + data_dict["kinetic_energy"].append(kinetic_energy) + data_dict["energy"].append(potential_energy + kinetic_energy) for long_name, idx in optional_indices.items(): data_dict[long_name].append(record[idx]) @@ -112,14 +120,20 @@ def parse_lammps_thermo_log(lammps_thermo_log: str) -> Dict[str, List[float]]: def main(): """Parse LAMMPS files and output a single parquet file.""" - parser = argparse.ArgumentParser(description="Convert LAMMPS outputs in parquet file compatible with a dataloader.") - parser.add_argument("--dump_file", type=str, help="LAMMPS dump file in yaml format.") - parser.add_argument("--thermo_file", type=str, help="LAMMPS thermo output file in yaml format.") + parser = argparse.ArgumentParser( + description="Convert LAMMPS outputs in parquet file compatible with a dataloader." + ) + parser.add_argument( + "--dump_file", type=str, help="LAMMPS dump file in yaml format." + ) + parser.add_argument( + "--thermo_file", type=str, help="LAMMPS thermo output file in yaml format." + ) parser.add_argument("--output_name", type=str, help="Output name") args = parser.parse_args() parse_lammps_output(args.dump_file, args.thermo_file, args.output_name) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/data/utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/data/utils.py index a147f898..86dfede2 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/data/utils.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/data/utils.py @@ -1,4 +1,5 @@ """Utility functions for data processing.""" + import logging import os from typing import Any, AnyStr, Dict, List, Tuple @@ -9,8 +10,9 @@ logger = logging.getLogger(__name__) -def crop_lammps_yaml(lammps_dump: str, lammps_thermo: str, crop_step: int, inplace: bool = False) \ - -> Tuple[List[Dict[AnyStr, Any]], Dict[AnyStr, Any]]: +def crop_lammps_yaml( + lammps_dump: str, lammps_thermo: str, crop_step: int, inplace: bool = False +) -> Tuple[List[Dict[AnyStr, Any]], Dict[AnyStr, Any]]: """Remove the first steps of a LAMMPS run to remove structures near the starting point. Args: @@ -25,13 +27,17 @@ def crop_lammps_yaml(lammps_dump: str, lammps_thermo: str, crop_step: int, inpla cropped LAMMPS thermodynamic output file """ if not os.path.exists(lammps_dump): - raise ValueError(f'{lammps_dump} does not exist. Please provide a valid LAMMPS dump file as yaml.') + raise ValueError( + f"{lammps_dump} does not exist. Please provide a valid LAMMPS dump file as yaml." + ) if not os.path.exists(lammps_thermo): - raise ValueError(f'{lammps_thermo} does not exist. Please provide a valid LAMMPS thermo log file as yaml.') + raise ValueError( + f"{lammps_thermo} does not exist. Please provide a valid LAMMPS thermo log file as yaml." + ) # get the atom information (positions and forces) from the LAMMPS 'dump' file - with open(lammps_dump, 'r') as f: + with open(lammps_dump, "r") as f: logger.info("loading dump file....") dump_yaml = yaml.load_all(f, Loader=CLoader) logger.info("creating list of documents...") @@ -39,16 +45,18 @@ def crop_lammps_yaml(lammps_dump: str, lammps_thermo: str, crop_step: int, inpla # every MD iteration is saved as a separate document in the yaml file # prepare a dataframe to get all the data if crop_step >= len(dump_yaml): - raise ValueError(f"Trying to remove {crop_step} steps in a run of {len(dump_yaml)} steps.") + raise ValueError( + f"Trying to remove {crop_step} steps in a run of {len(dump_yaml)} steps." + ) logger.info("cropping documents...") dump_yaml = dump_yaml[crop_step:] # get the total energy from the LAMMPS thermodynamic output - with open(lammps_thermo, 'r') as f: + with open(lammps_thermo, "r") as f: logger.info("loading thermo file....") thermo_yaml = yaml.load(f, Loader=CLoader) logger.info("cropping thermo file....") - thermo_yaml['data'] = thermo_yaml['data'][crop_step:] + thermo_yaml["data"] = thermo_yaml["data"][crop_step:] if inplace: with open("test_yaml.yaml", "w") as f: diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/generators/constrained_langevin_generator.py b/src/diffusion_for_multi_scale_molecular_dynamics/generators/constrained_langevin_generator.py index 9cbed410..31ad891a 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/generators/constrained_langevin_generator.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/generators/constrained_langevin_generator.py @@ -2,23 +2,30 @@ import numpy as np import torch -from crystal_diffusion.generators.langevin_generator import LangevinGenerator -from crystal_diffusion.generators.predictor_corrector_position_generator import \ +from tqdm import tqdm + +from diffusion_for_multi_scale_molecular_dynamics.generators.langevin_generator import \ + LangevinGenerator +from diffusion_for_multi_scale_molecular_dynamics.generators.predictor_corrector_position_generator import \ PredictorCorrectorSamplingParameters -from crystal_diffusion.models.score_networks.score_network import ScoreNetwork -from crystal_diffusion.samplers.noisy_relative_coordinates_sampler import \ +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_network import \ + ScoreNetwork +from diffusion_for_multi_scale_molecular_dynamics.samplers.noisy_relative_coordinates_sampler import \ NoisyRelativeCoordinatesSampler -from crystal_diffusion.utils.basis_transformations import \ +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters -from tqdm import tqdm @dataclass(kw_only=True) class ConstrainedLangevinGeneratorParameters(PredictorCorrectorSamplingParameters): """Hyper-parameters for diffusion sampling with the predictor-corrector algorithm.""" - algorithm: str = 'constrained_langevin' - constrained_relative_coordinates: np.ndarray # the positions that must be satisfied at the end of sampling. + + algorithm: str = "constrained_langevin" + constrained_relative_coordinates: ( + np.ndarray + ) # the positions that must be satisfied at the end of sampling. class ConstrainedLangevinGenerator(LangevinGenerator): @@ -29,24 +36,34 @@ class ConstrainedLangevinGenerator(LangevinGenerator): "RePaint: Inpainting using Denoising Diffusion Probabilistic Models". """ - def __init__(self, - noise_parameters: NoiseParameters, - sampling_parameters: ConstrainedLangevinGeneratorParameters, - sigma_normalized_score_network: ScoreNetwork, - ): + def __init__( + self, + noise_parameters: NoiseParameters, + sampling_parameters: ConstrainedLangevinGeneratorParameters, + sigma_normalized_score_network: ScoreNetwork, + ): """Init method.""" - super().__init__(noise_parameters, sampling_parameters, sigma_normalized_score_network) - - self.constraint_relative_coordinates = torch.from_numpy(sampling_parameters.constrained_relative_coordinates) - - assert len(self.constraint_relative_coordinates.shape) == 2, \ - "The constrained relative coordinates have the wrong shape" - - number_of_constraints, spatial_dimensions = self.constraint_relative_coordinates.shape - assert number_of_constraints <= self.number_of_atoms, \ - "There are more constrained positions than atoms!" - assert spatial_dimensions <= self.spatial_dimension, \ - "The spatial dimension of the constrained positions is inconsistent" + super().__init__( + noise_parameters, sampling_parameters, sigma_normalized_score_network + ) + + self.constraint_relative_coordinates = torch.from_numpy( + sampling_parameters.constrained_relative_coordinates + ) + + assert ( + len(self.constraint_relative_coordinates.shape) == 2 + ), "The constrained relative coordinates have the wrong shape" + + number_of_constraints, spatial_dimensions = ( + self.constraint_relative_coordinates.shape + ) + assert ( + number_of_constraints <= self.number_of_atoms + ), "There are more constrained positions than atoms!" + assert ( + spatial_dimensions <= self.spatial_dimension + ), "The spatial dimension of the constrained positions is inconsistent" # Without loss of generality, we impose that the first positions are constrained. # This should have no consequence for a permutation equivariant model. @@ -59,7 +76,9 @@ def _apply_constraint(self, x: torch.Tensor, device: torch.device) -> None: """This method applies the coordinate constraint in place on the input configuration.""" x[:, self.constraint_mask] = self.constraint_relative_coordinates.to(device) - def sample(self, number_of_samples: int, device: torch.device, unit_cell: torch.Tensor) -> torch.Tensor: + def sample( + self, number_of_samples: int, device: torch.device, unit_cell: torch.Tensor + ) -> torch.Tensor: """Sample. This method draws samples, imposing the satisfaction of positional constraints. @@ -73,35 +92,50 @@ def sample(self, number_of_samples: int, device: torch.device, unit_cell: torch. Returns: samples: relative coordinates samples. """ - assert unit_cell.size() == (number_of_samples, self.spatial_dimension, self.spatial_dimension), \ - "Unit cell passed to sample should be of size (number of sample, spatial dimension, spatial dimension" \ + assert unit_cell.size() == ( + number_of_samples, + self.spatial_dimension, + self.spatial_dimension, + ), ( + "Unit cell passed to sample should be of size (number of sample, spatial dimension, spatial dimension" + f"Got {unit_cell.size()}" + ) # Initialize a configuration that satisfy the constraint, but is otherwise random. # Since the noising process is 'atom-per-atom', the non-constrained position should have no impact. - x0_known = map_relative_coordinates_to_unit_cell(self.initialize(number_of_samples)).to(device) + x0_known = map_relative_coordinates_to_unit_cell( + self.initialize(number_of_samples) + ).to(device) self._apply_constraint(x0_known, device) - x_ip1 = map_relative_coordinates_to_unit_cell(self.initialize(number_of_samples)).to(device) + x_ip1 = map_relative_coordinates_to_unit_cell( + self.initialize(number_of_samples) + ).to(device) forces = torch.zeros_like(x_ip1) - broadcasting = torch.ones(number_of_samples, self.number_of_atoms, self.spatial_dimension).to(device) + broadcasting = torch.ones( + number_of_samples, self.number_of_atoms, self.spatial_dimension + ).to(device) for i in tqdm(range(self.number_of_discretization_steps - 1, -1, -1)): sigma_i = self.noise.sigma[i] broadcast_sigmas_i = sigma_i * broadcasting # Noise an example satisfying the constraints from t_0 to t_i - x_i_known = ( - self.noisy_relative_coordinates_sampler.get_noisy_relative_coordinates_sample(x0_known, - broadcast_sigmas_i)) + x_i_known = self.noisy_relative_coordinates_sampler.get_noisy_relative_coordinates_sample( + x0_known, broadcast_sigmas_i + ) # Denoise from t_{i+1} to t_i - x_i = map_relative_coordinates_to_unit_cell(self.predictor_step(x_ip1, i + 1, unit_cell, forces)) + x_i = map_relative_coordinates_to_unit_cell( + self.predictor_step(x_ip1, i + 1, unit_cell, forces) + ) # Combine the known and unknown x_i[:, self.constraint_mask] = x_i_known[:, self.constraint_mask] for _ in range(self.number_of_corrector_steps): - x_i = map_relative_coordinates_to_unit_cell(self.corrector_step(x_i, i, unit_cell, forces)) + x_i = map_relative_coordinates_to_unit_cell( + self.corrector_step(x_i, i, unit_cell, forces) + ) x_ip1 = x_i # apply the constraint one last time diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/generators/instantiate_generator.py b/src/diffusion_for_multi_scale_molecular_dynamics/generators/instantiate_generator.py index d7d15501..dfdaf083 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/generators/instantiate_generator.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/generators/instantiate_generator.py @@ -1,37 +1,51 @@ -from crystal_diffusion.generators.langevin_generator import LangevinGenerator -from crystal_diffusion.generators.ode_position_generator import \ +from diffusion_for_multi_scale_molecular_dynamics.generators.langevin_generator import \ + LangevinGenerator +from diffusion_for_multi_scale_molecular_dynamics.generators.ode_position_generator import \ ExplodingVarianceODEPositionGenerator -from crystal_diffusion.generators.sde_position_generator import \ - ExplodingVarianceSDEPositionGenerator -from crystal_diffusion.models.score_networks import ScoreNetwork -from src.crystal_diffusion.generators.position_generator import \ +from diffusion_for_multi_scale_molecular_dynamics.generators.position_generator import \ SamplingParameters -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters +from diffusion_for_multi_scale_molecular_dynamics.generators.sde_position_generator import \ + ExplodingVarianceSDEPositionGenerator +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_network import \ + ScoreNetwork +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters -def instantiate_generator(sampling_parameters: SamplingParameters, - noise_parameters: NoiseParameters, - sigma_normalized_score_network: ScoreNetwork): +def instantiate_generator( + sampling_parameters: SamplingParameters, + noise_parameters: NoiseParameters, + sigma_normalized_score_network: ScoreNetwork, +): """Instantiate generator.""" - assert sampling_parameters.algorithm in ['ode', 'sde', 'predictor_corrector'], \ - "Unknown algorithm. Possible choices are 'ode', 'sde' and 'predictor_corrector'" + assert sampling_parameters.algorithm in [ + "ode", + "sde", + "predictor_corrector", + ], "Unknown algorithm. Possible choices are 'ode', 'sde' and 'predictor_corrector'" match sampling_parameters.algorithm: - case 'predictor_corrector': - generator = LangevinGenerator(sampling_parameters=sampling_parameters, - noise_parameters=noise_parameters, - sigma_normalized_score_network=sigma_normalized_score_network) - case 'ode': + case "predictor_corrector": + generator = LangevinGenerator( + sampling_parameters=sampling_parameters, + noise_parameters=noise_parameters, + sigma_normalized_score_network=sigma_normalized_score_network, + ) + case "ode": generator = ExplodingVarianceODEPositionGenerator( sampling_parameters=sampling_parameters, noise_parameters=noise_parameters, - sigma_normalized_score_network=sigma_normalized_score_network) - case 'sde': + sigma_normalized_score_network=sigma_normalized_score_network, + ) + case "sde": generator = ExplodingVarianceSDEPositionGenerator( sampling_parameters=sampling_parameters, noise_parameters=noise_parameters, - sigma_normalized_score_network=sigma_normalized_score_network) + sigma_normalized_score_network=sigma_normalized_score_network, + ) case _: - raise NotImplementedError(f"algorithm '{sampling_parameters.algorithm}' is not implemented") + raise NotImplementedError( + f"algorithm '{sampling_parameters.algorithm}' is not implemented" + ) return generator diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/generators/langevin_generator.py b/src/diffusion_for_multi_scale_molecular_dynamics/generators/langevin_generator.py index ed7d9888..e68dd2e2 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/generators/langevin_generator.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/generators/langevin_generator.py @@ -1,14 +1,15 @@ import torch -from crystal_diffusion.generators.predictor_corrector_position_generator import ( + +from diffusion_for_multi_scale_molecular_dynamics.generators.predictor_corrector_position_generator import ( PredictorCorrectorPositionGenerator, PredictorCorrectorSamplingParameters) -from crystal_diffusion.models.score_networks.score_network import ScoreNetwork -from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, - NOISY_RELATIVE_COORDINATES, TIME, - UNIT_CELL) -from crystal_diffusion.utils.sample_trajectory import ( - NoOpPredictorCorrectorSampleTrajectory, PredictorCorrectorSampleTrajectory) -from src.crystal_diffusion.samplers.variance_sampler import ( +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_network import \ + ScoreNetwork +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import ( ExplodingVarianceSampler, NoiseParameters) +from diffusion_for_multi_scale_molecular_dynamics.utils.sample_trajectory import ( + NoOpPredictorCorrectorSampleTrajectory, PredictorCorrectorSampleTrajectory) class LangevinGenerator(PredictorCorrectorPositionGenerator): @@ -19,15 +20,18 @@ class LangevinGenerator(PredictorCorrectorPositionGenerator): "Generative Modeling by Estimating Gradients of the Data Distribution" """ - def __init__(self, - noise_parameters: NoiseParameters, - sampling_parameters: PredictorCorrectorSamplingParameters, - sigma_normalized_score_network: ScoreNetwork, - ): + def __init__( + self, + noise_parameters: NoiseParameters, + sampling_parameters: PredictorCorrectorSamplingParameters, + sigma_normalized_score_network: ScoreNetwork, + ): """Init method.""" - super().__init__(number_of_discretization_steps=noise_parameters.total_time_steps, - number_of_corrector_steps=sampling_parameters.number_of_corrector_steps, - spatial_dimension=sampling_parameters.spatial_dimension) + super().__init__( + number_of_discretization_steps=noise_parameters.total_time_steps, + number_of_corrector_steps=sampling_parameters.number_of_corrector_steps, + spatial_dimension=sampling_parameters.spatial_dimension, + ) self.noise_parameters = noise_parameters sampler = ExplodingVarianceSampler(noise_parameters) @@ -42,15 +46,24 @@ def __init__(self, def initialize(self, number_of_samples: int): """This method must initialize the samples from the fully noised distribution.""" - relative_coordinates = torch.rand(number_of_samples, self.number_of_atoms, self.spatial_dimension) + relative_coordinates = torch.rand( + number_of_samples, self.number_of_atoms, self.spatial_dimension + ) return relative_coordinates def _draw_gaussian_sample(self, number_of_samples): - return torch.randn(number_of_samples, self.number_of_atoms, self.spatial_dimension) - - def _get_sigma_normalized_scores(self, x: torch.Tensor, time: float, - noise: float, unit_cell: torch.Tensor, cartesian_forces: torch.Tensor - ) -> torch.Tensor: + return torch.randn( + number_of_samples, self.number_of_atoms, self.spatial_dimension + ) + + def _get_sigma_normalized_scores( + self, + x: torch.Tensor, + time: float, + noise: float, + unit_cell: torch.Tensor, + cartesian_forces: torch.Tensor, + ) -> torch.Tensor: """Get sigma normalized scores. Args: @@ -69,15 +82,27 @@ def _get_sigma_normalized_scores(self, x: torch.Tensor, time: float, time_tensor = time * torch.ones(number_of_samples, 1).to(x) noise_tensor = noise * torch.ones(number_of_samples, 1).to(x) - augmented_batch = {NOISY_RELATIVE_COORDINATES: x, TIME: time_tensor, NOISE: noise_tensor, UNIT_CELL: unit_cell, - CARTESIAN_FORCES: cartesian_forces} + augmented_batch = { + NOISY_RELATIVE_COORDINATES: x, + TIME: time_tensor, + NOISE: noise_tensor, + UNIT_CELL: unit_cell, + CARTESIAN_FORCES: cartesian_forces, + } # TODO do not hard-code conditional to False - need to be able to condition sampling - predicted_normalized_scores = self.sigma_normalized_score_network(augmented_batch, conditional=False) + predicted_normalized_scores = self.sigma_normalized_score_network( + augmented_batch, conditional=False + ) return predicted_normalized_scores - def predictor_step(self, x_i: torch.Tensor, index_i: int, unit_cell: torch.Tensor, cartesian_forces: torch.Tensor - ) -> torch.Tensor: + def predictor_step( + self, + x_i: torch.Tensor, + index_i: int, + unit_cell: torch.Tensor, + cartesian_forces: torch.Tensor, + ) -> torch.Tensor: """Predictor step. Args: @@ -89,8 +114,9 @@ def predictor_step(self, x_i: torch.Tensor, index_i: int, unit_cell: torch.Tenso Returns: x_im1 : sampled relative coordinates, at time step i - 1. """ - assert 1 <= index_i <= self.number_of_discretization_steps, \ - "The predictor step can only be invoked for index_i between 1 and the total number of discretization steps." + assert ( + 1 <= index_i <= self.number_of_discretization_steps + ), "The predictor step can only be invoked for index_i between 1 and the total number of discretization steps." number_of_samples = x_i.shape[0] z = self._draw_gaussian_sample(number_of_samples).to(x_i) @@ -100,17 +126,30 @@ def predictor_step(self, x_i: torch.Tensor, index_i: int, unit_cell: torch.Tenso g_i = self.noise.g[idx].to(x_i) g2_i = self.noise.g_squared[idx].to(x_i) sigma_i = self.noise.sigma[idx].to(x_i) - sigma_score_i = self._get_sigma_normalized_scores(x_i, t_i, sigma_i, unit_cell, cartesian_forces) + sigma_score_i = self._get_sigma_normalized_scores( + x_i, t_i, sigma_i, unit_cell, cartesian_forces + ) x_im1 = x_i + g2_i / sigma_i * sigma_score_i + g_i * z self.sample_trajectory_recorder.record_unit_cell(unit_cell=unit_cell) - self.sample_trajectory_recorder.record_predictor_step(i_index=index_i, time=t_i, sigma=sigma_i, - x_i=x_i, x_im1=x_im1, scores=sigma_score_i) + self.sample_trajectory_recorder.record_predictor_step( + i_index=index_i, + time=t_i, + sigma=sigma_i, + x_i=x_i, + x_im1=x_im1, + scores=sigma_score_i, + ) return x_im1 - def corrector_step(self, x_i: torch.Tensor, index_i: int, unit_cell: torch.Tensor, cartesian_forces: torch.Tensor - ) -> torch.Tensor: + def corrector_step( + self, + x_i: torch.Tensor, + index_i: int, + unit_cell: torch.Tensor, + cartesian_forces: torch.Tensor, + ) -> torch.Tensor: """Corrector Step. Args: @@ -122,9 +161,10 @@ def corrector_step(self, x_i: torch.Tensor, index_i: int, unit_cell: torch.Tenso Returns: corrected x_i : sampled relative coordinates, after corrector step. """ - assert 0 <= index_i <= self.number_of_discretization_steps - 1, \ - ("The corrector step can only be invoked for index_i between 0 and " - "the total number of discretization steps minus 1.") + assert 0 <= index_i <= self.number_of_discretization_steps - 1, ( + "The corrector step can only be invoked for index_i between 0 and " + "the total number of discretization steps minus 1." + ) number_of_samples = x_i.shape[0] z = self._draw_gaussian_sample(number_of_samples).to(x_i) @@ -135,19 +175,28 @@ def corrector_step(self, x_i: torch.Tensor, index_i: int, unit_cell: torch.Tenso if index_i == 0: # TODO: we are extrapolating here; the score network will never have seen this time step... - sigma_i = self.noise_parameters.sigma_min # no need to change device, this is a float - t_i = 0. # same for device - this is a float + sigma_i = ( + self.noise_parameters.sigma_min + ) # no need to change device, this is a float + t_i = 0.0 # same for device - this is a float else: idx = index_i - 1 # python starts indices at zero sigma_i = self.noise.sigma[idx].to(x_i) t_i = self.noise.time[idx].to(x_i) - sigma_score_i = self._get_sigma_normalized_scores(x_i, t_i, sigma_i, unit_cell, cartesian_forces) + sigma_score_i = self._get_sigma_normalized_scores( + x_i, t_i, sigma_i, unit_cell, cartesian_forces + ) corrected_x_i = x_i + eps_i / sigma_i * sigma_score_i + sqrt_2eps_i * z - self.sample_trajectory_recorder.record_corrector_step(i_index=index_i, time=t_i, - sigma=sigma_i, x_i=x_i, corrected_x_i=corrected_x_i, - scores=sigma_score_i) + self.sample_trajectory_recorder.record_corrector_step( + i_index=index_i, + time=t_i, + sigma=sigma_i, + x_i=x_i, + corrected_x_i=corrected_x_i, + scores=sigma_score_i, + ) return corrected_x_i diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/generators/load_sampling_parameters.py b/src/diffusion_for_multi_scale_molecular_dynamics/generators/load_sampling_parameters.py index ef633bf8..57d841e8 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/generators/load_sampling_parameters.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/generators/load_sampling_parameters.py @@ -1,16 +1,18 @@ from typing import Any, AnyStr, Dict -from crystal_diffusion.generators.ode_position_generator import \ +from diffusion_for_multi_scale_molecular_dynamics.generators.ode_position_generator import \ ODESamplingParameters -from crystal_diffusion.generators.predictor_corrector_position_generator import \ +from diffusion_for_multi_scale_molecular_dynamics.generators.position_generator import \ + SamplingParameters +from diffusion_for_multi_scale_molecular_dynamics.generators.predictor_corrector_position_generator import \ PredictorCorrectorSamplingParameters -from crystal_diffusion.generators.sde_position_generator import \ +from diffusion_for_multi_scale_molecular_dynamics.generators.sde_position_generator import \ SDESamplingParameters -from src.crystal_diffusion.generators.position_generator import \ - SamplingParameters -def load_sampling_parameters(sampling_parameter_dictionary: Dict[AnyStr, Any]) -> SamplingParameters: +def load_sampling_parameters( + sampling_parameter_dictionary: Dict[AnyStr, Any] +) -> SamplingParameters: """Load sampling parameters. Extract the needed information from the configuration dictionary. @@ -21,18 +23,25 @@ def load_sampling_parameters(sampling_parameter_dictionary: Dict[AnyStr, Any]) - Returns: sampling_parameters: the relevant configuration object. """ - assert 'algorithm' in sampling_parameter_dictionary, "The sampling parameters must select an algorithm." - algorithm = sampling_parameter_dictionary['algorithm'] + assert ( + "algorithm" in sampling_parameter_dictionary + ), "The sampling parameters must select an algorithm." + algorithm = sampling_parameter_dictionary["algorithm"] - assert algorithm in ['ode', 'sde', 'predictor_corrector'], \ - "Unknown algorithm. Possible choices are 'ode', 'sde' and 'predictor_corrector'" + assert algorithm in [ + "ode", + "sde", + "predictor_corrector", + ], "Unknown algorithm. Possible choices are 'ode', 'sde' and 'predictor_corrector'" match algorithm: - case 'predictor_corrector': - sampling_parameters = PredictorCorrectorSamplingParameters(**sampling_parameter_dictionary) - case 'ode': + case "predictor_corrector": + sampling_parameters = PredictorCorrectorSamplingParameters( + **sampling_parameter_dictionary + ) + case "ode": sampling_parameters = ODESamplingParameters(**sampling_parameter_dictionary) - case 'sde': + case "sde": sampling_parameters = SDESamplingParameters(**sampling_parameter_dictionary) case _: raise NotImplementedError(f"algorithm '{algorithm}' is not implemented") diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/generators/ode_position_generator.py b/src/diffusion_for_multi_scale_molecular_dynamics/generators/ode_position_generator.py index 49f53ebf..d8f1bc2c 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/generators/ode_position_generator.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/generators/ode_position_generator.py @@ -5,28 +5,35 @@ import einops import torch import torchode as to -from crystal_diffusion.models.score_networks.score_network import ScoreNetwork -from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, - NOISY_RELATIVE_COORDINATES, TIME, - UNIT_CELL) -from crystal_diffusion.utils.basis_transformations import \ - map_relative_coordinates_to_unit_cell -from crystal_diffusion.utils.sample_trajectory import (NoOpODESampleTrajectory, - ODESampleTrajectory) -from src.crystal_diffusion.generators.position_generator import ( - PositionGenerator, SamplingParameters) -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters from torchode import Solution +from diffusion_for_multi_scale_molecular_dynamics.generators.position_generator import ( + PositionGenerator, SamplingParameters) +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_network import \ + ScoreNetwork +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ + map_relative_coordinates_to_unit_cell +from diffusion_for_multi_scale_molecular_dynamics.utils.sample_trajectory import ( + NoOpODESampleTrajectory, ODESampleTrajectory) + logger = logging.getLogger(__name__) @dataclass(kw_only=True) class ODESamplingParameters(SamplingParameters): """Hyper-parameters for diffusion sampling with the ode algorithm.""" - algorithm: str = 'ode' - absolute_solver_tolerance: float = 1.0e-3 # the absolute error tolerance passed to the ODE solver. - relative_solver_tolerance: float = 1.0e-2 # the relative error tolerance passed to the ODE solver. + + algorithm: str = "ode" + absolute_solver_tolerance: float = ( + 1.0e-3 # the absolute error tolerance passed to the ODE solver. + ) + relative_solver_tolerance: float = ( + 1.0e-2 # the relative error tolerance passed to the ODE solver. + ) class ExplodingVarianceODEPositionGenerator(PositionGenerator): @@ -36,11 +43,12 @@ class ExplodingVarianceODEPositionGenerator(PositionGenerator): It assumes that the diffusion noise is parameterized in the 'Exploding Variance' scheme. """ - def __init__(self, - noise_parameters: NoiseParameters, - sampling_parameters: ODESamplingParameters, - sigma_normalized_score_network: ScoreNetwork, - ): + def __init__( + self, + noise_parameters: NoiseParameters, + sampling_parameters: ODESamplingParameters, + sigma_normalized_score_network: ScoreNetwork, + ): """Init method. Args: @@ -54,8 +62,9 @@ def __init__(self, self.noise_parameters = noise_parameters self.sigma_normalized_score_network = sigma_normalized_score_network - assert self.noise_parameters.total_time_steps >= 2, \ - "There must at least be two time steps in the noise parameters to define the limits t0 and tf." + assert ( + self.noise_parameters.total_time_steps >= 2 + ), "There must at least be two time steps in the noise parameters to define the limits t0 and tf." self.number_of_atoms = sampling_parameters.number_of_atoms self.spatial_dimension = sampling_parameters.spatial_dimension self.absolute_solver_tolerance = sampling_parameters.absolute_solver_tolerance @@ -80,7 +89,10 @@ def _get_exploding_variance_sigma(self, times): Returns: sigmas: value of the noise parameter. """ - sigmas = self.noise_parameters.sigma_min ** (1.0 - times) * self.noise_parameters.sigma_max ** times + sigmas = ( + self.noise_parameters.sigma_min ** (1.0 - times) + * self.noise_parameters.sigma_max**times + ) return sigmas def _get_ode_prefactor(self, sigmas): @@ -107,14 +119,20 @@ def _get_ode_prefactor(self, sigmas): Returns: ode prefactor: the prefactor in the ODE. """ - log_ratio = torch.log(torch.tensor(self.noise_parameters.sigma_max / self.noise_parameters.sigma_min)) + log_ratio = torch.log( + torch.tensor( + self.noise_parameters.sigma_max / self.noise_parameters.sigma_min + ) + ) ode_prefactor = log_ratio * sigmas return ode_prefactor def generate_ode_term(self, unit_cell: torch.Tensor) -> Callable: """Generate the ode_term needed to compute the ODE solution.""" - def ode_term(times: torch.Tensor, flat_relative_coordinates: torch.Tensor) -> torch.Tensor: + def ode_term( + times: torch.Tensor, flat_relative_coordinates: torch.Tensor + ) -> torch.Tensor: """ODE term. This function is in the format required by the ODE solver. @@ -131,28 +149,38 @@ def ode_term(times: torch.Tensor, flat_relative_coordinates: torch.Tensor) -> to sigmas = self._get_exploding_variance_sigma(times) ode_prefactor = self._get_ode_prefactor(sigmas) - relative_coordinates = einops.rearrange(flat_relative_coordinates, - "batch (natom space) -> batch natom space", - natom=self.number_of_atoms, - space=self.spatial_dimension) - - batch = {NOISY_RELATIVE_COORDINATES: map_relative_coordinates_to_unit_cell(relative_coordinates), - NOISE: sigmas.unsqueeze(-1), - TIME: times.unsqueeze(-1), - UNIT_CELL: unit_cell, - CARTESIAN_FORCES: torch.zeros_like(relative_coordinates) # TODO: handle forces correctly. - } + relative_coordinates = einops.rearrange( + flat_relative_coordinates, + "batch (natom space) -> batch natom space", + natom=self.number_of_atoms, + space=self.spatial_dimension, + ) + + batch = { + NOISY_RELATIVE_COORDINATES: map_relative_coordinates_to_unit_cell( + relative_coordinates + ), + NOISE: sigmas.unsqueeze(-1), + TIME: times.unsqueeze(-1), + UNIT_CELL: unit_cell, + CARTESIAN_FORCES: torch.zeros_like( + relative_coordinates + ), # TODO: handle forces correctly. + } # Shape [batch_size, number of atoms, spatial dimension] sigma_normalized_scores = self.sigma_normalized_score_network(batch) - flat_sigma_normalized_scores = einops.rearrange(sigma_normalized_scores, - "batch natom space -> batch (natom space)") + flat_sigma_normalized_scores = einops.rearrange( + sigma_normalized_scores, "batch natom space -> batch (natom space)" + ) return -ode_prefactor.unsqueeze(-1) * flat_sigma_normalized_scores return ode_term - def sample(self, number_of_samples: int, device: torch.device, unit_cell: torch.Tensor) -> torch.Tensor: + def sample( + self, number_of_samples: int, device: torch.device, unit_cell: torch.Tensor + ) -> torch.Tensor: """Sample. This method draws a position sample. @@ -168,21 +196,30 @@ def sample(self, number_of_samples: int, device: torch.device, unit_cell: torch. """ ode_term = self.generate_ode_term(unit_cell) - initial_relative_coordinates = ( - map_relative_coordinates_to_unit_cell(self.initialize(number_of_samples)).to(device)) + initial_relative_coordinates = map_relative_coordinates_to_unit_cell( + self.initialize(number_of_samples) + ).to(device) - y0 = einops.rearrange(initial_relative_coordinates, 'batch natom space -> batch (natom space)') + y0 = einops.rearrange( + initial_relative_coordinates, "batch natom space -> batch (natom space)" + ) - evaluation_times = torch.linspace(self.tf, self.t0, self.noise_parameters.total_time_steps).to(device) + evaluation_times = torch.linspace( + self.tf, self.t0, self.noise_parameters.total_time_steps + ).to(device) - t_eval = einops.repeat(evaluation_times, 't -> batch t', batch=number_of_samples) + t_eval = einops.repeat( + evaluation_times, "t -> batch t", batch=number_of_samples + ) term = to.ODETerm(ode_term) step_method = to.Dopri5(term=term) - step_size_controller = to.IntegralController(atol=self.absolute_solver_tolerance, - rtol=self.relative_solver_tolerance, - term=term) + step_size_controller = to.IntegralController( + atol=self.absolute_solver_tolerance, + rtol=self.relative_solver_tolerance, + term=term, + ) solver = to.AutoDiffAdjoint(step_method, step_size_controller) # jit_solver = torch.compile(solver) # Compilation is not necessary, and breaks on the cluster... jit_solver = solver @@ -198,14 +235,22 @@ def sample(self, number_of_samples: int, device: torch.device, unit_cell: torch. # only the final time (ie, t0) is the real sample. flat_relative_coordinates = sol.ys[:, -1, :] - relative_coordinates = einops.rearrange(flat_relative_coordinates, - 'batch (natom space) -> batch natom space', - natom=self.number_of_atoms, - space=self.spatial_dimension) + relative_coordinates = einops.rearrange( + flat_relative_coordinates, + "batch (natom space) -> batch natom space", + natom=self.number_of_atoms, + space=self.spatial_dimension, + ) return map_relative_coordinates_to_unit_cell(relative_coordinates) - def record_sample(self, ode_term: Callable, sol: Solution, evaluation_times: torch.Tensor, unit_cell: torch.Tensor): + def record_sample( + self, + ode_term: Callable, + sol: Solution, + evaluation_times: torch.Tensor, + unit_cell: torch.Tensor, + ): """Record sample. This method takes care of recomputing the normalized score on the solution trajectory and record it to the @@ -222,30 +267,41 @@ def record_sample(self, ode_term: Callable, sol: Solution, evaluation_times: tor number_of_samples = sol.ys.shape[0] self.sample_trajectory_recorder.record_unit_cell(unit_cell) - record_relative_coordinates = einops.rearrange(sol.ys, - 'batch times (natom space) -> batch times natom space', - natom=self.number_of_atoms, - space=self.spatial_dimension) + record_relative_coordinates = einops.rearrange( + sol.ys, + "batch times (natom space) -> batch times natom space", + natom=self.number_of_atoms, + space=self.spatial_dimension, + ) sigmas = self._get_exploding_variance_sigma(evaluation_times) ode_prefactor = self._get_ode_prefactor(sigmas) list_flat_normalized_scores = [] for time_idx, (time, gamma) in enumerate(zip(evaluation_times, ode_prefactor)): times = time * torch.ones(number_of_samples).to(sol.ys) # The score network must be called again to get scores at intermediate times - flat_normalized_score = -ode_term(times=times, - flat_relative_coordinates=sol.ys[:, time_idx]) / gamma + flat_normalized_score = ( + -ode_term(times=times, flat_relative_coordinates=sol.ys[:, time_idx]) + / gamma + ) list_flat_normalized_scores.append(flat_normalized_score) - record_normalized_scores = einops.rearrange(torch.stack(list_flat_normalized_scores), - "time batch (natom space) -> batch time natom space", - natom=self.number_of_atoms, space=self.spatial_dimension) - self.sample_trajectory_recorder.record_ode_solution(times=evaluation_times, - sigmas=sigmas, - relative_coordinates=record_relative_coordinates, - normalized_scores=record_normalized_scores, - stats=sol.stats, - status=sol.status) + record_normalized_scores = einops.rearrange( + torch.stack(list_flat_normalized_scores), + "time batch (natom space) -> batch time natom space", + natom=self.number_of_atoms, + space=self.spatial_dimension, + ) + self.sample_trajectory_recorder.record_ode_solution( + times=evaluation_times, + sigmas=sigmas, + relative_coordinates=record_relative_coordinates, + normalized_scores=record_normalized_scores, + stats=sol.stats, + status=sol.status, + ) def initialize(self, number_of_samples: int): """This method must initialize the samples from the fully noised distribution.""" - relative_coordinates = torch.rand(number_of_samples, self.number_of_atoms, self.spatial_dimension) + relative_coordinates = torch.rand( + number_of_samples, self.number_of_atoms, self.spatial_dimension + ) return relative_coordinates diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/generators/position_generator.py b/src/diffusion_for_multi_scale_molecular_dynamics/generators/position_generator.py index 6ae2dd39..b102227a 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/generators/position_generator.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/generators/position_generator.py @@ -8,22 +8,31 @@ @dataclass(kw_only=True) class SamplingParameters: """Hyper-parameters for diffusion sampling.""" + algorithm: str spatial_dimension: int = 3 # the dimension of Euclidean space where atoms live. - number_of_atoms: int # the number of atoms that must be generated in a sampled configuration. + number_of_atoms: ( + int # the number of atoms that must be generated in a sampled configuration. + ) number_of_samples: int # iterate up to number_of_samples with batches of this size # if None, use number_of_samples as batchsize sample_batchsize: Optional[int] = None - cell_dimensions: List[float] # unit cell dimensions; the unit cell is assumed to be an orthogonal box. - record_samples: bool = False # should the predictor and corrector steps be recorded to a file + cell_dimensions: List[ + float + ] # unit cell dimensions; the unit cell is assumed to be an orthogonal box. + record_samples: bool = ( + False # should the predictor and corrector steps be recorded to a file + ) class PositionGenerator(ABC): """This defines the interface for position generators.""" @abstractmethod - def sample(self, number_of_samples: int, device: torch.device, unit_cell: torch.Tensor) -> torch.Tensor: + def sample( + self, number_of_samples: int, device: torch.device, unit_cell: torch.Tensor + ) -> torch.Tensor: """Sample. This method draws a position sample. diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/generators/predictor_corrector_position_generator.py b/src/diffusion_for_multi_scale_molecular_dynamics/generators/predictor_corrector_position_generator.py index ba7178cc..f8c8a582 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/generators/predictor_corrector_position_generator.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/generators/predictor_corrector_position_generator.py @@ -3,36 +3,49 @@ from dataclasses import dataclass import torch -from crystal_diffusion.utils.basis_transformations import \ - map_relative_coordinates_to_unit_cell -from src.crystal_diffusion.generators.position_generator import ( - PositionGenerator, SamplingParameters) from tqdm import tqdm +from diffusion_for_multi_scale_molecular_dynamics.generators.position_generator import ( + PositionGenerator, SamplingParameters) +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ + map_relative_coordinates_to_unit_cell + logger = logging.getLogger(__name__) @dataclass(kw_only=True) class PredictorCorrectorSamplingParameters(SamplingParameters): """Hyper-parameters for diffusion sampling with the predictor-corrector algorithm.""" - algorithm: str = 'predictor_corrector' + + algorithm: str = "predictor_corrector" number_of_corrector_steps: int = 1 class PredictorCorrectorPositionGenerator(PositionGenerator): """This defines the interface for predictor-corrector position generators.""" - def __init__(self, number_of_discretization_steps: int, number_of_corrector_steps: int, spatial_dimension: int, - **kwargs): + def __init__( + self, + number_of_discretization_steps: int, + number_of_corrector_steps: int, + spatial_dimension: int, + **kwargs, + ): """Init method.""" - assert number_of_discretization_steps > 0, "The number of discretization steps should be larger than zero" - assert number_of_corrector_steps >= 0, "The number of corrector steps should be non-negative" + assert ( + number_of_discretization_steps > 0 + ), "The number of discretization steps should be larger than zero" + assert ( + number_of_corrector_steps >= 0 + ), "The number of corrector steps should be non-negative" self.number_of_discretization_steps = number_of_discretization_steps self.number_of_corrector_steps = number_of_corrector_steps self.spatial_dimension = spatial_dimension - def sample(self, number_of_samples: int, device: torch.device, unit_cell: torch.Tensor) -> torch.Tensor: + def sample( + self, number_of_samples: int, device: torch.device, unit_cell: torch.Tensor + ) -> torch.Tensor: """Sample. This method draws a sample using the PC sampler algorithm. @@ -46,23 +59,39 @@ def sample(self, number_of_samples: int, device: torch.device, unit_cell: torch. Returns: samples: relative coordinates samples. """ - assert unit_cell.size() == (number_of_samples, self.spatial_dimension, self.spatial_dimension), \ - "Unit cell passed to sample should be of size (number of sample, spatial dimension, spatial dimension" \ + assert unit_cell.size() == ( + number_of_samples, + self.spatial_dimension, + self.spatial_dimension, + ), ( + "Unit cell passed to sample should be of size (number of sample, spatial dimension, spatial dimension" + f"Got {unit_cell.size()}" + ) - x_ip1 = map_relative_coordinates_to_unit_cell(self.initialize(number_of_samples)).to(device) + x_ip1 = map_relative_coordinates_to_unit_cell( + self.initialize(number_of_samples) + ).to(device) forces = torch.zeros_like(x_ip1) for i in tqdm(range(self.number_of_discretization_steps - 1, -1, -1)): - x_i = map_relative_coordinates_to_unit_cell(self.predictor_step(x_ip1, i + 1, unit_cell, forces)) + x_i = map_relative_coordinates_to_unit_cell( + self.predictor_step(x_ip1, i + 1, unit_cell, forces) + ) for _ in range(self.number_of_corrector_steps): - x_i = map_relative_coordinates_to_unit_cell(self.corrector_step(x_i, i, unit_cell, forces)) + x_i = map_relative_coordinates_to_unit_cell( + self.corrector_step(x_i, i, unit_cell, forces) + ) x_ip1 = x_i return x_i @abstractmethod - def predictor_step(self, x_ip1: torch.Tensor, ip1: int, unit_cell: torch.Tensor, cartesian_forces: torch.Tensor - ) -> torch.Tensor: + def predictor_step( + self, + x_ip1: torch.Tensor, + ip1: int, + unit_cell: torch.Tensor, + cartesian_forces: torch.Tensor, + ) -> torch.Tensor: """Predictor step. It is assumed that there are N predictor steps, with index "i" running from N-1 to 0. @@ -79,8 +108,13 @@ def predictor_step(self, x_ip1: torch.Tensor, ip1: int, unit_cell: torch.Tensor, pass @abstractmethod - def corrector_step(self, x_i: torch.Tensor, i: int, unit_cell: torch.Tensor, cartesian_forces: torch.Tensor - ) -> torch.Tensor: + def corrector_step( + self, + x_i: torch.Tensor, + i: int, + unit_cell: torch.Tensor, + cartesian_forces: torch.Tensor, + ) -> torch.Tensor: """Corrector step. It is assumed that there are N predictor steps, with index "i" running from N-1 to 0. diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/generators/sde_position_generator.py b/src/diffusion_for_multi_scale_molecular_dynamics/generators/sde_position_generator.py index f2432942..4b9dcab7 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/generators/sde_position_generator.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/generators/sde_position_generator.py @@ -4,16 +4,19 @@ import einops import torch import torchsde -from crystal_diffusion.models.score_networks import ScoreNetwork -from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, - NOISY_RELATIVE_COORDINATES, TIME, - UNIT_CELL) -from crystal_diffusion.utils.basis_transformations import \ - map_relative_coordinates_to_unit_cell -from crystal_diffusion.utils.sample_trajectory import SDESampleTrajectory -from src.crystal_diffusion.generators.position_generator import ( + +from diffusion_for_multi_scale_molecular_dynamics.generators.position_generator import ( PositionGenerator, SamplingParameters) -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks import \ + ScoreNetwork +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ + map_relative_coordinates_to_unit_cell +from diffusion_for_multi_scale_molecular_dynamics.utils.sample_trajectory import \ + SDESampleTrajectory logger = logging.getLogger(__name__) @@ -21,12 +24,17 @@ @dataclass(kw_only=True) class SDESamplingParameters(SamplingParameters): """Hyper-parameters for diffusion sampling with the sde algorithm.""" - algorithm: str = 'sde' - sde_type: str = 'ito' - method: str = 'euler' + + algorithm: str = "sde" + sde_type: str = "ito" + method: str = "euler" adaptative: bool = False - absolute_solver_tolerance: float = 1.0e-7 # the absolute error tolerance passed to the SDE solver. - relative_solver_tolerance: float = 1.0e-5 # the relative error tolerance passed to the SDE solver. + absolute_solver_tolerance: float = ( + 1.0e-7 # the absolute error tolerance passed to the SDE solver. + ) + relative_solver_tolerance: float = ( + 1.0e-5 # the relative error tolerance passed to the SDE solver. + ) class SDE(torch.nn.Module): @@ -35,16 +43,19 @@ class SDE(torch.nn.Module): This class computes the drift and the diffusion coefficients in order to be consistent with the expectations of the torchsde library. """ - noise_type = 'diagonal' # we assume that there is a distinct Wiener process for each component. - sde_type = 'ito' - - def __init__(self, - noise_parameters: NoiseParameters, - sampling_parameters: SDESamplingParameters, - sigma_normalized_score_network: ScoreNetwork, - unit_cells: torch.Tensor, - initial_diffusion_time: torch.Tensor, - final_diffusion_time: torch.Tensor): + + noise_type = "diagonal" # we assume that there is a distinct Wiener process for each component. + sde_type = "ito" + + def __init__( + self, + noise_parameters: NoiseParameters, + sampling_parameters: SDESamplingParameters, + sigma_normalized_score_network: ScoreNetwork, + unit_cells: torch.Tensor, + initial_diffusion_time: torch.Tensor, + final_diffusion_time: torch.Tensor, + ): """Init method. This class will provide drift and diffusion for the torchsde solver. The SDE will be solved @@ -69,7 +80,9 @@ def __init__(self, self.initial_diffusion_time = initial_diffusion_time self.final_diffusion_time = final_diffusion_time - def _get_exploding_variance_sigma(self, diffusion_time: torch.Tensor) -> torch.Tensor: + def _get_exploding_variance_sigma( + self, diffusion_time: torch.Tensor + ) -> torch.Tensor: """Get Exploding Variance Sigma. In the 'exploding variance' scheme, the noise is defined by @@ -82,11 +95,15 @@ def _get_exploding_variance_sigma(self, diffusion_time: torch.Tensor) -> torch.T Returns: sigma: value of the noise parameter. """ - sigma = (self.noise_parameters.sigma_min ** (1.0 - diffusion_time) - * self.noise_parameters.sigma_max ** diffusion_time) + sigma = ( + self.noise_parameters.sigma_min ** (1.0 - diffusion_time) + * self.noise_parameters.sigma_max**diffusion_time + ) return sigma - def _get_diffusion_coefficient_g_squared(self, diffusion_time: torch.Tensor) -> torch.Tensor: + def _get_diffusion_coefficient_g_squared( + self, diffusion_time: torch.Tensor + ) -> torch.Tensor: """Get diffusion coefficient g(t)^2. The noise is given by sigma(t) = sigma_{min} (sigma_{max} / sigma_{min})^t @@ -99,9 +116,11 @@ def _get_diffusion_coefficient_g_squared(self, diffusion_time: torch.Tensor) -> coefficient_g : the coefficient g(t) """ s_min = torch.tensor(self.noise_parameters.sigma_min) - ratio = torch.tensor(self.noise_parameters.sigma_max / self.noise_parameters.sigma_min) + ratio = torch.tensor( + self.noise_parameters.sigma_max / self.noise_parameters.sigma_min + ) - g_squared = 2.0 * (s_min * ratio ** diffusion_time) ** 2 * torch.log(ratio) + g_squared = 2.0 * (s_min * ratio**diffusion_time) ** 2 * torch.log(ratio) return g_squared def _get_diffusion_time(self, sde_time: torch.Tensor) -> torch.Tensor: @@ -115,7 +134,9 @@ def _get_diffusion_time(self, sde_time: torch.Tensor) -> torch.Tensor: """ return self.final_diffusion_time - sde_time - def f(self, sde_time: torch.Tensor, flat_relative_coordinates: torch.Tensor) -> torch.Tensor: + def f( + self, sde_time: torch.Tensor, flat_relative_coordinates: torch.Tensor + ) -> torch.Tensor: """Drift function. Args: @@ -128,9 +149,12 @@ def f(self, sde_time: torch.Tensor, flat_relative_coordinates: torch.Tensor) -> """ diffusion_time = self._get_diffusion_time(sde_time) - sigma_normalized_scores = self.get_sigma_normalized_score(diffusion_time, flat_relative_coordinates) - flat_sigma_normalized_scores = einops.rearrange(sigma_normalized_scores, - "batch natom space -> batch (natom space)") + sigma_normalized_scores = self.get_sigma_normalized_score( + diffusion_time, flat_relative_coordinates + ) + flat_sigma_normalized_scores = einops.rearrange( + sigma_normalized_scores, "batch natom space -> batch (natom space)" + ) g_squared = self._get_diffusion_coefficient_g_squared(diffusion_time) sigma = self._get_exploding_variance_sigma(diffusion_time) @@ -141,8 +165,9 @@ def f(self, sde_time: torch.Tensor, flat_relative_coordinates: torch.Tensor) -> return prefactor * flat_sigma_normalized_scores - def get_sigma_normalized_score(self, diffusion_time: torch.Tensor, - flat_relative_coordinates: torch.Tensor) -> torch.Tensor: + def get_sigma_normalized_score( + self, diffusion_time: torch.Tensor, flat_relative_coordinates: torch.Tensor + ) -> torch.Tensor: """Get sigma normalized score. This is a utility method to wrap around the computation of the sigma normalized score in this context, @@ -160,18 +185,27 @@ def get_sigma_normalized_score(self, diffusion_time: torch.Tensor, batch_size = flat_relative_coordinates.shape[0] sigma = self._get_exploding_variance_sigma(diffusion_time) sigmas = einops.repeat(sigma.unsqueeze(0), "1 -> batch 1", batch=batch_size) - times = einops.repeat(diffusion_time.unsqueeze(0), "1 -> batch 1", batch=batch_size) - - relative_coordinates = einops.rearrange(flat_relative_coordinates, - "batch (natom space) -> batch natom space", - natom=self.number_of_atoms, - space=self.spatial_dimension) - batch = {NOISY_RELATIVE_COORDINATES: map_relative_coordinates_to_unit_cell(relative_coordinates), - NOISE: sigmas, - TIME: times, - UNIT_CELL: self.unit_cells, - CARTESIAN_FORCES: torch.zeros_like(relative_coordinates) # TODO: handle forces correctly. - } + times = einops.repeat( + diffusion_time.unsqueeze(0), "1 -> batch 1", batch=batch_size + ) + + relative_coordinates = einops.rearrange( + flat_relative_coordinates, + "batch (natom space) -> batch natom space", + natom=self.number_of_atoms, + space=self.spatial_dimension, + ) + batch = { + NOISY_RELATIVE_COORDINATES: map_relative_coordinates_to_unit_cell( + relative_coordinates + ), + NOISE: sigmas, + TIME: times, + UNIT_CELL: self.unit_cells, + CARTESIAN_FORCES: torch.zeros_like( + relative_coordinates + ), # TODO: handle forces correctly. + } # Shape [batch_size, number of atoms, spatial dimension] sigma_normalized_scores = self.sigma_normalized_score_network(batch) return sigma_normalized_scores @@ -191,11 +225,13 @@ class ExplodingVarianceSDEPositionGenerator(PositionGenerator): This class generates position samples by solving a stochastic differential equation (SDE). It assumes that the diffusion noise is parameterized in the 'Exploding Variance' scheme. """ - def __init__(self, - noise_parameters: NoiseParameters, - sampling_parameters: SDESamplingParameters, - sigma_normalized_score_network: ScoreNetwork, - ): + + def __init__( + self, + noise_parameters: NoiseParameters, + sampling_parameters: SDESamplingParameters, + sigma_normalized_score_network: ScoreNetwork, + ): """Init method. Args: @@ -220,19 +256,25 @@ def __init__(self, def get_sde(self, unit_cells: torch.Tensor) -> SDE: """Get SDE.""" - return SDE(noise_parameters=self.noise_parameters, - sampling_parameters=self.sampling_parameters, - sigma_normalized_score_network=self.sigma_normalized_score_network, - unit_cells=unit_cells, - initial_diffusion_time=self.initial_diffusion_time, - final_diffusion_time=self.final_diffusion_time) + return SDE( + noise_parameters=self.noise_parameters, + sampling_parameters=self.sampling_parameters, + sigma_normalized_score_network=self.sigma_normalized_score_network, + unit_cells=unit_cells, + initial_diffusion_time=self.initial_diffusion_time, + final_diffusion_time=self.final_diffusion_time, + ) def initialize(self, number_of_samples: int): """This method must initialize the samples from the fully noised distribution.""" - relative_coordinates = torch.rand(number_of_samples, self.number_of_atoms, self.spatial_dimension) + relative_coordinates = torch.rand( + number_of_samples, self.number_of_atoms, self.spatial_dimension + ) return relative_coordinates - def sample(self, number_of_samples: int, device: torch.device, unit_cell: torch.Tensor) -> torch.Tensor: + def sample( + self, number_of_samples: int, device: torch.device, unit_cell: torch.Tensor + ) -> torch.Tensor: """Sample. This method draws a position sample. @@ -249,24 +291,36 @@ def sample(self, number_of_samples: int, device: torch.device, unit_cell: torch. sde = self.get_sde(unit_cell) sde.to(device) - initial_relative_coordinates = ( - map_relative_coordinates_to_unit_cell(self.initialize(number_of_samples)).to(device)) - y0 = einops.rearrange(initial_relative_coordinates, 'batch natom space -> batch (natom space)') + initial_relative_coordinates = map_relative_coordinates_to_unit_cell( + self.initialize(number_of_samples) + ).to(device) + y0 = einops.rearrange( + initial_relative_coordinates, "batch natom space -> batch (natom space)" + ) - sde_times = torch.linspace(self.initial_diffusion_time, self.final_diffusion_time, - self.noise_parameters.total_time_steps).to(device) + sde_times = torch.linspace( + self.initial_diffusion_time, + self.final_diffusion_time, + self.noise_parameters.total_time_steps, + ).to(device) - dt = (self.final_diffusion_time - self.initial_diffusion_time) / (self.noise_parameters.total_time_steps - 1) + dt = (self.final_diffusion_time - self.initial_diffusion_time) / ( + self.noise_parameters.total_time_steps - 1 + ) with torch.no_grad(): # Dimensions [number of time steps, number of samples, natom x spatial_dimension] logger.info("Starting SDE solver...") - ys = torchsde.sdeint(sde, y0, sde_times, - method=self.sampling_parameters.method, - dt=dt, - adaptive=self.sampling_parameters.adaptative, - atol=self.sampling_parameters.absolute_solver_tolerance, - rtol=self.sampling_parameters.relative_solver_tolerance) + ys = torchsde.sdeint( + sde, + y0, + sde_times, + method=self.sampling_parameters.method, + dt=dt, + adaptive=self.sampling_parameters.adaptative, + atol=self.sampling_parameters.absolute_solver_tolerance, + rtol=self.sampling_parameters.relative_solver_tolerance, + ) logger.info("SDE solver Finished.") if self.record_samples: @@ -275,10 +329,12 @@ def sample(self, number_of_samples: int, device: torch.device, unit_cell: torch. # only the final sde time (ie, diffusion time t0) is the real sample. flat_relative_coordinates = ys[-1, :, :] - relative_coordinates = einops.rearrange(flat_relative_coordinates, - 'batch (natom space) -> batch natom space', - natom=self.number_of_atoms, - space=self.spatial_dimension) + relative_coordinates = einops.rearrange( + flat_relative_coordinates, + "batch (natom space) -> batch natom space", + natom=self.number_of_atoms, + space=self.spatial_dimension, + ) return map_relative_coordinates_to_unit_cell(relative_coordinates) @@ -302,28 +358,38 @@ def record_sample(self, sde: SDE, ys: torch.Tensor, sde_times: torch.Tensor): sigmas = [] evaluation_times = [] # Reverse the times since the SDE times are inverted compared to the diffusion times. - for sde_time, flat_relative_coordinates in zip(sde_times.flip(dims=(0,)), ys.flip(dims=(0,))): + for sde_time, flat_relative_coordinates in zip( + sde_times.flip(dims=(0,)), ys.flip(dims=(0,)) + ): diffusion_time = sde._get_diffusion_time(sde_time) sigma = sde._get_exploding_variance_sigma(diffusion_time) sigmas.append(sigma) evaluation_times.append(diffusion_time) with torch.no_grad(): - normalized_scores = sde.get_sigma_normalized_score(diffusion_time, flat_relative_coordinates) + normalized_scores = sde.get_sigma_normalized_score( + diffusion_time, flat_relative_coordinates + ) list_normalized_scores.append(normalized_scores) sigmas = torch.tensor(sigmas) evaluation_times = torch.tensor(evaluation_times) - record_normalized_scores = einops.rearrange(torch.stack(list_normalized_scores), - "time batch natom space -> batch time natom space") - - record_relative_coordinates = einops.rearrange(ys.flip(dims=(0,)), - 'times batch (natom space) -> batch times natom space', - natom=self.number_of_atoms, - space=self.spatial_dimension) - - self.sample_trajectory_recorder.record_sde_solution(times=evaluation_times, - sigmas=sigmas, - relative_coordinates=record_relative_coordinates, - normalized_scores=record_normalized_scores) + record_normalized_scores = einops.rearrange( + torch.stack(list_normalized_scores), + "time batch natom space -> batch time natom space", + ) + + record_relative_coordinates = einops.rearrange( + ys.flip(dims=(0,)), + "times batch (natom space) -> batch times natom space", + natom=self.number_of_atoms, + space=self.spatial_dimension, + ) + + self.sample_trajectory_recorder.record_sde_solution( + times=evaluation_times, + sigmas=sigmas, + relative_coordinates=record_relative_coordinates, + normalized_scores=record_normalized_scores, + ) diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/loggers/logger_loader.py b/src/diffusion_for_multi_scale_molecular_dynamics/loggers/logger_loader.py index a54ed3d0..c5b69f9e 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/loggers/logger_loader.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/loggers/logger_loader.py @@ -29,7 +29,9 @@ def get_run_name(hyper_params: Dict[AnyStr, Any]) -> str: return run_name -def create_all_loggers(hyper_params: Dict[AnyStr, Any], output_directory: str) -> List[Logger]: +def create_all_loggers( + hyper_params: Dict[AnyStr, Any], output_directory: str +) -> List[Logger]: """Create all loggers. This method instantiates all machine learning loggers defined in `hyper_params`. @@ -49,18 +51,18 @@ def create_all_loggers(hyper_params: Dict[AnyStr, Any], output_directory: str) - full_run_name = f"{experiment_name}/{run_name}" - for logger_name in hyper_params.get('logging', []): + for logger_name in hyper_params.get("logging", []): match logger_name: case "csv": - logger = CSVLogger(save_dir=output_directory, - name="csv_logs") + logger = CSVLogger(save_dir=output_directory, name="csv_logs") case "tensorboard": - logger = TensorBoardLogger(save_dir=output_directory, - default_hp_metric=False, - name="tensorboard_logs", - version=0, # Necessary to resume tensorboard logging - ) + logger = TensorBoardLogger( + save_dir=output_directory, + default_hp_metric=False, + name="tensorboard_logs", + version=0, # Necessary to resume tensorboard logging + ) case "comet": # The comet logger will read the API key from ~/.comet.logger. @@ -73,20 +75,25 @@ def create_all_loggers(hyper_params: Dict[AnyStr, Any], output_directory: str) - # - when resuming an experiment, read the experiment key from the yaml file. # --> The presence or absence of this yaml file will serve as a signal of whether are starting # from scratch or resuming. - experiment_key = read_and_validate_comet_experiment_key(full_run_name=full_run_name, - output_directory=output_directory) + experiment_key = read_and_validate_comet_experiment_key( + full_run_name=full_run_name, output_directory=output_directory + ) # if 'experiment_key' is None, a new Comet Experiment will be created. If it is not None, # an ExistingExperiment will be created in order to resume logging. - logger = CometLogger(project_name='institut-courtois', # hardcoding the project. - save_dir=output_directory, - experiment_key=experiment_key, - experiment_name=full_run_name) + logger = CometLogger( + project_name="institut-courtois", # hardcoding the project. + save_dir=output_directory, + experiment_key=experiment_key, + experiment_name=full_run_name, + ) if experiment_key is None: experiment_key = logger.version - write_comet_experiment_key(experiment_key=experiment_key, - full_run_name=full_run_name, - output_directory=output_directory) + write_comet_experiment_key( + experiment_key=experiment_key, + full_run_name=full_run_name, + output_directory=output_directory, + ) case other: raise ValueError(f"Logger {other} is not implemented. Review input") @@ -96,7 +103,9 @@ def create_all_loggers(hyper_params: Dict[AnyStr, Any], output_directory: str) - return all_loggers -def write_comet_experiment_key(experiment_key: str, full_run_name: str, output_directory: str): +def write_comet_experiment_key( + experiment_key: str, full_run_name: str, output_directory: str +): """Write comet experiment key. Args: @@ -108,11 +117,13 @@ def write_comet_experiment_key(experiment_key: str, full_run_name: str, output_d No returns. """ data = {full_run_name: experiment_key} - with open(os.path.join(output_directory, "comet_experiment_key.yaml"), 'w') as fd: + with open(os.path.join(output_directory, "comet_experiment_key.yaml"), "w") as fd: yaml.dump(data, fd) -def read_and_validate_comet_experiment_key(full_run_name: str, output_directory: str) -> Union[str, None]: +def read_and_validate_comet_experiment_key( + full_run_name: str, output_directory: str +) -> Union[str, None]: """Read and validate Comet's experiment key. Args: @@ -127,16 +138,23 @@ def read_and_validate_comet_experiment_key(full_run_name: str, output_directory: if os.path.isfile(key_file): with open(key_file) as fd: data = yaml.load(fd, Loader=yaml.FullLoader) - assert full_run_name in data, (f"The experiment full name is {full_run_name}; this is not the full name " - f"that was used to initially generate this experiment. " - f"Something is inconsistent and requires a manual fix.") + assert full_run_name in data, ( + f"The experiment full name is {full_run_name}; this is not the full name " + f"that was used to initially generate this experiment. " + f"Something is inconsistent and requires a manual fix." + ) experiment_key = data[full_run_name] return experiment_key -def log_figure(figure: plt.figure, global_step: int, pl_logger: Logger, - dataset: str = "train", name: str = "samples") -> None: +def log_figure( + figure: plt.figure, + global_step: int, + pl_logger: Logger, + dataset: str = "train", + name: str = "samples", +) -> None: """Log figure. Args: @@ -150,14 +168,20 @@ def log_figure(figure: plt.figure, global_step: int, pl_logger: Logger, No return """ if type(pl_logger) is CometLogger: - pl_logger.experiment.log_figure(figure_name=f"{dataset}/{name}", figure=figure, step=global_step) + pl_logger.experiment.log_figure( + figure_name=f"{dataset}/{name}", figure=figure, step=global_step + ) elif type(pl_logger) is TensorBoardLogger: - pl_logger.experiment.add_figure(f"{dataset}/{name}", figure, global_step=global_step) + pl_logger.experiment.add_figure( + f"{dataset}/{name}", figure, global_step=global_step + ) elif type(pl_logger) is CSVLogger: # perform a deepcopy to prevent blanking the figure after writing to disk fig = deepcopy(figure) - figure_directory = os.path.join(pl_logger.root_dir, 'figures') + figure_directory = os.path.join(pl_logger.root_dir, "figures") os.makedirs(figure_directory, exist_ok=True) - figure_path = os.path.join(figure_directory, f"{dataset}_{name}_step={global_step}.png") + figure_path = os.path.join( + figure_directory, f"{dataset}_{name}_step={global_step}.png" + ) fig.savefig(figure_path) del fig diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/main_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/main_utils.py index 688c6159..8b2f53fa 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/main_utils.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/main_utils.py @@ -14,13 +14,18 @@ @dataclass(kw_only=True) class MetricResult: """Metric result class that is self documenting.""" + report: bool = False # is there something to report - metric_name: Union[str, None] = None # default to None, if there is nothing to report + metric_name: Union[str, None] = ( + None # default to None, if there is nothing to report + ) mode: Union[str, None] # default to None, if there is nothing to report metric_value: float = np.NaN # default to NaN, if there is nothing to report -def get_optimized_metric_name_and_mode(hyper_params: Dict[AnyStr, Any]) -> Tuple[Union[str, None], Union[str, None]]: +def get_optimized_metric_name_and_mode( + hyper_params: Dict[AnyStr, Any] +) -> Tuple[Union[str, None], Union[str, None]]: """Get optimized metric name and mode. Args: @@ -30,9 +35,9 @@ def get_optimized_metric_name_and_mode(hyper_params: Dict[AnyStr, Any]) -> Tuple metric_name, metric_mode: the name and mode (min or max) for the metric to be optimized. """ # By convention, it is assumed that the metric to be reported is the early stopping metric. - if 'early_stopping' in hyper_params: - early_stopping_params = hyper_params['early_stopping'] - return early_stopping_params['metric'], early_stopping_params['mode'] + if "early_stopping" in hyper_params: + early_stopping_params = hyper_params["early_stopping"] + return early_stopping_params["metric"], early_stopping_params["mode"] else: return None, None @@ -52,12 +57,18 @@ def get_crash_metric_result(hyper_params: Dict[AnyStr, Any]) -> MetricResult: metric_name, mode = get_optimized_metric_name_and_mode(hyper_params) if metric_name is None: - return MetricResult(report=False, metric_name=None, mode=None, metric_value=np.NaN) + return MetricResult( + report=False, metric_name=None, mode=None, metric_value=np.NaN + ) else: - return MetricResult(report=True, metric_name=metric_name, mode=mode, metric_value=np.NaN) + return MetricResult( + report=True, metric_name=metric_name, mode=mode, metric_value=np.NaN + ) -def get_name_and_sign_of_orion_optimization_objective(metric_name: str, mode: str) -> Tuple[str, int]: +def get_name_and_sign_of_orion_optimization_objective( + metric_name: str, mode: str +) -> Tuple[str, int]: """Names and signs. The Orion optimizer seeks to minimize an objective. Some metrics must be maximized, @@ -84,7 +95,9 @@ def get_name_and_sign_of_orion_optimization_objective(metric_name: str, mode: st return optimization_objective_name, optimization_sign -def report_to_orion_if_on(metric_result: MetricResult, run_time_error: Union[None, RuntimeError]): +def report_to_orion_if_on( + metric_result: MetricResult, run_time_error: Union[None, RuntimeError] +): """Report to Orion if on. This function manages how to report the metric to Orion. If Orion is not turned on, or if there @@ -103,26 +116,35 @@ def report_to_orion_if_on(metric_result: MetricResult, run_time_error: Union[Non if orion.client.cli.IS_ORION_ON: optimization_objective_name, optimization_sign = ( - get_name_and_sign_of_orion_optimization_objective(metric_result.metric_name, metric_result.mode)) + get_name_and_sign_of_orion_optimization_objective( + metric_result.metric_name, metric_result.mode + ) + ) report_value = optimization_sign * metric_result.metric_value if run_time_error is None: logger.info("Reporting Results to ORION...") - results = dict(name=optimization_objective_name, type="objective", value=report_value) + results = dict( + name=optimization_objective_name, type="objective", value=report_value + ) orion.client.report_results([results]) logger.info(" Done reporting Results to ORION.") - elif 'CUDA out of memory' in str(run_time_error): - logger.error('model was out of memory - reporting a bad trial so ' - 'that Orion avoids models that are too large') + elif "CUDA out of memory" in str(run_time_error): + logger.error( + "model was out of memory - reporting a bad trial so " + "that Orion avoids models that are too large" + ) orion.client.report_bad_trial(name=optimization_objective_name) else: logger.error(f"Run time error : {run_time_error}- interrupting Orion trial") orion.client.interrupt_trial() -def load_and_backup_hyperparameters(config_file_path: Union[str, None], output_directory: str) -> Dict[str, Any]: +def load_and_backup_hyperparameters( + config_file_path: Union[str, None], output_directory: str +) -> Dict[str, Any]: """Load and process hyperparameters. If a configuration file is provided, this method reads in the hyperparameters. It either makes a copy of the @@ -142,7 +164,9 @@ def load_and_backup_hyperparameters(config_file_path: Union[str, None], output_d hyper_params = _get_hyperparameters(config_file_path) if orion.client.cli.IS_ORION_ON: - logging.info("The Orion client is ON: Orion will manage configuration file copies.") + logging.info( + "The Orion client is ON: Orion will manage configuration file copies." + ) else: config_backup_path = os.path.join(output_directory, "config_backup.yaml") _create_or_validate_backup_configuration(config_backup_path, hyper_params) @@ -150,33 +174,45 @@ def load_and_backup_hyperparameters(config_file_path: Union[str, None], output_d return hyper_params -def _create_or_validate_backup_configuration(config_backup_path: str, hyper_params: Dict[str, Any]) -> None: +def _create_or_validate_backup_configuration( + config_backup_path: str, hyper_params: Dict[str, Any] +) -> None: """Create or validate a backup of the hyperparameters.""" if os.path.exists(config_backup_path): - logging.info(f"The backup configuration file {config_backup_path} already exists. " - f"Validating hyperparameters are identical...") - - with open(config_backup_path, 'r') as stream: - logging.info(f"Reading backup hyperparameters from file {config_backup_path}") + logging.info( + f"The backup configuration file {config_backup_path} already exists. " + f"Validating hyperparameters are identical..." + ) + + with open(config_backup_path, "r") as stream: + logging.info( + f"Reading backup hyperparameters from file {config_backup_path}" + ) backup_hyper_params = yaml.load(stream, Loader=yaml.FullLoader) hp_differences = deepdiff.DeepDiff(backup_hyper_params, hyper_params) - assert hp_differences == {}, (f"Incompatible backup configuration file already present in output directory! " - f"The configuration difference is {hp_differences}. Manual clean up is needed.") + assert hp_differences == {}, ( + f"Incompatible backup configuration file already present in output directory! " + f"The configuration difference is {hp_differences}. Manual clean up is needed." + ) else: - logging.info(f"Writing a copy of the configuration file to backup configuration file {config_backup_path}.") - with open(config_backup_path, 'w') as steam: + logging.info( + f"Writing a copy of the configuration file to backup configuration file {config_backup_path}." + ) + with open(config_backup_path, "w") as steam: yaml.dump(hyper_params, steam) def _get_hyperparameters(config_file_path: Union[str, None]) -> Dict[str, Any]: """Get the hyperparameters.""" if config_file_path is None: - logging.info("No configuration file was provided. The hyperparameters are set to an empty dictionary.") + logging.info( + "No configuration file was provided. The hyperparameters are set to an empty dictionary." + ) hyper_params = dict() else: logging.info(f"Reading in hyperparameters from file {config_file_path}") - with open(config_file_path, 'r') as stream: + with open(config_file_path, "r") as stream: hyper_params = yaml.load(stream, Loader=yaml.FullLoader) return hyper_params diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/metrics/kolmogorov_smirnov_metrics.py b/src/diffusion_for_multi_scale_molecular_dynamics/metrics/kolmogorov_smirnov_metrics.py index 7af4e714..ac758b49 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/metrics/kolmogorov_smirnov_metrics.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/metrics/kolmogorov_smirnov_metrics.py @@ -56,9 +56,12 @@ def compute_kolmogorov_smirnov_distance_and_pvalue(self) -> Tuple[float, float]: reference_samples = self.reference_samples_metric.compute() predicted_samples = self.predicted_samples_metric.compute() - test_result = ss.ks_2samp(predicted_samples.detach().cpu().numpy(), - reference_samples.detach().cpu().numpy(), - alternative='two-sided', method='auto') + test_result = ss.ks_2samp( + predicted_samples.detach().cpu().numpy(), + reference_samples.detach().cpu().numpy(), + alternative="two-sided", + method="auto", + ) # The "test statistic" of the two-sided KS test is the largest vertical distance between # the empirical CDFs of the two samples. The larger this is, the less likely the two diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/metrics/sampling_metrics_parameters.py b/src/diffusion_for_multi_scale_molecular_dynamics/metrics/sampling_metrics_parameters.py index 86ad5642..0e6fae66 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/metrics/sampling_metrics_parameters.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/metrics/sampling_metrics_parameters.py @@ -8,6 +8,9 @@ class SamplingMetricsParameters: This dataclass configures what metrics should be computed given that samples have been generated. """ + compute_energies: bool = False # should the energies be computed - compute_structure_factor: bool = False # should the structure factor (distances distribution) be recorded + compute_structure_factor: bool = ( + False # should the structure factor (distances distribution) be recorded + ) structure_factor_max_distance: float = 10.0 # cutoff for the structure factor diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/mlip/mtp_train.py b/src/diffusion_for_multi_scale_molecular_dynamics/mlip/mtp_train.py index 93312518..100b78c4 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/mlip/mtp_train.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/mlip/mtp_train.py @@ -2,20 +2,24 @@ Running the main() runs a debugging example. Entry points are train_mtp. """ + import argparse from typing import Dict, Tuple import pandas as pd -from crystal_diffusion.mlip.mtp_utils import (MTPInputs, - crawl_lammps_directory, - prepare_mtp_inputs_from_lammps) -from crystal_diffusion.models.mlip.mtp import MTPWithMLIP3 from sklearn.metrics import mean_absolute_error -atom_dict = {1: 'Si'} +from diffusion_for_multi_scale_molecular_dynamics.mlip.mtp_utils import ( + MTPInputs, crawl_lammps_directory, prepare_mtp_inputs_from_lammps) +from diffusion_for_multi_scale_molecular_dynamics.models.mlip.mtp import \ + MTPWithMLIP3 + +atom_dict = {1: "Si"} -def prepare_dataset(root_data_dir: str, atom_dict: Dict[int, str], mode: str = "train") -> MTPInputs: +def prepare_dataset( + root_data_dir: str, atom_dict: Dict[int, str], mode: str = "train" +) -> MTPInputs: """Prepare the dataset in a given directory into a MTP format. Args: @@ -28,11 +32,15 @@ def prepare_dataset(root_data_dir: str, atom_dict: Dict[int, str], mode: str = " data in the MTPInputs dataclass """ lammps_outputs, thermo_outputs = crawl_lammps_directory(root_data_dir, mode) - mtp_dataset = prepare_mtp_inputs_from_lammps(lammps_outputs, thermo_outputs, atom_dict) + mtp_dataset = prepare_mtp_inputs_from_lammps( + lammps_outputs, thermo_outputs, atom_dict + ) return mtp_dataset -def train_mtp(train_inputs: MTPInputs, mlip_folder_path: str, save_dir: str) -> MTPWithMLIP3: +def train_mtp( + train_inputs: MTPInputs, mlip_folder_path: str, save_dir: str +) -> MTPWithMLIP3: """Create and train an MTP potential. Args: @@ -60,7 +68,9 @@ def train_mtp(train_inputs: MTPInputs, mlip_folder_path: str, save_dir: str) -> return mtp -def evaluate_mtp(eval_inputs: MTPInputs, mtp: MTPWithMLIP3) -> Tuple[pd.DataFrame, pd.DataFrame]: +def evaluate_mtp( + eval_inputs: MTPInputs, mtp: MTPWithMLIP3 +) -> Tuple[pd.DataFrame, pd.DataFrame]: """Evaluate a trained MTP potential. Args: @@ -80,7 +90,9 @@ def evaluate_mtp(eval_inputs: MTPInputs, mtp: MTPWithMLIP3) -> Tuple[pd.DataFram return df_orig, df_predict -def get_metrics_from_pred(df_orig: pd.DataFrame, df_predict: pd.DataFrame) -> Tuple[float, float]: +def get_metrics_from_pred( + df_orig: pd.DataFrame, df_predict: pd.DataFrame +) -> Tuple[float, float]: """Get mean absolute error on energy and forces from the outputs of MTP. Args: @@ -92,33 +104,53 @@ def get_metrics_from_pred(df_orig: pd.DataFrame, df_predict: pd.DataFrame) -> Tu """ # from demo in maml # get a single predicted energy per structure - predicted_energy = df_predict.groupby('structure_index').agg({'energy': 'mean', 'atom_index': 'count'}) + predicted_energy = df_predict.groupby("structure_index").agg( + {"energy": "mean", "atom_index": "count"} + ) # normalize by number of atoms - predicted_energy = (predicted_energy['energy'] / predicted_energy['atom_index']).to_numpy() + predicted_energy = ( + predicted_energy["energy"] / predicted_energy["atom_index"] + ).to_numpy() # same for ground truth - gt_energy = df_orig.groupby('structure_index').agg({'energy': 'mean', 'atom_index': 'count'}) - gt_energy = (gt_energy['energy'] / gt_energy['atom_index']).to_numpy() + gt_energy = df_orig.groupby("structure_index").agg( + {"energy": "mean", "atom_index": "count"} + ) + gt_energy = (gt_energy["energy"] / gt_energy["atom_index"]).to_numpy() - predicted_forces = (df_predict[['fx', 'fy', 'fz']].to_numpy().flatten()) - gt_forces = (df_orig[['fx', 'fy', 'fz']].to_numpy().flatten()) + predicted_forces = df_predict[["fx", "fy", "fz"]].to_numpy().flatten() + gt_forces = df_orig[["fx", "fy", "fz"]].to_numpy().flatten() - return mean_absolute_error(predicted_energy, gt_energy), mean_absolute_error(predicted_forces, gt_forces) + return mean_absolute_error(predicted_energy, gt_energy), mean_absolute_error( + predicted_forces, gt_forces + ) def main(): """Train and evaluate an example for MTP.""" parser = argparse.ArgumentParser() - parser.add_argument('--lammps_yaml', help='path to LAMMPS yaml file', required=True, nargs='+') - parser.add_argument('--lammps_thermo', help='path to LAMMPS thermo output', required=True, nargs='+') - parser.add_argument('--mlip_dir', help='directory to MLIP compilation folder', required=True) - parser.add_argument('--output_dir', help='path to folder where outputs will be saved', required=True) + parser.add_argument( + "--lammps_yaml", help="path to LAMMPS yaml file", required=True, nargs="+" + ) + parser.add_argument( + "--lammps_thermo", help="path to LAMMPS thermo output", required=True, nargs="+" + ) + parser.add_argument( + "--mlip_dir", help="directory to MLIP compilation folder", required=True + ) + parser.add_argument( + "--output_dir", help="path to folder where outputs will be saved", required=True + ) args = parser.parse_args() lammps_yaml = args.lammps_yaml lammps_thermo_yaml = args.lammps_thermo - assert len(lammps_yaml) == len(lammps_thermo_yaml), "LAMMPS outputs yaml should match thermodynamics output." + assert len(lammps_yaml) == len( + lammps_thermo_yaml + ), "LAMMPS outputs yaml should match thermodynamics output." - mtp_inputs = prepare_mtp_inputs_from_lammps(lammps_yaml, lammps_thermo_yaml, atom_dict) + mtp_inputs = prepare_mtp_inputs_from_lammps( + lammps_yaml, lammps_thermo_yaml, atom_dict + ) mtp = train_mtp(mtp_inputs, args.mlip_dir, args.output_dir) print("Training is done") df_orig, df_predict = evaluate_mtp(mtp_inputs, mtp) @@ -127,5 +159,5 @@ def main(): print(f"MAE of training force prediction is {force_mae} eV/Ã…") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/mlip/mtp_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/mlip/mtp_utils.py index d2cbf298..498a8756 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/mlip/mtp_utils.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/mlip/mtp_utils.py @@ -13,13 +13,15 @@ @dataclass(kw_only=True) class MTPInputs: """Create a dataclass to train or evaluate a MTP model.""" + structure: List[Structure] forces: List[List[List[float]]] # num samples x num atoms x spatial dimension energy: List[float] -def extract_structure_and_forces_from_file(filename: str, atom_dict: Dict[int, Any], forces_avail: bool = True) -> \ - Tuple[List[Structure], Optional[List[List[float]]]]: +def extract_structure_and_forces_from_file( + filename: str, atom_dict: Dict[int, Any], forces_avail: bool = True +) -> Tuple[List[Structure], Optional[List[List[float]]]]: """Convert LAMMPS yaml output in a format compatible with MTP training and evaluation methods. Args: @@ -33,26 +35,34 @@ def extract_structure_and_forces_from_file(filename: str, atom_dict: Dict[int, A """ structures = [] forces = [] if forces_avail else None - with (open(filename, 'r') as f): + with open(filename, "r") as f: l_yaml = yaml.safe_load_all(f) - for d in l_yaml: # loop over LAMMPS outputs and convert in pymatgen Structure objects + for ( + d + ) in ( + l_yaml + ): # loop over LAMMPS outputs and convert in pymatgen Structure objects # lattice in yaml is 3 x 2 [0, x_lim] # we assume a rectangular lattice for now with the 2nd coordinates as the lattice vectors lattice = np.zeros((3, 3)) - for i, x in enumerate(d['box']): + for i, x in enumerate(d["box"]): lattice[i, i] = x[1] - type_idx = d['keywords'].index('type') - species = [atom_dict[x[type_idx]] for x in d['data']] # convert to atom type - coords_idx = [d['keywords'].index(x) for x in ['x', 'y', 'z']] - coords = [[x[i] for i in coords_idx] for x in d['data']] - pm_structure = Structure(lattice=lattice, - species=species, - coords=coords, - coords_are_cartesian=True) + type_idx = d["keywords"].index("type") + species = [ + atom_dict[x[type_idx]] for x in d["data"] + ] # convert to atom type + coords_idx = [d["keywords"].index(x) for x in ["x", "y", "z"]] + coords = [[x[i] for i in coords_idx] for x in d["data"]] + pm_structure = Structure( + lattice=lattice, + species=species, + coords=coords, + coords_are_cartesian=True, + ) structures.append(pm_structure) if forces_avail: - force_idx = [d['keywords'].index(x) for x in ['fx', 'fy', 'fz']] - structure_forces = [[x[i] for i in force_idx] for x in d['data']] + force_idx = [d["keywords"].index(x) for x in ["fx", "fy", "fz"]] + structure_forces = [[x[i] for i in force_idx] for x in d["data"]] forces.append(structure_forces) return structures, forces @@ -66,19 +76,20 @@ def extract_energy_from_thermo_log(filename: str) -> List[float]: Returns: list of energies (1 value per configuration) """ - with open(filename, 'r') as f: + with open(filename, "r") as f: log_yaml = yaml.safe_load(f) - kin_idx = log_yaml['keywords'].index('KinEng') - pot_idx = log_yaml['keywords'].index('PotEng') - energies = [x[kin_idx] + x[pot_idx] for x in log_yaml['data']] + kin_idx = log_yaml["keywords"].index("KinEng") + pot_idx = log_yaml["keywords"].index("PotEng") + energies = [x[kin_idx] + x[pot_idx] for x in log_yaml["data"]] return energies -def prepare_mtp_inputs_from_lammps(output_yaml: List[str], - thermo_yaml: List[str], - atom_dict: Dict[int, Any], - get_forces: bool = True, - ) -> MTPInputs: +def prepare_mtp_inputs_from_lammps( + output_yaml: List[str], + thermo_yaml: List[str], + atom_dict: Dict[int, Any], + get_forces: bool = True, +) -> MTPInputs: """Convert a list of LAMMPS output files and thermodynamic output files to MTP input format. Args: @@ -90,24 +101,26 @@ def prepare_mtp_inputs_from_lammps(output_yaml: List[str], Returns: dataclass used as inputs to train and evaluation a MTP model """ - mtp_inputs = { - 'structure': [], - 'energy': [], - 'forces': [] - } + mtp_inputs = {"structure": [], "energy": [], "forces": []} for filename in output_yaml: - structures, forces = extract_structure_and_forces_from_file(filename, atom_dict, get_forces) - mtp_inputs['structure'] += structures - mtp_inputs['forces'] += forces # will be None if get_forces is False + structures, forces = extract_structure_and_forces_from_file( + filename, atom_dict, get_forces + ) + mtp_inputs["structure"] += structures + mtp_inputs["forces"] += forces # will be None if get_forces is False for filename in thermo_yaml: - mtp_inputs['energy'] += extract_energy_from_thermo_log(filename) - mtp_inputs = MTPInputs(structure=mtp_inputs['structure'], - energy=mtp_inputs['energy'], - forces=mtp_inputs['forces']) + mtp_inputs["energy"] += extract_energy_from_thermo_log(filename) + mtp_inputs = MTPInputs( + structure=mtp_inputs["structure"], + energy=mtp_inputs["energy"], + forces=mtp_inputs["forces"], + ) return mtp_inputs -def crawl_lammps_directory(folder_name: str, folder_name_pattern: str = "train") -> Tuple[List[str], List[str]]: +def crawl_lammps_directory( + folder_name: str, folder_name_pattern: str = "train" +) -> Tuple[List[str], List[str]]: """Crawl through a folder and find the LAMMPS output files in folders containing a specified pattern in their name. LAMMPS outputs should end with dump.yaml and Thermondynamics variables files should end with thermo.yaml @@ -124,8 +137,16 @@ def crawl_lammps_directory(folder_name: str, folder_name_pattern: str = "train") lammps_output_files, thermo_output_files = [], [] for dirpath, _, filenames in os.walk(folder_name): if re.search(folder_name_pattern, dirpath): - lammps_output_files.extend([os.path.join(dirpath, f) for f in filenames if f.endswith("dump.yaml")]) - thermo_output_files.extend([os.path.join(dirpath, f) for f in filenames if f.endswith("thermo.yaml")]) + lammps_output_files.extend( + [os.path.join(dirpath, f) for f in filenames if f.endswith("dump.yaml")] + ) + thermo_output_files.extend( + [ + os.path.join(dirpath, f) + for f in filenames + if f.endswith("thermo.yaml") + ] + ) return lammps_output_files, thermo_output_files @@ -142,12 +163,14 @@ def concat_mtp_inputs(input1: MTPInputs, input2: MTPInputs) -> MTPInputs: concat_inputs = MTPInputs( structure=input1.structure + input2.structure, forces=input1.forces + input2.forces, - energy=input1.energy + input2.energy + energy=input1.energy + input2.energy, ) return concat_inputs -def get_metrics_from_pred(df_orig: pd.DataFrame, df_predict: pd.DataFrame) -> Tuple[float, float]: +def get_metrics_from_pred( + df_orig: pd.DataFrame, df_predict: pd.DataFrame +) -> Tuple[float, float]: """Get mean absolute error on energy and forces from the outputs of MTP. Args: @@ -159,14 +182,22 @@ def get_metrics_from_pred(df_orig: pd.DataFrame, df_predict: pd.DataFrame) -> Tu """ # from demo in maml # get a single predicted energy per structure - predicted_energy = df_predict.groupby('structure_index').agg({'energy': 'mean', 'atom_index': 'count'}) + predicted_energy = df_predict.groupby("structure_index").agg( + {"energy": "mean", "atom_index": "count"} + ) # normalize by number of atoms - predicted_energy = (predicted_energy['energy'] / predicted_energy['atom_index']).to_numpy() + predicted_energy = ( + predicted_energy["energy"] / predicted_energy["atom_index"] + ).to_numpy() # same for ground truth - gt_energy = df_orig.groupby('structure_index').agg({'energy': 'mean', 'atom_index': 'count'}) - gt_energy = (gt_energy['energy'] / gt_energy['atom_index']).to_numpy() + gt_energy = df_orig.groupby("structure_index").agg( + {"energy": "mean", "atom_index": "count"} + ) + gt_energy = (gt_energy["energy"] / gt_energy["atom_index"]).to_numpy() - predicted_forces = (df_predict[['fx', 'fy', 'fz']].to_numpy().flatten()) - gt_forces = (df_orig[['fx', 'fy', 'fz']].to_numpy().flatten()) + predicted_forces = df_predict[["fx", "fy", "fz"]].to_numpy().flatten() + gt_forces = df_orig[["fx", "fy", "fz"]].to_numpy().flatten() - return mean_absolute_error(predicted_energy, gt_energy), mean_absolute_error(predicted_forces, gt_forces) + return mean_absolute_error(predicted_energy, gt_energy), mean_absolute_error( + predicted_forces, gt_forces + ) diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/diffusion_mace.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/diffusion_mace.py index b4919e12..2af13346 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/diffusion_mace.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/diffusion_mace.py @@ -1,11 +1,6 @@ from typing import AnyStr, Callable, Dict, List, Optional, Type, Union import torch -from crystal_diffusion.models.mace_utils import (get_adj_matrix, - reshape_from_e3nn_to_mace, - reshape_from_mace_to_e3nn) -from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, - NOISY_CARTESIAN_POSITIONS, UNIT_CELL) from e3nn import o3 from e3nn.nn import Activation, BatchNorm, NormActivation from mace.modules import (EquivariantProductBasisBlock, InteractionBlock, @@ -13,9 +8,15 @@ from mace.modules.utils import get_edge_vectors_and_lengths from torch_geometric.data import Data +from diffusion_for_multi_scale_molecular_dynamics.models.mace_utils import ( + get_adj_matrix, reshape_from_e3nn_to_mace, reshape_from_mace_to_e3nn) +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, NOISE, NOISY_CARTESIAN_POSITIONS, UNIT_CELL) + class LinearVectorReadoutBlock(torch.nn.Module): """Linear readout block for vector representation.""" + def __init__(self, irreps_in: o3.Irreps): """Init method.""" super().__init__() @@ -26,7 +27,9 @@ def forward(self, x: torch.Tensor) -> torch.Tensor: return self.linear(x) -def input_to_diffusion_mace(batch: Dict[AnyStr, torch.Tensor], radial_cutoff: float) -> Data: +def input_to_diffusion_mace( + batch: Dict[AnyStr, torch.Tensor], radial_cutoff: float +) -> Data: """Convert score network input to Diffusion MACE input. Args: @@ -42,44 +45,61 @@ def input_to_diffusion_mace(batch: Dict[AnyStr, torch.Tensor], radial_cutoff: fl basis_vectors = batch[UNIT_CELL] # batch, spatial_dimension, spatial_dimension - adj_matrix, shift_matrix, batch_tensor, num_edges = get_adj_matrix(positions=cartesian_positions, - basis_vectors=basis_vectors, - radial_cutoff=radial_cutoff) + adj_matrix, shift_matrix, batch_tensor, num_edges = get_adj_matrix( + positions=cartesian_positions, + basis_vectors=basis_vectors, + radial_cutoff=radial_cutoff, + ) # node features are int corresponding to atom type # TODO handle different atom types atom_types = torch.zeros(batch_size * n_atom_per_graph) - node_attrs = torch.nn.functional.one_hot(atom_types.long(), num_classes=1).to(atom_types) + node_attrs = torch.nn.functional.one_hot(atom_types.long(), num_classes=1).to( + atom_types + ) # The node diffusion scalars will be the diffusion noise sigma, which is constant for each structure in the batch. # We broadcast to each node to avoid complex broadcasting logic within the model itself. # TODO: it might be better to define the noise as a 'global' graph attribute, and find 'the right way' of # mixing it with bona fide node features within the model. - noises = batch[NOISE] + 1 # [batch_size, 1] - add 1 to avoid getting a zero at sigma=0 (initialization issues) - node_diffusion_scalars = noises.repeat_interleave(n_atom_per_graph, dim=0) # [flat_batch_size, 1] - edge_diffusion_scalars = noises.repeat_interleave(num_edges.long().to(device=noises.device), dim=0) + noises = ( + batch[NOISE] + 1 + ) # [batch_size, 1] - add 1 to avoid getting a zero at sigma=0 (initialization issues) + node_diffusion_scalars = noises.repeat_interleave( + n_atom_per_graph, dim=0 + ) # [flat_batch_size, 1] + edge_diffusion_scalars = noises.repeat_interleave( + num_edges.long().to(device=noises.device), dim=0 + ) # [batchsize * natoms, spatial dimension] flat_cartesian_positions = cartesian_positions.view(-1, spatial_dimension) # pointer tensor that yields the first node index for each batch - this is a fixed tensor in our case - ptr = torch.arange(0, n_atom_per_graph * batch_size + 1, step=n_atom_per_graph) # 0, natoms, 2 * natoms, ... + ptr = torch.arange( + 0, n_atom_per_graph * batch_size + 1, step=n_atom_per_graph + ) # 0, natoms, 2 * natoms, ... - flat_basis_vectors = basis_vectors.view(-1, spatial_dimension) # batch * spatial_dimension, spatial_dimension + flat_basis_vectors = basis_vectors.view( + -1, spatial_dimension + ) # batch * spatial_dimension, spatial_dimension # create the pytorch-geometric graph - forces = batch[CARTESIAN_FORCES].view(-1, spatial_dimension) # batch * n_atom_per_graph, spatial dimension - - graph_data = Data(edge_index=adj_matrix, - node_attrs=node_attrs.to(device), - node_diffusion_scalars=node_diffusion_scalars.to(device), - edge_diffusion_scalars=edge_diffusion_scalars.to(device), - positions=flat_cartesian_positions, - ptr=ptr.to(device), - batch=batch_tensor.to(device), - shifts=shift_matrix, - cell=flat_basis_vectors, - forces=forces, - ) + forces = batch[CARTESIAN_FORCES].view( + -1, spatial_dimension + ) # batch * n_atom_per_graph, spatial dimension + + graph_data = Data( + edge_index=adj_matrix, + node_attrs=node_attrs.to(device), + node_diffusion_scalars=node_diffusion_scalars.to(device), + edge_diffusion_scalars=edge_diffusion_scalars.to(device), + positions=flat_cartesian_positions, + ptr=ptr.to(device), + batch=batch_tensor.to(device), + shifts=shift_matrix, + cell=flat_basis_vectors, + forces=forces, + ) return graph_data @@ -90,6 +110,7 @@ class DiffusionMACE(torch.nn.Module): "MACE: Higher Order Equivariant Message Passing Neural Networks for Fast and Accurate Force Fields" will be referenced in the comments as the PAPER. """ + def __init__( self, r_max: float, @@ -116,9 +137,12 @@ def __init__( tanh_after_interaction: bool = True, ): """Init method.""" - assert num_elements == 1, "only a single element can be used at this time. Set 'num_elements' to 1." - assert len(atomic_numbers) == 1, \ - "only a single element can be used at this time. Set 'atomic_numbers' to length 1." + assert ( + num_elements == 1 + ), "only a single element can be used at this time. Set 'num_elements' to 1." + assert ( + len(atomic_numbers) == 1 + ), "only a single element can be used at this time. Set 'atomic_numbers' to length 1." super().__init__() self.register_buffer( "atomic_numbers", torch.tensor(atomic_numbers, dtype=torch.int64) @@ -146,35 +170,47 @@ def __init__( number_of_node_scalar_dimensions = 1 number_of_hidden_diffusion_scalar_dimensions = mlp_irreps.count(scalar_irrep) - diffusion_scalar_irreps_in = o3.Irreps([(number_of_node_scalar_dimensions, scalar_irrep)]) - diffusion_scalar_irreps_out = o3.Irreps([(number_of_hidden_diffusion_scalar_dimensions, scalar_irrep)]) + diffusion_scalar_irreps_in = o3.Irreps( + [(number_of_node_scalar_dimensions, scalar_irrep)] + ) + diffusion_scalar_irreps_out = o3.Irreps( + [(number_of_hidden_diffusion_scalar_dimensions, scalar_irrep)] + ) self.diffusion_scalar_embedding = torch.nn.Sequential() - linear = o3.Linear(irreps_in=diffusion_scalar_irreps_in, - irreps_out=diffusion_scalar_irreps_out, - biases=True) + linear = o3.Linear( + irreps_in=diffusion_scalar_irreps_in, + irreps_out=diffusion_scalar_irreps_out, + biases=True, + ) self.diffusion_scalar_embedding.append(linear) non_linearity = Activation(irreps_in=diffusion_scalar_irreps_out, acts=[gate]) for _ in range(number_of_mlp_layers): self.diffusion_scalar_embedding.append(non_linearity) - linear = o3.Linear(irreps_in=diffusion_scalar_irreps_out, - irreps_out=diffusion_scalar_irreps_out, - biases=True) + linear = o3.Linear( + irreps_in=diffusion_scalar_irreps_out, + irreps_out=diffusion_scalar_irreps_out, + biases=True, + ) self.diffusion_scalar_embedding.append(linear) # The node_attr is the one-hot version of the atom types. node_attr_irreps = o3.Irreps([(num_elements, scalar_irrep)]) # Perform a tensor product to mix the diffusion scalar and node attributes - self.attribute_mixing = o3.FullyConnectedTensorProduct(irreps_in1=diffusion_scalar_irreps_out, - irreps_in2=node_attr_irreps, - irreps_out=node_attr_irreps, - irrep_normalization='norm') + self.attribute_mixing = o3.FullyConnectedTensorProduct( + irreps_in1=diffusion_scalar_irreps_out, + irreps_in2=node_attr_irreps, + irreps_out=node_attr_irreps, + irrep_normalization="norm", + ) number_of_hidden_scalar_dimensions = hidden_irreps.count(scalar_irrep) - node_feats_irreps = o3.Irreps([(number_of_hidden_scalar_dimensions, scalar_irrep)]) + node_feats_irreps = o3.Irreps( + [(number_of_hidden_scalar_dimensions, scalar_irrep)] + ) # The "node embedding" corresponds to W^{(1)} in the definition of A^{(1)}, eq. 9 of the PAPER. self.node_embedding = LinearNodeEmbeddingBlock( irreps_in=node_attr_irreps, irreps_out=node_feats_irreps @@ -190,18 +226,22 @@ def __init__( edge_feats_irreps = o3.Irreps([(self.radial_embedding.out_dim, scalar_irrep)]) if num_edge_hidden_layers > 0: - self.edge_attribute_mixing = o3.FullyConnectedTensorProduct(irreps_in1=diffusion_scalar_irreps_out, - irreps_in2=edge_feats_irreps, - irreps_out=edge_hidden_irreps, - irrep_normalization='norm') + self.edge_attribute_mixing = o3.FullyConnectedTensorProduct( + irreps_in1=diffusion_scalar_irreps_out, + irreps_in2=edge_feats_irreps, + irreps_out=edge_hidden_irreps, + irrep_normalization="norm", + ) self.edge_hidden_layers = torch.nn.Sequential() edge_non_linearity = Activation(irreps_in=edge_hidden_irreps, acts=[gate]) for i in range(num_edge_hidden_layers): if i != 0: self.edge_hidden_layers.append(edge_non_linearity) - edge_hidden_layer = o3.Linear(irreps_in=edge_hidden_irreps, - irreps_out=edge_hidden_irreps, - biases=False) + edge_hidden_layer = o3.Linear( + irreps_in=edge_hidden_irreps, + irreps_out=edge_hidden_irreps, + biases=False, + ) self.edge_hidden_layers.append(edge_hidden_layer) else: self.edge_attribute_mixing, self.edge_hidden_layers = None, None @@ -209,7 +249,9 @@ def __init__( # The "spherical harmonics" correspond to Y_{lm} in the definition of A^{(1)}, eq. 9 of the PAPER. sh_irreps = o3.Irreps.spherical_harmonics(max_ell) - interaction_irreps = (sh_irreps * number_of_hidden_scalar_dimensions).sort()[0].simplify() + interaction_irreps = ( + (sh_irreps * number_of_hidden_scalar_dimensions).sort()[0].simplify() + ) self.spherical_harmonics = o3.SphericalHarmonics( sh_irreps, normalize=True, normalization="component" ) @@ -233,7 +275,9 @@ def __init__( self.interactions = torch.nn.ModuleList([inter]) if tanh_after_interaction: - self.interactions_tanh = torch.nn.ModuleList([NormActivation(inter.target_irreps, torch.tanh)]) + self.interactions_tanh = torch.nn.ModuleList( + [NormActivation(inter.target_irreps, torch.tanh)] + ) else: self.interactions_tanh = None @@ -261,9 +305,7 @@ def __init__( self.use_batchnorm = use_batchnorm if self.use_batchnorm: - self.batch_norms = torch.nn.ModuleList([ - BatchNorm(node_feats_irreps) - ]) + self.batch_norms = torch.nn.ModuleList([BatchNorm(node_feats_irreps)]) hidden_irreps_out = hidden_irreps @@ -282,7 +324,9 @@ def __init__( self.interactions.append(inter) if self.interactions_tanh is not None: - self.interactions_tanh.append(NormActivation(interaction_irreps, torch.tanh)) + self.interactions_tanh.append( + NormActivation(interaction_irreps, torch.tanh) + ) # prod compute h^{(t+1)} from A^{(t)} and h^{(t)}, computing B and the messages internally. prod = EquivariantProductBasisBlock( @@ -304,9 +348,9 @@ def __init__( # Apply a MLP with a bias on the forces as a conditional feature. This would be a 1o irrep forces_irreps_in = o3.Irreps("1x1o") forces_irreps_embedding = o3.Irreps(f"{condition_embedding_size}x1o") - self.condition_embedding_layer = o3.Linear(irreps_in=forces_irreps_in, - irreps_out=forces_irreps_embedding, - biases=False) # can't have biases with 1o irreps + self.condition_embedding_layer = o3.Linear( + irreps_in=forces_irreps_in, irreps_out=forces_irreps_embedding, biases=False + ) # can't have biases with 1o irreps # conditional layers for the forces as a conditional feature to guide the diffusion self.conditional_layers = torch.nn.ModuleList([]) @@ -314,18 +358,24 @@ def __init__( cond_layer = o3.Linear( irreps_in=forces_irreps_embedding, irreps_out=hidden_irreps_out, - biases=False + biases=False, ) self.conditional_layers.append(cond_layer) - def forward(self, data: Dict[str, torch.Tensor], conditional: bool = False) -> torch.Tensor: + def forward( + self, data: Dict[str, torch.Tensor], conditional: bool = False + ) -> torch.Tensor: """Forward method.""" # Setup # Augment the node attributes with information from the diffusion scalar. - diffusion_scalar_embeddings = self.diffusion_scalar_embedding(data["node_diffusion_scalars"]) + diffusion_scalar_embeddings = self.diffusion_scalar_embedding( + data["node_diffusion_scalars"] + ) raw_node_attributes = data["node_attrs"] - augmented_node_attributes = self.attribute_mixing(diffusion_scalar_embeddings, raw_node_attributes) + augmented_node_attributes = self.attribute_mixing( + diffusion_scalar_embeddings, raw_node_attributes + ) # Embeddings node_feats = self.node_embedding(augmented_node_attributes) @@ -337,14 +387,20 @@ def forward(self, data: Dict[str, torch.Tensor], conditional: bool = False) -> t edge_attrs = self.spherical_harmonics(vectors) edge_feats = self.radial_embedding(lengths) if self.edge_attribute_mixing is not None: - edge_diffusion_scalar_embeddings = self.diffusion_scalar_embedding(data["edge_diffusion_scalars"]) - augmented_edge_attributes = self.edge_attribute_mixing(edge_diffusion_scalar_embeddings, edge_feats) + edge_diffusion_scalar_embeddings = self.diffusion_scalar_embedding( + data["edge_diffusion_scalars"] + ) + augmented_edge_attributes = self.edge_attribute_mixing( + edge_diffusion_scalar_embeddings, edge_feats + ) edge_feats = self.edge_hidden_layers(augmented_edge_attributes) - forces_embedding = self.condition_embedding_layer(data["forces"]) # 0e + 1o embedding + forces_embedding = self.condition_embedding_layer( + data["forces"] + ) # 0e + 1o embedding for i, (interaction, product, cond_layer) in enumerate( - zip(self.interactions, self.products, self.conditional_layers) + zip(self.interactions, self.products, self.conditional_layers) ): if self.use_batchnorm: node_feats = self.batch_norms[i](node_feats) @@ -359,18 +415,24 @@ def forward(self, data: Dict[str, torch.Tensor], conditional: bool = False) -> t if self.interactions_tanh is not None: # reshape from (node, channels, (l_max + 1)**2) to a (node, -1) tensor compatible with e3nn - node_feats = reshape_from_mace_to_e3nn(node_feats, self.interactions_tanh[i].irreps_in) + node_feats = reshape_from_mace_to_e3nn( + node_feats, self.interactions_tanh[i].irreps_in + ) # apply non-linearity node_feats = self.interactions_tanh[i](node_feats) # reshape from e3nn shape to mace format (node, channels, (l_max+1)**2) - node_feats = reshape_from_e3nn_to_mace(node_feats, self.interactions_tanh[i].irreps_out) + node_feats = reshape_from_e3nn_to_mace( + node_feats, self.interactions_tanh[i].irreps_out + ) node_feats = product( node_feats=node_feats, sc=sc, node_attrs=augmented_node_attributes, ) - if conditional: # modify the node features to account for the conditional features i.e. forces + if ( + conditional + ): # modify the node features to account for the conditional features i.e. forces force_embed = cond_layer(forces_embedding) node_feats += force_embed diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/egnn.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/egnn.py index 6aa34c40..86befaa9 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/egnn.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/egnn.py @@ -7,13 +7,15 @@ The file is modified from the original download to fit our own linting style and add additional controls. """ + from typing import Callable, Tuple import torch -from crystal_diffusion.models.egnn_utils import (unsorted_segment_mean, - unsorted_segment_sum) from torch import nn +from diffusion_for_multi_scale_molecular_dynamics.models.egnn_utils import ( + unsorted_segment_mean, unsorted_segment_sum) + class E_GCL(nn.Module): """E(n) Equivariant Convolutional Layer.""" @@ -65,19 +67,26 @@ def __init__( if coords_agg not in ["mean", "sum"]: raise ValueError(f"coords_agg should be mean or sum. Got {coords_agg}") - self.coords_agg_fn = unsorted_segment_sum if coords_agg == "sum" else unsorted_segment_mean - self.msg_agg_fn = unsorted_segment_sum if message_agg == "sum" else unsorted_segment_mean + self.coords_agg_fn = ( + unsorted_segment_sum if coords_agg == "sum" else unsorted_segment_mean + ) + self.msg_agg_fn = ( + unsorted_segment_sum if message_agg == "sum" else unsorted_segment_mean + ) # message update MLP i.e. message m_{ij} used in the graph neural network. # \phi_e is eq. (3) in https://arxiv.org/pdf/2102.09844 # Input is a concatenation of the two node features and distance message_input_size = input_size * 2 + 1 self.message_mlp = nn.Sequential( - nn.Linear(message_input_size, message_hidden_dimensions_size), - act_fn + nn.Linear(message_input_size, message_hidden_dimensions_size), act_fn ) for _ in range(message_n_hidden_dimensions): - self.message_mlp.append(nn.Linear(message_hidden_dimensions_size, message_hidden_dimensions_size)) + self.message_mlp.append( + nn.Linear( + message_hidden_dimensions_size, message_hidden_dimensions_size + ) + ) self.message_mlp.append(act_fn) # node update mlp. Input is the node feature (size input_size) and the aggregated messages from neighbors @@ -85,11 +94,12 @@ def __init__( # \phi_h in eq. (6) in https://arxiv.org/pdf/2102.09844 node_input_size = input_size + message_hidden_dimensions_size self.node_mlp = nn.Sequential( - nn.Linear(node_input_size, node_hidden_dimensions_size), - act_fn + nn.Linear(node_input_size, node_hidden_dimensions_size), act_fn ) for _ in range(node_n_hidden_dimensions): - self.node_mlp.append(nn.Linear(node_hidden_dimensions_size, node_hidden_dimensions_size)) + self.node_mlp.append( + nn.Linear(node_hidden_dimensions_size, node_hidden_dimensions_size) + ) self.node_mlp.append(act_fn) self.node_mlp.append(nn.Linear(node_hidden_dimensions_size, output_size)) @@ -97,12 +107,20 @@ def __init__( # \phi_x in eq.(4) in https://arxiv.org/pdf/2102.09844 coordinate_input_size = message_hidden_dimensions_size - self.coord_mlp = nn.Sequential(nn.Linear(coordinate_input_size, coordinate_hidden_dimensions_size)) + self.coord_mlp = nn.Sequential( + nn.Linear(coordinate_input_size, coordinate_hidden_dimensions_size) + ) self.coord_mlp.append(act_fn) for _ in range(coordinate_n_hidden_dimensions): - self.coord_mlp.append(nn.Linear(coordinate_hidden_dimensions_size, coordinate_hidden_dimensions_size)) + self.coord_mlp.append( + nn.Linear( + coordinate_hidden_dimensions_size, coordinate_hidden_dimensions_size + ) + ) self.coord_mlp.append(act_fn) - final_coordinate_layer = nn.Linear(coordinate_hidden_dimensions_size, 1, bias=False) + final_coordinate_layer = nn.Linear( + coordinate_hidden_dimensions_size, 1, bias=False + ) # based on the original implementation - multiply the random initialization by 0.001 (default is 1) # torch.nn.init.xavier_uniform_(final_coordinate_layer.weight, gain=0.001) self.coord_mlp.append(final_coordinate_layer) # initialized with a different @@ -110,9 +128,13 @@ def __init__( self.coord_mlp.append(nn.Tanh()) if self.attention: - self.att_mlp = nn.Sequential(nn.Linear(message_hidden_dimensions_size, 1), nn.Sigmoid()) + self.att_mlp = nn.Sequential( + nn.Linear(message_hidden_dimensions_size, 1), nn.Sigmoid() + ) - def message_model(self, source: torch.Tensor, target: torch.Tensor, radial: torch.Tensor) -> torch.Tensor: + def message_model( + self, source: torch.Tensor, target: torch.Tensor, radial: torch.Tensor + ) -> torch.Tensor: r"""Constructs the message m_{ij} from source (j) to target (i). .. math:: @@ -136,8 +158,9 @@ def message_model(self, source: torch.Tensor, target: torch.Tensor, radial: torc out = out * att_val return out - def node_model(self, x: torch.Tensor, edge_index: torch.Tensor, messages: torch.Tensor - ) -> torch.Tensor: + def node_model( + self, x: torch.Tensor, edge_index: torch.Tensor, messages: torch.Tensor + ) -> torch.Tensor: r"""Update the node features. .. math:: @@ -153,15 +176,22 @@ def node_model(self, x: torch.Tensor, edge_index: torch.Tensor, messages: torch. updated node features. size: number of nodes, output_size """ row = edge_index[:, 0] - agg = self.msg_agg_fn(messages, row, num_segments=x.size(0)) # sum messages m_i = \sum_j m_{ij} + agg = self.msg_agg_fn( + messages, row, num_segments=x.size(0) + ) # sum messages m_i = \sum_j m_{ij} agg = torch.cat([x, agg], dim=1) # concat h_i and m_i out = self.node_mlp(agg) if self.residual: # optional skip connection out = x + out return out - def coord_model(self, coord: torch.Tensor, edge_index: torch.Tensor, coord_diff: torch.Tensor, - messages: torch.Tensor) -> torch.Tensor: + def coord_model( + self, + coord: torch.Tensor, + edge_index: torch.Tensor, + coord_diff: torch.Tensor, + messages: torch.Tensor, + ) -> torch.Tensor: r"""Update the coordinates. .. math:: @@ -183,7 +213,9 @@ def coord_model(self, coord: torch.Tensor, edge_index: torch.Tensor, coord_diff: coord += agg return coord - def coord2radial(self, edge_index: torch.Tensor, coord: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: + def coord2radial( + self, edge_index: torch.Tensor, coord: torch.Tensor + ) -> Tuple[torch.Tensor, torch.Tensor]: """Compute distances between linked nodes. Args: @@ -206,8 +238,9 @@ def coord2radial(self, edge_index: torch.Tensor, coord: torch.Tensor) -> Tuple[t return radial, coord_diff - def forward(self, h: torch.Tensor, edge_index: torch.Tensor, coord: torch.Tensor - ) -> Tuple[torch.Tensor, torch.Tensor]: + def forward( + self, h: torch.Tensor, edge_index: torch.Tensor, coord: torch.Tensor + ) -> Tuple[torch.Tensor, torch.Tensor]: """Compute node embeddings and coordinates. Args: @@ -232,6 +265,7 @@ def forward(self, h: torch.Tensor, edge_index: torch.Tensor, coord: torch.Tensor class EGNN(nn.Module): """EGNN model.""" + def __init__( self, input_size: int, @@ -295,7 +329,9 @@ def __init__( ) ) - def forward(self, h: torch.Tensor, edges: torch.Tensor, x: torch.Tensor) -> torch.Tensor: + def forward( + self, h: torch.Tensor, edges: torch.Tensor, x: torch.Tensor + ) -> torch.Tensor: """Forward instructions for the model. Args: diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/egnn_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/egnn_utils.py index d1b03359..fa2417fd 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/egnn_utils.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/egnn_utils.py @@ -1,12 +1,16 @@ from typing import List import torch -from crystal_diffusion.models.mace_utils import get_adj_matrix -from crystal_diffusion.utils.basis_transformations import \ + +from diffusion_for_multi_scale_molecular_dynamics.models.mace_utils import \ + get_adj_matrix +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ get_positions_from_coordinates -def unsorted_segment_sum(data: torch.Tensor, segment_ids: torch.Tensor, num_segments: int) -> torch.Tensor: +def unsorted_segment_sum( + data: torch.Tensor, segment_ids: torch.Tensor, num_segments: int +) -> torch.Tensor: """Sum all the elements in data by their ids. For example, data could be messages from atoms j to i. We want to sum all messages going to i, i.e. sum all elements @@ -22,7 +26,9 @@ def unsorted_segment_sum(data: torch.Tensor, segment_ids: torch.Tensor, num_segm tensor with the sum of data elements over ids. size: (num_segments, number of features) """ result_shape = (num_segments, data.size(1)) # output size - result = torch.zeros(result_shape).to(data) # results starting as zeros - same dtype and device as data + result = torch.zeros(result_shape).to( + data + ) # results starting as zeros - same dtype and device as data # tensor size manipulation to use a scatter_add operation # from (number of elements) to (number of elements, number of features) i.e. same size as data segment_ids = segment_ids.unsqueeze(-1).expand(-1, data.size(1)) @@ -32,7 +38,9 @@ def unsorted_segment_sum(data: torch.Tensor, segment_ids: torch.Tensor, num_segm return result -def unsorted_segment_mean(data: torch.Tensor, segment_ids: torch.Tensor, num_segments: int) -> torch.Tensor: +def unsorted_segment_mean( + data: torch.Tensor, segment_ids: torch.Tensor, num_segments: int +) -> torch.Tensor: """Average all the elements in data by their ids. For example, data could be messages from atoms j to i. We want to average all messages going to i @@ -48,12 +56,18 @@ def unsorted_segment_mean(data: torch.Tensor, segment_ids: torch.Tensor, num_seg tensor with the average of data elements over ids. size: (num_segments, number of features) """ result_shape = (num_segments, data.size(1)) # output size - segment_ids = segment_ids.unsqueeze(-1).expand(-1, data.size(1)) # tensor size manipulation for the backward pass + segment_ids = segment_ids.unsqueeze(-1).expand( + -1, data.size(1) + ) # tensor size manipulation for the backward pass result = torch.zeros(result_shape).to(data) # sum the component - count = torch.zeros(result_shape).to(data) # count the number of data elements for each id to take the average + count = torch.zeros(result_shape).to( + data + ) # count the number of data elements for each id to take the average result.scatter_add_(0, segment_ids, data) # sum the data elements count.scatter_add_(0, segment_ids, torch.ones_like(data)) - return result / count.clamp(min=1) # avoid dividing by zeros by clamping the counts to be at least 1 + return result / count.clamp( + min=1 + ) # avoid dividing by zeros by clamping the counts to be at least 1 def get_edges(n_nodes: int) -> List[List[int]]: @@ -90,8 +104,12 @@ def get_edges_batch(n_nodes: int, batch_size: int) -> torch.Tensor: return edges -def get_edges_with_radial_cutoff(relative_coordinates: torch.Tensor, unit_cell: torch.Tensor, - radial_cutoff: float = 4.0, drop_duplicate_edges: bool = True) -> torch.Tensor: +def get_edges_with_radial_cutoff( + relative_coordinates: torch.Tensor, + unit_cell: torch.Tensor, + radial_cutoff: float = 4.0, + drop_duplicate_edges: bool = True, +) -> torch.Tensor: """Get edges for a batch with a cutoff based on distance. Args: @@ -105,8 +123,12 @@ def get_edges_with_radial_cutoff(relative_coordinates: torch.Tensor, unit_cell: long tensor of size [number of edges, 2] with edge indices """ # get cartesian coordinates from relative coordinates - cartesian_coordinates = get_positions_from_coordinates(relative_coordinates, unit_cell) - adj_matrix, _, _, _ = get_adj_matrix(cartesian_coordinates, unit_cell, radial_cutoff) + cartesian_coordinates = get_positions_from_coordinates( + relative_coordinates, unit_cell + ) + adj_matrix, _, _, _ = get_adj_matrix( + cartesian_coordinates, unit_cell, radial_cutoff + ) # adj_matrix is a n_edges x 2 tensor with duplicates with different shifts. # the uplifting in 2 x spatial_dimension manages the shifts in a natural way. This means we can ignore the shifts # and possibly ignore the multiplicities i.e. no need to sum twice the contribution of a neighbor that we see diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/graph_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/graph_utils.py index 5750c4e0..3f3defc1 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/graph_utils.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/graph_utils.py @@ -1,14 +1,15 @@ from typing import Tuple import torch -from crystal_diffusion.utils.neighbors import ( + +from diffusion_for_multi_scale_molecular_dynamics.utils.neighbors import ( get_periodic_adjacency_information, shift_adjacency_matrix_indices_for_graph_batching) -def get_adj_matrix(positions: torch.Tensor, - basis_vectors: torch.Tensor, - radial_cutoff: float = 4.0) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: +def get_adj_matrix( + positions: torch.Tensor, basis_vectors: torch.Tensor, radial_cutoff: float = 4.0 +) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: """Create the adjacency and shift matrices. Args: @@ -30,15 +31,17 @@ def get_adj_matrix(positions: torch.Tensor, """ batch_size, number_of_atoms, spatial_dimensions = positions.shape - adjacency_info = get_periodic_adjacency_information(positions, basis_vectors, radial_cutoff) + adjacency_info = get_periodic_adjacency_information( + positions, basis_vectors, radial_cutoff + ) # The indices in the adjacency matrix must be shifted to account for the batching # of multiple distinct structures into a single disconnected graph. adjacency_matrix = adjacency_info.adjacency_matrix number_of_edges = adjacency_info.number_of_edges - shifted_adjacency_matrix = shift_adjacency_matrix_indices_for_graph_batching(adjacency_matrix, - number_of_edges, - number_of_atoms) + shifted_adjacency_matrix = shift_adjacency_matrix_indices_for_graph_batching( + adjacency_matrix, number_of_edges, number_of_atoms + ) shifts = adjacency_info.shifts batch_indices = adjacency_info.node_batch_indices diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/instantiate_diffusion_model.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/instantiate_diffusion_model.py index 3856e095..d6ff405f 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/instantiate_diffusion_model.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/instantiate_diffusion_model.py @@ -1,22 +1,29 @@ """Functions to instantiate a model based on the provided hyperparameters.""" + import logging from typing import Any, AnyStr, Dict -from crystal_diffusion.models.loss import create_loss_parameters -from crystal_diffusion.models.optimizer import create_optimizer_parameters -from crystal_diffusion.models.position_diffusion_lightning_model import ( +from diffusion_for_multi_scale_molecular_dynamics.models.loss import \ + create_loss_parameters +from diffusion_for_multi_scale_molecular_dynamics.models.optimizer import \ + create_optimizer_parameters +from diffusion_for_multi_scale_molecular_dynamics.models.position_diffusion_lightning_model import ( PositionDiffusionLightningModel, PositionDiffusionParameters) -from crystal_diffusion.models.scheduler import create_scheduler_parameters -from crystal_diffusion.models.score_networks.score_network_factory import \ +from diffusion_for_multi_scale_molecular_dynamics.models.scheduler import \ + create_scheduler_parameters +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_network_factory import \ create_score_network_parameters -from crystal_diffusion.samples.diffusion_sampling_parameters import \ +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters +from diffusion_for_multi_scale_molecular_dynamics.samples.diffusion_sampling_parameters import \ load_diffusion_sampling_parameters -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters logger = logging.getLogger(__name__) -def load_diffusion_model(hyper_params: Dict[AnyStr, Any]) -> PositionDiffusionLightningModel: +def load_diffusion_model( + hyper_params: Dict[AnyStr, Any] +) -> PositionDiffusionLightningModel: """Load a position diffusion model from the hyperparameters. Args: @@ -25,21 +32,25 @@ def load_diffusion_model(hyper_params: Dict[AnyStr, Any]) -> PositionDiffusionLi Returns: Diffusion model randomly initialized """ - globals_dict = dict(max_atom=hyper_params['data']['max_atom'], - spatial_dimension=hyper_params.get('spatial_dimension', 3)) + globals_dict = dict( + max_atom=hyper_params["data"]["max_atom"], + spatial_dimension=hyper_params.get("spatial_dimension", 3), + ) - score_network_dict = hyper_params['model']['score_network'] - score_network_parameters = create_score_network_parameters(score_network_dict, globals_dict) + score_network_dict = hyper_params["model"]["score_network"] + score_network_parameters = create_score_network_parameters( + score_network_dict, globals_dict + ) - optimizer_configuration_dict = hyper_params['optimizer'] + optimizer_configuration_dict = hyper_params["optimizer"] optimizer_parameters = create_optimizer_parameters(optimizer_configuration_dict) scheduler_parameters = create_scheduler_parameters(hyper_params) - model_dict = hyper_params['model'] + model_dict = hyper_params["model"] loss_parameters = create_loss_parameters(model_dict) - noise_dict = model_dict['noise'] + noise_dict = model_dict["noise"] noise_parameters = NoiseParameters(**noise_dict) diffusion_sampling_parameters = load_diffusion_sampling_parameters(hyper_params) @@ -54,6 +65,6 @@ def load_diffusion_model(hyper_params: Dict[AnyStr, Any]) -> PositionDiffusionLi ) model = PositionDiffusionLightningModel(diffusion_params) - logger.info('model info:\n' + str(model) + '\n') + logger.info("model info:\n" + str(model) + "\n") return model diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/loss.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/loss.py index 8af9aa47..90daaf11 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/loss.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/loss.py @@ -2,26 +2,30 @@ from typing import Any, Dict import torch -from crystal_diffusion.utils.configuration_parsing import \ + +from diffusion_for_multi_scale_molecular_dynamics.utils.configuration_parsing import \ create_parameters_from_configuration_dictionary @dataclass(kw_only=True) class LossParameters: """Specific Hyper-parameters for the loss function.""" + algorithm: str @dataclass(kw_only=True) class MSELossParameters(LossParameters): """Specific Hyper-parameters for the MSE loss function.""" - algorithm: str = 'mse' + + algorithm: str = "mse" @dataclass(kw_only=True) class WeightedMSELossParameters(LossParameters): """Specific Hyper-parameters for the weighted MSE loss function.""" - algorithm: str = 'weighted_mse' + + algorithm: str = "weighted_mse" # The default values are chosen to lead to a flat loss curve vs. sigma, based on preliminary experiments. # These parameters have no effect if the algorithm is 'mse'. # The default parameters are chosen such that weights(sigma=0.5) \sim 10^3 @@ -31,14 +35,18 @@ class WeightedMSELossParameters(LossParameters): class LossCalculator(torch.nn.Module): """Class to calculate the loss.""" + def __init__(self, loss_parameters: LossParameters): """Init method.""" super().__init__() self.loss_parameters = loss_parameters - def calculate_unreduced_loss(self, predicted_normalized_scores: torch.tensor, - target_normalized_conditional_scores: torch.tensor, - sigmas: torch.Tensor) -> torch.tensor: + def calculate_unreduced_loss( + self, + predicted_normalized_scores: torch.tensor, + target_normalized_conditional_scores: torch.tensor, + sigmas: torch.Tensor, + ) -> torch.tensor: """Calculate unreduced Loss. All inputs are assumed to be tensors of dimension [batch_size, number_of_atoms, spatial_dimension]. In @@ -57,14 +65,18 @@ def calculate_unreduced_loss(self, predicted_normalized_scores: torch.tensor, class MSELossCalculator(LossCalculator): """Class to calculate the MSE loss.""" + def __init__(self, loss_parameters: MSELossParameters): """Init method.""" super().__init__(loss_parameters) - self.mse_loss = torch.nn.MSELoss(reduction='none') - - def calculate_unreduced_loss(self, predicted_normalized_scores: torch.tensor, - target_normalized_conditional_scores: torch.tensor, - sigmas: torch.Tensor) -> torch.tensor: + self.mse_loss = torch.nn.MSELoss(reduction="none") + + def calculate_unreduced_loss( + self, + predicted_normalized_scores: torch.tensor, + target_normalized_conditional_scores: torch.tensor, + sigmas: torch.Tensor, + ) -> torch.tensor: """Calculate unreduced Loss. All inputs are assumed to be tensors of dimension [batch_size, number_of_atoms, spatial_dimension]. In @@ -78,14 +90,20 @@ def calculate_unreduced_loss(self, predicted_normalized_scores: torch.tensor, Returns: unreduced_loss: a tensor of shape [batch_size, number_of_atoms, spatial_dimension]. Its mean is the loss. """ - assert predicted_normalized_scores.shape == target_normalized_conditional_scores.shape == sigmas.shape, \ - "Inconsistent shapes" - unreduced_loss = self.mse_loss(predicted_normalized_scores, target_normalized_conditional_scores) + assert ( + predicted_normalized_scores.shape + == target_normalized_conditional_scores.shape + == sigmas.shape + ), "Inconsistent shapes" + unreduced_loss = self.mse_loss( + predicted_normalized_scores, target_normalized_conditional_scores + ) return unreduced_loss class WeightedMSELossCalculator(MSELossCalculator): """Class to calculate the loss.""" + def __init__(self, loss_parameters: WeightedMSELossParameters): """Init method.""" super().__init__(loss_parameters) @@ -97,9 +115,12 @@ def _exponential_weights(self, sigmas): weights = torch.exp(self.exponent * (sigmas - self.sigma0)) + 1.0 return weights - def calculate_unreduced_loss(self, predicted_normalized_scores: torch.tensor, - target_normalized_conditional_scores: torch.tensor, - sigmas: torch.Tensor) -> torch.tensor: + def calculate_unreduced_loss( + self, + predicted_normalized_scores: torch.tensor, + target_normalized_conditional_scores: torch.tensor, + sigmas: torch.Tensor, + ) -> torch.tensor: """Calculate unreduced Loss. All inputs are assumed to be tensors of dimension [batch_size, number_of_atoms, spatial_dimension]. In @@ -113,17 +134,24 @@ def calculate_unreduced_loss(self, predicted_normalized_scores: torch.tensor, Returns: unreduced_loss: a tensor of shape [batch_size, number_of_atoms, spatial_dimension]. It's mean is the loss. """ - assert predicted_normalized_scores.shape == target_normalized_conditional_scores.shape == sigmas.shape, \ - "Inconsistent shapes" - - unreduced_mse_loss = self.mse_loss(predicted_normalized_scores, target_normalized_conditional_scores) + assert ( + predicted_normalized_scores.shape + == target_normalized_conditional_scores.shape + == sigmas.shape + ), "Inconsistent shapes" + + unreduced_mse_loss = self.mse_loss( + predicted_normalized_scores, target_normalized_conditional_scores + ) weights = self._exponential_weights(sigmas) unreduced_loss = unreduced_mse_loss * weights return unreduced_loss -LOSS_PARAMETERS_BY_ALGO = dict(mse=MSELossParameters, weighted_mse=WeightedMSELossParameters) +LOSS_PARAMETERS_BY_ALGO = dict( + mse=MSELossParameters, weighted_mse=WeightedMSELossParameters +) LOSS_BY_ALGO = dict(mse=MSELossCalculator, weighted_mse=WeightedMSELossCalculator) @@ -138,13 +166,14 @@ def create_loss_parameters(model_dictionary: Dict[str, Any]) -> LossParameters: Returns: loss_parameters: the loss parameters. """ - default_dict = dict(algorithm='mse') + default_dict = dict(algorithm="mse") loss_config_dictionary = model_dictionary.get("loss", default_dict) - loss_parameters = ( - create_parameters_from_configuration_dictionary(configuration=loss_config_dictionary, - identifier="algorithm", - options=LOSS_PARAMETERS_BY_ALGO)) + loss_parameters = create_parameters_from_configuration_dictionary( + configuration=loss_config_dictionary, + identifier="algorithm", + options=LOSS_PARAMETERS_BY_ALGO, + ) return loss_parameters @@ -160,7 +189,8 @@ def create_loss_calculator(loss_parameters: LossParameters) -> LossCalculator: loss_calculator : the loss calculator. """ algorithm = loss_parameters.algorithm - assert algorithm in LOSS_BY_ALGO.keys(), \ - f"Algorithm {algorithm} is not implemented. Possible choices are {LOSS_BY_ALGO.keys()}" + assert ( + algorithm in LOSS_BY_ALGO.keys() + ), f"Algorithm {algorithm} is not implemented. Possible choices are {LOSS_BY_ALGO.keys()}" return LOSS_BY_ALGO[algorithm](loss_parameters) diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/mace_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/mace_utils.py index fde3cf5b..1a7e1595 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/mace_utils.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/mace_utils.py @@ -3,11 +3,14 @@ from typing import AnyStr, Dict, Tuple import torch -from crystal_diffusion.models.graph_utils import get_adj_matrix -from crystal_diffusion.namespace import NOISY_CARTESIAN_POSITIONS, UNIT_CELL from e3nn import o3 from torch_geometric.data import Data +from diffusion_for_multi_scale_molecular_dynamics.models.graph_utils import \ + get_adj_matrix +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + NOISY_CARTESIAN_POSITIONS, UNIT_CELL) + def input_to_mace(x: Dict[AnyStr, torch.Tensor], radial_cutoff: float) -> Data: """Convert score network input to MACE input. @@ -24,31 +27,41 @@ def input_to_mace(x: Dict[AnyStr, torch.Tensor], radial_cutoff: float) -> Data: batch_size, n_atom_per_graph, spatial_dimension = noisy_cartesian_positions.shape device = noisy_cartesian_positions.device - adj_matrix, shift_matrix, batch_tensor, _ = get_adj_matrix(positions=noisy_cartesian_positions, - basis_vectors=cell, - radial_cutoff=radial_cutoff) + adj_matrix, shift_matrix, batch_tensor, _ = get_adj_matrix( + positions=noisy_cartesian_positions, + basis_vectors=cell, + radial_cutoff=radial_cutoff, + ) # node features are int corresponding to atom type # TODO handle different atom types - node_attrs = torch.nn.functional.one_hot((torch.ones(batch_size * n_atom_per_graph) * 14).long(), - num_classes=89).float() - flat_positions = noisy_cartesian_positions.view(-1, spatial_dimension) # [batchsize * natoms, spatial dimension] + node_attrs = torch.nn.functional.one_hot( + (torch.ones(batch_size * n_atom_per_graph) * 14).long(), num_classes=89 + ).float() + flat_positions = noisy_cartesian_positions.view( + -1, spatial_dimension + ) # [batchsize * natoms, spatial dimension] # pointer tensor that yields the first node index for each batch - this is a fixed tensor in our case - ptr = torch.arange(0, n_atom_per_graph * batch_size + 1, step=n_atom_per_graph) # 0, natoms, 2 * natoms, ... + ptr = torch.arange( + 0, n_atom_per_graph * batch_size + 1, step=n_atom_per_graph + ) # 0, natoms, 2 * natoms, ... cell = cell.view(-1, cell.size(-1)) # batch * spatial_dimension, spatial_dimension # create the pytorch-geometric graph - graph_data = Data(edge_index=adj_matrix, - node_attrs=node_attrs.to(device), - positions=flat_positions, - ptr=ptr.to(device), - batch=batch_tensor.to(device), - shifts=shift_matrix, - cell=cell - ) + graph_data = Data( + edge_index=adj_matrix, + node_attrs=node_attrs.to(device), + positions=flat_positions, + ptr=ptr.to(device), + batch=batch_tensor.to(device), + shifts=shift_matrix, + cell=cell, + ) return graph_data -def build_mace_output_nodes_irreducible_representation(hidden_irreps_string: str, num_interactions: int) -> o3.Irreps: +def build_mace_output_nodes_irreducible_representation( + hidden_irreps_string: str, num_interactions: int +) -> o3.Irreps: """Build the mace output node irreps. Args: @@ -66,7 +79,9 @@ def build_mace_output_nodes_irreducible_representation(hidden_irreps_string: str # E3NN irreps gymnastics is a bit fragile. We have to build the scalar representation explicitly scalar_hidden_irreps = o3.Irreps(f"{hidden_irreps[0].mul}x{hidden_irreps[0].ir}") - total_irreps = o3.Irreps('') # An empty irrep to start the "sum", which is really a concatenation + total_irreps = o3.Irreps( + "" + ) # An empty irrep to start the "sum", which is really a concatenation for _ in range(num_interactions - 1): total_irreps += hidden_irreps @@ -86,21 +101,28 @@ def get_pretrained_mace_output_node_features_irreps(model_name: str) -> o3.Irrep """ match model_name: case "small": - irreps = build_mace_output_nodes_irreducible_representation(hidden_irreps_string="128x0e", - num_interactions=2) + irreps = build_mace_output_nodes_irreducible_representation( + hidden_irreps_string="128x0e", num_interactions=2 + ) case "medium": - irreps = build_mace_output_nodes_irreducible_representation(hidden_irreps_string="128x0e + 128x1o", - num_interactions=2) + irreps = build_mace_output_nodes_irreducible_representation( + hidden_irreps_string="128x0e + 128x1o", num_interactions=2 + ) case "large": - irreps = build_mace_output_nodes_irreducible_representation(hidden_irreps_string="128x0e + 128x1o + 128x2e", - num_interactions=2) + irreps = build_mace_output_nodes_irreducible_representation( + hidden_irreps_string="128x0e + 128x1o + 128x2e", num_interactions=2 + ) case _: - raise ValueError(f"Model name should be small, medium or large. Got {model_name}") + raise ValueError( + f"Model name should be small, medium or large. Got {model_name}" + ) return irreps -def get_pretrained_mace(model_name: str, model_savedir_path: str) -> Tuple[torch.nn.Module, int]: +def get_pretrained_mace( + model_name: str, model_savedir_path: str +) -> Tuple[torch.nn.Module, int]: """Download and load a pre-trained MACE network. Based on the mace-torch library. @@ -114,15 +136,25 @@ def get_pretrained_mace(model_name: str, model_savedir_path: str) -> Tuple[torch model: the pre-trained MACE model as a torch.nn.Module node_feats_output_size: size of the node features embedding in the model's output """ - assert model_name in ["small", "medium", "large"], f"Model name should be small, medium or large. Got {model_name}" + assert model_name in [ + "small", + "medium", + "large", + ], f"Model name should be small, medium or large. Got {model_name}" # from mace library code urls = dict( - small=("https://tinyurl.com/46jrkm3v", 256), # 2023-12-10-mace-128-L0_energy_epoch-249.model - medium=("https://tinyurl.com/5yyxdm76", 640), # 2023-12-03-mace-128-L1_epoch-199.model + small=( + "https://tinyurl.com/46jrkm3v", + 256, + ), # 2023-12-10-mace-128-L0_energy_epoch-249.model + medium=( + "https://tinyurl.com/5yyxdm76", + 640, + ), # 2023-12-03-mace-128-L1_epoch-199.model large=("https://tinyurl.com/5f5yavf3", 1280), # MACE_MPtrj_2022.9.model ) - checkpoint_url, node_feats_output_size = (urls.get(model_name, urls["medium"])) + checkpoint_url, node_feats_output_size = urls.get(model_name, urls["medium"]) checkpoint_url_name = "".join( c for c in os.path.basename(checkpoint_url) if c.isalnum() or c in "_" @@ -134,14 +166,18 @@ def get_pretrained_mace(model_name: str, model_savedir_path: str) -> Tuple[torch # download and save to disk _, http_msg = urllib.request.urlretrieve(checkpoint_url, cached_model_path) if "Content-Type: text/html" in http_msg: - raise RuntimeError(f"Model download failed, please check the URL {checkpoint_url}") + raise RuntimeError( + f"Model download failed, please check the URL {checkpoint_url}" + ) model = torch.load(f=cached_model_path).float() return model, node_feats_output_size -def get_normalized_irreps_permutation_indices(irreps: o3.Irreps) -> Tuple[o3.Irreps, torch.Tensor]: +def get_normalized_irreps_permutation_indices( + irreps: o3.Irreps, +) -> Tuple[o3.Irreps, torch.Tensor]: """Get normalized irreps and permutation indices. Args: @@ -192,7 +228,9 @@ def reshape_from_mace_to_e3nn(x: torch.Tensor, irreps: o3.Irreps) -> torch.Tenso x_ = [] for ell in range(irreps.lmax + 1): # for example, for l=1, take indices 1, 2, 3 (in the last index) and flatten as a channel * 3 tensor - x_l = x[:, :, (ell ** 2):(ell + 1)**2].reshape(node, -1) # node, channel * (2l + 1) + x_l = x[:, :, (ell**2): (ell + 1) ** 2].reshape( + node, -1 + ) # node, channel * (2l + 1) x_.append(x_l) # stack the flatten irrep tensors together return torch.cat(x_, dim=-1) diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/mlip/mtp.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/mlip/mtp.py index b6bc20ea..f4195616 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/mlip/mtp.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/mlip/mtp.py @@ -4,6 +4,7 @@ However, it cannot be called as a standard lightning module as it relies on the MLIP-3 library for the model implementation. """ + import itertools import os import re @@ -15,34 +16,50 @@ import numpy as np import pandas as pd -from crystal_diffusion.mlip.mtp_utils import (MTPInputs, concat_mtp_inputs, - crawl_lammps_directory, - prepare_mtp_inputs_from_lammps) from maml.apps.pes import MTPotential from maml.utils import check_structures_forces_stresses, pool_from from monty.io import zopen from monty.tempfile import ScratchDir from pymatgen.core import Structure +from diffusion_for_multi_scale_molecular_dynamics.mlip.mtp_utils import ( + MTPInputs, concat_mtp_inputs, crawl_lammps_directory, + prepare_mtp_inputs_from_lammps) + @dataclass(kw_only=True) class MTPArguments: """Arguments to train an MTP with the MLIP3 library.""" + mlip_path: str # path to MLIP3 library name: Optional[str] = None # MTP param: Optional[Dict[Any, Any]] = None unfitted_mtp: str = "08.almtp" # Define the initial mtp file. Default to 08g.amltp - fitted_mtp_savedir: str = '../' # save directory for the fitted MTP. Defaults to '../' (current wd) + fitted_mtp_savedir: str = ( + "../" # save directory for the fitted MTP. Defaults to '../' (current wd) + ) max_dist: float = 5 # The actual radial cutoff. Defaults to 5. - radial_basis_size: int = 8 # Relevant to number of radial basis function. Defaults to 8. + radial_basis_size: int = ( + 8 # Relevant to number of radial basis function. Defaults to 8. + ) max_iter: int = 1000 # The number of maximum iteration. Defaults to 1000. energy_weight: float = 1 # The weight of energy. Defaults to 1 force_weight: float = 1e-2 # The weight of forces. Defaults to 1e-2 - stress_weight: float = 1e-3 # The weight of stresses. Zero-weight can be assigned. Defaults to 1e-3. - init_params: str = "same" # how to initialize parameters if a potential was not pre-fitted: "same" or "random". - scale_by_force: float = 0 # If > 0 then configurations near equilibrium get more weight. Defaults to 0. - bfgs_conv_tol: float = 1e-3 # Stop training if error dropped by a factor smaller than this over 50 BFGS iterations. - weighting: str = "vibration" # How to weight configuration with different sizes relative to each other. + stress_weight: float = ( + 1e-3 # The weight of stresses. Zero-weight can be assigned. Defaults to 1e-3. + ) + init_params: str = ( + "same" # how to initialize parameters if a potential was not pre-fitted: "same" or "random". + ) + scale_by_force: float = ( + 0 # If > 0 then configurations near equilibrium get more weight. Defaults to 0. + ) + bfgs_conv_tol: float = ( + 1e-3 # Stop training if error dropped by a factor smaller than this over 50 BFGS iterations. + ) + weighting: str = ( + "vibration" # How to weight configuration with different sizes relative to each other. + ) # Choose from "vibrations", "molecules" and "structures". Defaults to "vibration". @@ -57,9 +74,13 @@ def __init__(self, mtp_args: MTPArguments): """ super().__init__(mtp_args.name, mtp_args.param) self.mlp_command = os.path.join(mtp_args.mlip_path, "build", "mlp") - assert os.path.exists(self.mlp_command), "mlp command not found in mlip-3 build folder" + assert os.path.exists( + self.mlp_command + ), "mlp command not found in mlip-3 build folder" self.mlp_templates = os.path.join(mtp_args.mlip_path, "MTP_templates") - assert os.path.exists(self.mlp_templates), "MTP templates not found in mlip-3 folder" + assert os.path.exists( + self.mlp_templates + ), "MTP templates not found in mlip-3 folder" self.fitted_mtp = None self.elements = None self.mtp_args = mtp_args @@ -80,10 +101,9 @@ def to_lammps_format(self): # ) pass - def evaluate(self, - dataset: MTPInputs, - mlip_name: str = 'mtp_fitted.almtp' - ) -> Tuple[pd.DataFrame, pd.DataFrame]: + def evaluate( + self, dataset: MTPInputs, mlip_name: str = "mtp_fitted.almtp" + ) -> Tuple[pd.DataFrame, pd.DataFrame]: """Evaluate energies, forces, stresses and MaxVol gamma factor of structures with trained MTP. Args: @@ -98,8 +118,8 @@ def evaluate(self, dataframe with ground truth energies, forces dataframe with predicted energies, forces, MaxVol gamma (nbh grades) """ - if not mlip_name.endswith('.almtp'): - mlip_name += '.almtp' + if not mlip_name.endswith(".almtp"): + mlip_name += ".almtp" assert os.path.exists(mlip_name), f"Trained MTP does not exists: {mlip_name}" original_file = "original.cfgs" @@ -112,17 +132,27 @@ def evaluate(self, predict_pool = pool_from(test_structures, dataset.energy, test_forces) local_mtp_name = "mtp.almtp" - with ScratchDir("."): # mlip needs a tmp_work_dir - we will manually copy relevant outputs elsewhere + with ScratchDir( + "." + ): # mlip needs a tmp_work_dir - we will manually copy relevant outputs elsewhere # write the structures to evaluate in a mlp compatible format original_file = self.write_cfg(original_file, cfg_pool=predict_pool) # TODO how to handle when GT is not available - df_orig = self.read_cfgs(original_file, nbh_grade=False) # read original values as a DataFrame + df_orig = self.read_cfgs( + original_file, nbh_grade=False + ) # read original values as a DataFrame # copy the trained mtp in the scratchdir shutil.copyfile(mlip_name, os.path.join(os.getcwd(), local_mtp_name)) # calculate_grade is the method to get the forces, energy & maxvol values - cmd = [self.mlp_command, "calculate_grade", local_mtp_name, original_file, predict_file] - predict_file += '.0' # added by mlp... + cmd = [ + self.mlp_command, + "calculate_grade", + local_mtp_name, + original_file, + predict_file, + ] + predict_file += ".0" # added by mlp... stdout, rc = self._call_mlip(cmd) # check that MTP was called properly @@ -130,7 +160,9 @@ def evaluate(self, error_msg = f"mlp exited with return code {rc}" msg = stdout.decode("utf-8").split("\n")[:-1] try: - error_line = next(i for i, m in enumerate(msg) if m.startswith("ERROR")) + error_line = next( + i for i, m in enumerate(msg) if m.startswith("ERROR") + ) error_msg += ", ".join(msg[error_line:]) except Exception: error_msg += msg[-1] @@ -150,6 +182,7 @@ def read_cfgs(self, filename: str, nbh_grade: bool = False) -> pd.DataFrame: Returns: dataframe with energies, forces, optional nbh grades (MaxVol gamma) """ + def formatify(string: str) -> List[float]: """Convert string to a list of float.""" return [float(s) for s in string.split()] @@ -216,22 +249,28 @@ def convert_to_dataframe(docs: List[Dict[str, Any]]) -> pd.DataFrame: """ df = defaultdict(list) for s_idx, d in enumerate(docs): - n_atom = d['num_atoms'] + n_atom = d["num_atoms"] outputs = d["outputs"] pos_arr = np.array(outputs["position"]) - assert n_atom == pos_arr.shape[0], "Number of positions do not match number of atoms" + assert ( + n_atom == pos_arr.shape[0] + ), "Number of positions do not match number of atoms" force_arr = np.array(outputs["forces"]) - assert n_atom == force_arr.shape[0], "Number of forces do not match number of atoms" - for i, x in enumerate(['x', 'y', 'z']): + assert ( + n_atom == force_arr.shape[0] + ), "Number of forces do not match number of atoms" + for i, x in enumerate(["x", "y", "z"]): df[x] += pos_arr[:, i].tolist() - df[f'f{x}'] += force_arr[:, i].tolist() - df['energy'] += [outputs['energy']] * n_atom # copy the value to all atoms + df[f"f{x}"] += force_arr[:, i].tolist() + df["energy"] += [outputs["energy"]] * n_atom # copy the value to all atoms if "nbh_grades" in outputs.keys(): nbh_grades = outputs["nbh_grades"] - assert n_atom == len(nbh_grades), "Number of gamma values do not match number of atoms" - df['nbh_grades'] += nbh_grades - df['atom_index'] += list(range(n_atom)) - df['structure_index'] += [s_idx] * n_atom + assert n_atom == len( + nbh_grades + ), "Number of gamma values do not match number of atoms" + df["nbh_grades"] += nbh_grades + df["atom_index"] += list(range(n_atom)) + df["structure_index"] += [s_idx] * n_atom df = pd.DataFrame(df) return df @@ -265,10 +304,10 @@ def _call_cmd_to_stdout(cmd: List[str], output_file: TextIO): @staticmethod def prepare_dataset_from_lammps( - root_data_dir: str, - atom_dict: Dict[int, str], - mode: str = "train", - get_forces: bool = True, + root_data_dir: str, + atom_dict: Dict[int, str], + mode: str = "train", + get_forces: bool = True, ) -> MTPInputs: """Get the LAMMPS in a folder and organize them as inputs for a MTP. @@ -282,17 +321,19 @@ def prepare_dataset_from_lammps( inputs for MTP in the MTPInputs dataclass """ lammps_outputs, thermo_outputs = crawl_lammps_directory(root_data_dir, mode) - mtp_dataset = prepare_mtp_inputs_from_lammps(lammps_outputs, thermo_outputs, atom_dict, get_forces=get_forces) + mtp_dataset = prepare_mtp_inputs_from_lammps( + lammps_outputs, thermo_outputs, atom_dict, get_forces=get_forces + ) return mtp_dataset @staticmethod def prepare_dataset_from_numpy( - cartesian_positions: np.ndarray, - box: np.ndarray, - forces: np.ndarray, - energy: float, - atom_type: np.ndarray, - atom_dict: Dict[int, str] = {1: 'Si'} + cartesian_positions: np.ndarray, + box: np.ndarray, + forces: np.ndarray, + energy: float, + atom_type: np.ndarray, + atom_dict: Dict[int, str] = {1: "Si"}, ) -> MTPInputs: """Convert numpy array variables to a format compatible with MTP. @@ -311,9 +352,11 @@ def prepare_dataset_from_numpy( lattice=box, species=[atom_dict[x] for x in atom_type], coords=cartesian_positions, - coords_are_cartesian=True + coords_are_cartesian=True, ) - forces = forces.tolist() # from Nx3 np array to a list of length N where each element is a list of 3 forces + forces = ( + forces.tolist() + ) # from Nx3 np array to a list of length N where each element is a list of 3 forces return MTPInputs(structure=[structure], forces=[forces], energy=[energy]) @staticmethod @@ -331,7 +374,7 @@ def merge_inputs(mtp_inputs: List[MTPInputs]) -> MTPInputs: merged_inputs = concat_mtp_inputs(merged_inputs, x) return merged_inputs - def train(self, dataset: MTPInputs, mlip_name: str = 'mtp_fitted.almtp') -> str: + def train(self, dataset: MTPInputs, mlip_name: str = "mtp_fitted.almtp") -> str: """Training data with moment tensor method using MLIP-3. Override the base class method. @@ -347,24 +390,30 @@ def train(self, dataset: MTPInputs, mlip_name: str = 'mtp_fitted.almtp') -> str: Returns: fitted_mtp: path to the fitted MTP """ - train_structures, train_forces, train_stresses = check_structures_forces_stresses( - dataset.structure, dataset.forces, None + train_structures, train_forces, train_stresses = ( + check_structures_forces_stresses(dataset.structure, dataset.forces, None) ) # last argument is for stresses - not used currently train_pool = pool_from(train_structures, dataset.energy, train_forces) - elements = sorted(set(itertools.chain(*[struct.species for struct in train_structures]))) + elements = sorted( + set(itertools.chain(*[struct.species for struct in train_structures])) + ) self.elements = [str(element) for element in elements] # TODO move to __init__ atoms_filename = "train.cfgs" - with ((ScratchDir("."))): # create a tmpdir - deleted afterwards - atoms_filename = self.write_cfg(filename=atoms_filename, cfg_pool=train_pool) + with ScratchDir("."): # create a tmpdir - deleted afterwards + atoms_filename = self.write_cfg( + filename=atoms_filename, cfg_pool=train_pool + ) if not self.mtp_args.unfitted_mtp: raise RuntimeError("No specific parameter file provided.") mtp_file_path = os.path.join(self.mlp_templates, self.mtp_args.unfitted_mtp) - shutil.copyfile(mtp_file_path, os.path.join(os.getcwd(), self.mtp_args.unfitted_mtp)) + shutil.copyfile( + mtp_file_path, os.path.join(os.getcwd(), self.mtp_args.unfitted_mtp) + ) commands = [self.mlp_command, "mindist", atoms_filename] with open("min_dist", "w") as f: self._call_cmd_to_stdout(commands, f) @@ -376,8 +425,8 @@ def train(self, dataset: MTPInputs, mlip_name: str = 'mtp_fitted.almtp') -> str: # min_dist = float(lines[-1].split(split_symbol)[1]) save_fitted_mtp = mlip_name - if not save_fitted_mtp.endswith('.almtp'): - save_fitted_mtp += '.almtp' + if not save_fitted_mtp.endswith(".almtp"): + save_fitted_mtp += ".almtp" cmds_list = [ self.mlp_command, @@ -401,7 +450,9 @@ def train(self, dataset: MTPInputs, mlip_name: str = 'mtp_fitted.almtp') -> str: error_msg = f"MLP exited with return code {rc}" msg = stdout.decode("utf-8").split("\n")[:-1] try: - error_line = next(i for i, m in enumerate(msg) if m.startswith("ERROR")) + error_line = next( + i for i, m in enumerate(msg) if m.startswith("ERROR") + ) error_msg += ", ".join(msg[error_line:]) except Exception: error_msg += msg[-1] diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/normalized_score_fokker_planck_error.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/normalized_score_fokker_planck_error.py index 2d57d9e1..a3f2949b 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/normalized_score_fokker_planck_error.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/normalized_score_fokker_planck_error.py @@ -2,14 +2,17 @@ import einops import torch -from crystal_diffusion.models.score_networks import ScoreNetwork -from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, - NOISY_RELATIVE_COORDINATES, TIME, - UNIT_CELL) -from crystal_diffusion.samplers.exploding_variance import ExplodingVariance -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters from torch.func import jacrev +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks import \ + ScoreNetwork +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) +from diffusion_for_multi_scale_molecular_dynamics.samplers.exploding_variance import \ + ExplodingVariance +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters + class NormalizedScoreFokkerPlanckError(torch.nn.Module): """Class to calculate the Normalized Score Fokker Planck Error. @@ -253,9 +256,9 @@ def get_normalized_score_fokker_planck_error_by_iterating_over_batch( list_errors = [] for x, t, c in zip(relative_coordinates, times, unit_cells): # Iterate over the elements of the batch. In effect, compute over "batch_size = 1" tensors. - errors = self.get_normalized_score_fokker_planck_error(x.unsqueeze(0), - t.unsqueeze(0), - c.unsqueeze(0)).squeeze(0) + errors = self.get_normalized_score_fokker_planck_error( + x.unsqueeze(0), t.unsqueeze(0), c.unsqueeze(0) + ).squeeze(0) list_errors.append(errors) return torch.stack(list_errors) diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/optimizer.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/optimizer.py index 97e8cc7b..1607f7f0 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/optimizer.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/optimizer.py @@ -3,16 +3,18 @@ from typing import Any, Dict import torch -from crystal_diffusion.utils.configuration_parsing import \ - create_parameters_from_configuration_dictionary from torch import optim +from diffusion_for_multi_scale_molecular_dynamics.utils.configuration_parsing import \ + create_parameters_from_configuration_dictionary + logger = logging.getLogger(__name__) @dataclass(kw_only=True) class OptimizerParameters: """Parameters for the optimizer.""" + name: str learning_rate: float weight_decay: float = 0.0 @@ -22,7 +24,9 @@ class OptimizerParameters: OPTIMIZER_PARAMETERS_BY_NAME = dict(adam=OptimizerParameters, adamw=OptimizerParameters) -def create_optimizer_parameters(optimizer_configuration_dictionary: Dict[str, Any]) -> OptimizerParameters: +def create_optimizer_parameters( + optimizer_configuration_dictionary: Dict[str, Any] +) -> OptimizerParameters: """Create Optimizer Parameters. Args: @@ -31,14 +35,17 @@ def create_optimizer_parameters(optimizer_configuration_dictionary: Dict[str, An Returns: optimizer_parameters: a Dataclass with the optimizer parameters. """ - optimizer_parameters = ( - create_parameters_from_configuration_dictionary(configuration=optimizer_configuration_dictionary, - identifier="name", - options=OPTIMIZER_PARAMETERS_BY_NAME)) + optimizer_parameters = create_parameters_from_configuration_dictionary( + configuration=optimizer_configuration_dictionary, + identifier="name", + options=OPTIMIZER_PARAMETERS_BY_NAME, + ) return optimizer_parameters -def load_optimizer(optimizer_parameters: OptimizerParameters, model: torch.nn.Module) -> optim.Optimizer: +def load_optimizer( + optimizer_parameters: OptimizerParameters, model: torch.nn.Module +) -> optim.Optimizer: """Instantiate the optimizer. Args: @@ -48,13 +55,14 @@ def load_optimizer(optimizer_parameters: OptimizerParameters, model: torch.nn.Mo Returns: optimizer : The optimizer for the given model """ - assert optimizer_parameters.name in OPTIMIZERS_BY_NAME.keys(), \ - f"Optimizer '{optimizer_parameters.name}' is not defined." + assert ( + optimizer_parameters.name in OPTIMIZERS_BY_NAME.keys() + ), f"Optimizer '{optimizer_parameters.name}' is not defined." optimizer_class = OPTIMIZERS_BY_NAME[optimizer_parameters.name] # Adapt the input configurations to the optimizer constructor expectations. parameters_dict = asdict(optimizer_parameters) - parameters_dict.pop('name') - parameters_dict['lr'] = parameters_dict.pop('learning_rate') + parameters_dict.pop("name") + parameters_dict["lr"] = parameters_dict.pop("learning_rate") return optimizer_class(model.parameters(), **parameters_dict) diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/position_diffusion_lightning_model.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/position_diffusion_lightning_model.py index 9d7bf3b7..143b2999 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/position_diffusion_lightning_model.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/position_diffusion_lightning_model.py @@ -4,38 +4,42 @@ import pytorch_lightning as pl import torch -from crystal_diffusion.generators.instantiate_generator import \ + +from diffusion_for_multi_scale_molecular_dynamics.generators.instantiate_generator import \ instantiate_generator -from crystal_diffusion.metrics.kolmogorov_smirnov_metrics import \ +from diffusion_for_multi_scale_molecular_dynamics.metrics.kolmogorov_smirnov_metrics import \ KolmogorovSmirnovMetrics -from crystal_diffusion.models.loss import (LossParameters, - create_loss_calculator) -from crystal_diffusion.models.optimizer import (OptimizerParameters, - load_optimizer) -from crystal_diffusion.models.scheduler import (SchedulerParameters, - load_scheduler_dictionary) -from crystal_diffusion.models.score_networks.score_network import \ +from diffusion_for_multi_scale_molecular_dynamics.models.loss import ( + LossParameters, create_loss_calculator) +from diffusion_for_multi_scale_molecular_dynamics.models.optimizer import ( + OptimizerParameters, load_optimizer) +from diffusion_for_multi_scale_molecular_dynamics.models.scheduler import ( + SchedulerParameters, load_scheduler_dictionary) +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_network import \ ScoreNetworkParameters -from crystal_diffusion.models.score_networks.score_network_factory import \ +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_network_factory import \ create_score_network -from crystal_diffusion.namespace import (CARTESIAN_FORCES, CARTESIAN_POSITIONS, - NOISE, NOISY_RELATIVE_COORDINATES, - RELATIVE_COORDINATES, TIME, UNIT_CELL) -from crystal_diffusion.oracle.energies import compute_oracle_energies -from crystal_diffusion.samplers.noisy_relative_coordinates_sampler import \ +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, CARTESIAN_POSITIONS, NOISE, NOISY_RELATIVE_COORDINATES, + RELATIVE_COORDINATES, TIME, UNIT_CELL) +from diffusion_for_multi_scale_molecular_dynamics.oracle.energies import \ + compute_oracle_energies +from diffusion_for_multi_scale_molecular_dynamics.samplers.noisy_relative_coordinates_sampler import \ NoisyRelativeCoordinatesSampler -from crystal_diffusion.samples.diffusion_sampling_parameters import \ +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import ( + ExplodingVarianceSampler, NoiseParameters) +from diffusion_for_multi_scale_molecular_dynamics.samples.diffusion_sampling_parameters import \ DiffusionSamplingParameters -from crystal_diffusion.score.wrapped_gaussian_score import \ +from diffusion_for_multi_scale_molecular_dynamics.samples.sampling import \ + create_batch_of_samples +from diffusion_for_multi_scale_molecular_dynamics.score.wrapped_gaussian_score import \ get_sigma_normalized_score -from crystal_diffusion.utils.basis_transformations import ( +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import ( get_positions_from_coordinates, map_relative_coordinates_to_unit_cell) -from crystal_diffusion.utils.structure_utils import compute_distances_in_batch -from crystal_diffusion.utils.tensor_utils import \ +from diffusion_for_multi_scale_molecular_dynamics.utils.structure_utils import \ + compute_distances_in_batch +from diffusion_for_multi_scale_molecular_dynamics.utils.tensor_utils import \ broadcast_batch_tensor_to_all_dimensions -from src.crystal_diffusion.samplers.variance_sampler import ( - ExplodingVarianceSampler, NoiseParameters) -from src.crystal_diffusion.samples.sampling import create_batch_of_samples logger = logging.getLogger(__name__) diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/scheduler.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/scheduler.py index 1adc4558..44ffca8e 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/scheduler.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/scheduler.py @@ -1,10 +1,11 @@ from dataclasses import asdict, dataclass from typing import Any, AnyStr, Dict, Union -from crystal_diffusion.utils.configuration_parsing import \ - create_parameters_from_configuration_dictionary from torch import optim +from diffusion_for_multi_scale_molecular_dynamics.utils.configuration_parsing import \ + create_parameters_from_configuration_dictionary + @dataclass(kw_only=True) class SchedulerParameters: diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/__init__.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/__init__.py index a6f3a01a..e48fdcf9 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/__init__.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/__init__.py @@ -1,4 +1,4 @@ # flake8: noqa # Import here to avoid circular imports elsewhere. -from crystal_diffusion.models.score_networks.score_network import ( +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_network import ( ScoreNetwork, ScoreNetworkParameters) diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/analytical_score_network.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/analytical_score_network.py index f1cbf3b0..fa7540c6 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/analytical_score_network.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/analytical_score_network.py @@ -10,35 +10,43 @@ This goal of this module is to investigate and understand the properties of diffusion. It is not meant to generate 'production' results. """ + import itertools from dataclasses import dataclass from typing import Any, AnyStr, Dict import einops import torch -from crystal_diffusion.models.score_networks.score_network import ( + +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_network import ( ScoreNetwork, ScoreNetworkParameters) -from crystal_diffusion.namespace import NOISE, NOISY_RELATIVE_COORDINATES -from crystal_diffusion.score.wrapped_gaussian_score import \ +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + NOISE, NOISY_RELATIVE_COORDINATES) +from diffusion_for_multi_scale_molecular_dynamics.score.wrapped_gaussian_score import \ get_sigma_normalized_score -from crystal_diffusion.utils.basis_transformations import \ +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell @dataclass(kw_only=True) class AnalyticalScoreNetworkParameters(ScoreNetworkParameters): """Specific Hyper-parameters for analytical score networks.""" - architecture: str = 'analytical' - number_of_atoms: int # the number of atoms in a configuration. + + architecture: str = "analytical" + number_of_atoms: int # the number of atoms in a configuration. kmax: int # the maximum lattice translation along any dimension. Translations will be [-kmax,..,kmax]. - equilibrium_relative_coordinates: torch.Tensor # Should have shape [number_of_atoms, spatial_dimensions] + equilibrium_relative_coordinates: ( + torch.Tensor + ) # Should have shape [number_of_atoms, spatial_dimensions] # Harmonic energy is defined as U = 1/2 u^T . Phi . u for "u" # the relative coordinate displacements. The 'inverse covariance' is beta Phi # and should be unitless. This is assumed to be proportional to the identity, such that # (beta Phi)^{-1} = sigma_d^2 Id, where the shape of Id is # [number_of_atoms, spatial_dimensions,number_of_atoms, spatial_dimensions] variance_parameter: float # sigma_d^2 - use_permutation_invariance: bool = False # should the analytical score consider every coordinate permutations. + use_permutation_invariance: bool = ( + False # should the analytical score consider every coordinate permutations. + ) class AnalyticalScoreNetwork(ScoreNetwork): @@ -60,11 +68,15 @@ def __init__(self, hyper_params: AnalyticalScoreNetworkParameters): self.nd = self.natoms * self.spatial_dimension self.kmax = hyper_params.kmax - assert hyper_params.variance_parameter >= 0., "the variance parameter should be non negative." + assert ( + hyper_params.variance_parameter >= 0.0 + ), "the variance parameter should be non negative." self.sigma_d_square = hyper_params.variance_parameter - assert hyper_params.equilibrium_relative_coordinates.shape == (self.natoms, self.spatial_dimension), \ - "equilibrium relative coordinates have the wrong shape" + assert hyper_params.equilibrium_relative_coordinates.shape == ( + self.natoms, + self.spatial_dimension, + ), "equilibrium relative coordinates have the wrong shape" self.use_permutation_invariance = hyper_params.use_permutation_invariance self.device = hyper_params.equilibrium_relative_coordinates.device @@ -73,30 +85,45 @@ def __init__(self, hyper_params: AnalyticalScoreNetworkParameters): self.translations_k = self._get_all_translations(self.kmax).to(self.device) self.number_of_translations = len(self.translations_k) - self.equilibrium_relative_coordinates = hyper_params.equilibrium_relative_coordinates + self.equilibrium_relative_coordinates = ( + hyper_params.equilibrium_relative_coordinates + ) if self.use_permutation_invariance: # Shape : [natom!, natoms, spatial dimension] - self.all_x0 = self._get_all_equilibrium_permutations(self.equilibrium_relative_coordinates) + self.all_x0 = self._get_all_equilibrium_permutations( + self.equilibrium_relative_coordinates + ) else: # Shape : [1, natoms, spatial dimension] - self.all_x0 = einops.rearrange(hyper_params.equilibrium_relative_coordinates, 'natom d -> 1 natom d') + self.all_x0 = einops.rearrange( + hyper_params.equilibrium_relative_coordinates, "natom d -> 1 natom d" + ) @staticmethod def _get_all_translations(kmax: int) -> torch.Tensor: return torch.arange(-kmax, kmax + 1) @staticmethod - def _get_all_equilibrium_permutations(relative_coordinates: torch.Tensor) -> torch.Tensor: + def _get_all_equilibrium_permutations( + relative_coordinates: torch.Tensor, + ) -> torch.Tensor: number_of_atoms = relative_coordinates.shape[0] # Shape : [number of permutations, number of atoms] - perm_indices = torch.stack([torch.tensor(perm) for perm in itertools.permutations(range(number_of_atoms))]) + perm_indices = torch.stack( + [ + torch.tensor(perm) + for perm in itertools.permutations(range(number_of_atoms)) + ] + ) equilibrium_permutations = relative_coordinates[perm_indices] return equilibrium_permutations - def _forward_unchecked(self, batch: Dict[AnyStr, Any], conditional: bool = False) -> torch.Tensor: + def _forward_unchecked( + self, batch: Dict[AnyStr, Any], conditional: bool = False + ) -> torch.Tensor: """Forward unchecked. This method assumes that the input data has already been checked with respect to expectations @@ -115,7 +142,9 @@ def _forward_unchecked(self, batch: Dict[AnyStr, Any], conditional: bool = False list_unnormalized_log_prob = [] for x0 in self.all_x0: - unnormalized_log_prob = self._compute_unnormalized_log_probability(sigmas, xt, x0) + unnormalized_log_prob = self._compute_unnormalized_log_probability( + sigmas, xt, x0 + ) list_unnormalized_log_prob.append(unnormalized_log_prob) list_unnormalized_log_prob = torch.stack(list_unnormalized_log_prob) @@ -123,38 +152,60 @@ def _forward_unchecked(self, batch: Dict[AnyStr, Any], conditional: bool = False grad_outputs = [torch.ones_like(log_probs)] - scores = torch.autograd.grad(outputs=[log_probs], - inputs=[xt], - grad_outputs=grad_outputs)[0] + scores = torch.autograd.grad( + outputs=[log_probs], inputs=[xt], grad_outputs=grad_outputs + )[0] # We actually want sigma x score. - broadcast_sigmas = einops.repeat(sigmas, 'batch 1 -> batch n d', n=self.natoms, d=self.spatial_dimension) + broadcast_sigmas = einops.repeat( + sigmas, "batch 1 -> batch n d", n=self.natoms, d=self.spatial_dimension + ) sigma_normalized_scores = broadcast_sigmas * scores return sigma_normalized_scores - def _compute_unnormalized_log_probability(self, sigmas: torch.Tensor, - xt: torch.Tensor, - x_eq: torch.Tensor) -> torch.Tensor: + def _compute_unnormalized_log_probability( + self, sigmas: torch.Tensor, xt: torch.Tensor, x_eq: torch.Tensor + ) -> torch.Tensor: batch_size = sigmas.shape[0] # Recast various spatial arrays to the correct dimensions to combine them, # in dimensions [batch, nd, number_of_translations] - effective_variance = einops.repeat(sigmas ** 2 + self.sigma_d_square, 'batch 1 -> batch nd t', - t=self.number_of_translations, nd=self.nd) - - sampling_coordinates = einops.repeat(xt, 'batch natom d -> batch (natom d) t', - batch=batch_size, t=self.number_of_translations) - - equilibrium_coordinates = einops.repeat(x_eq, 'natom d -> batch (natom d) t', - batch=batch_size, t=self.number_of_translations) - - translations = einops.repeat(self.translations_k, 't -> batch nd t', batch=batch_size, nd=self.nd) - - exponent = -0.5 * (sampling_coordinates - equilibrium_coordinates - translations) ** 2 / effective_variance + effective_variance = einops.repeat( + sigmas**2 + self.sigma_d_square, + "batch 1 -> batch nd t", + t=self.number_of_translations, + nd=self.nd, + ) + + sampling_coordinates = einops.repeat( + xt, + "batch natom d -> batch (natom d) t", + batch=batch_size, + t=self.number_of_translations, + ) + + equilibrium_coordinates = einops.repeat( + x_eq, + "natom d -> batch (natom d) t", + batch=batch_size, + t=self.number_of_translations, + ) + + translations = einops.repeat( + self.translations_k, "t -> batch nd t", batch=batch_size, nd=self.nd + ) + + exponent = ( + -0.5 + * (sampling_coordinates - equilibrium_coordinates - translations) ** 2 + / effective_variance + ) # logsumexp on lattice translation vectors, then sum on spatial indices - unnormalized_log_prob = torch.logsumexp(exponent, dim=2, keepdim=False).sum(dim=1) + unnormalized_log_prob = torch.logsumexp(exponent, dim=2, keepdim=False).sum( + dim=1 + ) return unnormalized_log_prob @@ -174,11 +225,14 @@ def __init__(self, hyper_params: AnalyticalScoreNetworkParameters): hyper_params : hyper parameters from the config file. """ super(TargetScoreBasedAnalyticalScoreNetwork, self).__init__(hyper_params) - assert not hyper_params.use_permutation_invariance, \ - "This implementation is only valid in the absence of permutation equivariance." + assert ( + not hyper_params.use_permutation_invariance + ), "This implementation is only valid in the absence of permutation equivariance." self.x0 = self.all_x0[0] - def _forward_unchecked(self, batch: Dict[AnyStr, Any], conditional: bool = False) -> torch.Tensor: + def _forward_unchecked( + self, batch: Dict[AnyStr, Any], conditional: bool = False + ) -> torch.Tensor: """Forward unchecked. This method assumes that the input data has already been checked with respect to expectations @@ -194,18 +248,22 @@ def _forward_unchecked(self, batch: Dict[AnyStr, Any], conditional: bool = False sigmas = batch[NOISE] # dimension: [batch_size, 1] xt = batch[NOISY_RELATIVE_COORDINATES] - broadcast_sigmas = einops.repeat(sigmas, - "batch 1 -> batch natoms spatial_dimension", - natoms=self.natoms, - spatial_dimension=self.spatial_dimension) + broadcast_sigmas = einops.repeat( + sigmas, + "batch 1 -> batch natoms spatial_dimension", + natoms=self.natoms, + spatial_dimension=self.spatial_dimension, + ) broadcast_effective_sigmas = (broadcast_sigmas**2 + self.sigma_d_square).sqrt() delta_relative_coordinates = map_relative_coordinates_to_unit_cell(xt - self.x0) - misnormalized_scores = get_sigma_normalized_score(delta_relative_coordinates, - broadcast_effective_sigmas, - kmax=self.kmax) + misnormalized_scores = get_sigma_normalized_score( + delta_relative_coordinates, broadcast_effective_sigmas, kmax=self.kmax + ) - sigma_normalized_scores = broadcast_sigmas / broadcast_effective_sigmas * misnormalized_scores + sigma_normalized_scores = ( + broadcast_sigmas / broadcast_effective_sigmas * misnormalized_scores + ) return sigma_normalized_scores diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/diffusion_mace_score_network.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/diffusion_mace_score_network.py index 31269025..3120e58a 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/diffusion_mace_score_network.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/diffusion_mace_score_network.py @@ -2,45 +2,63 @@ from typing import AnyStr, Dict, List import torch -from crystal_diffusion.models.diffusion_mace import (DiffusionMACE, - input_to_diffusion_mace) -from crystal_diffusion.models.score_networks.score_network import ( - ScoreNetwork, ScoreNetworkParameters) -from crystal_diffusion.namespace import (NOISY_CARTESIAN_POSITIONS, - NOISY_RELATIVE_COORDINATES, UNIT_CELL) -from crystal_diffusion.utils.basis_transformations import ( - get_positions_from_coordinates, get_reciprocal_basis_vectors) from e3nn import o3 from mace.modules import gate_dict, interaction_classes from mace.tools.torch_geometric.dataloader import Collater +from diffusion_for_multi_scale_molecular_dynamics.models.diffusion_mace import ( + DiffusionMACE, input_to_diffusion_mace) +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_network import ( + ScoreNetwork, ScoreNetworkParameters) +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + NOISY_CARTESIAN_POSITIONS, NOISY_RELATIVE_COORDINATES, UNIT_CELL) +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import ( + get_positions_from_coordinates, get_reciprocal_basis_vectors) + @dataclass(kw_only=True) class DiffusionMACEScoreNetworkParameters(ScoreNetworkParameters): """Specific Hyper-parameters for Diffusion MACE score networks.""" - architecture: str = 'diffusion_mace' + + architecture: str = "diffusion_mace" number_of_atoms: int # the number of atoms in a configuration. number_of_elements: int = 1 # The number of distinct elements present r_max: float = 5.0 num_bessel: int = 8 num_polynomial_cutoff: int = 5 - num_edge_hidden_layers: int = 0 # layers mixing sigma in edge features. Set to 0 to not add sigma in edges + num_edge_hidden_layers: int = ( + 0 # layers mixing sigma in edge features. Set to 0 to not add sigma in edges + ) edge_hidden_irreps: str = "16x0e" max_ell: int = 2 interaction_cls: str = "RealAgnosticResidualInteractionBlock" interaction_cls_first: str = "RealAgnosticInteractionBlock" num_interactions: int = 2 hidden_irreps: str = "128x0e + 128x1o" # irreps for hidden node states - mlp_irreps: str = "16x0e" # irreps for the embedding of the diffusion scalar features - number_of_mlp_layers: int = 3 # number of MLP layers for the embedding of the diffusion scalar features + mlp_irreps: str = ( + "16x0e" # irreps for the embedding of the diffusion scalar features + ) + number_of_mlp_layers: int = ( + 3 # number of MLP layers for the embedding of the diffusion scalar features + ) avg_num_neighbors: int = 1 # normalization factor for the message correlation: int = 3 - gate: str = "silu" # non linearity for last readout - choices: ["silu", "tanh", "abs", "None"] - radial_MLP: List[int] = field(default_factory=lambda: [64, 64, 64]) # "width of the radial MLP" - radial_type: str = "bessel" # type of radial basis functions - choices=["bessel", "gaussian", "chebyshev"] - condition_embedding_size: int = 64 # dimension of the conditional variable embedding - assumed to be l=1 (odd) + gate: str = ( + "silu" # non linearity for last readout - choices: ["silu", "tanh", "abs", "None"] + ) + radial_MLP: List[int] = field( + default_factory=lambda: [64, 64, 64] + ) # "width of the radial MLP" + radial_type: str = ( + "bessel" # type of radial basis functions - choices=["bessel", "gaussian", "chebyshev"] + ) + condition_embedding_size: int = ( + 64 # dimension of the conditional variable embedding - assumed to be l=1 (odd) + ) use_batchnorm: bool = False - tanh_after_interaction: bool = True # use a tanh non-linearity (based on irreps norm) in the message-passing + tanh_after_interaction: bool = ( + True # use a tanh non-linearity (based on irreps norm) in the message-passing + ) class DiffusionMACEScoreNetwork(ScoreNetwork): @@ -66,7 +84,9 @@ def __init__(self, hyper_params: DiffusionMACEScoreNetworkParameters): edge_hidden_irreps=o3.Irreps(hyper_params.edge_hidden_irreps), max_ell=hyper_params.max_ell, interaction_cls=interaction_classes[hyper_params.interaction_cls], - interaction_cls_first=interaction_classes[hyper_params.interaction_cls_first], + interaction_cls_first=interaction_classes[ + hyper_params.interaction_cls_first + ], num_interactions=hyper_params.num_interactions, num_elements=hyper_params.number_of_elements, hidden_irreps=o3.Irreps(hyper_params.hidden_irreps), @@ -80,7 +100,7 @@ def __init__(self, hyper_params: DiffusionMACEScoreNetworkParameters): radial_type=hyper_params.radial_type, condition_embedding_size=hyper_params.condition_embedding_size, use_batchnorm=hyper_params.use_batchnorm, - tanh_after_interaction=hyper_params.tanh_after_interaction + tanh_after_interaction=hyper_params.tanh_after_interaction, ) self._natoms = hyper_params.number_of_atoms @@ -95,7 +115,9 @@ def _check_batch(self, batch: Dict[AnyStr, torch.Tensor]): number_of_atoms == self._natoms ), "The dimension corresponding to the number of atoms is not consistent with the configuration." - def _forward_unchecked(self, batch: Dict[AnyStr, torch.Tensor], conditional: bool = False) -> torch.Tensor: + def _forward_unchecked( + self, batch: Dict[AnyStr, torch.Tensor], conditional: bool = False + ) -> torch.Tensor: """Forward unchecked. This method assumes that the input data has already been checked with respect to expectations @@ -113,13 +135,19 @@ def _forward_unchecked(self, batch: Dict[AnyStr, torch.Tensor], conditional: boo batch_size, number_of_atoms, spatial_dimension = relative_coordinates.shape basis_vectors = batch[UNIT_CELL] - batch[NOISY_CARTESIAN_POSITIONS] = get_positions_from_coordinates(relative_coordinates, basis_vectors) + batch[NOISY_CARTESIAN_POSITIONS] = get_positions_from_coordinates( + relative_coordinates, basis_vectors + ) graph_input = input_to_diffusion_mace(batch, radial_cutoff=self.r_max) flat_cartesian_scores = self.diffusion_mace_network(graph_input, conditional) - cartesian_scores = flat_cartesian_scores.reshape(batch_size, number_of_atoms, spatial_dimension) + cartesian_scores = flat_cartesian_scores.reshape( + batch_size, number_of_atoms, spatial_dimension + ) - reciprocal_basis_vectors_as_columns = get_reciprocal_basis_vectors(basis_vectors) + reciprocal_basis_vectors_as_columns = get_reciprocal_basis_vectors( + basis_vectors + ) scores = torch.bmm(cartesian_scores, reciprocal_basis_vectors_as_columns) return scores diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/egnn_score_network.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/egnn_score_network.py index d90987a2..dd49c531 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/egnn_score_network.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/egnn_score_network.py @@ -3,18 +3,22 @@ import einops import torch -from crystal_diffusion.models.egnn import EGNN -from crystal_diffusion.models.egnn_utils import (get_edges_batch, - get_edges_with_radial_cutoff) -from crystal_diffusion.models.score_networks import ScoreNetworkParameters -from crystal_diffusion.models.score_networks.score_network import ScoreNetwork -from crystal_diffusion.namespace import (NOISE, NOISY_RELATIVE_COORDINATES, - UNIT_CELL) + +from diffusion_for_multi_scale_molecular_dynamics.models.egnn import EGNN +from diffusion_for_multi_scale_molecular_dynamics.models.egnn_utils import ( + get_edges_batch, get_edges_with_radial_cutoff) +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks import \ + ScoreNetworkParameters +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_network import \ + ScoreNetwork +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + NOISE, NOISY_RELATIVE_COORDINATES, UNIT_CELL) @dataclass(kw_only=True) class EGNNScoreNetworkParameters(ScoreNetworkParameters): """Specific Hyper-parameters for ENN score networks.""" + architecture: str = "egnn" message_n_hidden_dimensions: int = 1 message_hidden_dimensions_size: int = 16 @@ -29,7 +33,7 @@ class EGNNScoreNetworkParameters(ScoreNetworkParameters): coords_agg: str = "mean" message_agg: str = "mean" n_layers: int = 4 - edges: str = 'fully_connected' + edges: str = "fully_connected" radial_cutoff: Union[float, None] = None drop_duplicate_edges: bool = True @@ -52,21 +56,30 @@ def __init__(self, hyper_params: EGNNScoreNetworkParameters): self.number_of_features_per_node = 1 self.spatial_dimension = hyper_params.spatial_dimension - projection_matrices = self._create_block_diagonal_projection_matrices(self.spatial_dimension) - self.register_parameter('projection_matrices', - torch.nn.Parameter(projection_matrices, requires_grad=False)) + projection_matrices = self._create_block_diagonal_projection_matrices( + self.spatial_dimension + ) + self.register_parameter( + "projection_matrices", + torch.nn.Parameter(projection_matrices, requires_grad=False), + ) self.edges = hyper_params.edges - assert self.edges in ["fully_connected", "radial_cutoff"], \ - f'Edges type should be fully_connected or radial_cutoff. Got {self.edges}' + assert self.edges in [ + "fully_connected", + "radial_cutoff", + ], f"Edges type should be fully_connected or radial_cutoff. Got {self.edges}" self.radial_cutoff = hyper_params.radial_cutoff if self.edges == "fully_connected": - assert self.radial_cutoff is None, "Specifying a radial cutoff is inconsistent with edges=fully_connected." + assert ( + self.radial_cutoff is None + ), "Specifying a radial cutoff is inconsistent with edges=fully_connected." else: - assert type(self.radial_cutoff) is float, \ - "A floating point value for the radial cutoff is needed for edges=radial_cutoff." + assert ( + type(self.radial_cutoff) is float + ), "A floating point value for the radial cutoff is needed for edges=radial_cutoff." self.drop_duplicate_edges = hyper_params.drop_duplicate_edges @@ -88,7 +101,9 @@ def __init__(self, hyper_params: EGNNScoreNetworkParameters): ) @staticmethod - def _create_block_diagonal_projection_matrices(spatial_dimension: int) -> torch.Tensor: + def _create_block_diagonal_projection_matrices( + spatial_dimension: int, + ) -> torch.Tensor: """Create block diagonal projection matrices. This method creates the "Gamma" matrices that are needed to project the higher dimensional @@ -108,11 +123,15 @@ def _create_block_diagonal_projection_matrices(spatial_dimension: int) -> torch. dimension [spatial_dimension, 2 x spatial_dimension, 2 x spatial_dimension]. """ zeros = torch.zeros(2, 2) - dimensional_projector = torch.tensor([[0., -1.], [1., 0.]]) + dimensional_projector = torch.tensor([[0.0, -1.0], [1.0, 0.0]]) projection_matrices = [] for space_idx in range(spatial_dimension): - blocks = space_idx * [zeros] + [dimensional_projector] + (spatial_dimension - space_idx - 1) * [zeros] + blocks = ( + space_idx * [zeros] + + [dimensional_projector] + + (spatial_dimension - space_idx - 1) * [zeros] + ) projection_matrices.append(torch.block_diag(*blocks)) return torch.stack(projection_matrices) @@ -138,7 +157,9 @@ def _get_node_attributes(batch: Dict[AnyStr, torch.Tensor]) -> torch.Tensor: return repeated_sigmas @staticmethod - def _get_euclidean_positions(flat_relative_coordinates: torch.Tensor) -> torch.Tensor: + def _get_euclidean_positions( + flat_relative_coordinates: torch.Tensor, + ) -> torch.Tensor: """Get Euclidean positions. Get the positions that take points on the torus into a higher dimensional @@ -168,19 +189,20 @@ def _forward_unchecked( batch_size, number_of_atoms, spatial_dimension = relative_coordinates.shape if self.edges == "fully_connected": - edges = get_edges_batch( - n_nodes=number_of_atoms, batch_size=batch_size - ) + edges = get_edges_batch(n_nodes=number_of_atoms, batch_size=batch_size) else: edges = get_edges_with_radial_cutoff( - relative_coordinates, batch[UNIT_CELL], self.radial_cutoff, - drop_duplicate_edges=self.drop_duplicate_edges + relative_coordinates, + batch[UNIT_CELL], + self.radial_cutoff, + drop_duplicate_edges=self.drop_duplicate_edges, ) edges = edges.to(relative_coordinates.device) flat_relative_coordinates = einops.rearrange( - relative_coordinates, "batch natom spatial_dimension -> (batch natom) spatial_dimension" + relative_coordinates, + "batch natom spatial_dimension -> (batch natom) spatial_dimension", ) # Uplift the relative coordinates to the embedding Euclidean space. @@ -201,13 +223,17 @@ def _forward_unchecked( # - z is the uplifted "positions" in the 2 x spatial_dimension Euclidean space # - hat_z is the output of the EGNN model, also in 2 x spatial_dimension # - Gamma^alpha are the projection matrices - flat_normalized_scores = einops.einsum(euclidean_positions, self.projection_matrices, raw_normalized_score, - "nodes i, alpha i j, nodes j-> nodes alpha") + flat_normalized_scores = einops.einsum( + euclidean_positions, + self.projection_matrices, + raw_normalized_score, + "nodes i, alpha i j, nodes j-> nodes alpha", + ) normalized_scores = einops.rearrange( flat_normalized_scores, "(batch natoms) spatial_dimension -> batch natoms spatial_dimension", batch=batch_size, - natoms=number_of_atoms + natoms=number_of_atoms, ) return normalized_scores diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/force_field_augmented_score_network.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/force_field_augmented_score_network.py index 59ef4f5c..4008e4d9 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/force_field_augmented_score_network.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/force_field_augmented_score_network.py @@ -3,12 +3,15 @@ import einops import torch -from crystal_diffusion.models.score_networks import ScoreNetwork -from crystal_diffusion.namespace import NOISY_RELATIVE_COORDINATES, UNIT_CELL -from crystal_diffusion.utils.basis_transformations import ( + +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks import \ + ScoreNetwork +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + NOISY_RELATIVE_COORDINATES, UNIT_CELL) +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import ( get_positions_from_coordinates, get_reciprocal_basis_vectors, get_relative_coordinates_from_cartesian_positions) -from crystal_diffusion.utils.neighbors import ( +from diffusion_for_multi_scale_molecular_dynamics.utils.neighbors import ( AdjacencyInfo, get_periodic_adjacency_information) @@ -37,6 +40,7 @@ class ForceFieldAugmentedScoreNetwork(torch.nn.Module): to such proximity: a repulsive force field will encourage atoms to separate during diffusion. """ + def __init__( self, score_network: ScoreNetwork, force_field_parameters: ForceFieldParameters ): @@ -163,7 +167,9 @@ def _get_cartesian_pseudo_forces( list_pseudo_force_components = [] for space_idx in range(spatial_dimension): - pseudo_force_component = torch.zeros(natoms * batch_size).to(cartesian_pseudo_force_contributions) + pseudo_force_component = torch.zeros(natoms * batch_size).to( + cartesian_pseudo_force_contributions + ) pseudo_force_component.scatter_add_( dim=0, index=node_idx, diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/mace_score_network.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/mace_score_network.py index b0c5b838..998e428e 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/mace_score_network.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/mace_score_network.py @@ -3,30 +3,32 @@ import numpy as np import torch -from crystal_diffusion.models.mace_utils import ( - build_mace_output_nodes_irreducible_representation, get_pretrained_mace, - get_pretrained_mace_output_node_features_irreps, input_to_mace) -from crystal_diffusion.models.score_networks.score_network import ( - ScoreNetwork, ScoreNetworkParameters) -from crystal_diffusion.models.score_networks.score_prediction_head import ( - MaceScorePredictionHeadParameters, instantiate_mace_prediction_head) -from crystal_diffusion.namespace import (NOISY_CARTESIAN_POSITIONS, - NOISY_RELATIVE_COORDINATES, TIME, - UNIT_CELL) from e3nn import o3 from mace.modules import MACE, gate_dict, interaction_classes from mace.tools import get_atomic_number_table_from_zs from mace.tools.torch_geometric.dataloader import Collater +from diffusion_for_multi_scale_molecular_dynamics.models.mace_utils import ( + build_mace_output_nodes_irreducible_representation, get_pretrained_mace, + get_pretrained_mace_output_node_features_irreps, input_to_mace) +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_network import ( + ScoreNetwork, ScoreNetworkParameters) +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_prediction_head import ( + MaceScorePredictionHeadParameters, instantiate_mace_prediction_head) +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + NOISY_CARTESIAN_POSITIONS, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) + @dataclass(kw_only=True) class MACEScoreNetworkParameters(ScoreNetworkParameters): """Specific Hyper-parameters for MACE score networks.""" - architecture: str = 'mace' + architecture: str = "mace" number_of_atoms: int # the number of atoms in a configuration. - use_pretrained: Optional[str] = None # if None, do not use pre-trained ; else, should be in small, medium or large - pretrained_weights_path: str = '../' # path to pre-trained model weights + use_pretrained: Optional[str] = ( + None # if None, do not use pre-trained ; else, should be in small, medium or large + ) + pretrained_weights_path: str = "../" # path to pre-trained model weights r_max: float = 5.0 num_bessel: int = 8 num_polynomial_cutoff: int = 5 @@ -39,9 +41,15 @@ class MACEScoreNetworkParameters(ScoreNetworkParameters): atomic_energies: np.ndarray = field(default_factory=lambda: np.zeros((89,))) avg_num_neighbors: int = 1 # normalization factor for the message correlation: int = 3 - gate: str = "silu" # non linearity for last readout - choices: ["silu", "tanh", "abs", "None"] - radial_MLP: List[int] = field(default_factory=lambda: [64, 64, 64]) # "width of the radial MLP" - radial_type: str = "bessel" # type of radial basis functions - choices=["bessel", "gaussian", "chebyshev"] + gate: str = ( + "silu" # non linearity for last readout - choices: ["silu", "tanh", "abs", "None"] + ) + radial_MLP: List[int] = field( + default_factory=lambda: [64, 64, 64] + ) # "width of the radial MLP" + radial_type: str = ( + "bessel" # type of radial basis functions - choices=["bessel", "gaussian", "chebyshev"] + ) prediction_head_parameters: MaceScorePredictionHeadParameters @@ -59,7 +67,9 @@ def __init__(self, hyper_params: MACEScoreNetworkParameters): """ super(MACEScoreNetwork, self).__init__(hyper_params) - self.z_table = get_atomic_number_table_from_zs(list(range(89))) # need 89 for pre-trained model + self.z_table = get_atomic_number_table_from_zs( + list(range(89)) + ) # need 89 for pre-trained model # dataloader self.r_max = hyper_params.r_max self.collate_fn = Collater(follow_batch=[None], exclude_keys=[None]) @@ -70,7 +80,9 @@ def __init__(self, hyper_params: MACEScoreNetworkParameters): num_polynomial_cutoff=hyper_params.num_polynomial_cutoff, max_ell=hyper_params.max_ell, interaction_cls=interaction_classes[hyper_params.interaction_cls], - interaction_cls_first=interaction_classes[hyper_params.interaction_cls_first], + interaction_cls_first=interaction_classes[ + hyper_params.interaction_cls_first + ], num_interactions=hyper_params.num_interactions, num_elements=len(self.z_table), hidden_irreps=o3.Irreps(hyper_params.hidden_irreps), @@ -81,25 +93,35 @@ def __init__(self, hyper_params: MACEScoreNetworkParameters): correlation=hyper_params.correlation, gate=gate_dict[hyper_params.gate], radial_MLP=hyper_params.radial_MLP, - radial_type=hyper_params.radial_type + radial_type=hyper_params.radial_type, ) self._natoms = hyper_params.number_of_atoms - if hyper_params.use_pretrained is None or hyper_params.use_pretrained == 'None': + if hyper_params.use_pretrained is None or hyper_params.use_pretrained == "None": self.mace_network = MACE(**mace_config) output_node_features_irreps = ( - build_mace_output_nodes_irreducible_representation(hyper_params.hidden_irreps, - hyper_params.num_interactions)) + build_mace_output_nodes_irreducible_representation( + hyper_params.hidden_irreps, hyper_params.num_interactions + ) + ) else: - output_node_features_irreps = get_pretrained_mace_output_node_features_irreps(hyper_params.use_pretrained) - self.mace_network, mace_output_size = get_pretrained_mace(hyper_params.use_pretrained, - hyper_params.pretrained_weights_path) - assert output_node_features_irreps.dim == mace_output_size, "Something is wrong with pretrained dimensions." + output_node_features_irreps = ( + get_pretrained_mace_output_node_features_irreps( + hyper_params.use_pretrained + ) + ) + self.mace_network, mace_output_size = get_pretrained_mace( + hyper_params.use_pretrained, hyper_params.pretrained_weights_path + ) + assert ( + output_node_features_irreps.dim == mace_output_size + ), "Something is wrong with pretrained dimensions." self.mace_output_size = output_node_features_irreps.dim - self.prediction_head = instantiate_mace_prediction_head(output_node_features_irreps, - hyper_params.prediction_head_parameters) + self.prediction_head = instantiate_mace_prediction_head( + output_node_features_irreps, hyper_params.prediction_head_parameters + ) def _check_batch(self, batch: Dict[AnyStr, torch.Tensor]): super(MACEScoreNetwork, self)._check_batch(batch) @@ -108,7 +130,9 @@ def _check_batch(self, batch: Dict[AnyStr, torch.Tensor]): number_of_atoms == self._natoms ), "The dimension corresponding to the number of atoms is not consistent with the configuration." - def _forward_unchecked(self, batch: Dict[AnyStr, torch.Tensor], conditional: bool = False) -> torch.Tensor: + def _forward_unchecked( + self, batch: Dict[AnyStr, torch.Tensor], conditional: bool = False + ) -> torch.Tensor: """Forward unchecked. This method assumes that the input data has already been checked with respect to expectations @@ -124,19 +148,25 @@ def _forward_unchecked(self, batch: Dict[AnyStr, torch.Tensor], conditional: boo """ del conditional # TODO implement conditional relative_coordinates = batch[NOISY_RELATIVE_COORDINATES] - batch[NOISY_CARTESIAN_POSITIONS] = torch.bmm(relative_coordinates, batch[UNIT_CELL]) # positions in Angstrom + batch[NOISY_CARTESIAN_POSITIONS] = torch.bmm( + relative_coordinates, batch[UNIT_CELL] + ) # positions in Angstrom graph_input = input_to_mace(batch, radial_cutoff=self.r_max) - mace_output = self.mace_network(graph_input, compute_force=False, training=self.training) + mace_output = self.mace_network( + graph_input, compute_force=False, training=self.training + ) # The node features are organized as (batchsize * natoms, output_size) in the mace output because # torch_geometric puts all the graphs in a batch in a single large graph. - flat_node_features = mace_output['node_feats'] + flat_node_features = mace_output["node_feats"] # The times have a single value per batch element; we repeat the array so that there is one value per atom, # with this value the same for all atoms belonging to the same graph. times = batch[TIME].to(relative_coordinates.device) # shape [batch_size, 1] flat_times = times[graph_input.batch] # shape [batch_size * natoms, 1] - flat_scores = self.prediction_head(flat_node_features, flat_times) # shape [batch_size * natoms, spatial_dim] + flat_scores = self.prediction_head( + flat_node_features, flat_times + ) # shape [batch_size * natoms, spatial_dim] # Reshape the scores to have an explicit batch dimension scores = flat_scores.reshape(-1, self._natoms, self.spatial_dimension) diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/mlp_score_network.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/mlp_score_network.py index c5e989cc..5606b04f 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/mlp_score_network.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/mlp_score_network.py @@ -2,23 +2,28 @@ from typing import AnyStr, Dict import torch -from crystal_diffusion.models.score_networks.score_network import ( - ScoreNetwork, ScoreNetworkParameters) -from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, - NOISY_RELATIVE_COORDINATES) from torch import nn +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_network import ( + ScoreNetwork, ScoreNetworkParameters) +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, NOISE, NOISY_RELATIVE_COORDINATES) + @dataclass(kw_only=True) class MLPScoreNetworkParameters(ScoreNetworkParameters): """Specific Hyper-parameters for MLP score networks.""" - architecture: str = 'mlp' + architecture: str = "mlp" number_of_atoms: int # the number of atoms in a configuration. n_hidden_dimensions: int # the number of hidden layers. hidden_dimensions_size: int # the dimensions of the hidden layers. - embedding_dimensions_size: int # the dimension of the embedding of the noise parameter. - condition_embedding_size: int = 64 # dimension of the conditional variable embedding + embedding_dimensions_size: ( + int # the dimension of the embedding of the noise parameter. + ) + condition_embedding_size: int = ( + 64 # dimension of the conditional variable embedding + ) class MLPScoreNetwork(ScoreNetwork): @@ -34,15 +39,21 @@ def __init__(self, hyper_params: MLPScoreNetworkParameters): hyper_params : hyper parameters from the config file. """ super(MLPScoreNetwork, self).__init__(hyper_params) - hidden_dimensions = [hyper_params.hidden_dimensions_size] * hyper_params.n_hidden_dimensions + hidden_dimensions = [ + hyper_params.hidden_dimensions_size + ] * hyper_params.n_hidden_dimensions self._natoms = hyper_params.number_of_atoms output_dimension = self.spatial_dimension * self._natoms input_dimension = output_dimension + hyper_params.embedding_dimensions_size - self.noise_embedding_layer = nn.Linear(1, hyper_params.embedding_dimensions_size) + self.noise_embedding_layer = nn.Linear( + 1, hyper_params.embedding_dimensions_size + ) - self.condition_embedding_layer = nn.Linear(output_dimension, hyper_params.condition_embedding_size) + self.condition_embedding_layer = nn.Linear( + output_dimension, hyper_params.condition_embedding_size + ) self.flatten = nn.Flatten() self.mlp_layers = nn.ModuleList() @@ -50,9 +61,13 @@ def __init__(self, hyper_params: MLPScoreNetworkParameters): input_dimensions = [input_dimension] + hidden_dimensions output_dimensions = hidden_dimensions + [output_dimension] - for input_dimension, output_dimension in zip(input_dimensions, output_dimensions): + for input_dimension, output_dimension in zip( + input_dimensions, output_dimensions + ): self.mlp_layers.append(nn.Linear(input_dimension, output_dimension)) - self.conditional_layers.append(nn.Linear(hyper_params.condition_embedding_size, output_dimension)) + self.conditional_layers.append( + nn.Linear(hyper_params.condition_embedding_size, output_dimension) + ) self.non_linearity = nn.ReLU() def _check_batch(self, batch: Dict[AnyStr, torch.Tensor]): @@ -62,7 +77,9 @@ def _check_batch(self, batch: Dict[AnyStr, torch.Tensor]): number_of_atoms == self._natoms ), "The dimension corresponding to the number of atoms is not consistent with the configuration." - def _forward_unchecked(self, batch: Dict[AnyStr, torch.Tensor], conditional: bool = False) -> torch.Tensor: + def _forward_unchecked( + self, batch: Dict[AnyStr, torch.Tensor], conditional: bool = False + ) -> torch.Tensor: """Forward unchecked. This method assumes that the input data has already been checked with respect to expectations @@ -80,14 +97,20 @@ def _forward_unchecked(self, batch: Dict[AnyStr, torch.Tensor], conditional: boo # shape [batch_size, number_of_atoms, spatial_dimension] sigmas = batch[NOISE].to(relative_coordinates.device) # shape [batch_size, 1] - noise_embedding = self.noise_embedding_layer(sigmas) # shape [batch_size, embedding_dimension] + noise_embedding = self.noise_embedding_layer( + sigmas + ) # shape [batch_size, embedding_dimension] input = torch.cat([self.flatten(relative_coordinates), noise_embedding], dim=1) - forces_input = self.condition_embedding_layer(self.flatten(batch[CARTESIAN_FORCES])) + forces_input = self.condition_embedding_layer( + self.flatten(batch[CARTESIAN_FORCES]) + ) output = input - for i, (layer, condition_layer) in enumerate(zip(self.mlp_layers, self.conditional_layers)): + for i, (layer, condition_layer) in enumerate( + zip(self.mlp_layers, self.conditional_layers) + ): if i != 0: output = self.non_linearity(output) output = layer(output) diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/score_network.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/score_network.py index 2701e654..ad9b6722 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/score_network.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/score_network.py @@ -4,29 +4,35 @@ Relative coordinates are with respect to lattice vectors which define the periodic unit cell. """ + import os from dataclasses import dataclass from typing import AnyStr, Dict, Optional import torch -from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, - NOISY_RELATIVE_COORDINATES, TIME, - UNIT_CELL) + +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) # mac fun time # for mace, conflict with mac # https://stackoverflow.com/questions/53014306/error-15-initializing-libiomp5-dylib-but-found-libiomp5-dylib-already- \ # initial -os.environ['KMP_DUPLICATE_LIB_OK'] = 'True' +os.environ["KMP_DUPLICATE_LIB_OK"] = "True" @dataclass(kw_only=True) class ScoreNetworkParameters: """Base Hyper-parameters for score networks.""" + architecture: str spatial_dimension: int = 3 # the dimension of Euclidean space where atoms live. - conditional_prob: float = 0. # probability of making a conditional forward - else, do a unconditional forward - conditional_gamma: float = 2. # conditional score weighting - see eq. B45 in MatterGen + conditional_prob: float = ( + 0.0 # probability of making a conditional forward - else, do a unconditional forward + ) + conditional_gamma: float = ( + 2.0 # conditional score weighting - see eq. B45 in MatterGen + ) # p_\gamma(x|c) = p(c|x)^\gamma p(x) @@ -70,22 +76,28 @@ def _check_batch(self, batch: Dict[AnyStr, torch.Tensor]): Returns: None. """ - assert NOISY_RELATIVE_COORDINATES in batch, \ - (f"The relative coordinates should be present in " - f"the batch dictionary with key '{NOISY_RELATIVE_COORDINATES}'") + assert NOISY_RELATIVE_COORDINATES in batch, ( + f"The relative coordinates should be present in " + f"the batch dictionary with key '{NOISY_RELATIVE_COORDINATES}'" + ) relative_coordinates = batch[NOISY_RELATIVE_COORDINATES] relative_coordinates_shape = relative_coordinates.shape batch_size = relative_coordinates_shape[0] assert ( - len(relative_coordinates_shape) == 3 and relative_coordinates_shape[2] == self.spatial_dimension + len(relative_coordinates_shape) == 3 + and relative_coordinates_shape[2] == self.spatial_dimension ), "The relative coordinates are expected to be in a tensor of shape [batch_size, number_of_atoms, 3]" assert torch.logical_and( relative_coordinates >= 0.0, relative_coordinates < 1.0 - ).all(), "All components of the relative coordinates are expected to be in [0,1)." + ).all(), ( + "All components of the relative coordinates are expected to be in [0,1)." + ) - assert TIME in batch, f"The time step should be present in the batch dictionary with key '{TIME}'" + assert ( + TIME in batch + ), f"The time step should be present in the batch dictionary with key '{TIME}'" times = batch[TIME] time_shape = times.shape @@ -100,10 +112,16 @@ def _check_batch(self, batch: Dict[AnyStr, torch.Tensor]): times >= 0.0, times <= 1.0 ).all(), "The times are expected to be normalized between 0 and 1." - assert NOISE in batch, "There should be a 'noise' parameter in the batch dictionary." - assert batch[NOISE].shape == times.shape, "the 'noise' parameter should have the same shape as the 'time'." + assert ( + NOISE in batch + ), "There should be a 'noise' parameter in the batch dictionary." + assert ( + batch[NOISE].shape == times.shape + ), "the 'noise' parameter should have the same shape as the 'time'." - assert UNIT_CELL in batch, f"The unit cell should be present in the batch dictionary with key '{UNIT_CELL}'" + assert ( + UNIT_CELL in batch + ), f"The unit cell should be present in the batch dictionary with key '{UNIT_CELL}'" unit_cell = batch[UNIT_CELL] unit_cell_shape = unit_cell.shape @@ -111,23 +129,30 @@ def _check_batch(self, batch: Dict[AnyStr, torch.Tensor]): unit_cell_shape[0] == batch_size ), "the batch size dimension is inconsistent between positions and unit cell." assert ( - len(unit_cell_shape) == 3 and unit_cell_shape[1] == self.spatial_dimension + len(unit_cell_shape) == 3 + and unit_cell_shape[1] == self.spatial_dimension and unit_cell_shape[2] == self.spatial_dimension ), "The unit cell is expected to be in a tensor of shape [batch_size, spatial_dimension, spatial_dimension]." if self.conditional_prob > 0: - assert CARTESIAN_FORCES in batch, \ - (f"The cartesian forces should be present in " - f"the batch dictionary with key '{CARTESIAN_FORCES}'") + assert CARTESIAN_FORCES in batch, ( + f"The cartesian forces should be present in " + f"the batch dictionary with key '{CARTESIAN_FORCES}'" + ) cartesian_forces = batch[CARTESIAN_FORCES] cartesian_forces_shape = cartesian_forces.shape assert ( - len(cartesian_forces_shape) == 3 and cartesian_forces_shape[2] == self.spatial_dimension - ), ("The cartesian forces are expected to be in a tensor of shape [batch_size, number_of_atoms," - f"{self.spatial_dimension}]") - - def forward(self, batch: Dict[AnyStr, torch.Tensor], conditional: Optional[bool] = None) -> torch.Tensor: + len(cartesian_forces_shape) == 3 + and cartesian_forces_shape[2] == self.spatial_dimension + ), ( + "The cartesian forces are expected to be in a tensor of shape [batch_size, number_of_atoms," + f"{self.spatial_dimension}]" + ) + + def forward( + self, batch: Dict[AnyStr, torch.Tensor], conditional: Optional[bool] = None + ) -> torch.Tensor: """Model forward. Args: @@ -140,14 +165,26 @@ def forward(self, batch: Dict[AnyStr, torch.Tensor], conditional: Optional[bool] """ self._check_batch(batch) if conditional is None: - conditional = torch.rand(1,) < self.conditional_prob + conditional = ( + torch.rand( + 1, + ) + < self.conditional_prob + ) if not conditional: return self._forward_unchecked(batch, conditional=False) else: - return (self._forward_unchecked(batch, conditional=True) * self.conditional_gamma - + self._forward_unchecked(batch, conditional=False) * (1 - self.conditional_gamma)) - - def _forward_unchecked(self, batch: Dict[AnyStr, torch.Tensor], conditional: bool = False) -> torch.Tensor: + return self._forward_unchecked( + batch, conditional=True + ) * self.conditional_gamma + self._forward_unchecked( + batch, conditional=False + ) * ( + 1 - self.conditional_gamma + ) + + def _forward_unchecked( + self, batch: Dict[AnyStr, torch.Tensor], conditional: bool = False + ) -> torch.Tensor: """Forward unchecked. This method assumes that the input data has already been checked with respect to expectations diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/score_network_factory.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/score_network_factory.py index b6f2dc38..f161236b 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/score_network_factory.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/score_network_factory.py @@ -1,32 +1,38 @@ import dataclasses from typing import Any, AnyStr, Dict -from crystal_diffusion.models.score_networks import (ScoreNetwork, - ScoreNetworkParameters) -from crystal_diffusion.models.score_networks.diffusion_mace_score_network import ( +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks import ( + ScoreNetwork, ScoreNetworkParameters) +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.diffusion_mace_score_network import ( DiffusionMACEScoreNetwork, DiffusionMACEScoreNetworkParameters) -from crystal_diffusion.models.score_networks.egnn_score_network import ( +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.egnn_score_network import ( EGNNScoreNetwork, EGNNScoreNetworkParameters) -from crystal_diffusion.models.score_networks.mace_score_network import ( +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.mace_score_network import ( MACEScoreNetwork, MACEScoreNetworkParameters) -from crystal_diffusion.models.score_networks.mlp_score_network import ( +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.mlp_score_network import ( MLPScoreNetwork, MLPScoreNetworkParameters) -from crystal_diffusion.models.score_networks.score_prediction_head import ( +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_prediction_head import ( MaceEquivariantScorePredictionHeadParameters, MaceMLPScorePredictionHeadParameters) -from crystal_diffusion.utils.configuration_parsing import \ +from diffusion_for_multi_scale_molecular_dynamics.utils.configuration_parsing import \ create_parameters_from_configuration_dictionary -SCORE_NETWORKS_BY_ARCH = dict(mlp=MLPScoreNetwork, - mace=MACEScoreNetwork, - diffusion_mace=DiffusionMACEScoreNetwork, - egnn=EGNNScoreNetwork) -SCORE_NETWORK_PARAMETERS_BY_ARCH = dict(mlp=MLPScoreNetworkParameters, - mace=MACEScoreNetworkParameters, - diffusion_mace=DiffusionMACEScoreNetworkParameters, - egnn=EGNNScoreNetworkParameters) -MACE_PREDICTION_HEAD_BY_NAME = dict(mlp=MaceMLPScorePredictionHeadParameters, - equivariant=MaceEquivariantScorePredictionHeadParameters) +SCORE_NETWORKS_BY_ARCH = dict( + mlp=MLPScoreNetwork, + mace=MACEScoreNetwork, + diffusion_mace=DiffusionMACEScoreNetwork, + egnn=EGNNScoreNetwork, +) +SCORE_NETWORK_PARAMETERS_BY_ARCH = dict( + mlp=MLPScoreNetworkParameters, + mace=MACEScoreNetworkParameters, + diffusion_mace=DiffusionMACEScoreNetworkParameters, + egnn=EGNNScoreNetworkParameters, +) +MACE_PREDICTION_HEAD_BY_NAME = dict( + mlp=MaceMLPScorePredictionHeadParameters, + equivariant=MaceEquivariantScorePredictionHeadParameters, +) def create_score_network(score_network_parameters: ScoreNetworkParameters): @@ -35,16 +41,21 @@ def create_score_network(score_network_parameters: ScoreNetworkParameters): This is a factory method responsible for instantiating the score network. """ architecture = score_network_parameters.architecture - assert architecture in SCORE_NETWORKS_BY_ARCH.keys(), \ - f"Architecture {architecture} is not implemented. Possible choices are {SCORE_NETWORKS_BY_ARCH.keys()}" + assert ( + architecture in SCORE_NETWORKS_BY_ARCH.keys() + ), f"Architecture {architecture} is not implemented. Possible choices are {SCORE_NETWORKS_BY_ARCH.keys()}" - instantiated_score_network: ScoreNetwork = SCORE_NETWORKS_BY_ARCH[architecture](score_network_parameters) + instantiated_score_network: ScoreNetwork = SCORE_NETWORKS_BY_ARCH[architecture]( + score_network_parameters + ) return instantiated_score_network -def create_score_network_parameters(score_network_dictionary: Dict[AnyStr, Any], - global_parameters_dictionary: Dict[AnyStr, Any]) -> ScoreNetworkParameters: +def create_score_network_parameters( + score_network_dictionary: Dict[AnyStr, Any], + global_parameters_dictionary: Dict[AnyStr, Any], +) -> ScoreNetworkParameters: """Create the score network parameters. Args: @@ -54,30 +65,40 @@ def create_score_network_parameters(score_network_dictionary: Dict[AnyStr, Any], Returns: score_network_parameters: the dataclass configuration object describing the score network. """ - assert 'architecture' in score_network_dictionary, "The architecture of the score network must be specified." - score_network_architecture = score_network_dictionary['architecture'] + assert ( + "architecture" in score_network_dictionary + ), "The architecture of the score network must be specified." + score_network_architecture = score_network_dictionary["architecture"] - assert score_network_architecture in SCORE_NETWORK_PARAMETERS_BY_ARCH.keys(), \ - (f"Architecture {score_network_architecture} is not implemented. " - f"Possible choices are {SCORE_NETWORK_PARAMETERS_BY_ARCH.keys()}") + assert score_network_architecture in SCORE_NETWORK_PARAMETERS_BY_ARCH.keys(), ( + f"Architecture {score_network_architecture} is not implemented. " + f"Possible choices are {SCORE_NETWORK_PARAMETERS_BY_ARCH.keys()}" + ) - score_network_dataclass = SCORE_NETWORK_PARAMETERS_BY_ARCH[score_network_architecture] + score_network_dataclass = SCORE_NETWORK_PARAMETERS_BY_ARCH[ + score_network_architecture + ] # Augment the configuration dictionary with a head (if present) and relevant global parameters. augmented_score_network_dictionary = dict(score_network_dictionary) - if 'prediction_head_parameters' in score_network_dictionary: - head_config = score_network_dictionary['prediction_head_parameters'] - prediction_head_parameters = ( - create_parameters_from_configuration_dictionary(configuration=head_config, - identifier="name", - options=MACE_PREDICTION_HEAD_BY_NAME)) - augmented_score_network_dictionary['prediction_head_parameters'] = prediction_head_parameters + if "prediction_head_parameters" in score_network_dictionary: + head_config = score_network_dictionary["prediction_head_parameters"] + prediction_head_parameters = create_parameters_from_configuration_dictionary( + configuration=head_config, + identifier="name", + options=MACE_PREDICTION_HEAD_BY_NAME, + ) + augmented_score_network_dictionary["prediction_head_parameters"] = ( + prediction_head_parameters + ) # Validate that there are no contradictions between the score config and global parameters for key, value in augmented_score_network_dictionary.items(): if key in global_parameters_dictionary: - assert global_parameters_dictionary[key] == value, f"inconsistent configuration values for {key}" + assert ( + global_parameters_dictionary[key] == value + ), f"inconsistent configuration values for {key}" # Complete the score config with global values all_fields = [field.name for field in dataclasses.fields(score_network_dataclass)] @@ -85,6 +106,8 @@ def create_score_network_parameters(score_network_dictionary: Dict[AnyStr, Any], if key in all_fields: augmented_score_network_dictionary[key] = value - score_network_parameters = score_network_dataclass(**augmented_score_network_dictionary) + score_network_parameters = score_network_dataclass( + **augmented_score_network_dictionary + ) return score_network_parameters diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/score_prediction_head.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/score_prediction_head.py index 47c680cf..ab9c4e0b 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/score_prediction_head.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/models/score_networks/score_prediction_head.py @@ -2,30 +2,39 @@ import e3nn import torch -from crystal_diffusion.models.mace_utils import \ - get_normalized_irreps_permutation_indices from e3nn import o3 from e3nn.nn import Activation from mace.modules import LinearNodeEmbeddingBlock, gate_dict from torch import nn +from diffusion_for_multi_scale_molecular_dynamics.models.mace_utils import \ + get_normalized_irreps_permutation_indices + @dataclass(kw_only=True) class MaceScorePredictionHeadParameters: """Base Hyper-parameters for score networks.""" + name: str # this must be overloaded to identify the type of prediction head spatial_dimension: int = 3 # the dimension of Euclidean space where atoms live. class MaceScorePredictionHead(nn.Module): """A Base class for the head that predicts the scores given node features from MACE.""" - def __init__(self, output_node_features_irreps: o3.Irreps, hyper_params: MaceScorePredictionHeadParameters): + + def __init__( + self, + output_node_features_irreps: o3.Irreps, + hyper_params: MaceScorePredictionHeadParameters, + ): """Init method.""" super().__init__() self.output_node_features_irreps = output_node_features_irreps self.hyper_params = hyper_params - def forward(self, flat_node_features: torch.Tensor, flat_times: torch.Tensor) -> torch.Tensor: + def forward( + self, flat_node_features: torch.Tensor, flat_times: torch.Tensor + ) -> torch.Tensor: """Forward method. Here, 'flat' means that the batch dimension and the number_of_atoms dimensions are combined (flattened). @@ -43,30 +52,44 @@ def forward(self, flat_node_features: torch.Tensor, flat_times: torch.Tensor) -> @dataclass(kw_only=True) class MaceMLPScorePredictionHeadParameters(MaceScorePredictionHeadParameters): """Parameters for a MLP prediction head.""" - name: str = 'mlp' - hidden_dimensions_size: int # dimension of a linear layer + + name: str = "mlp" + hidden_dimensions_size: int # dimension of a linear layer n_hidden_dimensions: int # number of linear layers in the MLP class MaceMLPScorePredictionHead(MaceScorePredictionHead): """A MLP head to predict scores given node features from MACE.""" - def __init__(self, output_node_features_irreps: o3.Irreps, hyper_params: MaceMLPScorePredictionHeadParameters): + + def __init__( + self, + output_node_features_irreps: o3.Irreps, + hyper_params: MaceMLPScorePredictionHeadParameters, + ): """Init method.""" super().__init__(output_node_features_irreps, hyper_params) - hidden_dimensions = [hyper_params.hidden_dimensions_size] * hyper_params.n_hidden_dimensions + hidden_dimensions = [ + hyper_params.hidden_dimensions_size + ] * hyper_params.n_hidden_dimensions self.mlp_layers = torch.nn.Sequential() # TODO we could add a linear layer to the times before concat with mace_output - input_dimensions = [output_node_features_irreps.dim + 1] + hidden_dimensions # add 1 for the times + input_dimensions = [ + output_node_features_irreps.dim + 1 + ] + hidden_dimensions # add 1 for the times output_dimensions = hidden_dimensions + [hyper_params.spatial_dimension] add_relus = len(input_dimensions) * [True] add_relus[-1] = False - for input_dimension, output_dimension, add_relu in zip(input_dimensions, output_dimensions, add_relus): + for input_dimension, output_dimension, add_relu in zip( + input_dimensions, output_dimensions, add_relus + ): self.mlp_layers.append(nn.Linear(input_dimension, output_dimension)) if add_relu: self.mlp_layers.append(nn.ReLU()) - def forward(self, flat_node_features: torch.Tensor, flat_times: torch.Tensor) -> torch.Tensor: + def forward( + self, flat_node_features: torch.Tensor, flat_times: torch.Tensor + ) -> torch.Tensor: """Forward method.""" mlp_input = torch.cat([flat_node_features, flat_times], dim=-1) # pass through the final MLP layers @@ -77,16 +100,23 @@ def forward(self, flat_node_features: torch.Tensor, flat_times: torch.Tensor) -> @dataclass(kw_only=True) class MaceEquivariantScorePredictionHeadParameters(MaceScorePredictionHeadParameters): """Parameters for an equivariant prediction head.""" - name: str = 'equivariant' + + name: str = "equivariant" time_embedding_irreps: str = "16x0e" - gate: str = "silu" # non linearity for last readout - choices: ["silu", "tanh", "abs", "None"] + gate: str = ( + "silu" # non linearity for last readout - choices: ["silu", "tanh", "abs", "None"] + ) number_of_layers: int class MaceEquivariantScorePredictionHead(MaceScorePredictionHead): """An Equivariant head to predict scores given node features from MACE.""" - def __init__(self, output_node_features_irreps: o3.Irreps, - hyper_params: MaceEquivariantScorePredictionHeadParameters): + + def __init__( + self, + output_node_features_irreps: o3.Irreps, + hyper_params: MaceEquivariantScorePredictionHeadParameters, + ): """Init method.""" super().__init__(output_node_features_irreps, hyper_params) @@ -94,8 +124,9 @@ def __init__(self, output_node_features_irreps: o3.Irreps, time_irreps_in = o3.Irreps("1x0e") # time is a scalar time_irreps_out = o3.Irreps(hyper_params.time_embedding_irreps) - self.time_embedding_linear_layer = LinearNodeEmbeddingBlock(irreps_in=time_irreps_in, - irreps_out=time_irreps_out) + self.time_embedding_linear_layer = LinearNodeEmbeddingBlock( + irreps_in=time_irreps_in, irreps_out=time_irreps_out + ) # The concatenated node features have representation 'output_node_features_irreps', which # is potentially out of order. We will pre-concatenate the time embedding to this data. @@ -103,8 +134,7 @@ def __init__(self, output_node_features_irreps: o3.Irreps, # by the various subsequent layers. head_input_irreps = time_irreps_out + output_node_features_irreps - sorted_irreps, _ = ( - get_normalized_irreps_permutation_indices(head_input_irreps)) + sorted_irreps, _ = get_normalized_irreps_permutation_indices(head_input_irreps) # mix the time embedding 0e irrep with the different components of the MACE output and avoid a dimensionality # explosion with FullyConnectedTensorProduct that only computes the specified channels in the output and applies @@ -112,7 +142,7 @@ def __init__(self, output_node_features_irreps: o3.Irreps, self.time_mixing_layer = o3.FullyConnectedTensorProduct( irreps_in1=time_irreps_out, irreps_in2=output_node_features_irreps, - irreps_out=sorted_irreps + irreps_out=sorted_irreps, ) self.head = torch.nn.Sequential() @@ -133,10 +163,14 @@ def __init__(self, output_node_features_irreps: o3.Irreps, self.head.append(non_linearity) # the output is a single vector. - linear_readout = o3.Linear(irreps_in=sorted_irreps, irreps_out=o3.Irreps("1x1o")) + linear_readout = o3.Linear( + irreps_in=sorted_irreps, irreps_out=o3.Irreps("1x1o") + ) self.head.append(linear_readout) - def forward(self, flat_node_features: torch.Tensor, flat_times: torch.Tensor) -> torch.Tensor: + def forward( + self, flat_node_features: torch.Tensor, flat_times: torch.Tensor + ) -> torch.Tensor: """Forward method. Here, 'flat' means that the batch dimension and the number_of_atoms dimensions are combined (flattened). @@ -156,12 +190,15 @@ def forward(self, flat_node_features: torch.Tensor, flat_times: torch.Tensor) -> # Register the possible MACE prediction heads as key: model class -MACE_PREDICTION_HEADS = dict(mlp=MaceMLPScorePredictionHead, equivariant=MaceEquivariantScorePredictionHead) +MACE_PREDICTION_HEADS = dict( + mlp=MaceMLPScorePredictionHead, equivariant=MaceEquivariantScorePredictionHead +) -def instantiate_mace_prediction_head(output_node_features_irreps: o3.Irreps, - prediction_head_parameters: MaceScorePredictionHeadParameters) \ - -> MaceScorePredictionHead: +def instantiate_mace_prediction_head( + output_node_features_irreps: o3.Irreps, + prediction_head_parameters: MaceScorePredictionHeadParameters, +) -> MaceScorePredictionHead: """Instantiate MACE prediction head. Args: @@ -172,8 +209,12 @@ def instantiate_mace_prediction_head(output_node_features_irreps: o3.Irreps, prediction_head: torch module to predict the scores from the output of MACE. """ head_name = prediction_head_parameters.name - assert head_name in MACE_PREDICTION_HEADS, f"MACE prediction head '{head_name}' is not implemented" + assert ( + head_name in MACE_PREDICTION_HEADS + ), f"MACE prediction head '{head_name}' is not implemented" head_class = MACE_PREDICTION_HEADS[head_name] - prediction_head = head_class(output_node_features_irreps, prediction_head_parameters) + prediction_head = head_class( + output_node_features_irreps, prediction_head_parameters + ) return prediction_head diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/namespace.py b/src/diffusion_for_multi_scale_molecular_dynamics/namespace.py index 21c1851c..2fb6ebeb 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/namespace.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/namespace.py @@ -10,12 +10,16 @@ # # r = \sum_{i} x_i a_i, where { a_i } are the basis vectors defining the lattice. -CARTESIAN_POSITIONS = "cartesian_positions" # position in real cartesian space -RELATIVE_COORDINATES = "relative_coordinates" # coordinates in the unit cell basis +CARTESIAN_POSITIONS = "cartesian_positions" # position in real cartesian space +RELATIVE_COORDINATES = "relative_coordinates" # coordinates in the unit cell basis CARTESIAN_FORCES = "cartesian_forces" -NOISY_RELATIVE_COORDINATES = "noisy_relative_coordinates" # relative coordinates perturbed by diffusion noise -NOISY_CARTESIAN_POSITIONS = "noisy_cartesian_positions" # cartesian positions perturbed by diffusion noise +NOISY_RELATIVE_COORDINATES = ( + "noisy_relative_coordinates" # relative coordinates perturbed by diffusion noise +) +NOISY_CARTESIAN_POSITIONS = ( + "noisy_cartesian_positions" # cartesian positions perturbed by diffusion noise +) TIME = "time" # diffusion time NOISE = "noise_parameter" # the exploding variance sigma parameter UNIT_CELL = "unit_cell" # unit cell definition diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/oracle/__init__.py b/src/diffusion_for_multi_scale_molecular_dynamics/oracle/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/oracle/energies.py b/src/diffusion_for_multi_scale_molecular_dynamics/oracle/energies.py index ccc69659..b49a25c5 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/oracle/energies.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/oracle/energies.py @@ -4,8 +4,11 @@ import numpy as np import torch -from crystal_diffusion.namespace import CARTESIAN_POSITIONS, UNIT_CELL -from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps + +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_POSITIONS, UNIT_CELL) +from diffusion_for_multi_scale_molecular_dynamics.oracle.lammps import \ + get_energy_and_forces_from_lammps logger = logging.getLogger(__name__) @@ -23,11 +26,13 @@ def compute_oracle_energies(samples: Dict[AnyStr, torch.Tensor]) -> torch.Tensor Returns: energies: a numpy array with the computed energies. """ - assert CARTESIAN_POSITIONS in samples, \ - f"the field '{CARTESIAN_POSITIONS}' must be present in the sample dictionary" + assert ( + CARTESIAN_POSITIONS in samples + ), f"the field '{CARTESIAN_POSITIONS}' must be present in the sample dictionary" - assert UNIT_CELL in samples, \ - f"the field '{UNIT_CELL}' must be present in the sample dictionary" + assert ( + UNIT_CELL in samples + ), f"the field '{UNIT_CELL}' must be present in the sample dictionary" # Dimension [batch_size, space_dimension, space_dimension] basis_vectors = samples[UNIT_CELL].detach().cpu().numpy() @@ -43,10 +48,9 @@ def compute_oracle_energies(samples: Dict[AnyStr, torch.Tensor]) -> torch.Tensor list_energy = [] with tempfile.TemporaryDirectory() as tmp_work_dir: for positions, box in zip(cartesian_positions, basis_vectors): - energy, forces = get_energy_and_forces_from_lammps(positions, - box, - atom_types, - tmp_work_dir=tmp_work_dir) + energy, forces = get_energy_and_forces_from_lammps( + positions, box, atom_types, tmp_work_dir=tmp_work_dir + ) list_energy.append(energy) logger.info("Done computing energies from Oracle") return torch.tensor(list_energy) diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/oracle/lammps.py b/src/diffusion_for_multi_scale_molecular_dynamics/oracle/lammps.py index cd0bd43c..6cd96fbf 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/oracle/lammps.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/oracle/lammps.py @@ -1,4 +1,5 @@ """Call LAMMPS to get the forces and energy in a given configuration.""" + import os from pathlib import Path from typing import Dict, Tuple @@ -7,16 +8,19 @@ import numpy as np import pandas as pd import yaml -from crystal_diffusion import DATA_DIR from pymatgen.core import Element +from diffusion_for_multi_scale_molecular_dynamics import DATA_DIR + -def get_energy_and_forces_from_lammps(cartesian_positions: np.ndarray, - box: np.ndarray, - atom_types: np.ndarray, - atom_type_map: Dict[int, str] = {1: 'Si'}, - tmp_work_dir: str = './', - pair_coeff_dir: Path = DATA_DIR) -> Tuple[float, pd.DataFrame]: +def get_energy_and_forces_from_lammps( + cartesian_positions: np.ndarray, + box: np.ndarray, + atom_types: np.ndarray, + atom_type_map: Dict[int, str] = {1: "Si"}, + tmp_work_dir: str = "./", + pair_coeff_dir: Path = DATA_DIR, +) -> Tuple[float, pd.DataFrame]: """Call LAMMPS to compute the forces on all atoms in a configuration. Args: @@ -33,40 +37,62 @@ def get_energy_and_forces_from_lammps(cartesian_positions: np.ndarray, dataframe with x, y, z coordinates and fx, fy, fz information in a dataframe. """ n_atom = cartesian_positions.shape[0] - assert atom_types.shape == (n_atom, ), f"Atom types should match the number of atoms. Got {atom_types.shape}." + assert atom_types.shape == ( + n_atom, + ), f"Atom types should match the number of atoms. Got {atom_types.shape}." # create a lammps run, turning off logging lmp = lammps.lammps(cmdargs=["-log", "none", "-echo", "none", "-screen", "none"]) - assert np.allclose(box, np.diag(np.diag(box))), "only orthogonal LAMMPS box are valid" + assert np.allclose( + box, np.diag(np.diag(box)) + ), "only orthogonal LAMMPS box are valid" lmp.command("units metal") lmp.command("atom_style atomic") - lmp.command(f"region simbox block 0 {box[0, 0]} 0 {box[1, 1]} 0 {box[2, 2]}") # TODO what if box is not orthogonal + lmp.command( + f"region simbox block 0 {box[0, 0]} 0 {box[1, 1]} 0 {box[2, 2]}" + ) # TODO what if box is not orthogonal lmp.command("create_box 1 simbox") lmp.command("pair_style sw") for k, v in atom_type_map.items(): elem = Element(v) - lmp.command(f"mass {k} {elem.atomic_mass.real}") # the .real is to get the value without the unit + lmp.command( + f"mass {k} {elem.atomic_mass.real}" + ) # the .real is to get the value without the unit lmp.command(f"group {v} type {k}") - lmp.command(f"pair_coeff * * {os.path.join(pair_coeff_dir, f'{v.lower()}.sw')} {v}") + lmp.command( + f"pair_coeff * * {os.path.join(pair_coeff_dir, f'{v.lower()}.sw')} {v}" + ) for i in range(n_atom): - lmp.command(f"create_atoms {atom_types[i]} single {' '.join(map(str, cartesian_positions[i, :]))}") - lmp.command("fix 1 all nvt temp 300 300 0.01") # selections here do not matter because we only do 1 step + lmp.command( + f"create_atoms {atom_types[i]} single {' '.join(map(str, cartesian_positions[i, :]))}" + ) + lmp.command( + "fix 1 all nvt temp 300 300 0.01" + ) # selections here do not matter because we only do 1 step # TODO not good in 2D - lmp.command(f"dump 1 all yaml 1 {os.path.join(tmp_work_dir, 'dump.yaml')} id type x y z fx fy fz") - lmp.command("run 0") # 0 is the last step index - so run 0 means no MD update - just get the initial forces + lmp.command( + f"dump 1 all yaml 1 {os.path.join(tmp_work_dir, 'dump.yaml')} id type x y z fx fy fz" + ) + lmp.command( + "run 0" + ) # 0 is the last step index - so run 0 means no MD update - just get the initial forces # read information from lammps output with open(os.path.join(tmp_work_dir, "dump.yaml"), "r") as f: dump_yaml = yaml.safe_load_all(f) doc = next(iter(dump_yaml)) - forces = pd.DataFrame(doc['data'], columns=doc['keywords']).sort_values("id") # organize in a dataframe + forces = pd.DataFrame(doc["data"], columns=doc["keywords"]).sort_values( + "id" + ) # organize in a dataframe # get the energy - ke = lmp.get_thermo('ke') # kinetic energy - should be 0 as atoms are created with 0 velocity - pe = lmp.get_thermo('pe') # potential energy + ke = lmp.get_thermo( + "ke" + ) # kinetic energy - should be 0 as atoms are created with 0 velocity + pe = lmp.get_thermo("pe") # potential energy energy = ke + pe return energy, forces diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/sample_diffusion.py b/src/diffusion_for_multi_scale_molecular_dynamics/sample_diffusion.py index 08fd58c7..d6fe7fea 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/sample_diffusion.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/sample_diffusion.py @@ -2,6 +2,7 @@ This script is the entry point to draw samples from a pre-trained model checkpoint. """ + import argparse import logging import os @@ -10,21 +11,27 @@ from typing import Any, AnyStr, Dict, Optional, Union import torch -from crystal_diffusion.generators.instantiate_generator import \ + +from diffusion_for_multi_scale_molecular_dynamics.generators.instantiate_generator import \ instantiate_generator -from crystal_diffusion.generators.load_sampling_parameters import \ +from diffusion_for_multi_scale_molecular_dynamics.generators.load_sampling_parameters import \ load_sampling_parameters -from crystal_diffusion.main_utils import load_and_backup_hyperparameters -from crystal_diffusion.models.position_diffusion_lightning_model import \ - PositionDiffusionLightningModel -from crystal_diffusion.models.score_networks import ScoreNetwork -from crystal_diffusion.oracle.energies import compute_oracle_energies -from crystal_diffusion.utils.logging_utils import (get_git_hash, - setup_console_logger) -from src.crystal_diffusion.generators.position_generator import \ +from diffusion_for_multi_scale_molecular_dynamics.generators.position_generator import \ SamplingParameters -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters -from src.crystal_diffusion.samples.sampling import create_batch_of_samples +from diffusion_for_multi_scale_molecular_dynamics.main_utils import \ + load_and_backup_hyperparameters +from diffusion_for_multi_scale_molecular_dynamics.models.position_diffusion_lightning_model import \ + PositionDiffusionLightningModel +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks import \ + ScoreNetwork +from diffusion_for_multi_scale_molecular_dynamics.oracle.energies import \ + compute_oracle_energies +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters +from diffusion_for_multi_scale_molecular_dynamics.samples.sampling import \ + create_batch_of_samples +from diffusion_for_multi_scale_molecular_dynamics.utils.logging_utils import ( + get_git_hash, setup_console_logger) logger = logging.getLogger(__name__) diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/samplers/exploding_variance.py b/src/diffusion_for_multi_scale_molecular_dynamics/samplers/exploding_variance.py index 45dddb80..d37a5ae0 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/samplers/exploding_variance.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/samplers/exploding_variance.py @@ -1,5 +1,7 @@ import torch -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters + +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters class ExplodingVariance(torch.nn.Module): @@ -17,11 +19,19 @@ def __init__(self, noise_parameters: NoiseParameters): """ super().__init__() - self.sigma_min = torch.nn.Parameter(torch.tensor(noise_parameters.sigma_min), requires_grad=False) - self.sigma_max = torch.nn.Parameter(torch.tensor(noise_parameters.sigma_max), requires_grad=False) + self.sigma_min = torch.nn.Parameter( + torch.tensor(noise_parameters.sigma_min), requires_grad=False + ) + self.sigma_max = torch.nn.Parameter( + torch.tensor(noise_parameters.sigma_max), requires_grad=False + ) - self.ratio = torch.nn.Parameter(self.sigma_max / self.sigma_min, requires_grad=False) - self.log_ratio = torch.nn.Parameter(torch.log(self.sigma_max / self.sigma_min), requires_grad=False) + self.ratio = torch.nn.Parameter( + self.sigma_max / self.sigma_min, requires_grad=False + ) + self.log_ratio = torch.nn.Parameter( + torch.log(self.sigma_max / self.sigma_min), requires_grad=False + ) def get_sigma(self, times: torch.Tensor) -> torch.Tensor: """Get sigma. @@ -34,7 +44,7 @@ def get_sigma(self, times: torch.Tensor) -> torch.Tensor: Returns: sigmas: the standard deviation in the exploding variance scheme. """ - return self.sigma_min * self.ratio ** times + return self.sigma_min * self.ratio**times def get_sigma_time_derivative(self, times: torch.Tensor) -> torch.Tensor: """Get sigma time derivative. diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/samplers/noisy_relative_coordinates_sampler.py b/src/diffusion_for_multi_scale_molecular_dynamics/samplers/noisy_relative_coordinates_sampler.py index f1bd093b..c57e4fb5 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/samplers/noisy_relative_coordinates_sampler.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/samplers/noisy_relative_coordinates_sampler.py @@ -2,10 +2,12 @@ This module is responsible for sampling relative positions from the perturbation kernel. """ + from typing import Tuple import torch -from crystal_diffusion.utils.basis_transformations import \ + +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell @@ -17,6 +19,7 @@ class NoisyRelativeCoordinatesSampler: The random samples are produced by a separate method to make this code easy to test. """ + @staticmethod def _get_gaussian_noise(shape: Tuple[int]) -> torch.Tensor: """Get Gaussian noise. @@ -32,8 +35,9 @@ def _get_gaussian_noise(shape: Tuple[int]) -> torch.Tensor: return torch.randn(shape) @staticmethod - def get_noisy_relative_coordinates_sample(real_relative_coordinates: torch.Tensor, - sigmas: torch.Tensor) -> torch.Tensor: + def get_noisy_relative_coordinates_sample( + real_relative_coordinates: torch.Tensor, sigmas: torch.Tensor + ) -> torch.Tensor: """Get noisy relative coordinates sample. This method draws a sample from the perturbation kernel centered on the real_relative_coordinates @@ -54,10 +58,15 @@ def get_noisy_relative_coordinates_sample(real_relative_coordinates: torch.Tenso noisy_relative_coordinates: a sample of noised relative coordinates, of the same shape as real_relative_coordinates. """ - assert real_relative_coordinates.shape == sigmas.shape, \ - "sigmas array is expected to be of the same shape as the real_relative_coordinates array" + assert ( + real_relative_coordinates.shape == sigmas.shape + ), "sigmas array is expected to be of the same shape as the real_relative_coordinates array" - z_scores = NoisyRelativeCoordinatesSampler._get_gaussian_noise(real_relative_coordinates.shape).to(sigmas) + z_scores = NoisyRelativeCoordinatesSampler._get_gaussian_noise( + real_relative_coordinates.shape + ).to(sigmas) noise = (sigmas * z_scores).to(real_relative_coordinates) - noisy_relative_coordinates = map_relative_coordinates_to_unit_cell(real_relative_coordinates + noise) + noisy_relative_coordinates = map_relative_coordinates_to_unit_cell( + real_relative_coordinates + noise + ) return noisy_relative_coordinates diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/samplers/variance_sampler.py b/src/diffusion_for_multi_scale_molecular_dynamics/samplers/variance_sampler.py index 1081073e..a5ed687e 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/samplers/variance_sampler.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/samplers/variance_sampler.py @@ -11,6 +11,7 @@ @dataclass class NoiseParameters: """Noise schedule parameters.""" + total_time_steps: int time_delta: float = 1e-5 # the time schedule will cover the range [time_delta, 1] # As discussed in Appendix C of "SCORE-BASED GENERATIVE MODELING THROUGH STOCHASTIC DIFFERENTIAL EQUATIONS", @@ -78,27 +79,46 @@ def __init__(self, noise_parameters: NoiseParameters): super().__init__() self.noise_parameters = noise_parameters - self._time_array = torch.nn.Parameter(self._get_time_array(noise_parameters), requires_grad=False) + self._time_array = torch.nn.Parameter( + self._get_time_array(noise_parameters), requires_grad=False + ) - self._sigma_array = torch.nn.Parameter(self._create_sigma_array(noise_parameters, self._time_array), - requires_grad=False) - self._sigma_squared_array = torch.nn.Parameter(self._sigma_array**2, requires_grad=False) + self._sigma_array = torch.nn.Parameter( + self._create_sigma_array(noise_parameters, self._time_array), + requires_grad=False, + ) + self._sigma_squared_array = torch.nn.Parameter( + self._sigma_array**2, requires_grad=False + ) self._g_squared_array = torch.nn.Parameter( - self._create_g_squared_array(noise_parameters, self._sigma_squared_array), requires_grad=False) - self._g_array = torch.nn.Parameter(torch.sqrt(self._g_squared_array), requires_grad=False) + self._create_g_squared_array(noise_parameters, self._sigma_squared_array), + requires_grad=False, + ) + self._g_array = torch.nn.Parameter( + torch.sqrt(self._g_squared_array), requires_grad=False + ) self._epsilon_array = torch.nn.Parameter( - self._create_epsilon_array(noise_parameters, self._sigma_squared_array), requires_grad=False) - self._sqrt_two_epsilon_array = torch.nn.Parameter(torch.sqrt(2. * self._epsilon_array), requires_grad=False) + self._create_epsilon_array(noise_parameters, self._sigma_squared_array), + requires_grad=False, + ) + self._sqrt_two_epsilon_array = torch.nn.Parameter( + torch.sqrt(2.0 * self._epsilon_array), requires_grad=False + ) - self._maximum_random_index = torch.nn.Parameter(torch.tensor(noise_parameters.total_time_steps - 1), - requires_grad=False) - self._minimum_random_index = torch.nn.Parameter(torch.tensor(0), requires_grad=False) + self._maximum_random_index = torch.nn.Parameter( + torch.tensor(noise_parameters.total_time_steps - 1), requires_grad=False + ) + self._minimum_random_index = torch.nn.Parameter( + torch.tensor(0), requires_grad=False + ) @staticmethod def _get_time_array(noise_parameters: NoiseParameters) -> torch.Tensor: - return torch.linspace(noise_parameters.time_delta, 1., noise_parameters.total_time_steps) + return torch.linspace( + noise_parameters.time_delta, 1.0, noise_parameters.total_time_steps + ) @staticmethod def _create_sigma_array( @@ -111,7 +131,9 @@ def _create_sigma_array( return sigma @staticmethod - def _create_g_squared_array(noise_parameters: NoiseParameters, sigma_squared_array: torch.Tensor) -> torch.Tensor: + def _create_g_squared_array( + noise_parameters: NoiseParameters, sigma_squared_array: torch.Tensor + ) -> torch.Tensor: # g^2_{i} = sigma^2_{i} - sigma^2_{i-1}. For the first element (i=1), we set sigma_{0} = sigma_min. sigma_min = noise_parameters.sigma_min zeroth_value_tensor = torch.tensor([sigma_squared_array[0] - sigma_min**2]) @@ -120,15 +142,22 @@ def _create_g_squared_array(noise_parameters: NoiseParameters, sigma_squared_arr ) @staticmethod - def _create_epsilon_array(noise_parameters: NoiseParameters, sigma_squared_array: torch.Tensor) -> torch.Tensor: + def _create_epsilon_array( + noise_parameters: NoiseParameters, sigma_squared_array: torch.Tensor + ) -> torch.Tensor: sigma_squared_0 = noise_parameters.sigma_min**2 sigma_squared_1 = sigma_squared_array[0] eps = noise_parameters.corrector_step_epsilon - zeroth_value_tensor = torch.tensor([0.5 * eps * sigma_squared_0 / sigma_squared_1]) + zeroth_value_tensor = torch.tensor( + [0.5 * eps * sigma_squared_0 / sigma_squared_1] + ) return torch.cat( - [zeroth_value_tensor, 0.5 * eps * sigma_squared_array[:-1] / sigma_squared_1] + [ + zeroth_value_tensor, + 0.5 * eps * sigma_squared_array[:-1] / sigma_squared_1, + ] ) def _get_random_time_step_indices(self, shape: Tuple[int]) -> torch.Tensor: @@ -147,7 +176,7 @@ def _get_random_time_step_indices(self, shape: Tuple[int]) -> torch.Tensor: self._maximum_random_index + 1, # +1 because the maximum value is not sampled size=shape, - device=self._minimum_random_index.device + device=self._minimum_random_index.device, ) return random_indices @@ -198,8 +227,10 @@ def get_all_sampling_parameters(self) -> Tuple[Noise, LangevinDynamics]: sigma=self._sigma_array, sigma_squared=self._sigma_squared_array, g=self._g_array, - g_squared=self._g_squared_array) - langevin_dynamics = LangevinDynamics(epsilon=self._epsilon_array, - sqrt_2_epsilon=self._sqrt_two_epsilon_array) + g_squared=self._g_squared_array, + ) + langevin_dynamics = LangevinDynamics( + epsilon=self._epsilon_array, sqrt_2_epsilon=self._sqrt_two_epsilon_array + ) return noise, langevin_dynamics diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/samples/diffusion_sampling_parameters.py b/src/diffusion_for_multi_scale_molecular_dynamics/samples/diffusion_sampling_parameters.py index 2d4682a2..5a4f5fdb 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/samples/diffusion_sampling_parameters.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/samples/diffusion_sampling_parameters.py @@ -1,13 +1,14 @@ from dataclasses import dataclass from typing import Any, AnyStr, Dict, Union -from crystal_diffusion.generators.load_sampling_parameters import \ +from diffusion_for_multi_scale_molecular_dynamics.generators.load_sampling_parameters import \ load_sampling_parameters -from crystal_diffusion.metrics.sampling_metrics_parameters import \ - SamplingMetricsParameters -from src.crystal_diffusion.generators.position_generator import \ +from diffusion_for_multi_scale_molecular_dynamics.generators.position_generator import \ SamplingParameters -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters +from diffusion_for_multi_scale_molecular_dynamics.metrics.sampling_metrics_parameters import \ + SamplingMetricsParameters +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters @dataclass(kw_only=True) @@ -17,12 +18,21 @@ class DiffusionSamplingParameters: This dataclass holds various configuration objects that define how samples should be generated and evaluated (ie, metrics) during training. """ - sampling_parameters: SamplingParameters # Define the algorithm and parameters to draw samples. - noise_parameters: NoiseParameters # Noise for sampling, which can be different from training! - metrics_parameters: SamplingMetricsParameters # what should be done with the generated samples? + + sampling_parameters: ( + SamplingParameters # Define the algorithm and parameters to draw samples. + ) + noise_parameters: ( + NoiseParameters # Noise for sampling, which can be different from training! + ) + metrics_parameters: ( + SamplingMetricsParameters # what should be done with the generated samples? + ) -def load_diffusion_sampling_parameters(hyper_params: Dict[AnyStr, Any]) -> Union[DiffusionSamplingParameters, None]: +def load_diffusion_sampling_parameters( + hyper_params: Dict[AnyStr, Any] +) -> Union[DiffusionSamplingParameters, None]: """Load diffusion sampling parameters. Extract the needed information from the configuration dictionary. @@ -33,20 +43,28 @@ def load_diffusion_sampling_parameters(hyper_params: Dict[AnyStr, Any]) -> Union Returns: diffusion_sampling_parameters: the relevant configuration object. """ - if 'diffusion_sampling' not in hyper_params: + if "diffusion_sampling" not in hyper_params: return None - diffusion_sampling_dict = hyper_params['diffusion_sampling'] + diffusion_sampling_dict = hyper_params["diffusion_sampling"] - assert 'sampling' in diffusion_sampling_dict, "The sampling parameters must be defined to draw samples." - sampling_parameters = load_sampling_parameters(diffusion_sampling_dict['sampling']) + assert ( + "sampling" in diffusion_sampling_dict + ), "The sampling parameters must be defined to draw samples." + sampling_parameters = load_sampling_parameters(diffusion_sampling_dict["sampling"]) - assert 'noise' in diffusion_sampling_dict, "The noise parameters must be defined to draw samples." - noise_parameters = NoiseParameters(**diffusion_sampling_dict['noise']) + assert ( + "noise" in diffusion_sampling_dict + ), "The noise parameters must be defined to draw samples." + noise_parameters = NoiseParameters(**diffusion_sampling_dict["noise"]) - assert 'metrics' in diffusion_sampling_dict, "The metrics parameters must be defined to draw samples." - metrics_parameters = SamplingMetricsParameters(**diffusion_sampling_dict['metrics']) + assert ( + "metrics" in diffusion_sampling_dict + ), "The metrics parameters must be defined to draw samples." + metrics_parameters = SamplingMetricsParameters(**diffusion_sampling_dict["metrics"]) - return DiffusionSamplingParameters(sampling_parameters=sampling_parameters, - noise_parameters=noise_parameters, - metrics_parameters=metrics_parameters) + return DiffusionSamplingParameters( + sampling_parameters=sampling_parameters, + noise_parameters=noise_parameters, + metrics_parameters=metrics_parameters, + ) diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/samples/sampling.py b/src/diffusion_for_multi_scale_molecular_dynamics/samples/sampling.py index dc3545b7..34d2b3db 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/samples/sampling.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/samples/sampling.py @@ -1,21 +1,24 @@ import logging import torch -from crystal_diffusion.namespace import (CARTESIAN_POSITIONS, - RELATIVE_COORDINATES, UNIT_CELL) -from crystal_diffusion.utils.basis_transformations import \ + +from diffusion_for_multi_scale_molecular_dynamics.generators.position_generator import ( + PositionGenerator, SamplingParameters) +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_POSITIONS, RELATIVE_COORDINATES, UNIT_CELL) +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ get_positions_from_coordinates -from crystal_diffusion.utils.structure_utils import \ +from diffusion_for_multi_scale_molecular_dynamics.utils.structure_utils import \ get_orthogonal_basis_vectors -from src.crystal_diffusion.generators.position_generator import ( - PositionGenerator, SamplingParameters) logger = logging.getLogger(__name__) -def create_batch_of_samples(generator: PositionGenerator, - sampling_parameters: SamplingParameters, - device: torch.device): +def create_batch_of_samples( + generator: PositionGenerator, + sampling_parameters: SamplingParameters, + device: torch.device, +): """Create batch of samples. Utility function to drive the generation of samples. @@ -31,7 +34,9 @@ def create_batch_of_samples(generator: PositionGenerator, logger.info("Creating a batch of samples") number_of_samples = sampling_parameters.number_of_samples cell_dimensions = sampling_parameters.cell_dimensions - basis_vectors = get_orthogonal_basis_vectors(number_of_samples, cell_dimensions).to(device) + basis_vectors = get_orthogonal_basis_vectors(number_of_samples, cell_dimensions).to( + device + ) if sampling_parameters.sample_batchsize is None: sample_batch_size = number_of_samples @@ -39,18 +44,24 @@ def create_batch_of_samples(generator: PositionGenerator, sample_batch_size = sampling_parameters.sample_batchsize list_sampled_relative_coordinates = [] - for sampling_batch_indices in torch.split(torch.arange(number_of_samples), sample_batch_size): + for sampling_batch_indices in torch.split( + torch.arange(number_of_samples), sample_batch_size + ): basis_vectors_ = basis_vectors[sampling_batch_indices] - sampled_relative_coordinates = generator.sample(len(sampling_batch_indices), - unit_cell=basis_vectors_, - device=device) + sampled_relative_coordinates = generator.sample( + len(sampling_batch_indices), unit_cell=basis_vectors_, device=device + ) list_sampled_relative_coordinates.append(sampled_relative_coordinates) relative_coordinates = torch.concat(list_sampled_relative_coordinates) - cartesian_positions = get_positions_from_coordinates(relative_coordinates, basis_vectors) + cartesian_positions = get_positions_from_coordinates( + relative_coordinates, basis_vectors + ) - batch = {CARTESIAN_POSITIONS: cartesian_positions, - RELATIVE_COORDINATES: relative_coordinates, - UNIT_CELL: basis_vectors} + batch = { + CARTESIAN_POSITIONS: cartesian_positions, + RELATIVE_COORDINATES: relative_coordinates, + UNIT_CELL: basis_vectors, + } return batch diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/score/wrapped_gaussian_score.py b/src/diffusion_for_multi_scale_molecular_dynamics/score/wrapped_gaussian_score.py index 9a19fc4c..c7062291 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/score/wrapped_gaussian_score.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/score/wrapped_gaussian_score.py @@ -27,6 +27,7 @@ "Generative Modeling by Estimating Gradients of the Data Distribution", Song & Ermon """ + from typing import Optional import numpy as np @@ -36,7 +37,9 @@ U_THRESHOLD = torch.Tensor([0.5]) -def get_sigma_normalized_score_brute_force(u: float, sigma: float, kmax: Optional[int] = None) -> float: +def get_sigma_normalized_score_brute_force( + u: float, sigma: float, kmax: Optional[int] = None +) -> float: """Brute force implementation. A brute force implementation of the sigma normalized score to check that the main code is correct. @@ -93,11 +96,14 @@ def get_sigma_normalized_score( assert torch.logical_and( relative_coordinates >= 0, relative_coordinates < 1 ).all(), "the relative coordinates should all be in [0, 1)" - assert sigmas.shape == relative_coordinates.shape, \ - "The relative_coordinates and sigmas inputs should have the same shape" + assert ( + sigmas.shape == relative_coordinates.shape + ), "The relative_coordinates and sigmas inputs should have the same shape" device = relative_coordinates.device - assert sigmas.device == device, "relative_coordinates and sigmas should be on the same device." + assert ( + sigmas.device == device + ), "relative_coordinates and sigmas should be on the same device." total_number_of_elements = relative_coordinates.nelement() list_u = relative_coordinates.view(total_number_of_elements) @@ -131,7 +137,9 @@ def get_sigma_normalized_score( return sigma_normalized_scores -def _get_small_sigma_small_u_mask(list_u: torch.Tensor, list_sigma: torch.Tensor) -> torch.Tensor: +def _get_small_sigma_small_u_mask( + list_u: torch.Tensor, list_sigma: torch.Tensor +) -> torch.Tensor: """Get the boolean mask for small sigma and small u. Args: @@ -142,10 +150,15 @@ def _get_small_sigma_small_u_mask(list_u: torch.Tensor, list_sigma: torch.Tensor mask_1a : an array of booleans of shape [Nu] """ device = list_u.device - return torch.logical_and(list_sigma.to(device) <= SIGMA_THRESHOLD.to(device), list_u < U_THRESHOLD.to(device)) + return torch.logical_and( + list_sigma.to(device) <= SIGMA_THRESHOLD.to(device), + list_u < U_THRESHOLD.to(device), + ) -def _get_small_sigma_large_u_mask(list_u: torch.Tensor, list_sigma: torch.Tensor) -> torch.Tensor: +def _get_small_sigma_large_u_mask( + list_u: torch.Tensor, list_sigma: torch.Tensor +) -> torch.Tensor: """Get the boolean mask for small sigma and large u. Args: @@ -156,10 +169,15 @@ def _get_small_sigma_large_u_mask(list_u: torch.Tensor, list_sigma: torch.Tensor mask_1b : an array of booleans of shape [Nu] """ device = list_u.device - return torch.logical_and(list_sigma.to(device) <= SIGMA_THRESHOLD.to(device), list_u >= U_THRESHOLD.to(device)) + return torch.logical_and( + list_sigma.to(device) <= SIGMA_THRESHOLD.to(device), + list_u >= U_THRESHOLD.to(device), + ) -def _get_large_sigma_mask(list_u: torch.Tensor, list_sigma: torch.Tensor) -> torch.Tensor: +def _get_large_sigma_mask( + list_u: torch.Tensor, list_sigma: torch.Tensor +) -> torch.Tensor: """Get the boolean mask for large sigma. Args: @@ -214,9 +232,7 @@ def _get_s1b_exponential( column_sigma = list_sigma.view(list_u.nelement(), 1) exponential = torch.exp( - -0.5 - * ((list_k**2 - 1.0) + 2.0 * column_u * (list_k + 1.0)) - / column_sigma**2 + -0.5 * ((list_k**2 - 1.0) + 2.0 * column_u * (list_k + 1.0)) / column_sigma**2 ) return exponential @@ -259,7 +275,9 @@ def _get_sigma_normalized_score_1a( list_sigma_normalized_score : the sigma x s1a scores, with shape [Nu]. """ exponential = _get_s1a_exponential(list_u, list_sigma, list_k) - list_sigma_square_times_score = _get_sigma_square_times_score_1_from_exponential(exponential, list_u, list_k) + list_sigma_square_times_score = _get_sigma_square_times_score_1_from_exponential( + exponential, list_u, list_k + ) list_normalized_score = list_sigma_square_times_score / list_sigma return list_normalized_score @@ -280,7 +298,9 @@ def _get_sigma_normalized_score_1b( list_sigma_normalized_score : the sigma x s1b scores, with shape [Nu]. """ exponential = _get_s1b_exponential(list_u, list_sigma, list_k) - list_sigma_square_times_score = _get_sigma_square_times_score_1_from_exponential(exponential, list_u, list_k) + list_sigma_square_times_score = _get_sigma_square_times_score_1_from_exponential( + exponential, list_u, list_k + ) list_normalized_score = list_sigma_square_times_score / list_sigma return list_normalized_score @@ -325,7 +345,14 @@ def _get_sigma_normalized_s2( # The sum is over Nk, leaving arrays of dimensions [Nu] z2 = exp_upk.sum(dim=1) + (g_exponential_combination * cos).sum(dim=1) - deriv_z2 = -2.0 * pi * ((upk * exp_upk).sum(dim=1) + (g * g_exponential_combination * sin).sum(dim=1)) + deriv_z2 = ( + -2.0 + * pi + * ( + (upk * exp_upk).sum(dim=1) + + (g * g_exponential_combination * sin).sum(dim=1) + ) + ) list_sigma_normalized_scores_s2 = list_sigma * deriv_z2 / z2 return list_sigma_normalized_scores_s2 diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/train_diffusion.py b/src/diffusion_for_multi_scale_molecular_dynamics/train_diffusion.py index cc301218..fb5c34b3 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/train_diffusion.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/train_diffusion.py @@ -1,4 +1,5 @@ """Entry point to train a diffusion model.""" + import argparse import logging import os @@ -8,20 +9,22 @@ import pytorch_lightning import pytorch_lightning as pl import yaml -from crystal_diffusion.callbacks.callback_loader import create_all_callbacks -from crystal_diffusion.data.diffusion.data_loader import ( + +from diffusion_for_multi_scale_molecular_dynamics.callbacks.callback_loader import \ + create_all_callbacks +from diffusion_for_multi_scale_molecular_dynamics.data.diffusion.data_loader import ( LammpsForDiffusionDataModule, LammpsLoaderParameters) -from crystal_diffusion.loggers.logger_loader import create_all_loggers -from crystal_diffusion.main_utils import (MetricResult, - get_crash_metric_result, - get_optimized_metric_name_and_mode, - load_and_backup_hyperparameters, - report_to_orion_if_on) -from crystal_diffusion.models.instantiate_diffusion_model import \ +from diffusion_for_multi_scale_molecular_dynamics.loggers.logger_loader import \ + create_all_loggers +from diffusion_for_multi_scale_molecular_dynamics.main_utils import ( + MetricResult, get_crash_metric_result, get_optimized_metric_name_and_mode, + load_and_backup_hyperparameters, report_to_orion_if_on) +from diffusion_for_multi_scale_molecular_dynamics.models.instantiate_diffusion_model import \ load_diffusion_model -from crystal_diffusion.utils.hp_utils import check_and_log_hp -from crystal_diffusion.utils.logging_utils import (log_exp_details, - setup_console_logger) +from diffusion_for_multi_scale_molecular_dynamics.utils.hp_utils import \ + check_and_log_hp +from diffusion_for_multi_scale_molecular_dynamics.utils.logging_utils import ( + log_exp_details, setup_console_logger) logger = logging.getLogger(__name__) @@ -35,20 +38,43 @@ def main(args: typing.Optional[typing.Any] = None): """ parser = argparse.ArgumentParser() # __TODO__ check you need all the following CLI parameters - parser.add_argument('--config', - help='config file with generic hyper-parameters, such as optimizer, ' - 'batch_size, ... - in yaml format') - parser.add_argument('--data', help='path to a LAMMPS data set', required=True) - parser.add_argument('--processed_datadir', help='path to the processed data directory', required=True) - parser.add_argument('--dataset_working_dir', help='path to the Datasets working directory. Defaults to None', - default=None) - parser.add_argument('--output', help='path to outputs - will store files here', required=True) - parser.add_argument('--disable-progressbar', action='store_true', - help='will disable the progressbar while going over the mini-batch') - parser.add_argument('--start-from-scratch', action='store_true', - help='will not load any existing saved model - even if present') - parser.add_argument('--accelerator', help='PL trainer accelerator. Defaults to auto.', default='auto') - parser.add_argument('--devices', default=1, help='pytorch-lightning devices kwarg. Defaults to 1.') + parser.add_argument( + "--config", + help="config file with generic hyper-parameters, such as optimizer, " + "batch_size, ... - in yaml format", + ) + parser.add_argument("--data", help="path to a LAMMPS data set", required=True) + parser.add_argument( + "--processed_datadir", + help="path to the processed data directory", + required=True, + ) + parser.add_argument( + "--dataset_working_dir", + help="path to the Datasets working directory. Defaults to None", + default=None, + ) + parser.add_argument( + "--output", help="path to outputs - will store files here", required=True + ) + parser.add_argument( + "--disable-progressbar", + action="store_true", + help="will disable the progressbar while going over the mini-batch", + ) + parser.add_argument( + "--start-from-scratch", + action="store_true", + help="will not load any existing saved model - even if present", + ) + parser.add_argument( + "--accelerator", + help="PL trainer accelerator. Defaults to auto.", + default="auto", + ) + parser.add_argument( + "--devices", default=1, help="pytorch-lightning devices kwarg. Defaults to 1." + ) args = parser.parse_args(args) if os.path.exists(args.output) and args.start_from_scratch: @@ -68,9 +94,14 @@ def main(args: typing.Optional[typing.Any] = None): output_dir = args.output - hyper_params = load_and_backup_hyperparameters(config_file_path=args.config, output_directory=output_dir) + hyper_params = load_and_backup_hyperparameters( + config_file_path=args.config, output_directory=output_dir + ) - logger.info("Input hyper-parameters:\n" + yaml.dump(hyper_params, allow_unicode=True, default_flow_style=False)) + logger.info( + "Input hyper-parameters:\n" + + yaml.dump(hyper_params, allow_unicode=True, default_flow_style=False) + ) run(args, output_dir, hyper_params) @@ -88,7 +119,7 @@ def run(args, output_dir, hyper_params): if hyper_params["seed"] is not None: pytorch_lightning.seed_everything(hyper_params["seed"]) - data_params = LammpsLoaderParameters(**hyper_params['data']) + data_params = LammpsLoaderParameters(**hyper_params["data"]) datamodule = LammpsForDiffusionDataModule( lammps_run_dir=args.data, @@ -100,13 +131,15 @@ def run(args, output_dir, hyper_params): model = load_diffusion_model(hyper_params) try: - metric_result = train(model=model, - datamodule=datamodule, - output=output_dir, - hyper_params=hyper_params, - use_progress_bar=not args.disable_progressbar, - accelerator=args.accelerator, - devices=args.devices) + metric_result = train( + model=model, + datamodule=datamodule, + output=output_dir, + hyper_params=hyper_params, + use_progress_bar=not args.disable_progressbar, + accelerator=args.accelerator, + devices=args.devices, + ) run_time_error = None except RuntimeError as err: run_time_error = err @@ -119,14 +152,15 @@ def run(args, output_dir, hyper_params): report_to_orion_if_on(metric_result, run_time_error) -def train(model, - datamodule, - output: str, - hyper_params: typing.Dict[typing.AnyStr, typing.Any], - use_progress_bar: bool, - accelerator=None, - devices=None - ): +def train( + model, + datamodule, + output: str, + hyper_params: typing.Dict[typing.AnyStr, typing.Any], + use_progress_bar: bool, + accelerator=None, + devices=None, +): """Train a model: main training loop implementation. Args: @@ -138,46 +172,50 @@ def train(model, accelerator: PL trainer accelerator devices: PL devices to use """ - check_and_log_hp(['max_epoch'], hyper_params) + check_and_log_hp(["max_epoch"], hyper_params) - callbacks_dict = create_all_callbacks(hyper_params, output, verbose=use_progress_bar) + callbacks_dict = create_all_callbacks( + hyper_params, output, verbose=use_progress_bar + ) pl_loggers = create_all_loggers(hyper_params, output) for pl_logger in pl_loggers: pl_logger.log_hyperparams(hyper_params) trainer = pl.Trainer( callbacks=list(callbacks_dict.values()), - max_epochs=hyper_params['max_epoch'], - log_every_n_steps=hyper_params.get('log_every_n_steps', None), - fast_dev_run=hyper_params.get('fast_dev_run', False), + max_epochs=hyper_params["max_epoch"], + log_every_n_steps=hyper_params.get("log_every_n_steps", None), + fast_dev_run=hyper_params.get("fast_dev_run", False), accelerator=accelerator, devices=devices, logger=pl_loggers, - gradient_clip_val=hyper_params.get('gradient_clipping', 0), - accumulate_grad_batches=hyper_params.get('accumulate_grad_batches', 1), + gradient_clip_val=hyper_params.get("gradient_clipping", 0), + accumulate_grad_batches=hyper_params.get("accumulate_grad_batches", 1), ) # Using the keyword ckpt_path="last" tells the trainer to resume from the last # checkpoint, or to start from scratch if none exists. - trainer.fit(model, datamodule=datamodule, ckpt_path='last') + trainer.fit(model, datamodule=datamodule, ckpt_path="last") # By convention, it is assumed that the metric to be reported is the early stopping metric. - if 'early_stopping' in callbacks_dict: - early_stopping = callbacks_dict['early_stopping'] + if "early_stopping" in callbacks_dict: + early_stopping = callbacks_dict["early_stopping"] best_value = float(early_stopping.best_score.cpu().numpy()) metric_name, mode = get_optimized_metric_name_and_mode(hyper_params) for pl_logger in pl_loggers: pl_logger.log_metrics({f"best_{metric_name}": best_value}) - metric_result = MetricResult(report=True, metric_name=metric_name, mode=mode, metric_value=best_value) + metric_result = MetricResult( + report=True, metric_name=metric_name, mode=mode, metric_value=best_value + ) else: metric_result = MetricResult(report=False) return metric_result -if __name__ == '__main__': +if __name__ == "__main__": # Uncomment the following in order to use Pycharm's Remote Debugging server, which allows to # launch python commands through a bash script (and through Orion!). VERY useful for debugging. # This requires a professional edition of Pycharm and installing the pydevd_pycharm package with pip. diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/utils/basis_transformations.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/basis_transformations.py index 45d5f704..3eaeabf3 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/utils/basis_transformations.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/utils/basis_transformations.py @@ -26,7 +26,9 @@ def get_reciprocal_basis_vectors(basis_vectors: torch.Tensor) -> torch.Tensor: return reciprocal_basis_vectors -def get_positions_from_coordinates(relative_coordinates: torch.Tensor, basis_vectors: torch.Tensor) -> torch.Tensor: +def get_positions_from_coordinates( + relative_coordinates: torch.Tensor, basis_vectors: torch.Tensor +) -> torch.Tensor: """Get cartesian positions from relative coordinates. This method computes the positions in Euclidean space given the unitless coordinates and the basis vectors @@ -50,8 +52,9 @@ def get_positions_from_coordinates(relative_coordinates: torch.Tensor, basis_vec return cartesian_positions -def get_relative_coordinates_from_cartesian_positions(cartesian_positions: torch.Tensor, - reciprocal_basis_vectors: torch.Tensor) -> torch.Tensor: +def get_relative_coordinates_from_cartesian_positions( + cartesian_positions: torch.Tensor, reciprocal_basis_vectors: torch.Tensor +) -> torch.Tensor: """Get relative coordinates from cartesian positions. This method computes the relative coordinates from the positions in Euclidean space and the reciprocal @@ -84,7 +87,9 @@ def get_relative_coordinates_from_cartesian_positions(cartesian_positions: torch return relative_coordinates -def map_relative_coordinates_to_unit_cell(relative_coordinates: torch.Tensor) -> torch.Tensor: +def map_relative_coordinates_to_unit_cell( + relative_coordinates: torch.Tensor, +) -> torch.Tensor: """Map relative coordinates back to unit cell. The function torch.remainder does not always bring back the relative coordinates in the range [0, 1). @@ -105,5 +110,5 @@ def map_relative_coordinates_to_unit_cell(relative_coordinates: torch.Tensor) -> normalized_relative_coordinates: relative coordinates in the unit cell, ie, in the range [0, 1). """ normalized_relative_coordinates = torch.remainder(relative_coordinates, 1.0) - normalized_relative_coordinates[normalized_relative_coordinates == 1.] = 0. + normalized_relative_coordinates[normalized_relative_coordinates == 1.0] = 0.0 return normalized_relative_coordinates diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/utils/configuration_parsing.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/configuration_parsing.py index 5acccff0..23ab4e2a 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/utils/configuration_parsing.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/utils/configuration_parsing.py @@ -2,9 +2,9 @@ from typing import Any, Dict -def create_parameters_from_configuration_dictionary(configuration: Dict[str, Any], - identifier: str, - options: Dict[str, dataclass]) -> dataclass: +def create_parameters_from_configuration_dictionary( + configuration: Dict[str, Any], identifier: str, options: Dict[str, dataclass] +) -> dataclass: """Create Parameters from Configuration Dictionary. This method will instantiate a dataclass container describing configuration parameters @@ -21,13 +21,15 @@ def create_parameters_from_configuration_dictionary(configuration: Dict[str, Any parameters: a dataclass object of the appropriate type, instantiated with the content of the input configuration """ - assert identifier in configuration.keys(), \ - f"The identifier field '{identifier}' is missing from the configuration dictionary." + assert ( + identifier in configuration.keys() + ), f"The identifier field '{identifier}' is missing from the configuration dictionary." option_id = configuration[identifier] - assert option_id in options.keys(), \ - f"The option field '{option_id}' is missing from the options dictionary." + assert ( + option_id in options.keys() + ), f"The option field '{option_id}' is missing from the options dictionary." parameters = options[option_id](**configuration) return parameters diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/utils/file_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/file_utils.py index ead16538..07f95357 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/utils/file_utils.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/utils/file_utils.py @@ -14,5 +14,5 @@ def rsync_folder(source, target): # pragma: no cover if not os.path.exists(target): os.makedirs(target) - logger.info('rsyincing {} to {}'.format(source, target)) + logger.info("rsyincing {} to {}".format(source, target)) subprocess.check_call(["rsync", "-avzq", source, target]) diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/utils/hp_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/hp_utils.py index ed2433c3..b3c11862 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/utils/hp_utils.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/utils/hp_utils.py @@ -31,11 +31,11 @@ def check_hp(names, hps, allow_extra=True): msgs = [] if len(missing) > 0: - msgs.append(f'please add the missing hyper-parameters: {missing}') + msgs.append(f"please add the missing hyper-parameters: {missing}") if len(extra) > 0 and not allow_extra: - msgs.append(f'please remove the extra hyper-parameters: {extra}') + msgs.append(f"please remove the extra hyper-parameters: {extra}") if len(msgs) > 0: - raise ValueError('\n'.join(msgs)) + raise ValueError("\n".join(msgs)) def log_hp(names, hps): # pragma: no cover @@ -47,4 +47,4 @@ def log_hp(names, hps): # pragma: no cover """ for name in sorted(names): logger.info('\thp "{}" => "{}"'.format(name, hps[name])) - logger.info('\n') + logger.info("\n") diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/utils/logging_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/logging_utils.py index de6b3ceb..b1a5ecfe 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/utils/logging_utils.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/utils/logging_utils.py @@ -26,7 +26,9 @@ def setup_console_logger(experiment_dir: str): """ logging.captureWarnings(capture=True) - logging_format = "%(asctime)s - %(filename)s:%(lineno)s - %(funcName)20s() - %(message)s" + logging_format = ( + "%(asctime)s - %(filename)s:%(lineno)s - %(funcName)20s() - %(message)s" + ) root = logging.getLogger() root.setLevel(logging.INFO) @@ -54,7 +56,9 @@ def setup_analysis_logger(): root = logging.getLogger() root.setLevel(logging.INFO) - analysis_logging_format = "%(asctime)s - %(filename)s:%(lineno)s - %(funcName)20s() - %(message)s" + analysis_logging_format = ( + "%(asctime)s - %(filename)s:%(lineno)s - %(funcName)20s() - %(message)s" + ) formatter = logging.Formatter(analysis_logging_format) stream_handler = logging.StreamHandler(stream=sys.stdout) @@ -84,7 +88,7 @@ def write(self, message): Args: message: (str) message to print. """ - if message != '\n': + if message != "\n": self.printer(message) def flush(self): @@ -98,14 +102,14 @@ def get_git_hash(script_location): :param script_location: (str) path to the script inside the git repos we want to find. :return: (str) the git hash for the repository of the provided script. """ - if not script_location.endswith('.py'): - raise ValueError('script_location should point to a python script') + if not script_location.endswith(".py"): + raise ValueError("script_location should point to a python script") repo_folder = os.path.dirname(script_location) try: repo = Repo(repo_folder, search_parent_directories=True) commit_hash = repo.head.commit except (InvalidGitRepositoryError, ValueError): - commit_hash = 'git repository not found' + commit_hash = "git repository not found" return commit_hash @@ -118,8 +122,14 @@ def log_exp_details(script_location, args): git_hash = get_git_hash(script_location) hostname = socket.gethostname() dependencies = freeze.freeze() - details = "\nhostname: {}\ngit code hash: {}\ndata folder: {}\ndata folder (abs): {}\n\n" \ - "dependencies:\n{}".format( - hostname, git_hash, args.data, os.path.abspath(args.data), - '\n'.join(dependencies)) - logger.info('Experiment info:' + details + '\n') + details = ( + "\nhostname: {}\ngit code hash: {}\ndata folder: {}\ndata folder (abs): {}\n\n" + "dependencies:\n{}".format( + hostname, + git_hash, + args.data, + os.path.abspath(args.data), + "\n".join(dependencies), + ) + ) + logger.info("Experiment info:" + details + "\n") diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/utils/neighbors.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/neighbors.py index 7fdd16bc..5b652d3d 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/utils/neighbors.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/utils/neighbors.py @@ -4,31 +4,36 @@ for positions in the unit cell of a periodic structure. The aim is to do this efficiently on the GPU without CPU-GPU communications. """ + import itertools from collections import namedtuple import numpy as np import torch -from crystal_diffusion.utils.basis_transformations import \ - get_positions_from_coordinates from pykeops.torch import LazyTensor +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ + get_positions_from_coordinates + INDEX_PADDING_VALUE = -1 POSITION_PADDING_VALUE = np.NaN -AdjacencyInfo = namedtuple(typename="AdjacencyInfo", - field_names=["adjacency_matrix", # Adjacency matrix, in COO format - "shifts", # lattice vector shifts when computing neighbor distances - "edge_batch_indices", # original batch index of each edge. - "node_batch_indices", # original batch index of each node. - "number_of_edges", # number of edges in each batch element. - ]) +AdjacencyInfo = namedtuple( + typename="AdjacencyInfo", + field_names=[ + "adjacency_matrix", # Adjacency matrix, in COO format + "shifts", # lattice vector shifts when computing neighbor distances + "edge_batch_indices", # original batch index of each edge. + "node_batch_indices", # original batch index of each node. + "number_of_edges", # number of edges in each batch element. + ], +) -def get_periodic_adjacency_information(cartesian_positions: torch.Tensor, - basis_vectors: torch.Tensor, - radial_cutoff: float) -> AdjacencyInfo: +def get_periodic_adjacency_information( + cartesian_positions: torch.Tensor, basis_vectors: torch.Tensor, radial_cutoff: float +) -> AdjacencyInfo: """Get periodic adjacency information. This method computes all the neighbors within the radial cutoff, accounting for periodicity, and returns @@ -73,15 +78,23 @@ def get_periodic_adjacency_information(cartesian_positions: torch.Tensor, * the batch indices for each node * the number of edges for each structure in the batch. """ - assert len(cartesian_positions.shape) == 3, "Wrong number of dimensions for relative_coordinates" + assert ( + len(cartesian_positions.shape) == 3 + ), "Wrong number of dimensions for relative_coordinates" assert len(basis_vectors.shape) == 3, "Wrong number of dimensions for basis_vectors" spatial_dimension = 3 # We define this to avoid "magic numbers" in the code below. batch_size, max_natom, spatial_dimension_ = cartesian_positions.shape - assert spatial_dimension_ == spatial_dimension, "Wrong spatial dimension for relative_coordinates " - assert basis_vectors.shape == (batch_size, spatial_dimension, spatial_dimension), "Wrong shape for basis vectors" + assert ( + spatial_dimension_ == spatial_dimension + ), "Wrong spatial dimension for relative_coordinates " + assert basis_vectors.shape == ( + batch_size, + spatial_dimension, + spatial_dimension, + ), "Wrong shape for basis vectors" - assert radial_cutoff > 0., "The radial cutoff should be greater than zero" + assert radial_cutoff > 0.0, "The radial cutoff should be greater than zero" device = cartesian_positions.device @@ -89,19 +102,26 @@ def get_periodic_adjacency_information(cartesian_positions: torch.Tensor, zero = torch.tensor(0.0).to(device) # Check that the radial cutoff does not lead to possible neighbors beyond the first shell. - shortest_cell_crossing_distances = _get_shortest_distance_that_crosses_unit_cell(basis_vectors) - assert torch.all(shortest_cell_crossing_distances > radial_cutoff), \ - ("The radial cutoff is so large that neighbors could be located " - "beyond the first shell of periodic unit cell images.") + shortest_cell_crossing_distances = _get_shortest_distance_that_crosses_unit_cell( + basis_vectors + ) + assert torch.all(shortest_cell_crossing_distances > radial_cutoff), ( + "The radial cutoff is so large that neighbors could be located " + "beyond the first shell of periodic unit cell images." + ) # The relative coordinates lattice vectors have dimensions [number of lattice vectors, spatial_dimension] - relative_lattice_vectors = _get_relative_coordinates_lattice_vectors(number_of_shells=1).to(device) + relative_lattice_vectors = _get_relative_coordinates_lattice_vectors( + number_of_shells=1 + ).to(device) number_of_relative_lattice_vectors = len(relative_lattice_vectors) # Repeat the relative lattice vectors along the batch dimension; the basis vectors could potentially be # different for every batch element. batched_relative_lattice_vectors = relative_lattice_vectors.repeat(batch_size, 1, 1) - lattice_vectors = get_positions_from_coordinates(batched_relative_lattice_vectors, basis_vectors) + lattice_vectors = get_positions_from_coordinates( + batched_relative_lattice_vectors, basis_vectors + ) # The shifted_positions are composed of the positions, which are located within the unit cell, shifted by # the various lattice vectors. @@ -130,9 +150,18 @@ def get_periodic_adjacency_information(cartesian_positions: torch.Tensor, # # From the point of view of KeOps, the first 2 dimensions are "batch dimensions". The KeOps 'virtual' dimensions # are dim 2 and 3, which both corresponds to 'max_natom'. - x_i = LazyTensor(cartesian_positions.view(batch_size, 1, max_natom, 1, spatial_dimension)) - x_j = LazyTensor(shifted_positions.view(batch_size, number_of_relative_lattice_vectors, 1, - max_natom, spatial_dimension)) + x_i = LazyTensor( + cartesian_positions.view(batch_size, 1, max_natom, 1, spatial_dimension) + ) + x_j = LazyTensor( + shifted_positions.view( + batch_size, + number_of_relative_lattice_vectors, + 1, + max_natom, + spatial_dimension, + ) + ) # Symbolic matrix of squared distances # Dimensions: [batch_size, number_of_relative_lattice_vectors, max_natom, max_natom] @@ -141,20 +170,26 @@ def get_periodic_adjacency_information(cartesian_positions: torch.Tensor, # Identify the number of neighbors within the cutoff distance for every atom. # This triggers a real computation, which involves a 'compilation' the first time around. # This compilation time is only paid once per code execution. - max_k_array = (d_ij <= radial_cutoff**2).sum_reduction(dim=3) # sum on "j", the second 'virtual' dimension. + max_k_array = (d_ij <= radial_cutoff**2).sum_reduction( + dim=3 + ) # sum on "j", the second 'virtual' dimension. # This is the maximum number of neighbors for any atom in any structure in the batch. # Going forward, there is no need to look beyond this number of neighbors. max_number_of_neighbors = int(max_k_array.max()) # Use KeOps KNN functionalities to find neighbors and their indices. - squared_distances, dst_indices = d_ij.Kmin_argKmin(K=max_number_of_neighbors, dim=3) # find neighbors along "j" + squared_distances, dst_indices = d_ij.Kmin_argKmin( + K=max_number_of_neighbors, dim=3 + ) # find neighbors along "j" # Dimensions: [batch_size, number_of_relative_lattice_vectors, max_natom, max_number_of_neighbors] # The 'dst_indices' array corresponds to KeOps first 'virtual' dimension (the "i" dimension). This goes from # 0 to max_atom - 1 and correspond to atom indices (specifically, destination indices!). # Identify neighbors within the radial_cutoff, but avoiding self. - valid_neighbor_mask = torch.logical_and(zero < squared_distances, squared_distances <= radial_cutoff**2) + valid_neighbor_mask = torch.logical_and( + zero < squared_distances, squared_distances <= radial_cutoff**2 + ) # Dimensions: [batch_size, number_of_relative_lattice_vectors, max_natom, max_number_of_neighbors] # Combine all the non-batch dimensions to obtain the maximum number of edges per batch element @@ -174,19 +209,22 @@ def get_periodic_adjacency_information(cartesian_positions: torch.Tensor, adjacency_matrix_coo_format = torch.stack([source_indices, destination_indices]) - lattice_vector_shifts = _get_vectors_from_multiple_indices(lattice_vectors, - edge_batch_indices, - lattice_vector_indices) + lattice_vector_shifts = _get_vectors_from_multiple_indices( + lattice_vectors, edge_batch_indices, lattice_vector_indices + ) - return AdjacencyInfo(adjacency_matrix=adjacency_matrix_coo_format, - shifts=lattice_vector_shifts, - edge_batch_indices=edge_batch_indices, - node_batch_indices=torch.repeat_interleave(torch.arange(batch_size), max_natom), - number_of_edges=number_of_edges) + return AdjacencyInfo( + adjacency_matrix=adjacency_matrix_coo_format, + shifts=lattice_vector_shifts, + edge_batch_indices=edge_batch_indices, + node_batch_indices=torch.repeat_interleave(torch.arange(batch_size), max_natom), + number_of_edges=number_of_edges, + ) -def _get_relative_coordinates_lattice_vectors(number_of_shells: int = 1, - spatial_dimension: int = 3) -> torch.Tensor: +def _get_relative_coordinates_lattice_vectors( + number_of_shells: int = 1, spatial_dimension: int = 3 +) -> torch.Tensor: """Get relative coordinates lattice vectors. Get all the lattice vectors in relative coordinates from -number_of_shells to +number_of_shells, @@ -199,12 +237,16 @@ def _get_relative_coordinates_lattice_vectors(number_of_shells: int = 1, list_relative_lattice_vectors : all the lattice vectors in relative coordinates (ie, integers). """ shifts = range(-number_of_shells, number_of_shells + 1) - list_relative_lattice_vectors = 1.0 * torch.tensor(list(itertools.product(shifts, repeat=spatial_dimension))) + list_relative_lattice_vectors = 1.0 * torch.tensor( + list(itertools.product(shifts, repeat=spatial_dimension)) + ) return list_relative_lattice_vectors -def _get_shifted_positions(cartesian_positions: torch.Tensor, lattice_vectors: torch.Tensor) -> torch.Tensor: +def _get_shifted_positions( + cartesian_positions: torch.Tensor, lattice_vectors: torch.Tensor +) -> torch.Tensor: """Get shifted positions. Args: @@ -217,11 +259,15 @@ def _get_shifted_positions(cartesian_positions: torch.Tensor, lattice_vectors: t shifted_positions: the positions within the unit cell, shifted by the lattice vectors. Dimension [batch_size, number of relative lattice vectors, max_number_of_atoms, 3]. """ - shifted_positions = cartesian_positions[:, None, :, :] + lattice_vectors[:, :, None, :] + shifted_positions = ( + cartesian_positions[:, None, :, :] + lattice_vectors[:, :, None, :] + ) return shifted_positions -def _get_shortest_distance_that_crosses_unit_cell(basis_vectors: torch.Tensor) -> torch.Tensor: +def _get_shortest_distance_that_crosses_unit_cell( + basis_vectors: torch.Tensor, +) -> torch.Tensor: """Get the shortest distance that crosses unit cell. This method computes the shortest distance that crosses the unit cell. @@ -250,8 +296,12 @@ def _get_shortest_distance_that_crosses_unit_cell(basis_vectors: torch.Tensor) - # This idea must be repeated for the three pairs of planes bounding the unit cell. spatial_dimension = 3 assert len(basis_vectors.shape) == 3, "basis_vectors has wrong shape." - assert basis_vectors.shape[1] == spatial_dimension, "Basis vectors in wrong spatial dimension." - assert basis_vectors.shape[2] == spatial_dimension, "Basis vectors in wrong spatial dimension." + assert ( + basis_vectors.shape[1] == spatial_dimension + ), "Basis vectors in wrong spatial dimension." + assert ( + basis_vectors.shape[2] == spatial_dimension + ), "Basis vectors in wrong spatial dimension." a1 = basis_vectors[:, 0, :] a2 = basis_vectors[:, 1, :] a3 = basis_vectors[:, 2, :] @@ -266,14 +316,16 @@ def _get_shortest_distance_that_crosses_unit_cell(basis_vectors: torch.Tensor) - distances_13 = cell_volume / torch.linalg.norm(cross_product_13, dim=1) distances_23 = cell_volume / torch.linalg.norm(cross_product_23, dim=1) - distances = torch.stack([distances_12, distances_13, distances_23], dim=1).min(dim=1).values + distances = ( + torch.stack([distances_12, distances_13, distances_23], dim=1).min(dim=1).values + ) return distances -def _get_vectors_from_multiple_indices(vectors: torch.Tensor, - batch_indices: torch.Tensor, - vector_indices: torch.Tensor) -> torch.Tensor: +def _get_vectors_from_multiple_indices( + vectors: torch.Tensor, batch_indices: torch.Tensor, vector_indices: torch.Tensor +) -> torch.Tensor: """Get vectors from multiple indices. Extract vectors in higher dimensional tensor from multiple indices array. @@ -298,11 +350,13 @@ def _get_vectors_from_multiple_indices(vectors: torch.Tensor, return vectors[batch_indices, vector_indices] -def shift_adjacency_matrix_indices_for_graph_batching(adjacency_matrix: torch.Tensor, - num_edges: torch.Tensor, - number_of_atoms: int) -> torch.Tensor: +def shift_adjacency_matrix_indices_for_graph_batching( + adjacency_matrix: torch.Tensor, num_edges: torch.Tensor, number_of_atoms: int +) -> torch.Tensor: """Shift the node indices in the adjacency matrix for graph batching.""" - index_shifts = torch.arange(len(num_edges)).to(adjacency_matrix.device) * number_of_atoms + index_shifts = ( + torch.arange(len(num_edges)).to(adjacency_matrix.device) * number_of_atoms + ) adj_shifts = torch.repeat_interleave(index_shifts, num_edges).repeat(2, 1) return adjacency_matrix + adj_shifts diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/utils/ovito_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/ovito_utils.py index 16dc76b5..df72a18e 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/utils/ovito_utils.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/utils/ovito_utils.py @@ -5,6 +5,7 @@ session state file will already be prepopulated with some common pipeline elements. """ + from pathlib import Path import numpy as np diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/utils/sample_trajectory.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/sample_trajectory.py index c642a235..82407554 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/utils/sample_trajectory.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/utils/sample_trajectory.py @@ -22,7 +22,7 @@ def reset(self): def record_unit_cell(self, unit_cell: torch.Tensor): """Record unit cell.""" - self.data['unit_cell'] = unit_cell.detach().cpu() + self.data["unit_cell"] = unit_cell.detach().cpu() def standardize_data(self, data: Dict[AnyStr, Any]) -> Dict[AnyStr, Any]: """Method to transform the recorded data to a standard form.""" @@ -31,7 +31,7 @@ def standardize_data(self, data: Dict[AnyStr, Any]) -> Dict[AnyStr, Any]: def write_to_pickle(self, path_to_pickle: str): """Write standardized data to pickle file.""" standard_data = self.standardize_data(self.data) - with open(path_to_pickle, 'wb') as fd: + with open(path_to_pickle, "wb") as fd: torch.save(standard_data, fd) @@ -42,26 +42,34 @@ class ODESampleTrajectory(SampleTrajectory): an artifact that can then be analyzed off-line. """ - def record_ode_solution(self, times: torch.Tensor, sigmas: torch.Tensor, - relative_coordinates: torch.Tensor, normalized_scores: torch.Tensor, - stats: Dict, status: torch.Tensor): + def record_ode_solution( + self, + times: torch.Tensor, + sigmas: torch.Tensor, + relative_coordinates: torch.Tensor, + normalized_scores: torch.Tensor, + stats: Dict, + status: torch.Tensor, + ): """Record ODE solution information.""" - self.data['time'].append(times) - self.data['sigma'].append(sigmas) - self.data['relative_coordinates'].append(relative_coordinates) - self.data['normalized_scores'].append(normalized_scores) - self.data['stats'].append(stats) - self.data['status'].append(status) + self.data["time"].append(times) + self.data["sigma"].append(sigmas) + self.data["relative_coordinates"].append(relative_coordinates) + self.data["normalized_scores"].append(normalized_scores) + self.data["stats"].append(stats) + self.data["status"].append(status) def standardize_data(self, data: Dict[AnyStr, Any]) -> Dict[AnyStr, Any]: """Method to transform the recorded data to a standard form.""" - extra_fields = ['stats', 'status'] - standardized_data = dict(unit_cell=data['unit_cell'], - time=data['time'][0], - sigma=data['sigma'][0], - relative_coordinates=data['relative_coordinates'][0], - normalized_scores=data['normalized_scores'][0], - extra={key: data[key][0] for key in extra_fields}) + extra_fields = ["stats", "status"] + standardized_data = dict( + unit_cell=data["unit_cell"], + time=data["time"][0], + sigma=data["sigma"][0], + relative_coordinates=data["relative_coordinates"][0], + normalized_scores=data["normalized_scores"][0], + extra={key: data[key][0] for key in extra_fields}, + ) return standardized_data @@ -72,22 +80,28 @@ class SDESampleTrajectory(SampleTrajectory): an artifact that can then be analyzed off-line. """ - def record_sde_solution(self, times: torch.Tensor, sigmas: torch.Tensor, - relative_coordinates: torch.Tensor, normalized_scores: torch.Tensor): + def record_sde_solution( + self, + times: torch.Tensor, + sigmas: torch.Tensor, + relative_coordinates: torch.Tensor, + normalized_scores: torch.Tensor, + ): """Record ODE solution information.""" - self.data['time'].append(times) - self.data['sigma'].append(sigmas) - self.data['relative_coordinates'].append(relative_coordinates) - self.data['normalized_scores'].append(normalized_scores) + self.data["time"].append(times) + self.data["sigma"].append(sigmas) + self.data["relative_coordinates"].append(relative_coordinates) + self.data["normalized_scores"].append(normalized_scores) def standardize_data(self, data: Dict[AnyStr, Any]) -> Dict[AnyStr, Any]: """Method to transform the recorded data to a standard form.""" - standardized_data = dict(unit_cell=data['unit_cell'], - time=data['time'][0], - sigma=data['sigma'][0], - relative_coordinates=data['relative_coordinates'][0], - normalized_scores=data['normalized_scores'][0], - ) + standardized_data = dict( + unit_cell=data["unit_cell"], + time=data["time"][0], + sigma=data["sigma"][0], + relative_coordinates=data["relative_coordinates"][0], + normalized_scores=data["normalized_scores"][0], + ) return standardized_data @@ -98,9 +112,15 @@ def record_unit_cell(self, unit_cell: torch.Tensor): """No Op.""" return - def record_ode_solution(self, times: torch.Tensor, sigmas: torch.Tensor, - relative_coordinates: torch.Tensor, normalized_scores: torch.Tensor, - stats: Dict, status: torch.Tensor): + def record_ode_solution( + self, + times: torch.Tensor, + sigmas: torch.Tensor, + relative_coordinates: torch.Tensor, + normalized_scores: torch.Tensor, + stats: Dict, + status: torch.Tensor, + ): """No Op.""" return @@ -115,43 +135,70 @@ class PredictorCorrectorSampleTrajectory(SampleTrajectory): This class aims to record all details of the predictor-corrector diffusion sampling process. The goal is to produce an artifact that can then be analyzed off-line. """ - def record_predictor_step(self, i_index: int, time: float, sigma: float, - x_i: torch.Tensor, x_im1: torch.Tensor, scores: torch.Tensor): + + def record_predictor_step( + self, + i_index: int, + time: float, + sigma: float, + x_i: torch.Tensor, + x_im1: torch.Tensor, + scores: torch.Tensor, + ): """Record predictor step.""" - self.data['predictor_i_index'].append(i_index) - self.data['predictor_time'].append(time) - self.data['predictor_sigma'].append(sigma) - self.data['predictor_x_i'].append(x_i.detach().cpu()) - self.data['predictor_x_im1'].append(x_im1.detach().cpu()) - self.data['predictor_scores'].append(scores.detach().cpu()) - - def record_corrector_step(self, i_index: int, time: float, sigma: float, - x_i: torch.Tensor, corrected_x_i: torch.Tensor, scores: torch.Tensor): + self.data["predictor_i_index"].append(i_index) + self.data["predictor_time"].append(time) + self.data["predictor_sigma"].append(sigma) + self.data["predictor_x_i"].append(x_i.detach().cpu()) + self.data["predictor_x_im1"].append(x_im1.detach().cpu()) + self.data["predictor_scores"].append(scores.detach().cpu()) + + def record_corrector_step( + self, + i_index: int, + time: float, + sigma: float, + x_i: torch.Tensor, + corrected_x_i: torch.Tensor, + scores: torch.Tensor, + ): """Record corrector step.""" - self.data['corrector_i_index'].append(i_index) - self.data['corrector_time'].append(time) - self.data['corrector_sigma'].append(sigma) - self.data['corrector_x_i'].append(x_i.detach().cpu()) - self.data['corrector_corrected_x_i'].append(corrected_x_i.detach().cpu()) - self.data['corrector_scores'].append(scores.detach().cpu()) + self.data["corrector_i_index"].append(i_index) + self.data["corrector_time"].append(time) + self.data["corrector_sigma"].append(sigma) + self.data["corrector_x_i"].append(x_i.detach().cpu()) + self.data["corrector_corrected_x_i"].append(corrected_x_i.detach().cpu()) + self.data["corrector_scores"].append(scores.detach().cpu()) def standardize_data(self, data: Dict[AnyStr, Any]) -> Dict[AnyStr, Any]: """Method to transform the recorded data to a standard form.""" - predictor_relative_coordinates = einops.rearrange(torch.stack(data['predictor_x_i']), - "t b n d -> b t n d") - predictor_normalized_scores = einops.rearrange(torch.stack(data['predictor_scores']), - "t b n d -> b t n d") - - extra_fields = ['predictor_i_index', 'predictor_x_i', 'predictor_x_im1', - 'corrector_i_index', 'corrector_time', 'corrector_sigma', - 'corrector_x_i', 'corrector_corrected_x_i', 'corrector_scores'] - - standardized_data = dict(unit_cell=data['unit_cell'], - time=torch.tensor(data['predictor_time']), - sigma=torch.tensor(data['predictor_sigma']), - relative_coordinates=predictor_relative_coordinates, - normalized_scores=predictor_normalized_scores, - extra={key: data[key] for key in extra_fields}) + predictor_relative_coordinates = einops.rearrange( + torch.stack(data["predictor_x_i"]), "t b n d -> b t n d" + ) + predictor_normalized_scores = einops.rearrange( + torch.stack(data["predictor_scores"]), "t b n d -> b t n d" + ) + + extra_fields = [ + "predictor_i_index", + "predictor_x_i", + "predictor_x_im1", + "corrector_i_index", + "corrector_time", + "corrector_sigma", + "corrector_x_i", + "corrector_corrected_x_i", + "corrector_scores", + ] + + standardized_data = dict( + unit_cell=data["unit_cell"], + time=torch.tensor(data["predictor_time"]), + sigma=torch.tensor(data["predictor_sigma"]), + relative_coordinates=predictor_relative_coordinates, + normalized_scores=predictor_normalized_scores, + extra={key: data[key] for key in extra_fields}, + ) return standardized_data @@ -162,13 +209,27 @@ def record_unit_cell(self, unit_cell: torch.Tensor): """No Op.""" return - def record_predictor_step(self, i_index: int, time: float, sigma: float, - x_i: torch.Tensor, x_im1: torch.Tensor, scores: torch.Tensor): + def record_predictor_step( + self, + i_index: int, + time: float, + sigma: float, + x_i: torch.Tensor, + x_im1: torch.Tensor, + scores: torch.Tensor, + ): """No Op.""" return - def record_corrector_step(self, i_index: int, time: float, sigma: float, - x_i: torch.Tensor, corrected_x_i: torch.Tensor, scores: torch.Tensor): + def record_corrector_step( + self, + i_index: int, + time: float, + sigma: float, + x_i: torch.Tensor, + corrected_x_i: torch.Tensor, + scores: torch.Tensor, + ): """No Op.""" return diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/utils/structure_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/structure_utils.py index 7426bff3..0f74f229 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/utils/structure_utils.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/utils/structure_utils.py @@ -2,14 +2,17 @@ import numpy as np import torch -from crystal_diffusion.utils.neighbors import ( - _get_relative_coordinates_lattice_vectors, _get_shifted_positions, - get_periodic_adjacency_information, get_positions_from_coordinates) from pykeops.torch import LazyTensor from pymatgen.core import Lattice, Structure +from diffusion_for_multi_scale_molecular_dynamics.utils.neighbors import ( + _get_relative_coordinates_lattice_vectors, _get_shifted_positions, + get_periodic_adjacency_information, get_positions_from_coordinates) + -def create_structure(basis_vectors: np.ndarray, relative_coordinates: np.ndarray, species: List[str]) -> Structure: +def create_structure( + basis_vectors: np.ndarray, relative_coordinates: np.ndarray, species: List[str] +) -> Structure: """Create structure. A utility method to convert various arrays in to a pymatgen structure. @@ -24,15 +27,20 @@ def create_structure(basis_vectors: np.ndarray, relative_coordinates: np.ndarray """ lattice = Lattice(matrix=basis_vectors, pbc=(True, True, True)) - structure = Structure(lattice=lattice, - species=species, - coords=relative_coordinates, - coords_are_cartesian=False) + structure = Structure( + lattice=lattice, + species=species, + coords=relative_coordinates, + coords_are_cartesian=False, + ) return structure -def compute_distances_in_batch(cartesian_positions: torch.Tensor, unit_cell: torch.Tensor, max_distance: float, - ) -> torch.Tensor: +def compute_distances_in_batch( + cartesian_positions: torch.Tensor, + unit_cell: torch.Tensor, + max_distance: float, +) -> torch.Tensor: """Compute distances between atoms in a batch up to a cutoff distance. Args: @@ -52,23 +60,35 @@ def compute_distances_in_batch(cartesian_positions: torch.Tensor, unit_cell: tor zero = torch.tensor(0.0).to(device) # The relative coordinates lattice vectors have dimensions [number of lattice vectors, spatial_dimension] - relative_lattice_vectors = _get_relative_coordinates_lattice_vectors(number_of_shells=1, - spatial_dimension=spatial_dimension).to(device) + relative_lattice_vectors = _get_relative_coordinates_lattice_vectors( + number_of_shells=1, spatial_dimension=spatial_dimension + ).to(device) number_of_relative_lattice_vectors = len(relative_lattice_vectors) # Repeat the relative lattice vectors along the batch dimension; the basis vectors could potentially be # different for every batch element. batched_relative_lattice_vectors = relative_lattice_vectors.repeat(batch_size, 1, 1) - lattice_vectors = get_positions_from_coordinates(batched_relative_lattice_vectors, unit_cell) + lattice_vectors = get_positions_from_coordinates( + batched_relative_lattice_vectors, unit_cell + ) # The shifted_positions are composed of the positions, which are located within the unit cell, shifted by # the various lattice vectors. # Dimension [batch_size, number of relative lattice vectors, max_number_of_atoms, spatial_dimension]. shifted_positions = _get_shifted_positions(cartesian_positions, lattice_vectors) # KeOps will be used to compute the distance matrix, |p_i - p_j |^2, without overflowing memory. - x_i = LazyTensor(cartesian_positions.view(batch_size, 1, max_natom, 1, spatial_dimension)) - x_j = LazyTensor(shifted_positions.view(batch_size, number_of_relative_lattice_vectors, 1, - max_natom, spatial_dimension)) + x_i = LazyTensor( + cartesian_positions.view(batch_size, 1, max_natom, 1, spatial_dimension) + ) + x_j = LazyTensor( + shifted_positions.view( + batch_size, + number_of_relative_lattice_vectors, + 1, + max_natom, + spatial_dimension, + ) + ) # Symbolic matrix of squared distances d_ij = ((x_i - x_j) ** 2).sum(dim=4) # sum on the spatial_dimension variable. @@ -76,24 +96,32 @@ def compute_distances_in_batch(cartesian_positions: torch.Tensor, unit_cell: tor # Identify the number of neighbors within the cutoff distance for every atom. # This triggers a real computation, which involves a 'compilation' the first time around. # This compilation time is only paid once per code execution. - max_k_array = (d_ij <= radial_cutoff ** 2).sum_reduction(dim=3) # sum on "j", the second 'virtual' dimension. + max_k_array = (d_ij <= radial_cutoff**2).sum_reduction( + dim=3 + ) # sum on "j", the second 'virtual' dimension. # This is the maximum number of neighbors for any atom in any structure in the batch. # Going forward, there is no need to look beyond this number of neighbors. max_number_of_neighbors = int(max_k_array.max()) # Use KeOps KNN functionalities to find neighbors and their indices. - squared_distances, dst_indices = d_ij.Kmin_argKmin(K=max_number_of_neighbors, dim=3) # find neighbors along "j" + squared_distances, dst_indices = d_ij.Kmin_argKmin( + K=max_number_of_neighbors, dim=3 + ) # find neighbors along "j" # Dimensions: [batch_size, number_of_relative_lattice_vectors, max_natom, max_number_of_neighbors] # The 'dst_indices' array corresponds to KeOps first 'virtual' dimension (the "i" dimension). This goes from # 0 to max_atom - 1 and correspond to atom indices (specifically, destination indices!). distances = torch.sqrt(squared_distances.flatten()) # Identify neighbors within the radial_cutoff, but avoiding self. - valid_neighbor_mask = torch.logical_and(zero < distances, distances <= radial_cutoff) + valid_neighbor_mask = torch.logical_and( + zero < distances, distances <= radial_cutoff + ) return distances[valid_neighbor_mask] -def get_orthogonal_basis_vectors(batch_size: int, cell_dimensions: List[float]) -> torch.Tensor: +def get_orthogonal_basis_vectors( + batch_size: int, cell_dimensions: List[float] +) -> torch.Tensor: """Get orthogonal basis vectors. Args: @@ -103,13 +131,19 @@ def get_orthogonal_basis_vectors(batch_size: int, cell_dimensions: List[float]) Returns: basis_vectors: a diagonal matrix with the dimensions along the diagonal. """ - basis_vectors = torch.diag(torch.Tensor(cell_dimensions)).unsqueeze(0).repeat(batch_size, 1, 1) + basis_vectors = ( + torch.diag(torch.Tensor(cell_dimensions)).unsqueeze(0).repeat(batch_size, 1, 1) + ) return basis_vectors -def compute_distances(cartesian_positions: torch.Tensor, basis_vectors: torch.Tensor, max_distance: float): +def compute_distances( + cartesian_positions: torch.Tensor, basis_vectors: torch.Tensor, max_distance: float +): """Compute distances.""" - adj_info = get_periodic_adjacency_information(cartesian_positions, basis_vectors, radial_cutoff=max_distance) + adj_info = get_periodic_adjacency_information( + cartesian_positions, basis_vectors, radial_cutoff=max_distance + ) # The following are 1D arrays of length equal to the total number of neighbors for all batch elements # and all atoms. @@ -119,7 +153,9 @@ def compute_distances(cartesian_positions: torch.Tensor, basis_vectors: torch.Te bch = adj_info.edge_batch_indices src, dst = adj_info.adjacency_matrix - cartesian_displacements = cartesian_positions[bch, dst] - cartesian_positions[bch, src] + adj_info.shifts + cartesian_displacements = ( + cartesian_positions[bch, dst] - cartesian_positions[bch, src] + adj_info.shifts + ) distances = torch.linalg.norm(cartesian_displacements, dim=-1) # Identify neighbors within the radial_cutoff, but avoiding self. return distances[distances > 0.0] diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/utils/tensor_utils.py b/src/diffusion_for_multi_scale_molecular_dynamics/utils/tensor_utils.py index 813478c1..92b89e67 100644 --- a/src/diffusion_for_multi_scale_molecular_dynamics/utils/tensor_utils.py +++ b/src/diffusion_for_multi_scale_molecular_dynamics/utils/tensor_utils.py @@ -3,7 +3,9 @@ import torch -def broadcast_batch_tensor_to_all_dimensions(batch_values: torch.Tensor, final_shape: Tuple[int, ...]) -> torch.Tensor: +def broadcast_batch_tensor_to_all_dimensions( + batch_values: torch.Tensor, final_shape: Tuple[int, ...] +) -> torch.Tensor: """Broadcast batch tensor to all dimensions. A data batch is typically a tensor of shape [batch_size, n1, n2, ...] where n1, n2, etc constitute @@ -21,10 +23,14 @@ def broadcast_batch_tensor_to_all_dimensions(batch_values: torch.Tensor, final_s broadcast_values : tensor of shape [batch_size, n1, n2, ...], where all entries are identical along non-batch dimensions. """ - assert len(batch_values.shape) == 1, "The batch values should be a one-dimensional tensor." + assert ( + len(batch_values.shape) == 1 + ), "The batch values should be a one-dimensional tensor." batch_size = len(batch_values) - assert final_shape[0] == batch_size, "The final shape should have the batch_size as its first dimension." + assert ( + final_shape[0] == batch_size + ), "The final shape should have the batch_size as its first dimension." # reshape the batch_values array to have the same dimension as final_shape, with all values identical # for a given batch index. diff --git a/tests/active_learning_loop/test_benchmark.py b/tests/active_learning_loop/test_benchmark.py index b23579cc..e617661b 100644 --- a/tests/active_learning_loop/test_benchmark.py +++ b/tests/active_learning_loop/test_benchmark.py @@ -3,7 +3,9 @@ import pandas as pd import pytest -from crystal_diffusion.active_learning_loop.benchmark import ActiveLearningLoop + +from diffusion_for_multi_scale_molecular_dynamics.active_learning_loop.benchmark import \ + ActiveLearningLoop class TestActiveLearningLoop: @@ -33,8 +35,12 @@ def mock_al_loop(self, mocker, mock_yaml_config, meta_config): # Mock os.path.exists to always return True mocker.patch("os.path.exists", return_value=True) # Mock the instantiate function from hydra.utils - mock_instantiate = mocker.patch("crystal_diffusion.active_learning_loop.benchmark.instantiate") - mock_instantiate.side_effect = lambda x: x # Return the config itself for simplicity + mock_instantiate = mocker.patch( + "diffusion_for_multi_scale_molecular_dynamics.active_learning_loop.benchmark.instantiate" + ) + mock_instantiate.side_effect = ( + lambda x: x + ) # Return the config itself for simplicity # Create an instance of ActiveLearningLoop loop = ActiveLearningLoop(meta_config) @@ -43,14 +49,14 @@ def mock_al_loop(self, mocker, mock_yaml_config, meta_config): def test_parse_config(self, mock_al_loop, mock_yaml_config, meta_config): # Assertions to verify that the attributes were correctly set - assert mock_al_loop.data_paths == {'key1': 'value1'} - assert mock_al_loop.mlip_model == {'key2': 'value2'} - assert mock_al_loop.eval_config == {'key3': 'value3'} - assert mock_al_loop.structure_generation == {'key4': 'value4'} - assert mock_al_loop.oracle == {'key5': 'value5'} + assert mock_al_loop.data_paths == {"key1": "value1"} + assert mock_al_loop.mlip_model == {"key2": "value2"} + assert mock_al_loop.eval_config == {"key3": "value3"} + assert mock_al_loop.structure_generation == {"key4": "value4"} + assert mock_al_loop.oracle == {"key5": "value5"} # Verify that the file was opened and the path was checked - open.assert_called_once_with(meta_config, 'r') + open.assert_called_once_with(meta_config, "r") os.path.exists.assert_called_once_with(meta_config) def test_train_mlip(self, mocker, mock_yaml_config, mock_al_loop): @@ -71,10 +77,12 @@ def test_train_mlip(self, mocker, mock_yaml_config, mock_al_loop): mock_mlip_model.prepare_dataset_from_lammps.assert_called_once_with( root_data_dir="mock_training_data_dir", atom_dict=mock_al_loop.atom_dict, - mode="train" + mode="train", ) - mock_mlip_model.train.assert_called_once_with("mock_training_set", mlip_name="mlip_round_1") + mock_mlip_model.train.assert_called_once_with( + "mock_training_set", mlip_name="mlip_round_1" + ) # Verify the trained model path is correctly returned assert result == "mock_trained_mlip_model" @@ -88,10 +96,15 @@ def test_train_mlip(self, mocker, mock_yaml_config, mock_al_loop): # The prepare_dataset_from_lammps should not be called since we provided a training_set mock_mlip_model.prepare_dataset_from_lammps.assert_called_once() # No new call - mock_mlip_model.train.assert_called_with(custom_training_set, mlip_name="mlip_round_2") + mock_mlip_model.train.assert_called_with( + custom_training_set, mlip_name="mlip_round_2" + ) assert result == "mock_trained_mlip_model" - assert mock_al_loop.trained_mlips == ["mock_trained_mlip_model", "mock_trained_mlip_model"] + assert mock_al_loop.trained_mlips == [ + "mock_trained_mlip_model", + "mock_trained_mlip_model", + ] def test_evaluate_mlip(self, mock_al_loop, tmpdir): # Mocking the mlip_model's methods @@ -100,7 +113,9 @@ def test_evaluate_mlip(self, mock_al_loop, tmpdir): mock_prediction_df = pd.DataFrame({"atom_index": [0, 1], "force": [1.0, 2.0]}) # Mocking return values for the prepare_dataset_from_lammps and evaluate methods - mock_mlip_model.prepare_dataset_from_lammps.return_value = mock_evaluation_dataset + mock_mlip_model.prepare_dataset_from_lammps.return_value = ( + mock_evaluation_dataset + ) mock_mlip_model.evaluate.return_value = (None, mock_prediction_df) loop = mock_al_loop @@ -117,11 +132,13 @@ def test_evaluate_mlip(self, mock_al_loop, tmpdir): root_data_dir="mock_evaluation_data_dir", atom_dict=loop.atom_dict, mode="evaluation", - get_forces=True + get_forces=True, ) # Verify the evaluate method was called with the correct parameters - expected_mlip_name = os.path.join(mock_mlip_model.savedir, 'mlip_round_1.almtp') - mock_mlip_model.evaluate.assert_called_once_with(mock_evaluation_dataset, mlip_name=expected_mlip_name) + expected_mlip_name = os.path.join(mock_mlip_model.savedir, "mlip_round_1.almtp") + mock_mlip_model.evaluate.assert_called_once_with( + mock_evaluation_dataset, mlip_name=expected_mlip_name + ) # Verify the method returns the correct dataframe pd.testing.assert_frame_equal(result_df, mock_prediction_df) @@ -131,7 +148,9 @@ def test_evaluate_mlip(self, mock_al_loop, tmpdir): result_df = loop.evaluate_mlip(round=2, mlip_name=custom_mlip_name) # The evaluate method should be called with the custom mlip_name - mock_mlip_model.evaluate.assert_called_with(mock_evaluation_dataset, mlip_name=custom_mlip_name) + mock_mlip_model.evaluate.assert_called_with( + mock_evaluation_dataset, mlip_name=custom_mlip_name + ) pd.testing.assert_frame_equal(result_df, mock_prediction_df) @@ -142,5 +161,5 @@ def test_evaluate_mlip(self, mock_al_loop, tmpdir): root_data_dir="mock_evaluation_data_dir", atom_dict=loop.atom_dict, mode="evaluation", - get_forces=False + get_forces=False, ) diff --git a/tests/analysis/test_ovito_visualisation.py b/tests/analysis/test_ovito_visualisation.py index 5be2d2de..d2e5a2f8 100644 --- a/tests/analysis/test_ovito_visualisation.py +++ b/tests/analysis/test_ovito_visualisation.py @@ -1,11 +1,13 @@ """Unit tests for outputs to ovito.""" + import os import numpy as np import pandas as pd import pytest -from src.crystal_diffusion.analysis import (get_lattice_from_lammps, - mtp_predictions_to_ovito) + +from diffusion_for_multi_scale_molecular_dynamics.analysis.ovito_visualisation import ( + get_lattice_from_lammps, mtp_predictions_to_ovito) class TestMTP2Ovito: @@ -32,9 +34,7 @@ def test_get_lattice_from_lammps(self, lammps_output): # Get the lattice array from the function using the test file lattice = get_lattice_from_lammps(lammps_output) - expected_lattice = np.array([[4.0, 0.0, 0.0], - [0.0, 5.0, 0.0], - [0.0, 0.0, 6.0]]) + expected_lattice = np.array([[4.0, 0.0, 0.0], [0.0, 5.0, 0.0], [0.0, 0.0, 6.0]]) # Assert that the returned lattice is as expected np.testing.assert_array_equal(lattice, expected_lattice) @@ -42,13 +42,15 @@ def test_get_lattice_from_lammps(self, lammps_output): @pytest.fixture def fake_prediction_csv(self, tmpdir): # Create a DataFrame with mock prediction data - df = pd.DataFrame({ - 'structure_index': [1, 1, 2, 2], - 'x': [0.0, 1.0, 2.0, 3.0], - 'y': [0.1, 1.1, 2.1, 3.1], - 'z': [0.2, 1.2, 2.2, 3.2], - 'nbh_grades': [0.3, 1.3, 2.3, 3.3] - }) + df = pd.DataFrame( + { + "structure_index": [1, 1, 2, 2], + "x": [0.0, 1.0, 2.0, 3.0], + "y": [0.1, 1.1, 2.1, 3.1], + "z": [0.2, 1.2, 2.2, 3.2], + "nbh_grades": [0.3, 1.3, 2.3, 3.3], + } + ) file_path = os.path.join(tmpdir, "mock_predictions.csv") df.to_csv(file_path, index=False) @@ -56,11 +58,7 @@ def fake_prediction_csv(self, tmpdir): def test_mtp_predictions_to_ovito(self, fake_prediction_csv, tmpdir): # Define the lattice - lattice = np.array([ - [10.0, 0.0, 0.0], - [0.0, 10.0, 0.0], - [0.0, 0.0, 10.0] - ]) + lattice = np.array([[10.0, 0.0, 0.0], [0.0, 10.0, 0.0], [0.0, 0.0, 10.0]]) # output file name output_name = os.path.join(tmpdir, "test_output") # Run the conversion function @@ -78,10 +76,14 @@ def test_mtp_predictions_to_ovito(self, fake_prediction_csv, tmpdir): assert int(lines[0]) == int(lines[len(lines) // 2]) == 2 - expected_lattice_line = 'Lattice=\"10.0 0.0 0.0 0.0 10.0 0.0 0.0 0.0 10.0\" Origin=\"0 0 0\" ' - expected_lattice_line += 'pbc=\"T T T\" Properties=pos:R:3:MaxVolGamma:R:1\n' + expected_lattice_line = ( + 'Lattice="10.0 0.0 0.0 0.0 10.0 0.0 0.0 0.0 10.0" Origin="0 0 0" ' + ) + expected_lattice_line += 'pbc="T T T" Properties=pos:R:3:MaxVolGamma:R:1\n' assert lines[1] == lines[len(lines) // 2 + 1] == expected_lattice_line # check the atom values for x, y in enumerate([2, 3, 6, 7]): - assert np.array_equal(list(map(float, lines[y].split())), [x, x + 0.1, x + 0.2, x + 0.3]) + assert np.array_equal( + list(map(float, lines[y].split())), [x, x + 0.1, x + 0.2, x + 0.3] + ) diff --git a/tests/conftest.py b/tests/conftest.py index ac633de9..5fb60e8b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,9 +35,9 @@ def pytest_collection_modifyitems(config, items): item.add_marker(skip) -_available_devices = [torch.device('cpu')] +_available_devices = [torch.device("cpu")] if torch.cuda.is_available(): - _available_devices.append(torch.device('cuda')) + _available_devices.append(torch.device("cuda")) @pytest.fixture(params=_available_devices) @@ -47,10 +47,10 @@ def device(request): @pytest.fixture() def accelerator(device): - if str(device) == 'cpu': - return 'cpu' - elif str(device) == 'cuda': - return 'gpu' + if str(device) == "cpu": + return "cpu" + elif str(device) == "cuda": + return "gpu" else: raise ValueError("Wrong device") @@ -58,7 +58,9 @@ def accelerator(device): @pytest.fixture def basis_vectors(batch_size): # orthogonal boxes with dimensions between 5 and 10. - orthogonal_boxes = torch.stack([torch.diag(5. + 5. * torch.rand(3)) for _ in range(batch_size)]) + orthogonal_boxes = torch.stack( + [torch.diag(5.0 + 5.0 * torch.rand(3)) for _ in range(batch_size)] + ) # add a bit of noise to make the vectors not quite orthogonal basis_vectors = orthogonal_boxes + 0.1 * torch.randn(batch_size, 3, 3) return basis_vectors @@ -66,6 +68,7 @@ def basis_vectors(batch_size): class TestDiffusionDataBase: """This base class creates fake data and writes it to disk for testing.""" + @pytest.fixture(scope="class", autouse=True) def set_seed(self): """Set the random seed.""" @@ -92,9 +95,13 @@ def spatial_dimension(self): return 3 @pytest.fixture - def train_configuration_runs(self, number_of_train_runs, spatial_dimension, number_of_atoms): + def train_configuration_runs( + self, number_of_train_runs, spatial_dimension, number_of_atoms + ): """Generate multiple fake 'data' runs and return their configurations.""" - return get_configuration_runs(number_of_train_runs, spatial_dimension, number_of_atoms) + return get_configuration_runs( + number_of_train_runs, spatial_dimension, number_of_atoms + ) @pytest.fixture def all_train_configurations(self, train_configuration_runs): @@ -105,9 +112,13 @@ def all_train_configurations(self, train_configuration_runs): return all_configurations @pytest.fixture - def valid_configuration_runs(self, number_of_valid_runs, spatial_dimension, number_of_atoms): + def valid_configuration_runs( + self, number_of_valid_runs, spatial_dimension, number_of_atoms + ): """Generate multiple fake 'data' runs and return their configurations.""" - return get_configuration_runs(number_of_valid_runs, spatial_dimension, number_of_atoms) + return get_configuration_runs( + number_of_valid_runs, spatial_dimension, number_of_atoms + ) @pytest.fixture def all_valid_configurations(self, valid_configuration_runs): @@ -128,17 +139,21 @@ def paths(self, tmp_path, train_configuration_runs, valid_configuration_runs): raw_data_dir = tmp_path / "raw_data" raw_data_dir.mkdir() - for mode, list_configurations in zip(['train', 'valid'], [train_configuration_runs, valid_configuration_runs]): + for mode, list_configurations in zip( + ["train", "valid"], [train_configuration_runs, valid_configuration_runs] + ): for i, configurations in enumerate(list_configurations, 1): - run_directory = raw_data_dir / f'{mode}_run_{i}' + run_directory = raw_data_dir / f"{mode}_run_{i}" run_directory.mkdir() dump_docs = create_dump_yaml_documents(configurations) thermo_docs = create_thermo_yaml_documents(configurations) - write_to_yaml(dump_docs, str(run_directory / f'dump_{mode}.yaml')) - write_to_yaml(thermo_docs, str(run_directory / 'thermo_logs.yaml')) + write_to_yaml(dump_docs, str(run_directory / f"dump_{mode}.yaml")) + write_to_yaml(thermo_docs, str(run_directory / "thermo_logs.yaml")) processed_data_dir = tmp_path / "processed_data" processed_data_dir.mkdir() - return dict(raw_data_dir=str(raw_data_dir), processed_data_dir=str(processed_data_dir)) + return dict( + raw_data_dir=str(raw_data_dir), processed_data_dir=str(processed_data_dir) + ) diff --git a/tests/data/diffusion/test_data_loader.py b/tests/data/diffusion/test_data_loader.py index 0297d3a2..7b01a3c9 100644 --- a/tests/data/diffusion/test_data_loader.py +++ b/tests/data/diffusion/test_data_loader.py @@ -3,28 +3,30 @@ import pytest import torch -from crystal_diffusion.data.diffusion.data_loader import ( - LammpsForDiffusionDataModule, LammpsLoaderParameters) -from crystal_diffusion.namespace import (CARTESIAN_FORCES, CARTESIAN_POSITIONS, - RELATIVE_COORDINATES) +from diffusion_for_multi_scale_molecular_dynamics.data.diffusion.data_loader import ( + LammpsForDiffusionDataModule, LammpsLoaderParameters) +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, CARTESIAN_POSITIONS, RELATIVE_COORDINATES) from tests.conftest import TestDiffusionDataBase from tests.fake_data_utils import Configuration, find_aligning_permutation -def convert_configurations_to_dataset(configurations: List[Configuration]) -> Dict[str, torch.Tensor]: +def convert_configurations_to_dataset( + configurations: List[Configuration], +) -> Dict[str, torch.Tensor]: """Convert the input configuration into a dict of torch tensors comparable to a pytorch dataset.""" # The expected dataset keys are {'natom', 'box', 'cartesian_positions', 'relative_positions', 'type', # 'cartesian_forces', 'potential_energy'} data = defaultdict(list) for configuration in configurations: - data['natom'].append(len(configuration.ids)) - data['box'].append(configuration.cell_dimensions) + data["natom"].append(len(configuration.ids)) + data["box"].append(configuration.cell_dimensions) data[CARTESIAN_FORCES].append(configuration.cartesian_forces) data[CARTESIAN_POSITIONS].append(configuration.cartesian_positions) data[RELATIVE_COORDINATES].append(configuration.relative_coordinates) - data['type'].append(configuration.types) - data['potential_energy'].append(configuration.potential_energy) + data["type"].append(configuration.types) + data["potential_energy"].append(configuration.potential_energy) configuration_dataset = dict() for key, array in data.items(): @@ -37,103 +39,155 @@ class TestDiffusionDataLoader(TestDiffusionDataBase): @pytest.fixture def input_data_to_transform(self): return { - 'natom': [2], # batch size of 1 - 'box': [[1.0, 1.0, 1.0]], - CARTESIAN_POSITIONS: [[1., 2., 3, 4., 5, 6]], # for one batch, two atoms, 3D positions - CARTESIAN_FORCES: [[11., 12., 13, 14., 15, 16]], # for one batch, two atoms, 3D forces - RELATIVE_COORDINATES: [[1., 2., 3, 4., 5, 6]], - 'type': [[1, 2]], - 'potential_energy': [23.233], + "natom": [2], # batch size of 1 + "box": [[1.0, 1.0, 1.0]], + CARTESIAN_POSITIONS: [ + [1.0, 2.0, 3, 4.0, 5, 6] + ], # for one batch, two atoms, 3D positions + CARTESIAN_FORCES: [ + [11.0, 12.0, 13, 14.0, 15, 16] + ], # for one batch, two atoms, 3D forces + RELATIVE_COORDINATES: [[1.0, 2.0, 3, 4.0, 5, 6]], + "type": [[1, 2]], + "potential_energy": [23.233], } def test_dataset_transform(self, input_data_to_transform): result = LammpsForDiffusionDataModule.dataset_transform(input_data_to_transform) # Check keys in result - assert set(result.keys()) == {'natom', CARTESIAN_FORCES, CARTESIAN_POSITIONS, RELATIVE_COORDINATES, - 'box', 'type', 'potential_energy'} + assert set(result.keys()) == { + "natom", + CARTESIAN_FORCES, + CARTESIAN_POSITIONS, + RELATIVE_COORDINATES, + "box", + "type", + "potential_energy", + } # Check tensor types and shapes - assert torch.equal(result['natom'], torch.tensor(input_data_to_transform['natom']).long()) - assert result[CARTESIAN_POSITIONS].shape == (1, 2, 3) # (batchsize, natom, 3 [since it's 3D]) - assert result['box'].shape == (1, 3) - assert torch.equal(result['type'], torch.tensor(input_data_to_transform['type']).long()) - assert torch.equal(result['potential_energy'], torch.tensor(input_data_to_transform['potential_energy'])) + assert torch.equal( + result["natom"], torch.tensor(input_data_to_transform["natom"]).long() + ) + assert result[CARTESIAN_POSITIONS].shape == ( + 1, + 2, + 3, + ) # (batchsize, natom, 3 [since it's 3D]) + assert result["box"].shape == (1, 3) + assert torch.equal( + result["type"], torch.tensor(input_data_to_transform["type"]).long() + ) + assert torch.equal( + result["potential_energy"], + torch.tensor(input_data_to_transform["potential_energy"]), + ) # Check tensor types explicitly - assert result['natom'].dtype == torch.long - assert result[CARTESIAN_POSITIONS].dtype == torch.float32 # default dtype for torch.as_tensor with float inputs - assert result['box'].dtype == torch.float32 - assert result['type'].dtype == torch.long - assert result['potential_energy'].dtype == torch.float32 + assert result["natom"].dtype == torch.long + assert ( + result[CARTESIAN_POSITIONS].dtype == torch.float32 + ) # default dtype for torch.as_tensor with float inputs + assert result["box"].dtype == torch.float32 + assert result["type"].dtype == torch.long + assert result["potential_energy"].dtype == torch.float32 @pytest.fixture def input_data_to_pad(self): return { - 'natom': 2, # batch size of 1 - 'box': [1.0, 1.0, 1.0], - CARTESIAN_POSITIONS: [1., 2., 3, 4., 5, 6], # for one batch, two atoms, 3D positions - CARTESIAN_FORCES: [11., 12., 13, 14., 15, 16], - RELATIVE_COORDINATES: [1., 2., 3, 4., 5, 6], - 'type': [1, 2], - 'potential_energy': 23.233, + "natom": 2, # batch size of 1 + "box": [1.0, 1.0, 1.0], + CARTESIAN_POSITIONS: [ + 1.0, + 2.0, + 3, + 4.0, + 5, + 6, + ], # for one batch, two atoms, 3D positions + CARTESIAN_FORCES: [11.0, 12.0, 13, 14.0, 15, 16], + RELATIVE_COORDINATES: [1.0, 2.0, 3, 4.0, 5, 6], + "type": [1, 2], + "potential_energy": 23.233, } def test_pad_dataset(self, input_data_to_pad): max_atom = 5 # Assume we want to pad to a max of 5 atoms - padded_sample = LammpsForDiffusionDataModule.pad_samples(input_data_to_pad, max_atom) + padded_sample = LammpsForDiffusionDataModule.pad_samples( + input_data_to_pad, max_atom + ) # Check if the type and position have been padded correctly - assert len(padded_sample['type']) == max_atom + assert len(padded_sample["type"]) == max_atom assert padded_sample[CARTESIAN_POSITIONS].shape == torch.Size([max_atom * 3]) # Check that the padding uses -1 for type # 2 atoms in the input_data - last 3 atoms should be type -1 for k in range(max_atom - 2): - assert padded_sample['type'].tolist()[-(k + 1)] == -1 + assert padded_sample["type"].tolist()[-(k + 1)] == -1 # Check that the padding uses nan for position - assert torch.isnan(padded_sample[CARTESIAN_POSITIONS][-(max_atom - 2) * 3:]).all() + assert torch.isnan( + padded_sample[CARTESIAN_POSITIONS][-(max_atom - 2) * 3:] + ).all() @pytest.fixture def data_module_hyperparameters(self, number_of_atoms, spatial_dimension): - return LammpsLoaderParameters(batch_size=2, - num_workers=0, - max_atom=number_of_atoms, - spatial_dimension=spatial_dimension) + return LammpsLoaderParameters( + batch_size=2, + num_workers=0, + max_atom=number_of_atoms, + spatial_dimension=spatial_dimension, + ) @pytest.fixture() def data_module(self, paths, data_module_hyperparameters, tmpdir): - data_module = LammpsForDiffusionDataModule(lammps_run_dir=paths['raw_data_dir'], - processed_dataset_dir=paths['processed_data_dir'], - hyper_params=data_module_hyperparameters, - working_cache_dir=tmpdir) + data_module = LammpsForDiffusionDataModule( + lammps_run_dir=paths["raw_data_dir"], + processed_dataset_dir=paths["processed_data_dir"], + hyper_params=data_module_hyperparameters, + working_cache_dir=tmpdir, + ) data_module.setup() return data_module @pytest.fixture() - def real_and_test_datasets(self, mode, data_module, all_train_configurations, all_valid_configurations): + def real_and_test_datasets( + self, mode, data_module, all_train_configurations, all_valid_configurations + ): match mode: - case 'train': + case "train": data_module_dataset = data_module.train_dataset[:] - configuration_dataset = convert_configurations_to_dataset(all_train_configurations) - case 'valid': + configuration_dataset = convert_configurations_to_dataset( + all_train_configurations + ) + case "valid": data_module_dataset = data_module.valid_dataset[:] - configuration_dataset = convert_configurations_to_dataset(all_valid_configurations) + configuration_dataset = convert_configurations_to_dataset( + all_valid_configurations + ) case _: raise ValueError(f"Unknown mode {mode}") return data_module_dataset, configuration_dataset def test_dataset_feature_names(self, data_module): - expected_feature_names = {'natom', 'box', 'type', 'potential_energy', CARTESIAN_FORCES, CARTESIAN_POSITIONS, - RELATIVE_COORDINATES} + expected_feature_names = { + "natom", + "box", + "type", + "potential_energy", + CARTESIAN_FORCES, + CARTESIAN_POSITIONS, + RELATIVE_COORDINATES, + } assert set(data_module.train_dataset.features.keys()) == expected_feature_names assert set(data_module.valid_dataset.features.keys()) == expected_feature_names - @pytest.mark.parametrize('mode', ['train', 'valid']) + @pytest.mark.parametrize("mode", ["train", "valid"]) def test_dataset(self, real_and_test_datasets): data_module_dataset, configuration_dataset = real_and_test_datasets @@ -141,13 +195,17 @@ def test_dataset(self, real_and_test_datasets): assert set(data_module_dataset.keys()) == set(configuration_dataset.keys()) # the configurations and the data module dataset might not be in the same order. Try to build a mapping. - dataset_boxes = data_module_dataset['box'] - configuration_boxes = configuration_dataset['box'] + dataset_boxes = data_module_dataset["box"] + configuration_boxes = configuration_dataset["box"] - permutation_indices = find_aligning_permutation(dataset_boxes, configuration_boxes) + permutation_indices = find_aligning_permutation( + dataset_boxes, configuration_boxes + ) for field_name in data_module_dataset.keys(): computed_values = data_module_dataset[field_name] expected_values = configuration_dataset[field_name][permutation_indices] - torch.testing.assert_close(computed_values, expected_values, check_dtype=False) + torch.testing.assert_close( + computed_values, expected_values, check_dtype=False + ) diff --git a/tests/data/diffusion/test_data_preprocess.py b/tests/data/diffusion/test_data_preprocess.py index b11f8830..3d3e262f 100644 --- a/tests/data/diffusion/test_data_preprocess.py +++ b/tests/data/diffusion/test_data_preprocess.py @@ -3,11 +3,11 @@ import numpy as np import pandas as pd import pytest -from crystal_diffusion.data.diffusion.data_preprocess import \ - LammpsProcessorForDiffusion -from crystal_diffusion.namespace import (CARTESIAN_FORCES, CARTESIAN_POSITIONS, - RELATIVE_COORDINATES) +from diffusion_for_multi_scale_molecular_dynamics.data.diffusion.data_preprocess import \ + LammpsProcessorForDiffusion +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, CARTESIAN_POSITIONS, RELATIVE_COORDINATES) from tests.conftest import TestDiffusionDataBase from tests.fake_data_utils import generate_parquet_dataframe @@ -18,38 +18,59 @@ class TestDataProcess(TestDiffusionDataBase): def processor(self, paths): return LammpsProcessorForDiffusion(**paths) - def test_prepare_train_data(self, processor, paths, train_configuration_runs, number_of_train_runs): - list_files = processor.prepare_data(paths['raw_data_dir'], mode='train') + def test_prepare_train_data( + self, processor, paths, train_configuration_runs, number_of_train_runs + ): + list_files = processor.prepare_data(paths["raw_data_dir"], mode="train") assert len(list_files) == number_of_train_runs for run_number, configurations in enumerate(train_configuration_runs, 1): - expected_parquet_file = os.path.join(processor.data_dir, f"train_run_{run_number}.parquet") + expected_parquet_file = os.path.join( + processor.data_dir, f"train_run_{run_number}.parquet" + ) assert expected_parquet_file in list_files computed_df = pd.read_parquet(expected_parquet_file) expected_df = generate_parquet_dataframe(configurations) pd.testing.assert_frame_equal(computed_df, expected_df) - def test_prepare_valid_data(self, processor, paths, valid_configuration_runs, number_of_valid_runs): - list_files = processor.prepare_data(paths['raw_data_dir'], mode='valid') + def test_prepare_valid_data( + self, processor, paths, valid_configuration_runs, number_of_valid_runs + ): + list_files = processor.prepare_data(paths["raw_data_dir"], mode="valid") assert len(list_files) == number_of_valid_runs for run_number, configurations in enumerate(valid_configuration_runs, 1): - expected_parquet_file = os.path.join(processor.data_dir, f"valid_run_{run_number}.parquet") + expected_parquet_file = os.path.join( + processor.data_dir, f"valid_run_{run_number}.parquet" + ) assert expected_parquet_file in list_files computed_df = pd.read_parquet(expected_parquet_file) expected_df = generate_parquet_dataframe(configurations) pd.testing.assert_frame_equal(computed_df, expected_df) - def test_parse_lammps_run(self, processor, paths, train_configuration_runs, valid_configuration_runs): - expected_columns = ['natom', 'box', 'type', CARTESIAN_POSITIONS, CARTESIAN_FORCES, RELATIVE_COORDINATES, - 'potential_energy'] - - for mode, configuration_runs in zip(['train', 'valid'], [train_configuration_runs, valid_configuration_runs]): + def test_parse_lammps_run( + self, processor, paths, train_configuration_runs, valid_configuration_runs + ): + expected_columns = [ + "natom", + "box", + "type", + CARTESIAN_POSITIONS, + CARTESIAN_FORCES, + RELATIVE_COORDINATES, + "potential_energy", + ] + + for mode, configuration_runs in zip( + ["train", "valid"], [train_configuration_runs, valid_configuration_runs] + ): for run_number, configurations in enumerate(configuration_runs, 1): - run_dir = os.path.join(paths['raw_data_dir'], f"{mode}_run_{run_number}") + run_dir = os.path.join( + paths["raw_data_dir"], f"{mode}_run_{run_number}" + ) computed_df = processor.parse_lammps_run(run_dir) assert computed_df is not None assert not computed_df.empty @@ -66,20 +87,30 @@ def box_coordinates(self): @pytest.fixture def sample_coordinates(self, box_coordinates): # Sample data frame - return pd.DataFrame({ - 'box': [box_coordinates], - 'x': [[0.6, 0.06, 0.006, 0.00006]], - 'y': [[1.2, 0.12, 0.0012, 0.00012]], - 'z': [[1.8, 0.18, 0.018, 0.0018]] - }) + return pd.DataFrame( + { + "box": [box_coordinates], + "x": [[0.6, 0.06, 0.006, 0.00006]], + "y": [[1.2, 0.12, 0.0012, 0.00012]], + "z": [[1.8, 0.18, 0.018, 0.0018]], + } + ) def test_convert_coords_to_relative(self, sample_coordinates, box_coordinates): # Expected output: Each coordinate divided by 1, 2, 3 (the box limits) for index, row in sample_coordinates.iterrows(): - relative_coords = LammpsProcessorForDiffusion._convert_coords_to_relative(row) + relative_coords = LammpsProcessorForDiffusion._convert_coords_to_relative( + row + ) expected_coords = [] - for x, y, z in zip(row['x'], row['y'], row['z']): - expected_coords.extend([x / box_coordinates[0], y / box_coordinates[1], z / box_coordinates[2]]) + for x, y, z in zip(row["x"], row["y"], row["z"]): + expected_coords.extend( + [ + x / box_coordinates[0], + y / box_coordinates[1], + z / box_coordinates[2], + ] + ) assert relative_coords == expected_coords def test_convert_coords_to_relative2(self, processor, all_configurations): @@ -91,9 +122,18 @@ def test_convert_coords_to_relative2(self, processor, all_configurations): positions = configuration.cartesian_positions box = configuration.cell_dimensions - position_series = pd.Series({'x': positions[:, 0], 'y': positions[:, 1], 'z': positions[:, 2], 'box': box}) - - computed_coordinates = np.array(processor._convert_coords_to_relative(position_series)).reshape(natom, 3) + position_series = pd.Series( + { + "x": positions[:, 0], + "y": positions[:, 1], + "z": positions[:, 2], + "box": box, + } + ) + + computed_coordinates = np.array( + processor._convert_coords_to_relative(position_series) + ).reshape(natom, 3) np.testing.assert_almost_equal(computed_coordinates, expected_coordinates) def test_get_x_relative(self, processor, sample_coordinates): @@ -107,9 +147,19 @@ def test_flatten_positions_in_row(self): number_of_atoms = 12 spatial_dimensions = 3 position_data = np.random.rand(number_of_atoms, spatial_dimensions) - row = pd.Series(dict(x=list(position_data[:, 0]), y=list(position_data[:, 1]), z=list(position_data[:, 2]))) - - computed_flattened_positions = LammpsProcessorForDiffusion._flatten_positions_in_row(row) + row = pd.Series( + dict( + x=list(position_data[:, 0]), + y=list(position_data[:, 1]), + z=list(position_data[:, 2]), + ) + ) + + computed_flattened_positions = ( + LammpsProcessorForDiffusion._flatten_positions_in_row(row) + ) expected_flattened_positions = position_data.flatten() - np.testing.assert_almost_equal(expected_flattened_positions, computed_flattened_positions) + np.testing.assert_almost_equal( + expected_flattened_positions, computed_flattened_positions + ) diff --git a/tests/data/test_parse_lammps_output.py b/tests/data/test_parse_lammps_output.py index 605ba613..8e337a4f 100644 --- a/tests/data/test_parse_lammps_output.py +++ b/tests/data/test_parse_lammps_output.py @@ -4,9 +4,9 @@ import pandas as pd import pytest import yaml -from src.crystal_diffusion.data.parse_lammps_outputs import ( - parse_lammps_dump, parse_lammps_output, parse_lammps_thermo_log) +from src.diffusion_for_multi_scale_molecular_dynamics.data.parse_lammps_outputs import ( + parse_lammps_dump, parse_lammps_output, parse_lammps_thermo_log) from tests.fake_data_utils import (create_dump_yaml_documents, generate_fake_configuration, generate_parse_dump_output_dataframe, @@ -15,7 +15,7 @@ def generate_fake_yaml(filename, documents, multiple_docs=True): # Write the YAML content - with open(filename, 'w') as yaml_file: + with open(filename, "w") as yaml_file: if multiple_docs: yaml.dump_all(documents, yaml_file) else: @@ -27,7 +27,7 @@ def fake_yaml_content(): # fake LAMMPS output file with 4 MD steps in 1D for 3 atoms np.random.seed(23423) box = [[0, 0.6], [0, 1.6], [0, 2.6]] - keywords = ['id', 'type', 'x', 'y', 'z', 'fx', 'fy', 'fz'] + keywords = ["id", "type", "x", "y", "z", "fx", "fy", "fz"] number_of_documents = 4 list_atom_types = [1, 2, 1] @@ -47,7 +47,7 @@ def fake_yaml_content(): @pytest.fixture def fake_lammps_yaml(tmpdir, fake_yaml_content): - file = os.path.join(tmpdir, 'fake_lammps_dump.yaml') + file = os.path.join(tmpdir, "fake_lammps_dump.yaml") generate_fake_yaml(file, fake_yaml_content) return file @@ -56,24 +56,32 @@ def fake_lammps_yaml(tmpdir, fake_yaml_content): def fake_thermo_dataframe(pressure: bool, temperature: bool): np.random.seed(12231) number_of_rows = 4 - keywords = ['KinEng', 'PotEng'] + keywords = ["KinEng", "PotEng"] if pressure: - keywords.append('Press') + keywords.append("Press") if temperature: - keywords.append('Temp') + keywords.append("Temp") - fake_data_df = pd.DataFrame(np.random.rand(number_of_rows, len(keywords)), columns=keywords) + fake_data_df = pd.DataFrame( + np.random.rand(number_of_rows, len(keywords)), columns=keywords + ) return fake_data_df @pytest.fixture def expected_processed_thermo_dataframe(fake_thermo_dataframe: pd.DataFrame): - processed_thermo_dataframe = pd.DataFrame(fake_thermo_dataframe).rename(columns={'KinEng': 'kinetic_energy', - 'PotEng': 'potential_energy', - 'Press': 'pressure', - 'Temp': 'temperature'}) - processed_thermo_dataframe['energy'] = (processed_thermo_dataframe['kinetic_energy'] - + processed_thermo_dataframe['potential_energy']) + processed_thermo_dataframe = pd.DataFrame(fake_thermo_dataframe).rename( + columns={ + "KinEng": "kinetic_energy", + "PotEng": "potential_energy", + "Press": "pressure", + "Temp": "temperature", + } + ) + processed_thermo_dataframe["energy"] = ( + processed_thermo_dataframe["kinetic_energy"] + + processed_thermo_dataframe["potential_energy"] + ) return processed_thermo_dataframe @@ -82,16 +90,18 @@ def expected_processed_thermo_dataframe(fake_thermo_dataframe: pd.DataFrame): def fake_thermo_yaml(tmpdir, fake_thermo_dataframe): keywords = list(fake_thermo_dataframe.columns) data = fake_thermo_dataframe.values.tolist() - yaml_content = {'keywords': keywords, 'data': data} - file = os.path.join(tmpdir, 'fake_lammps_thermo.yaml') + yaml_content = {"keywords": keywords, "data": data} + file = os.path.join(tmpdir, "fake_lammps_thermo.yaml") generate_fake_yaml(file, yaml_content, multiple_docs=False) return file @pytest.mark.parametrize("pressure", [True, False]) @pytest.mark.parametrize("temperature", [True, False]) -def test_parse_lammps_outputs(fake_lammps_yaml, fake_thermo_yaml, expected_processed_thermo_dataframe, tmpdir): - output_name = os.path.join(tmpdir, 'test.parquet') +def test_parse_lammps_outputs( + fake_lammps_yaml, fake_thermo_yaml, expected_processed_thermo_dataframe, tmpdir +): + output_name = os.path.join(tmpdir, "test.parquet") parse_lammps_output(fake_lammps_yaml, fake_thermo_yaml, output_name) # check that a file exists assert os.path.exists(output_name) @@ -101,7 +111,10 @@ def test_parse_lammps_outputs(fake_lammps_yaml, fake_thermo_yaml, expected_proce assert len(df) == 4 - pd.testing.assert_frame_equal(expected_processed_thermo_dataframe, df[expected_processed_thermo_dataframe.columns]) + pd.testing.assert_frame_equal( + expected_processed_thermo_dataframe, + df[expected_processed_thermo_dataframe.columns], + ) @pytest.mark.parametrize("pressure", [True, False]) @@ -134,9 +147,12 @@ def number_of_configurations(): def configurations(number_of_configurations, spatial_dimension, number_of_atoms): """Generate multiple fake configurations.""" np.random.seed(23423423) - configurations = [generate_fake_configuration(spatial_dimension=spatial_dimension, - number_of_atoms=number_of_atoms) - for _ in range(number_of_configurations)] + configurations = [ + generate_fake_configuration( + spatial_dimension=spatial_dimension, number_of_atoms=number_of_atoms + ) + for _ in range(number_of_configurations) + ] return configurations @@ -159,9 +175,21 @@ def test_parse_lammps_dump(lammps_dump_path, expected_lammps_dump_dataframe): computed_lammps_dump_dataframe = pd.DataFrame(data_dict) - assert set(computed_lammps_dump_dataframe.columns) == set(expected_lammps_dump_dataframe) + assert set(computed_lammps_dump_dataframe.columns) == set( + expected_lammps_dump_dataframe + ) for colname in computed_lammps_dump_dataframe.columns: - computed_values = np.array([list_values for list_values in computed_lammps_dump_dataframe[colname].values]) - expected_values = np.array([list_values for list_values in expected_lammps_dump_dataframe[colname].values]) + computed_values = np.array( + [ + list_values + for list_values in computed_lammps_dump_dataframe[colname].values + ] + ) + expected_values = np.array( + [ + list_values + for list_values in expected_lammps_dump_dataframe[colname].values + ] + ) np.testing.assert_array_equal(computed_values, expected_values) diff --git a/tests/data/test_utils.py b/tests/data/test_utils.py index 03668987..12a4fbe3 100644 --- a/tests/data/test_utils.py +++ b/tests/data/test_utils.py @@ -1,6 +1,8 @@ import pytest import yaml -from crystal_diffusion.data.utils import crop_lammps_yaml + +from diffusion_for_multi_scale_molecular_dynamics.data.utils import \ + crop_lammps_yaml # Sample data for dump and thermo YAML files DUMP_YAML_CONTENT = """ @@ -49,7 +51,7 @@ def test_crop_lammps_yaml(dump_file, thermo_file): # Verify the function output assert len(cropped_dump) == len(dump_yaml_content) - crop_step - assert len(cropped_thermo['data']) == len(thermo_yaml_content['data']) - crop_step + assert len(cropped_thermo["data"]) == len(thermo_yaml_content["data"]) - crop_step # Testing exception for too large crop_step with pytest.raises(ValueError): diff --git a/tests/fake_data_utils.py b/tests/fake_data_utils.py index 390d6a96..442fa5f1 100644 --- a/tests/fake_data_utils.py +++ b/tests/fake_data_utils.py @@ -5,13 +5,25 @@ import pandas as pd import torch import yaml -from crystal_diffusion.namespace import (CARTESIAN_FORCES, CARTESIAN_POSITIONS, - RELATIVE_COORDINATES) -Configuration = namedtuple("Configuration", - ["spatial_dimension", CARTESIAN_POSITIONS, CARTESIAN_FORCES, RELATIVE_COORDINATES, - "types", "ids", "cell_dimensions", - "potential_energy", "kinetic_energy", "energy"]) +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, CARTESIAN_POSITIONS, RELATIVE_COORDINATES) + +Configuration = namedtuple( + "Configuration", + [ + "spatial_dimension", + CARTESIAN_POSITIONS, + CARTESIAN_FORCES, + RELATIVE_COORDINATES, + "types", + "ids", + "cell_dimensions", + "potential_energy", + "kinetic_energy", + "energy", + ], +) def generate_fake_configuration(spatial_dimension: int, number_of_atoms: int): @@ -27,23 +39,27 @@ def generate_fake_configuration(spatial_dimension: int, number_of_atoms: int): relative_coordinates = np.random.rand(number_of_atoms, spatial_dimension) - cell_dimensions = 5. + 5. * np.random.rand(spatial_dimension) # make sure the cell is big. + cell_dimensions = 5.0 + 5.0 * np.random.rand( + spatial_dimension + ) # make sure the cell is big. unit_cell_vectors = np.diag(cell_dimensions) positions = np.dot(relative_coordinates, unit_cell_vectors) potential_energy = np.random.rand() kinetic_energy = np.random.rand() energy = potential_energy + kinetic_energy - return Configuration(spatial_dimension=spatial_dimension, - relative_coordinates=relative_coordinates, - cartesian_positions=positions, - cartesian_forces=np.random.rand(number_of_atoms, spatial_dimension), - types=np.random.randint(1, 10, number_of_atoms), - ids=np.arange(1, number_of_atoms + 1), - cell_dimensions=cell_dimensions, - potential_energy=potential_energy, - kinetic_energy=kinetic_energy, - energy=energy) + return Configuration( + spatial_dimension=spatial_dimension, + relative_coordinates=relative_coordinates, + cartesian_positions=positions, + cartesian_forces=np.random.rand(number_of_atoms, spatial_dimension), + types=np.random.randint(1, 10, number_of_atoms), + ids=np.arange(1, number_of_atoms + 1), + cell_dimensions=cell_dimensions, + potential_energy=potential_energy, + kinetic_energy=kinetic_energy, + energy=energy, + ) def get_configuration_runs(number_of_runs, spatial_dimension, number_of_atoms): @@ -51,15 +67,20 @@ def get_configuration_runs(number_of_runs, spatial_dimension, number_of_atoms): list_configurations = [] for _ in range(number_of_runs): number_of_configs = np.random.randint(1, 16) - configurations = [generate_fake_configuration(spatial_dimension=spatial_dimension, - number_of_atoms=number_of_atoms) - for _ in range(number_of_configs)] + configurations = [ + generate_fake_configuration( + spatial_dimension=spatial_dimension, number_of_atoms=number_of_atoms + ) + for _ in range(number_of_configs) + ] list_configurations.append(configurations) return list_configurations -def generate_parse_dump_output_dataframe(configurations: List[Configuration]) -> pd.DataFrame: +def generate_parse_dump_output_dataframe( + configurations: List[Configuration], +) -> pd.DataFrame: """Generate parse lammps run Args: @@ -70,52 +91,73 @@ def generate_parse_dump_output_dataframe(configurations: List[Configuration]) -> """ rows = [] for configuration in configurations: - row = dict(box=configuration.cell_dimensions, id=list(configuration.ids), type=list(configuration.types)) - for coordinates, name in zip(configuration.cartesian_positions.transpose(), ['x', 'y', 'z']): + row = dict( + box=configuration.cell_dimensions, + id=list(configuration.ids), + type=list(configuration.types), + ) + for coordinates, name in zip( + configuration.cartesian_positions.transpose(), ["x", "y", "z"] + ): row[name] = list(coordinates) - for coordinate_forces, name in zip(configuration.cartesian_forces.transpose(), ['fx', 'fy', 'fz']): + for coordinate_forces, name in zip( + configuration.cartesian_forces.transpose(), ["fx", "fy", "fz"] + ): row[name] = list(coordinate_forces) rows.append(row) return pd.DataFrame(rows) -def create_dump_single_record(configuration: Configuration, timestep: int) -> Dict[str, Any]: +def create_dump_single_record( + configuration: Configuration, timestep: int +) -> Dict[str, Any]: spatial_dimension = configuration.spatial_dimension box = [[0, float(dimension)] for dimension in configuration.cell_dimensions] # keywords should be of the form : [id, type, x, y, z, fx, fy, fz, ] - keywords = ['id', 'type'] + keywords = ["id", "type"] - for direction, _ in zip(['x', 'y', 'z'], range(spatial_dimension)): + for direction, _ in zip(["x", "y", "z"], range(spatial_dimension)): keywords.append(direction) - for force_direction, _ in zip(['fx', 'fy', 'fz'], range(spatial_dimension)): + for force_direction, _ in zip(["fx", "fy", "fz"], range(spatial_dimension)): keywords.append(force_direction) # Each row of data should be a list in the same order as the keywords data = [] - for id, type, position, force in ( - zip(configuration.ids, configuration.types, configuration.cartesian_positions, - configuration.cartesian_forces)): - row = [int(id), int(type)] + [float(p) for p in position] + [float(f) for f in force] + for id, type, position, force in zip( + configuration.ids, + configuration.types, + configuration.cartesian_positions, + configuration.cartesian_forces, + ): + row = ( + [int(id), int(type)] + + [float(p) for p in position] + + [float(f) for f in force] + ) data.append(row) - document = dict(creator='fake LAMMPS for tests', - timestep=timestep, - natoms=len(configuration.ids), - boundary=2 * spatial_dimension * ['p'], - box=box, - keywords=keywords, - data=data) + document = dict( + creator="fake LAMMPS for tests", + timestep=timestep, + natoms=len(configuration.ids), + boundary=2 * spatial_dimension * ["p"], + box=box, + keywords=keywords, + data=data, + ) return document -def create_dump_yaml_documents(configurations: List[Configuration]) -> List[Dict[str, Any]]: +def create_dump_yaml_documents( + configurations: List[Configuration], +) -> List[Dict[str, Any]]: docs = [] for timestep, configuration in enumerate(configurations): docs.append(create_dump_single_record(configuration, timestep)) @@ -123,19 +165,38 @@ def create_dump_yaml_documents(configurations: List[Configuration]) -> List[Dict return docs -def create_thermo_yaml_documents(configurations: List[Configuration]) -> List[Dict[str, Any]]: - - keywords = ['Step', 'Temp', 'KinEng', 'PotEng', 'E_bond', 'E_angle', - 'E_dihed', 'E_impro', 'E_vdwl', 'E_coul', 'E_long', 'Press'] +def create_thermo_yaml_documents( + configurations: List[Configuration], +) -> List[Dict[str, Any]]: + + keywords = [ + "Step", + "Temp", + "KinEng", + "PotEng", + "E_bond", + "E_angle", + "E_dihed", + "E_impro", + "E_vdwl", + "E_coul", + "E_long", + "Press", + ] number_of_keywords = len(keywords) data = [] for timestep, configuration in enumerate(configurations): - row = ([timestep] - + [float(np.random.rand())] - + [float(configuration.kinetic_energy), float(configuration.potential_energy)] - + [float(np.random.rand()) for _ in range(number_of_keywords - 4)]) + row = ( + [timestep] + + [float(np.random.rand())] + + [ + float(configuration.kinetic_energy), + float(configuration.potential_energy), + ] + + [float(np.random.rand()) for _ in range(number_of_keywords - 4)] + ) data.append(row) document = dict(keywords=keywords, data=data) @@ -146,7 +207,9 @@ def write_to_yaml(documents: List[Dict[str, Any]], output_file_path: str): """Write to yaml.""" with open(output_file_path, "w") as fd: # This output format is not perfectly identical to what LAMMPS output, but very similar. - yaml.dump_all(documents, fd, sort_keys=False, default_flow_style=None, width=1000) + yaml.dump_all( + documents, fd, sort_keys=False, default_flow_style=None, width=1000 + ) def generate_parquet_dataframe(configurations: List[Configuration]) -> pd.DataFrame: @@ -156,25 +219,28 @@ def generate_parquet_dataframe(configurations: List[Configuration]) -> pd.DataFr # A = [ v1, v2] --> A.flatten(order=c) = [v1, v2, v3, v4], A.flatten(order=f) = [v1, v3, v2, v4]. # [ v3, v4] # C-style flattening is the correct convention to interoperate with pytorch reshaping operations. - relative_positions = configuration.relative_coordinates.flatten(order='c') - positions = configuration.cartesian_positions.flatten(order='c') - forces = configuration.cartesian_forces.flatten(order='c') + relative_positions = configuration.relative_coordinates.flatten(order="c") + positions = configuration.cartesian_positions.flatten(order="c") + forces = configuration.cartesian_forces.flatten(order="c") number_of_atoms = len(configuration.ids) box = configuration.cell_dimensions - row = dict(natom=number_of_atoms, - box=box, - type=configuration.types, - potential_energy=configuration.potential_energy, - cartesian_positions=positions, - relative_coordinates=relative_positions, - cartesian_forces=forces, - ) + row = dict( + natom=number_of_atoms, + box=box, + type=configuration.types, + potential_energy=configuration.potential_energy, + cartesian_positions=positions, + relative_coordinates=relative_positions, + cartesian_forces=forces, + ) rows.append(row) return pd.DataFrame(rows) -def find_aligning_permutation(first_2d_array: torch.Tensor, second_2d_array: torch.Tensor, tol=1e-6) -> torch.Tensor: +def find_aligning_permutation( + first_2d_array: torch.Tensor, second_2d_array: torch.Tensor, tol=1e-6 +) -> torch.Tensor: """Find aligning permutation, assuming the input two arrays contain the same information. This function computes and stores all distances. This scales quadratically, which is not good, but it should @@ -185,12 +251,15 @@ def find_aligning_permutation(first_2d_array: torch.Tensor, second_2d_array: tor number_of_vectors = first_2d_array.shape[0] - distances = torch.sum((first_2d_array[:, None, :] - second_2d_array[None, :, :])**2, dim=-1) + distances = torch.sum( + (first_2d_array[:, None, :] - second_2d_array[None, :, :]) ** 2, dim=-1 + ) matching_indices = (distances < tol).nonzero() - assert torch.allclose(matching_indices[:, 0], torch.arange(number_of_vectors)), \ - "There isn't exactly a one-to-one match between the two arrays" + assert torch.allclose( + matching_indices[:, 0], torch.arange(number_of_vectors) + ), "There isn't exactly a one-to-one match between the two arrays" permutation_indices = matching_indices[:, 1] diff --git a/tests/generators/conftest.py b/tests/generators/conftest.py index bed0b402..7699d60d 100644 --- a/tests/generators/conftest.py +++ b/tests/generators/conftest.py @@ -2,15 +2,19 @@ import pytest import torch -from crystal_diffusion.models.score_networks import (ScoreNetwork, - ScoreNetworkParameters) -from crystal_diffusion.namespace import NOISY_RELATIVE_COORDINATES + +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks import ( + ScoreNetwork, ScoreNetworkParameters) +from diffusion_for_multi_scale_molecular_dynamics.namespace import \ + NOISY_RELATIVE_COORDINATES class FakeScoreNetwork(ScoreNetwork): """A fake, smooth score network for the ODE solver.""" - def _forward_unchecked(self, batch: Dict[AnyStr, torch.Tensor], conditional: bool = False) -> torch.Tensor: + def _forward_unchecked( + self, batch: Dict[AnyStr, torch.Tensor], conditional: bool = False + ) -> torch.Tensor: return batch[NOISY_RELATIVE_COORDINATES] @@ -35,7 +39,9 @@ def spatial_dimension(self, request): @pytest.fixture() def unit_cell_sample(self, unit_cell_size, spatial_dimension, number_of_samples): - return torch.diag(torch.Tensor([unit_cell_size] * spatial_dimension)).repeat(number_of_samples, 1, 1) + return torch.diag(torch.Tensor([unit_cell_size] * spatial_dimension)).repeat( + number_of_samples, 1, 1 + ) @pytest.fixture() def cell_dimensions(self, unit_cell_size, spatial_dimension): @@ -43,4 +49,8 @@ def cell_dimensions(self, unit_cell_size, spatial_dimension): @pytest.fixture() def sigma_normalized_score_network(self, spatial_dimension): - return FakeScoreNetwork(ScoreNetworkParameters(architecture='dummy', spatial_dimension=spatial_dimension)) + return FakeScoreNetwork( + ScoreNetworkParameters( + architecture="dummy", spatial_dimension=spatial_dimension + ) + ) diff --git a/tests/generators/test_constrained_langevin_generator.py b/tests/generators/test_constrained_langevin_generator.py index 035634f4..d1aa431a 100644 --- a/tests/generators/test_constrained_langevin_generator.py +++ b/tests/generators/test_constrained_langevin_generator.py @@ -1,9 +1,9 @@ import einops import pytest import torch -from crystal_diffusion.generators.constrained_langevin_generator import ( - ConstrainedLangevinGenerator, ConstrainedLangevinGeneratorParameters) +from diffusion_for_multi_scale_molecular_dynamics.generators.constrained_langevin_generator import ( + ConstrainedLangevinGenerator, ConstrainedLangevinGeneratorParameters) from tests.generators.test_langevin_generator import TestLangevinGenerator @@ -15,40 +15,61 @@ def constrained_relative_coordinates(self, number_of_atoms, spatial_dimension): return torch.rand(number_of_constraints, spatial_dimension).numpy() @pytest.fixture() - def sampling_parameters(self, number_of_atoms, spatial_dimension, number_of_samples, cell_dimensions, - number_of_corrector_steps, unit_cell_size, constrained_relative_coordinates): + def sampling_parameters( + self, + number_of_atoms, + spatial_dimension, + number_of_samples, + cell_dimensions, + number_of_corrector_steps, + unit_cell_size, + constrained_relative_coordinates, + ): sampling_parameters = ConstrainedLangevinGeneratorParameters( number_of_corrector_steps=number_of_corrector_steps, number_of_atoms=number_of_atoms, number_of_samples=number_of_samples, cell_dimensions=cell_dimensions, spatial_dimension=spatial_dimension, - constrained_relative_coordinates=constrained_relative_coordinates) + constrained_relative_coordinates=constrained_relative_coordinates, + ) return sampling_parameters @pytest.fixture() - def pc_generator(self, noise_parameters, sampling_parameters, sigma_normalized_score_network): + def pc_generator( + self, noise_parameters, sampling_parameters, sigma_normalized_score_network + ): generator = ConstrainedLangevinGenerator( noise_parameters=noise_parameters, sampling_parameters=sampling_parameters, - sigma_normalized_score_network=sigma_normalized_score_network) + sigma_normalized_score_network=sigma_normalized_score_network, + ) return generator @pytest.fixture() def x(self, number_of_samples, number_of_atoms, spatial_dimension, device): - return torch.rand(number_of_samples, number_of_atoms, spatial_dimension).to(device) + return torch.rand(number_of_samples, number_of_atoms, spatial_dimension).to( + device + ) - def test_apply_constraint(self, pc_generator, x, constrained_relative_coordinates, device): + def test_apply_constraint( + self, pc_generator, x, constrained_relative_coordinates, device + ): batch_size = x.shape[0] original_x = torch.clone(x) pc_generator._apply_constraint(x, device) number_of_constraints = len(constrained_relative_coordinates) - constrained_x = einops.repeat(torch.from_numpy(constrained_relative_coordinates).to(device), - "n d -> b n d", b=batch_size) + constrained_x = einops.repeat( + torch.from_numpy(constrained_relative_coordinates).to(device), + "n d -> b n d", + b=batch_size, + ) torch.testing.assert_close(x[:, :number_of_constraints], constrained_x) - torch.testing.assert_close(x[:, number_of_constraints:], original_x[:, number_of_constraints:]) + torch.testing.assert_close( + x[:, number_of_constraints:], original_x[:, number_of_constraints:] + ) diff --git a/tests/generators/test_langevin_generator.py b/tests/generators/test_langevin_generator.py index 18e2190f..c1a4c3d0 100644 --- a/tests/generators/test_langevin_generator.py +++ b/tests/generators/test_langevin_generator.py @@ -1,13 +1,14 @@ import pytest import torch -from crystal_diffusion.generators.langevin_generator import LangevinGenerator -from crystal_diffusion.generators.predictor_corrector_position_generator import \ + +from diffusion_for_multi_scale_molecular_dynamics.generators.langevin_generator import \ + LangevinGenerator +from diffusion_for_multi_scale_molecular_dynamics.generators.predictor_corrector_position_generator import \ PredictorCorrectorSamplingParameters -from crystal_diffusion.utils.basis_transformations import \ +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell -from src.crystal_diffusion.samplers.variance_sampler import ( +from src.diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import ( ExplodingVarianceSampler, NoiseParameters) - from tests.generators.conftest import BaseTestGenerator @@ -23,42 +24,68 @@ def total_time_steps(self, request): @pytest.fixture() def noise_parameters(self, total_time_steps): - noise_parameters = NoiseParameters(total_time_steps=total_time_steps, - time_delta=0.1, - sigma_min=0.15, - corrector_step_epsilon=0.25) + noise_parameters = NoiseParameters( + total_time_steps=total_time_steps, + time_delta=0.1, + sigma_min=0.15, + corrector_step_epsilon=0.25, + ) return noise_parameters @pytest.fixture() - def sampling_parameters(self, number_of_atoms, spatial_dimension, cell_dimensions, number_of_samples, - number_of_corrector_steps, unit_cell_size): - sampling_parameters = PredictorCorrectorSamplingParameters(number_of_corrector_steps=number_of_corrector_steps, - number_of_atoms=number_of_atoms, - number_of_samples=number_of_samples, - cell_dimensions=cell_dimensions, - spatial_dimension=spatial_dimension) + def sampling_parameters( + self, + number_of_atoms, + spatial_dimension, + cell_dimensions, + number_of_samples, + number_of_corrector_steps, + unit_cell_size, + ): + sampling_parameters = PredictorCorrectorSamplingParameters( + number_of_corrector_steps=number_of_corrector_steps, + number_of_atoms=number_of_atoms, + number_of_samples=number_of_samples, + cell_dimensions=cell_dimensions, + spatial_dimension=spatial_dimension, + ) return sampling_parameters @pytest.fixture() - def pc_generator(self, noise_parameters, sampling_parameters, sigma_normalized_score_network): - generator = LangevinGenerator(noise_parameters=noise_parameters, - sampling_parameters=sampling_parameters, - sigma_normalized_score_network=sigma_normalized_score_network) + def pc_generator( + self, noise_parameters, sampling_parameters, sigma_normalized_score_network + ): + generator = LangevinGenerator( + noise_parameters=noise_parameters, + sampling_parameters=sampling_parameters, + sigma_normalized_score_network=sigma_normalized_score_network, + ) return generator - def test_smoke_sample(self, pc_generator, device, number_of_samples, unit_cell_sample): + def test_smoke_sample( + self, pc_generator, device, number_of_samples, unit_cell_sample + ): # Just a smoke test that we can sample without crashing. pc_generator.sample(number_of_samples, device, unit_cell_sample) @pytest.fixture() def x_i(self, number_of_samples, number_of_atoms, spatial_dimension, device): return map_relative_coordinates_to_unit_cell( - torch.rand(number_of_samples, number_of_atoms, spatial_dimension)).to(device) - - def test_predictor_step(self, mocker, pc_generator, noise_parameters, x_i, total_time_steps, number_of_samples, - unit_cell_sample): + torch.rand(number_of_samples, number_of_atoms, spatial_dimension) + ).to(device) + + def test_predictor_step( + self, + mocker, + pc_generator, + noise_parameters, + x_i, + total_time_steps, + number_of_samples, + unit_cell_sample, + ): sampler = ExplodingVarianceSampler(noise_parameters) noise, _ = sampler.get_all_sampling_parameters() @@ -71,7 +98,9 @@ def test_predictor_step(self, mocker, pc_generator, noise_parameters, x_i, total mocker.patch.object(pc_generator, "_draw_gaussian_sample", return_value=z) for index_i in range(1, total_time_steps + 1): - computed_sample = pc_generator.predictor_step(x_i, index_i, unit_cell_sample, forces) + computed_sample = pc_generator.predictor_step( + x_i, index_i, unit_cell_sample, forces + ) sigma_i = list_sigma[index_i - 1] t_i = list_time[index_i - 1] @@ -82,14 +111,27 @@ def test_predictor_step(self, mocker, pc_generator, noise_parameters, x_i, total g2 = sigma_i**2 - sigma_im1**2 - s_i = pc_generator._get_sigma_normalized_scores(x_i, t_i, sigma_i, unit_cell_sample, forces) / sigma_i + s_i = ( + pc_generator._get_sigma_normalized_scores( + x_i, t_i, sigma_i, unit_cell_sample, forces + ) + / sigma_i + ) expected_sample = x_i + g2 * s_i + torch.sqrt(g2) * z torch.testing.assert_close(computed_sample, expected_sample) - def test_corrector_step(self, mocker, pc_generator, noise_parameters, x_i, total_time_steps, number_of_samples, - unit_cell_sample): + def test_corrector_step( + self, + mocker, + pc_generator, + noise_parameters, + x_i, + total_time_steps, + number_of_samples, + unit_cell_sample, + ): sampler = ExplodingVarianceSampler(noise_parameters) noise, _ = sampler.get_all_sampling_parameters() @@ -104,19 +146,26 @@ def test_corrector_step(self, mocker, pc_generator, noise_parameters, x_i, total mocker.patch.object(pc_generator, "_draw_gaussian_sample", return_value=z) for index_i in range(0, total_time_steps): - computed_sample = pc_generator.corrector_step(x_i, index_i, unit_cell_sample, forces) + computed_sample = pc_generator.corrector_step( + x_i, index_i, unit_cell_sample, forces + ) if index_i == 0: sigma_i = sigma_min - t_i = 0. + t_i = 0.0 else: sigma_i = list_sigma[index_i - 1] t_i = list_time[index_i - 1] eps_i = 0.5 * epsilon * sigma_i**2 / sigma_1**2 - s_i = pc_generator._get_sigma_normalized_scores(x_i, t_i, sigma_i, unit_cell_sample, forces) / sigma_i + s_i = ( + pc_generator._get_sigma_normalized_scores( + x_i, t_i, sigma_i, unit_cell_sample, forces + ) + / sigma_i + ) - expected_sample = x_i + eps_i * s_i + torch.sqrt(2. * eps_i) * z + expected_sample = x_i + eps_i * s_i + torch.sqrt(2.0 * eps_i) * z torch.testing.assert_close(computed_sample, expected_sample) diff --git a/tests/generators/test_ode_position_generator.py b/tests/generators/test_ode_position_generator.py index 1564aba7..711ad09e 100644 --- a/tests/generators/test_ode_position_generator.py +++ b/tests/generators/test_ode_position_generator.py @@ -1,10 +1,10 @@ import pytest import torch -from crystal_diffusion.generators.ode_position_generator import ( + +from diffusion_for_multi_scale_molecular_dynamics.generators.ode_position_generator import ( ExplodingVarianceODEPositionGenerator, ODESamplingParameters) -from src.crystal_diffusion.samplers.variance_sampler import ( +from src.diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import ( ExplodingVarianceSampler, NoiseParameters) - from tests.generators.conftest import BaseTestGenerator @@ -16,29 +16,45 @@ class TestExplodingVarianceODEPositionGenerator(BaseTestGenerator): @pytest.fixture() def noise_parameters(self, total_time_steps, sigma_min): - return NoiseParameters(total_time_steps=total_time_steps, time_delta=0., sigma_min=sigma_min) + return NoiseParameters( + total_time_steps=total_time_steps, time_delta=0.0, sigma_min=sigma_min + ) @pytest.fixture() - def sampling_parameters(self, number_of_atoms, spatial_dimension, cell_dimensions, - number_of_samples, record_samples): - sampling_parameters = ODESamplingParameters(number_of_atoms=number_of_atoms, - spatial_dimension=spatial_dimension, - number_of_samples=number_of_samples, - cell_dimensions=cell_dimensions, - record_samples=record_samples) + def sampling_parameters( + self, + number_of_atoms, + spatial_dimension, + cell_dimensions, + number_of_samples, + record_samples, + ): + sampling_parameters = ODESamplingParameters( + number_of_atoms=number_of_atoms, + spatial_dimension=spatial_dimension, + number_of_samples=number_of_samples, + cell_dimensions=cell_dimensions, + record_samples=record_samples, + ) return sampling_parameters @pytest.fixture() - def ode_generator(self, noise_parameters, sampling_parameters, sigma_normalized_score_network): - generator = ExplodingVarianceODEPositionGenerator(noise_parameters=noise_parameters, - sampling_parameters=sampling_parameters, - sigma_normalized_score_network=sigma_normalized_score_network) + def ode_generator( + self, noise_parameters, sampling_parameters, sigma_normalized_score_network + ): + generator = ExplodingVarianceODEPositionGenerator( + noise_parameters=noise_parameters, + sampling_parameters=sampling_parameters, + sigma_normalized_score_network=sigma_normalized_score_network, + ) return generator def test_get_exploding_variance_sigma(self, ode_generator, noise_parameters): times = ExplodingVarianceSampler._get_time_array(noise_parameters) - expected_sigmas = ExplodingVarianceSampler._create_sigma_array(noise_parameters, times) + expected_sigmas = ExplodingVarianceSampler._create_sigma_array( + noise_parameters, times + ) computed_sigmas = ode_generator._get_exploding_variance_sigma(times) torch.testing.assert_close(expected_sigmas, computed_sigmas) @@ -46,17 +62,32 @@ def test_get_ode_prefactor(self, ode_generator, noise_parameters): times = ExplodingVarianceSampler._get_time_array(noise_parameters) sigmas = ode_generator._get_exploding_variance_sigma(times) - sig_ratio = torch.tensor(noise_parameters.sigma_max / noise_parameters.sigma_min) + sig_ratio = torch.tensor( + noise_parameters.sigma_max / noise_parameters.sigma_min + ) expected_ode_prefactor = torch.log(sig_ratio) * sigmas computed_ode_prefactor = ode_generator._get_ode_prefactor(sigmas) torch.testing.assert_close(expected_ode_prefactor, computed_ode_prefactor) - def test_smoke_sample(self, ode_generator, device, number_of_samples, - number_of_atoms, spatial_dimension, unit_cell_sample): + def test_smoke_sample( + self, + ode_generator, + device, + number_of_samples, + number_of_atoms, + spatial_dimension, + unit_cell_sample, + ): # Just a smoke test that we can sample without crashing. - relative_coordinates = ode_generator.sample(number_of_samples, device, unit_cell_sample) + relative_coordinates = ode_generator.sample( + number_of_samples, device, unit_cell_sample + ) - assert relative_coordinates.shape == (number_of_samples, number_of_atoms, spatial_dimension) + assert relative_coordinates.shape == ( + number_of_samples, + number_of_atoms, + spatial_dimension, + ) - assert relative_coordinates.min() >= 0. - assert relative_coordinates.max() < 1. + assert relative_coordinates.min() >= 0.0 + assert relative_coordinates.max() < 1.0 diff --git a/tests/generators/test_predictor_corrector_position_generator.py b/tests/generators/test_predictor_corrector_position_generator.py index b8ada474..49dd5f0f 100644 --- a/tests/generators/test_predictor_corrector_position_generator.py +++ b/tests/generators/test_predictor_corrector_position_generator.py @@ -1,10 +1,10 @@ import pytest import torch -from crystal_diffusion.generators.predictor_corrector_position_generator import \ + +from diffusion_for_multi_scale_molecular_dynamics.generators.predictor_corrector_position_generator import \ PredictorCorrectorPositionGenerator -from crystal_diffusion.utils.basis_transformations import \ +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell - from tests.generators.conftest import BaseTestGenerator @@ -18,18 +18,26 @@ def __init__( spatial_dimension: int, initial_sample: torch.Tensor, ): - super().__init__(number_of_discretization_steps, number_of_corrector_steps, spatial_dimension) + super().__init__( + number_of_discretization_steps, number_of_corrector_steps, spatial_dimension + ) self.initial_sample = initial_sample def initialize(self, number_of_samples: int): return self.initial_sample - def predictor_step(self, x_ip1: torch.Tensor, ip1: int, unit_cell: torch.Tensor, forces: torch.Tensor - ) -> torch.Tensor: + def predictor_step( + self, + x_ip1: torch.Tensor, + ip1: int, + unit_cell: torch.Tensor, + forces: torch.Tensor, + ) -> torch.Tensor: return 1.2 * x_ip1 + 3.4 + ip1 / 111.0 - def corrector_step(self, x_i: torch.Tensor, i: int, unit_cell: torch.Tensor, forces: torch.Tensor - ) -> torch.Tensor: + def corrector_step( + self, x_i: torch.Tensor, i: int, unit_cell: torch.Tensor, forces: torch.Tensor + ) -> torch.Tensor: return 0.56 * x_i + 7.89 + i / 117.0 @@ -46,10 +54,17 @@ def initial_sample(self, number_of_samples, number_of_atoms, spatial_dimension): @pytest.fixture def generator( - self, number_of_discretization_steps, number_of_corrector_steps, spatial_dimension, initial_sample + self, + number_of_discretization_steps, + number_of_corrector_steps, + spatial_dimension, + initial_sample, ): generator = FakePCGenerator( - number_of_discretization_steps, number_of_corrector_steps, spatial_dimension, initial_sample + number_of_discretization_steps, + number_of_corrector_steps, + spatial_dimension, + initial_sample, ) return generator @@ -69,14 +84,24 @@ def expected_samples( noisy_sample = map_relative_coordinates_to_unit_cell(initial_sample) x_ip1 = noisy_sample for i in list_i: - xi = map_relative_coordinates_to_unit_cell(generator.predictor_step(x_ip1, i + 1, unit_cell_sample, - torch.zeros_like(x_ip1))) + xi = map_relative_coordinates_to_unit_cell( + generator.predictor_step( + x_ip1, i + 1, unit_cell_sample, torch.zeros_like(x_ip1) + ) + ) for _ in list_j: - xi = map_relative_coordinates_to_unit_cell(generator.corrector_step(xi, i, unit_cell_sample, - torch.zeros_like(xi))) + xi = map_relative_coordinates_to_unit_cell( + generator.corrector_step( + xi, i, unit_cell_sample, torch.zeros_like(xi) + ) + ) x_ip1 = xi return xi - def test_sample(self, generator, number_of_samples, expected_samples, unit_cell_sample): - computed_samples = generator.sample(number_of_samples, torch.device('cpu'), unit_cell_sample) + def test_sample( + self, generator, number_of_samples, expected_samples, unit_cell_sample + ): + computed_samples = generator.sample( + number_of_samples, torch.device("cpu"), unit_cell_sample + ) torch.testing.assert_close(expected_samples, computed_samples) diff --git a/tests/generators/test_sde_position_generator.py b/tests/generators/test_sde_position_generator.py index 7950e90d..9cb36372 100644 --- a/tests/generators/test_sde_position_generator.py +++ b/tests/generators/test_sde_position_generator.py @@ -1,10 +1,10 @@ import pytest import torch -from crystal_diffusion.generators.sde_position_generator import ( + +from diffusion_for_multi_scale_molecular_dynamics.generators.sde_position_generator import ( SDE, ExplodingVarianceSDEPositionGenerator, SDESamplingParameters) -from src.crystal_diffusion.samplers.variance_sampler import ( +from src.diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import ( ExplodingVarianceSampler, NoiseParameters) - from tests.generators.conftest import BaseTestGenerator @@ -14,40 +14,64 @@ class TestExplodingVarianceSDEPositionGenerator(BaseTestGenerator): @pytest.fixture() def initial_diffusion_time(self): - return torch.tensor(0.) + return torch.tensor(0.0) @pytest.fixture() def final_diffusion_time(self): - return torch.tensor(1.) + return torch.tensor(1.0) @pytest.fixture() def noise_parameters(self, total_time_steps, sigma_min): - return NoiseParameters(total_time_steps=total_time_steps, time_delta=0., sigma_min=sigma_min) + return NoiseParameters( + total_time_steps=total_time_steps, time_delta=0.0, sigma_min=sigma_min + ) @pytest.fixture() - def sampling_parameters(self, number_of_atoms, spatial_dimension, cell_dimensions, - number_of_samples, record_samples): - sampling_parameters = SDESamplingParameters(number_of_atoms=number_of_atoms, - spatial_dimension=spatial_dimension, - number_of_samples=number_of_samples, - cell_dimensions=cell_dimensions, - record_samples=record_samples) + def sampling_parameters( + self, + number_of_atoms, + spatial_dimension, + cell_dimensions, + number_of_samples, + record_samples, + ): + sampling_parameters = SDESamplingParameters( + number_of_atoms=number_of_atoms, + spatial_dimension=spatial_dimension, + number_of_samples=number_of_samples, + cell_dimensions=cell_dimensions, + record_samples=record_samples, + ) return sampling_parameters @pytest.fixture() - def sde(self, noise_parameters, sampling_parameters, sigma_normalized_score_network, unit_cell_sample, - initial_diffusion_time, final_diffusion_time): - sde = SDE(noise_parameters=noise_parameters, - sampling_parameters=sampling_parameters, - sigma_normalized_score_network=sigma_normalized_score_network, - unit_cells=unit_cell_sample, - initial_diffusion_time=initial_diffusion_time, - final_diffusion_time=final_diffusion_time) + def sde( + self, + noise_parameters, + sampling_parameters, + sigma_normalized_score_network, + unit_cell_sample, + initial_diffusion_time, + final_diffusion_time, + ): + sde = SDE( + noise_parameters=noise_parameters, + sampling_parameters=sampling_parameters, + sigma_normalized_score_network=sigma_normalized_score_network, + unit_cells=unit_cell_sample, + initial_diffusion_time=initial_diffusion_time, + final_diffusion_time=final_diffusion_time, + ) return sde - def test_sde_get_diffusion_time(self, sde, initial_diffusion_time, final_diffusion_time): + def test_sde_get_diffusion_time( + self, sde, initial_diffusion_time, final_diffusion_time + ): - diffusion_time = (initial_diffusion_time + torch.rand(1) * (final_diffusion_time - initial_diffusion_time))[0] + diffusion_time = ( + initial_diffusion_time + + torch.rand(1) * (final_diffusion_time - initial_diffusion_time) + )[0] sde_time = final_diffusion_time - diffusion_time @@ -55,15 +79,25 @@ def test_sde_get_diffusion_time(self, sde, initial_diffusion_time, final_diffusi torch.testing.assert_close(computed_diffusion_time, diffusion_time) - def test_sde_g_squared(self, sde, noise_parameters, initial_diffusion_time, final_diffusion_time): + def test_sde_g_squared( + self, sde, noise_parameters, initial_diffusion_time, final_diffusion_time + ): - time_array = initial_diffusion_time + torch.rand(1) * (final_diffusion_time - initial_diffusion_time) + time_array = initial_diffusion_time + torch.rand(1) * ( + final_diffusion_time - initial_diffusion_time + ) - sigma = ExplodingVarianceSampler._create_sigma_array(noise_parameters=noise_parameters, - time_array=time_array)[0] + sigma = ExplodingVarianceSampler._create_sigma_array( + noise_parameters=noise_parameters, time_array=time_array + )[0] - expected_g_squared = (2. * sigma**2 - * torch.log(torch.tensor(noise_parameters.sigma_max / noise_parameters.sigma_min))) + expected_g_squared = ( + 2.0 + * sigma**2 + * torch.log( + torch.tensor(noise_parameters.sigma_max / noise_parameters.sigma_min) + ) + ) diffusion_time = time_array[0] @@ -72,18 +106,35 @@ def test_sde_g_squared(self, sde, noise_parameters, initial_diffusion_time, fina torch.testing.assert_close(computed_g_squared, expected_g_squared) @pytest.fixture() - def sde_generator(self, noise_parameters, sampling_parameters, sigma_normalized_score_network): - generator = ExplodingVarianceSDEPositionGenerator(noise_parameters=noise_parameters, - sampling_parameters=sampling_parameters, - sigma_normalized_score_network=sigma_normalized_score_network) + def sde_generator( + self, noise_parameters, sampling_parameters, sigma_normalized_score_network + ): + generator = ExplodingVarianceSDEPositionGenerator( + noise_parameters=noise_parameters, + sampling_parameters=sampling_parameters, + sigma_normalized_score_network=sigma_normalized_score_network, + ) return generator - def test_smoke_sample(self, sde_generator, device, number_of_samples, - number_of_atoms, spatial_dimension, unit_cell_sample): + def test_smoke_sample( + self, + sde_generator, + device, + number_of_samples, + number_of_atoms, + spatial_dimension, + unit_cell_sample, + ): # Just a smoke test that we can sample without crashing. - relative_coordinates = sde_generator.sample(number_of_samples, device, unit_cell_sample) - - assert relative_coordinates.shape == (number_of_samples, number_of_atoms, spatial_dimension) - - assert relative_coordinates.min() >= 0. - assert relative_coordinates.max() < 1. + relative_coordinates = sde_generator.sample( + number_of_samples, device, unit_cell_sample + ) + + assert relative_coordinates.shape == ( + number_of_samples, + number_of_atoms, + spatial_dimension, + ) + + assert relative_coordinates.min() >= 0.0 + assert relative_coordinates.max() < 1.0 diff --git a/tests/models/score_network/test_force_field_augmented_score_network.py b/tests/models/score_network/test_force_field_augmented_score_network.py index 498b039b..573fe18e 100644 --- a/tests/models/score_network/test_force_field_augmented_score_network.py +++ b/tests/models/score_network/test_force_field_augmented_score_network.py @@ -1,12 +1,12 @@ import pytest import torch -from crystal_diffusion.models.score_networks.force_field_augmented_score_network import ( + +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.force_field_augmented_score_network import ( ForceFieldAugmentedScoreNetwork, ForceFieldParameters) -from crystal_diffusion.models.score_networks.mlp_score_network import ( +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.mlp_score_network import ( MLPScoreNetwork, MLPScoreNetworkParameters) -from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, - NOISY_RELATIVE_COORDINATES, TIME, - UNIT_CELL) +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) @pytest.mark.parametrize("number_of_atoms", [4, 8, 16]) @@ -144,8 +144,7 @@ def test_get_cartesian_pseudo_forces( ) ) cartesian_pseudo_force_contributions = ( - force_field_augmented_score_network._get_cartesian_pseudo_forces_contributions( - cartesian_displacements)) + force_field_augmented_score_network._get_cartesian_pseudo_forces_contributions(cartesian_displacements)) computed_cartesian_pseudo_forces = ( force_field_augmented_score_network._get_cartesian_pseudo_forces( diff --git a/tests/models/score_network/test_score_network.py b/tests/models/score_network/test_score_network.py index d9a60e94..2465523c 100644 --- a/tests/models/score_network/test_score_network.py +++ b/tests/models/score_network/test_score_network.py @@ -6,25 +6,25 @@ import numpy as np import pytest import torch -from crystal_diffusion.models.score_networks.diffusion_mace_score_network import ( + +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.diffusion_mace_score_network import ( DiffusionMACEScoreNetwork, DiffusionMACEScoreNetworkParameters) -from crystal_diffusion.models.score_networks.egnn_score_network import ( +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.egnn_score_network import ( EGNNScoreNetwork, EGNNScoreNetworkParameters) -from crystal_diffusion.models.score_networks.mace_score_network import ( +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.mace_score_network import ( MACEScoreNetwork, MACEScoreNetworkParameters) -from crystal_diffusion.models.score_networks.mlp_score_network import ( +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.mlp_score_network import ( MLPScoreNetwork, MLPScoreNetworkParameters) -from crystal_diffusion.models.score_networks.score_network import ( +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_network import ( ScoreNetwork, ScoreNetworkParameters) -from crystal_diffusion.models.score_networks.score_network_factory import \ +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_network_factory import \ create_score_network_parameters -from crystal_diffusion.models.score_networks.score_prediction_head import ( +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_prediction_head import ( MaceEquivariantScorePredictionHeadParameters, MaceMLPScorePredictionHeadParameters) -from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, - NOISY_RELATIVE_COORDINATES, TIME, - UNIT_CELL) -from crystal_diffusion.utils.basis_transformations import \ +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell @@ -53,8 +53,11 @@ def set_random_seed(self): @pytest.fixture() def base_score_network(self, spatial_dimension): - return ScoreNetwork(ScoreNetworkParameters(architecture='dummy', - spatial_dimension=spatial_dimension)) + return ScoreNetwork( + ScoreNetworkParameters( + architecture="dummy", spatial_dimension=spatial_dimension + ) + ) @pytest.fixture() def good_batch(self, spatial_dimension): @@ -63,7 +66,12 @@ def good_batch(self, spatial_dimension): times = torch.rand(batch_size, 1) noises = torch.rand(batch_size, 1) unit_cell = torch.rand(batch_size, spatial_dimension, spatial_dimension) - return {NOISY_RELATIVE_COORDINATES: relative_coordinates, TIME: times, NOISE: noises, UNIT_CELL: unit_cell} + return { + NOISY_RELATIVE_COORDINATES: relative_coordinates, + TIME: times, + NOISE: noises, + UNIT_CELL: unit_cell, + } @pytest.fixture() def bad_batch(self, good_batch, problem): @@ -72,13 +80,16 @@ def bad_batch(self, good_batch, problem): match problem: case "position_name": - bad_batch_dict['bad_position_name'] = bad_batch_dict[NOISY_RELATIVE_COORDINATES] + bad_batch_dict["bad_position_name"] = bad_batch_dict[ + NOISY_RELATIVE_COORDINATES + ] del bad_batch_dict[NOISY_RELATIVE_COORDINATES] case "position_shape": shape = bad_batch_dict[NOISY_RELATIVE_COORDINATES].shape - bad_batch_dict[NOISY_RELATIVE_COORDINATES] = \ - bad_batch_dict[NOISY_RELATIVE_COORDINATES].reshape(shape[0], shape[1] // 2, shape[2] * 2) + bad_batch_dict[NOISY_RELATIVE_COORDINATES] = bad_batch_dict[ + NOISY_RELATIVE_COORDINATES + ].reshape(shape[0], shape[1] // 2, shape[2] * 2) case "position_range1": bad_batch_dict[NOISY_RELATIVE_COORDINATES][0, 0, 0] = 1.01 @@ -87,20 +98,24 @@ def bad_batch(self, good_batch, problem): bad_batch_dict[NOISY_RELATIVE_COORDINATES][1, 0, 0] = -0.01 case "time_name": - bad_batch_dict['bad_time_name'] = bad_batch_dict[TIME] + bad_batch_dict["bad_time_name"] = bad_batch_dict[TIME] del bad_batch_dict[TIME] case "time_shape": shape = bad_batch_dict[TIME].shape - bad_batch_dict[TIME] = bad_batch_dict[TIME].reshape(shape[0] // 2, shape[1] * 2) + bad_batch_dict[TIME] = bad_batch_dict[TIME].reshape( + shape[0] // 2, shape[1] * 2 + ) case "noise_name": - bad_batch_dict['bad_noise_name'] = bad_batch_dict[NOISE] + bad_batch_dict["bad_noise_name"] = bad_batch_dict[NOISE] del bad_batch_dict[NOISE] case "noise_shape": shape = bad_batch_dict[NOISE].shape - bad_batch_dict[NOISE] = bad_batch_dict[NOISE].reshape(shape[0] // 2, shape[1] * 2) + bad_batch_dict[NOISE] = bad_batch_dict[NOISE].reshape( + shape[0] // 2, shape[1] * 2 + ) case "time_range1": bad_batch_dict[TIME][5, 0] = 2.00 @@ -108,21 +123,37 @@ def bad_batch(self, good_batch, problem): bad_batch_dict[TIME][0, 0] = -0.05 case "cell_name": - bad_batch_dict['bad_unit_cell_key'] = bad_batch_dict[UNIT_CELL] + bad_batch_dict["bad_unit_cell_key"] = bad_batch_dict[UNIT_CELL] del bad_batch_dict[UNIT_CELL] case "cell_shape": shape = bad_batch_dict[UNIT_CELL].shape - bad_batch_dict[UNIT_CELL] = bad_batch_dict[UNIT_CELL].reshape(shape[0] // 2, shape[1] * 2, shape[2]) + bad_batch_dict[UNIT_CELL] = bad_batch_dict[UNIT_CELL].reshape( + shape[0] // 2, shape[1] * 2, shape[2] + ) return bad_batch_dict def test_check_batch_good(self, base_score_network, good_batch): base_score_network._check_batch(good_batch) - @pytest.mark.parametrize('problem', ['position_name', 'time_name', 'position_shape', - 'time_shape', 'noise_name', 'noise_shape', 'position_range1', - 'position_range2', 'time_range1', 'time_range2', 'cell_name', 'cell_shape']) + @pytest.mark.parametrize( + "problem", + [ + "position_name", + "time_name", + "position_shape", + "time_shape", + "noise_name", + "noise_shape", + "position_range1", + "position_range2", + "time_range1", + "time_range2", + "cell_name", + "cell_shape", + ], + ) def test_check_batch_bad(self, base_score_network, bad_batch): with pytest.raises(AssertionError): base_score_network._check_batch(bad_batch) @@ -137,11 +168,15 @@ class BaseTestScoreNetwork: @pytest.fixture() def score_network_parameters(self, *args): - raise NotImplementedError("This fixture must be implemented in the derived class.") + raise NotImplementedError( + "This fixture must be implemented in the derived class." + ) @pytest.fixture() def score_network(self, *args): - raise NotImplementedError("This fixture must be implemented in the derived class.") + raise NotImplementedError( + "This fixture must be implemented in the derived class." + ) @pytest.fixture(scope="class", autouse=True) def set_random_seed(self): @@ -158,18 +193,31 @@ def number_of_atoms(self): @pytest.fixture() def basis_vectors(self, batch_size, spatial_dimension): # orthogonal boxes with dimensions between 5 and 10. - orthogonal_boxes = torch.stack([torch.diag(5. + 5. * torch.rand(spatial_dimension)) for _ in range(batch_size)]) + orthogonal_boxes = torch.stack( + [ + torch.diag(5.0 + 5.0 * torch.rand(spatial_dimension)) + for _ in range(batch_size) + ] + ) # add a bit of noise to make the vectors not quite orthogonal - basis_vectors = orthogonal_boxes + 0.1 * torch.randn(batch_size, spatial_dimension, spatial_dimension) + basis_vectors = orthogonal_boxes + 0.1 * torch.randn( + batch_size, spatial_dimension, spatial_dimension + ) return basis_vectors @pytest.fixture - def relative_coordinates(self, batch_size, number_of_atoms, spatial_dimension, basis_vectors): - relative_coordinates = torch.rand(batch_size, number_of_atoms, spatial_dimension) + def relative_coordinates( + self, batch_size, number_of_atoms, spatial_dimension, basis_vectors + ): + relative_coordinates = torch.rand( + batch_size, number_of_atoms, spatial_dimension + ) return relative_coordinates @pytest.fixture - def cartesian_forces(self, batch_size, number_of_atoms, spatial_dimension, basis_vectors): + def cartesian_forces( + self, batch_size, number_of_atoms, spatial_dimension, basis_vectors + ): cartesian_forces = torch.rand(batch_size, number_of_atoms, spatial_dimension) return cartesian_forces @@ -187,16 +235,25 @@ def expected_score_shape(self, batch_size, number_of_atoms, spatial_dimension): return batch_size, number_of_atoms, spatial_dimension @pytest.fixture() - def batch(self, relative_coordinates, cartesian_forces, times, noises, basis_vectors): - return {NOISY_RELATIVE_COORDINATES: relative_coordinates, TIME: times, UNIT_CELL: basis_vectors, NOISE: noises, - CARTESIAN_FORCES: cartesian_forces} + def batch( + self, relative_coordinates, cartesian_forces, times, noises, basis_vectors + ): + return { + NOISY_RELATIVE_COORDINATES: relative_coordinates, + TIME: times, + UNIT_CELL: basis_vectors, + NOISE: noises, + CARTESIAN_FORCES: cartesian_forces, + } @pytest.fixture() def global_parameters_dictionary(self, spatial_dimension): return dict(spatial_dimension=spatial_dimension, irrelevant=123) @pytest.fixture() - def score_network_dictionary(self, score_network_parameters, global_parameters_dictionary): + def score_network_dictionary( + self, score_network_parameters, global_parameters_dictionary + ): dictionary = asdict(score_network_parameters) for key in global_parameters_dictionary.keys(): if key in dictionary: @@ -207,12 +264,18 @@ def test_output_shape(self, score_network, batch, expected_score_shape): scores = score_network(batch) assert scores.shape == expected_score_shape - def test_create_score_network_parameters(self, score_network_parameters, - score_network_dictionary, - global_parameters_dictionary): - computed_score_network_parameters = create_score_network_parameters(score_network_dictionary, - global_parameters_dictionary) - assert_parameters_are_the_same(computed_score_network_parameters, score_network_parameters) + def test_create_score_network_parameters( + self, + score_network_parameters, + score_network_dictionary, + global_parameters_dictionary, + ): + computed_score_network_parameters = create_score_network_parameters( + score_network_dictionary, global_parameters_dictionary + ) + assert_parameters_are_the_same( + computed_score_network_parameters, score_network_parameters + ) @pytest.mark.parametrize("spatial_dimension", [2, 3]) @@ -222,13 +285,21 @@ def test_create_score_network_parameters(self, score_network_parameters, class TestMLPScoreNetwork(BaseTestScoreNetwork): @pytest.fixture() - def score_network_parameters(self, number_of_atoms, spatial_dimension, - embedding_dimensions_size, n_hidden_dimensions, hidden_dimensions_size): - return MLPScoreNetworkParameters(spatial_dimension=spatial_dimension, - number_of_atoms=number_of_atoms, - embedding_dimensions_size=embedding_dimensions_size, - n_hidden_dimensions=n_hidden_dimensions, - hidden_dimensions_size=hidden_dimensions_size) + def score_network_parameters( + self, + number_of_atoms, + spatial_dimension, + embedding_dimensions_size, + n_hidden_dimensions, + hidden_dimensions_size, + ): + return MLPScoreNetworkParameters( + spatial_dimension=spatial_dimension, + number_of_atoms=number_of_atoms, + embedding_dimensions_size=embedding_dimensions_size, + n_hidden_dimensions=n_hidden_dimensions, + hidden_dimensions_size=hidden_dimensions_size, + ) @pytest.fixture() def score_network(self, score_network_parameters): @@ -241,18 +312,26 @@ def score_network(self, score_network_parameters): class TestMACEScoreNetworkMLPHead(BaseTestScoreNetwork): @pytest.fixture() - def prediction_head_parameters(self, spatial_dimension, n_hidden_dimensions, hidden_dimensions_size): - prediction_head_parameters = MaceMLPScorePredictionHeadParameters(spatial_dimension=spatial_dimension, - hidden_dimensions_size=hidden_dimensions_size, - n_hidden_dimensions=n_hidden_dimensions) + def prediction_head_parameters( + self, spatial_dimension, n_hidden_dimensions, hidden_dimensions_size + ): + prediction_head_parameters = MaceMLPScorePredictionHeadParameters( + spatial_dimension=spatial_dimension, + hidden_dimensions_size=hidden_dimensions_size, + n_hidden_dimensions=n_hidden_dimensions, + ) return prediction_head_parameters @pytest.fixture() - def score_network_parameters(self, number_of_atoms, spatial_dimension, prediction_head_parameters): - return MACEScoreNetworkParameters(spatial_dimension=spatial_dimension, - number_of_atoms=number_of_atoms, - r_max=3.0, - prediction_head_parameters=prediction_head_parameters) + def score_network_parameters( + self, number_of_atoms, spatial_dimension, prediction_head_parameters + ): + return MACEScoreNetworkParameters( + spatial_dimension=spatial_dimension, + number_of_atoms=number_of_atoms, + r_max=3.0, + prediction_head_parameters=prediction_head_parameters, + ) @pytest.fixture() def score_network(self, score_network_parameters): @@ -263,16 +342,21 @@ def score_network(self, score_network_parameters): class TestMACEScoreNetworkEquivariantHead(BaseTestScoreNetwork): @pytest.fixture() def prediction_head_parameters(self, spatial_dimension): - prediction_head_parameters = MaceEquivariantScorePredictionHeadParameters(spatial_dimension=spatial_dimension, - number_of_layers=2) + prediction_head_parameters = MaceEquivariantScorePredictionHeadParameters( + spatial_dimension=spatial_dimension, number_of_layers=2 + ) return prediction_head_parameters @pytest.fixture() - def score_network_parameters(self, number_of_atoms, spatial_dimension, prediction_head_parameters): - return MACEScoreNetworkParameters(spatial_dimension=spatial_dimension, - number_of_atoms=number_of_atoms, - r_max=3.0, - prediction_head_parameters=prediction_head_parameters) + def score_network_parameters( + self, number_of_atoms, spatial_dimension, prediction_head_parameters + ): + return MACEScoreNetworkParameters( + spatial_dimension=spatial_dimension, + number_of_atoms=number_of_atoms, + r_max=3.0, + prediction_head_parameters=prediction_head_parameters, + ) @pytest.fixture() def score_network(self, score_network_parameters): @@ -283,17 +367,18 @@ def score_network(self, score_network_parameters): class TestDiffusionMACEScoreNetwork(BaseTestScoreNetwork): @pytest.fixture() def score_network_parameters(self, number_of_atoms, spatial_dimension): - return DiffusionMACEScoreNetworkParameters(spatial_dimension=spatial_dimension, - number_of_atoms=number_of_atoms, - r_max=3.0, - num_bessel=4, - num_polynomial_cutoff=3, - hidden_irreps="8x0e + 8x1o", - mlp_irreps="8x0e", - number_of_mlp_layers=1, - correlation=2, - radial_MLP=[8, 8, 8], - ) + return DiffusionMACEScoreNetworkParameters( + spatial_dimension=spatial_dimension, + number_of_atoms=number_of_atoms, + r_max=3.0, + num_bessel=4, + num_polynomial_cutoff=3, + hidden_irreps="8x0e + 8x1o", + mlp_irreps="8x0e", + number_of_mlp_layers=1, + correlation=2, + radial_MLP=[8, 8, 8], + ) @pytest.fixture() def score_network(self, score_network_parameters): @@ -320,7 +405,12 @@ def basis_vectors(self, batch_size, spatial_dimension): # The basis vectors should form a cube in order to test the equivariance of the current implementation # of the EGNN model. The octaheral point group only applies in this case! acell = 5.5 - cubes = torch.stack([torch.diag(acell * torch.ones(spatial_dimension)) for _ in range(batch_size)]) + cubes = torch.stack( + [ + torch.diag(acell * torch.ones(spatial_dimension)) + for _ in range(batch_size) + ] + ) return cubes @pytest.fixture(params=[("fully_connected", None), ("radial_cutoff", 3.0)]) @@ -335,8 +425,14 @@ def score_network(self, score_network_parameters): @pytest.fixture() def octahedral_point_group_symmetries(self): - permutations = [torch.diag(torch.ones(3))[[idx]] for idx in itertools.permutations([0, 1, 2])] - sign_changes = [torch.diag(torch.tensor(diag)) for diag in itertools.product([-1., 1.], repeat=3)] + permutations = [ + torch.diag(torch.ones(3))[[idx]] + for idx in itertools.permutations([0, 1, 2]) + ] + sign_changes = [ + torch.diag(torch.tensor(diag)) + for diag in itertools.product([-1.0, 1.0], repeat=3) + ] symmetries = [] for permutation in permutations: @@ -345,14 +441,20 @@ def octahedral_point_group_symmetries(self): return symmetries - @pytest.mark.parametrize("edges, radial_cutoff", [("fully_connected", 3.0), ("radial_cutoff", None)]) + @pytest.mark.parametrize( + "edges, radial_cutoff", [("fully_connected", 3.0), ("radial_cutoff", None)] + ) def test_score_network_parameters(self, edges, radial_cutoff): - score_network_parameters = EGNNScoreNetworkParameters(edges=edges, radial_cutoff=radial_cutoff) + score_network_parameters = EGNNScoreNetworkParameters( + edges=edges, radial_cutoff=radial_cutoff + ) with pytest.raises(AssertionError): # Check that the code crashes when inconsistent parameters are fed in. EGNNScoreNetwork(score_network_parameters) - def test_create_block_diagonal_projection_matrices(self, score_network, spatial_dimension): + def test_create_block_diagonal_projection_matrices( + self, score_network, spatial_dimension + ): expected_matrices = [] for space_idx in range(spatial_dimension): matrix = torch.zeros(2 * spatial_dimension, 2 * spatial_dimension) @@ -362,15 +464,18 @@ def test_create_block_diagonal_projection_matrices(self, score_network, spatial_ expected_matrices = torch.stack(expected_matrices) - computed_matrices = score_network._create_block_diagonal_projection_matrices(spatial_dimension) + computed_matrices = score_network._create_block_diagonal_projection_matrices( + spatial_dimension + ) torch.testing.assert_close(computed_matrices, expected_matrices) @pytest.fixture() def flat_relative_coordinates(self, batch): relative_coordinates = batch[NOISY_RELATIVE_COORDINATES] - flat_relative_coordinates = einops.rearrange(relative_coordinates, - "batch natom space -> (batch natom) space") + flat_relative_coordinates = einops.rearrange( + relative_coordinates, "batch natom space -> (batch natom) space" + ) return flat_relative_coordinates @pytest.fixture() @@ -389,18 +494,32 @@ def expected_euclidean_positions(self, flat_relative_coordinates): expected_euclidean_positions = torch.stack(expected_euclidean_positions) return expected_euclidean_positions - def test_get_euclidean_positions(self, score_network, flat_relative_coordinates, expected_euclidean_positions): - computed_euclidean_positions = score_network._get_euclidean_positions(flat_relative_coordinates) - torch.testing.assert_close(expected_euclidean_positions, computed_euclidean_positions) + def test_get_euclidean_positions( + self, score_network, flat_relative_coordinates, expected_euclidean_positions + ): + computed_euclidean_positions = score_network._get_euclidean_positions( + flat_relative_coordinates + ) + torch.testing.assert_close( + expected_euclidean_positions, computed_euclidean_positions + ) @pytest.fixture() def global_translations(self, batch_size, number_of_atoms, spatial_dimension): - translations = einops.repeat(torch.rand(batch_size, spatial_dimension), - "batch spatial_dimension -> batch natoms spatial_dimension", - natoms=number_of_atoms) + translations = einops.repeat( + torch.rand(batch_size, spatial_dimension), + "batch spatial_dimension -> batch natoms spatial_dimension", + natoms=number_of_atoms, + ) return translations - def test_equivariance(self, score_network, batch, octahedral_point_group_symmetries, global_translations): + def test_equivariance( + self, + score_network, + batch, + octahedral_point_group_symmetries, + global_translations, + ): with torch.no_grad(): normalized_scores = score_network(batch) @@ -410,7 +529,9 @@ def test_equivariance(self, score_network, batch, octahedral_point_group_symmetr relative_coordinates = modified_batch[NOISY_RELATIVE_COORDINATES] op_relative_coordinates = relative_coordinates @ op + global_translations - op_relative_coordinates = map_relative_coordinates_to_unit_cell(op_relative_coordinates) + op_relative_coordinates = map_relative_coordinates_to_unit_cell( + op_relative_coordinates + ) modified_batch[NOISY_RELATIVE_COORDINATES] = op_relative_coordinates with torch.no_grad(): @@ -418,4 +539,6 @@ def test_equivariance(self, score_network, batch, octahedral_point_group_symmetr expected_modified_normalized_scores = normalized_scores @ op - torch.testing.assert_close(expected_modified_normalized_scores, modified_normalized_scores) + torch.testing.assert_close( + expected_modified_normalized_scores, modified_normalized_scores + ) diff --git a/tests/models/score_network/test_score_prediction_head.py b/tests/models/score_network/test_score_prediction_head.py index 447cf336..7c7d1b76 100644 --- a/tests/models/score_network/test_score_prediction_head.py +++ b/tests/models/score_network/test_score_prediction_head.py @@ -1,11 +1,12 @@ import pytest import torch -from crystal_diffusion.models.mace_utils import \ +from e3nn import o3 + +from diffusion_for_multi_scale_molecular_dynamics.models.mace_utils import \ build_mace_output_nodes_irreducible_representation -from crystal_diffusion.models.score_networks.score_prediction_head import ( +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_prediction_head import ( MaceEquivariantScorePredictionHead, MaceEquivariantScorePredictionHeadParameters) -from e3nn import o3 class TestMaceEquivariantScorePredictionHead: @@ -33,17 +34,23 @@ def hidden_irreps_string(self): @pytest.fixture() def output_node_features_irreps(self, hidden_irreps_string, num_interactions): output_node_features_irreps = ( - build_mace_output_nodes_irreducible_representation(hidden_irreps_string, num_interactions)) + build_mace_output_nodes_irreducible_representation( + hidden_irreps_string, num_interactions + ) + ) return output_node_features_irreps @pytest.fixture() def parameters(self): - return MaceEquivariantScorePredictionHeadParameters(time_embedding_irreps="4x0e", - number_of_layers=2) + return MaceEquivariantScorePredictionHeadParameters( + time_embedding_irreps="4x0e", number_of_layers=2 + ) @pytest.fixture() def prediction_head(self, output_node_features_irreps, parameters): - head = MaceEquivariantScorePredictionHead(output_node_features_irreps, parameters) + head = MaceEquivariantScorePredictionHead( + output_node_features_irreps, parameters + ) head.eval() return head @@ -56,12 +63,19 @@ def flat_times(self, times, number_of_atoms): return torch.repeat_interleave(times, number_of_atoms).reshape(-1, 1) @pytest.fixture() - def flat_node_features(self, batch_size, number_of_atoms, output_node_features_irreps): + def flat_node_features( + self, batch_size, number_of_atoms, output_node_features_irreps + ): flat_batch_size = batch_size * number_of_atoms return output_node_features_irreps.randn(flat_batch_size, -1) - def test_predictions_are_equivariant(self, prediction_head, flat_node_features, - flat_times, output_node_features_irreps): + def test_predictions_are_equivariant( + self, + prediction_head, + flat_node_features, + flat_times, + output_node_features_irreps, + ): vector_irreps = o3.Irreps("1x1o") random_rotation = o3.rand_matrix() diff --git a/tests/models/test_analytical_score_network.py b/tests/models/test_analytical_score_network.py index 7a14d3e4..b2d0af03 100644 --- a/tests/models/test_analytical_score_network.py +++ b/tests/models/test_analytical_score_network.py @@ -2,11 +2,12 @@ import pytest import torch -from crystal_diffusion.models.score_networks.analytical_score_network import ( + +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.analytical_score_network import ( AnalyticalScoreNetwork, AnalyticalScoreNetworkParameters, TargetScoreBasedAnalyticalScoreNetwork) -from crystal_diffusion.namespace import (NOISE, NOISY_RELATIVE_COORDINATES, - TIME, UNIT_CELL) +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) def factorial(n): @@ -52,33 +53,48 @@ def number_of_atoms(self, request): def equilibrium_relative_coordinates(self, number_of_atoms, spatial_dimension): return torch.rand(number_of_atoms, spatial_dimension) - @pytest.fixture(params=['finite', 'zero']) + @pytest.fixture(params=["finite", "zero"]) def variance_parameter(self, request): - if request.param == 'zero': - return 0. - elif request.param == 'finite': + if request.param == "zero": + return 0.0 + elif request.param == "finite": # Make the spring constants pretty large so that the displacements will be small inverse_variance = float(1000 * torch.rand(1)) - return 1. / inverse_variance + return 1.0 / inverse_variance @pytest.fixture() def batch(self, batch_size, number_of_atoms, spatial_dimension): - relative_coordinates = torch.rand(batch_size, number_of_atoms, spatial_dimension) + relative_coordinates = torch.rand( + batch_size, number_of_atoms, spatial_dimension + ) times = torch.rand(batch_size, 1) noises = torch.rand(batch_size, 1) unit_cell = torch.rand(batch_size, spatial_dimension, spatial_dimension) - return {NOISY_RELATIVE_COORDINATES: relative_coordinates, TIME: times, NOISE: noises, UNIT_CELL: unit_cell} + return { + NOISY_RELATIVE_COORDINATES: relative_coordinates, + TIME: times, + NOISE: noises, + UNIT_CELL: unit_cell, + } @pytest.fixture() - def score_network_parameters(self, number_of_atoms, spatial_dimension, kmax, - equilibrium_relative_coordinates, variance_parameter, use_permutation_invariance): + def score_network_parameters( + self, + number_of_atoms, + spatial_dimension, + kmax, + equilibrium_relative_coordinates, + variance_parameter, + use_permutation_invariance, + ): hyper_params = AnalyticalScoreNetworkParameters( number_of_atoms=number_of_atoms, spatial_dimension=spatial_dimension, kmax=kmax, equilibrium_relative_coordinates=equilibrium_relative_coordinates, variance_parameter=variance_parameter, - use_permutation_invariance=use_permutation_invariance) + use_permutation_invariance=use_permutation_invariance, + ) return hyper_params @pytest.fixture() @@ -94,30 +110,46 @@ def test_all_translations(self, kmax): expected_translations = torch.tensor(list(range(-kmax, kmax + 1))) torch.testing.assert_close(expected_translations, computed_translations) - def test_get_all_equilibrium_permutations(self, number_of_atoms, spatial_dimension, - equilibrium_relative_coordinates): + def test_get_all_equilibrium_permutations( + self, number_of_atoms, spatial_dimension, equilibrium_relative_coordinates + ): expected_permutations = [] for permutation_indices in itertools.permutations(range(number_of_atoms)): - expected_permutations.append(equilibrium_relative_coordinates[list(permutation_indices)]) + expected_permutations.append( + equilibrium_relative_coordinates[list(permutation_indices)] + ) expected_permutations = torch.stack(expected_permutations) computed_permutations = ( - AnalyticalScoreNetwork._get_all_equilibrium_permutations(equilibrium_relative_coordinates)) + AnalyticalScoreNetwork._get_all_equilibrium_permutations( + equilibrium_relative_coordinates + ) + ) - assert computed_permutations.shape == (factorial(number_of_atoms), number_of_atoms, spatial_dimension) + assert computed_permutations.shape == ( + factorial(number_of_atoms), + number_of_atoms, + spatial_dimension, + ) torch.testing.assert_close(expected_permutations, computed_permutations) - @pytest.mark.parametrize('use_permutation_invariance', [False]) - def test_compute_unnormalized_log_probability(self, equilibrium_relative_coordinates, variance_parameter, - kmax, batch, score_network): + @pytest.mark.parametrize("use_permutation_invariance", [False]) + def test_compute_unnormalized_log_probability( + self, + equilibrium_relative_coordinates, + variance_parameter, + kmax, + batch, + score_network, + ): sigmas = batch[NOISE] # dimension: [batch_size, 1] xt = batch[NOISY_RELATIVE_COORDINATES] - computed_log_prob = score_network._compute_unnormalized_log_probability(sigmas, - xt, - equilibrium_relative_coordinates) + computed_log_prob = score_network._compute_unnormalized_log_probability( + sigmas, xt, equilibrium_relative_coordinates + ) batch_size = sigmas.shape[0] @@ -131,25 +163,39 @@ def test_compute_unnormalized_log_probability(self, equilibrium_relative_coordin eq_coordinate = equilibrium_relative_coordinates[i, alpha] coordinate = xt[batch_idx, i, alpha] - sum_on_k = torch.tensor(0.) + sum_on_k = torch.tensor(0.0) for k in range(-kmax, kmax + 1): - exponent = -0.5 * (coordinate - eq_coordinate - k)**2 / (sigma**2 + variance_parameter) + exponent = ( + -0.5 + * (coordinate - eq_coordinate - k) ** 2 + / (sigma**2 + variance_parameter) + ) sum_on_k += torch.exp(exponent) expected_log_prob[batch_idx] += torch.log(sum_on_k) torch.testing.assert_close(expected_log_prob, computed_log_prob) - @pytest.mark.parametrize('number_of_atoms, use_permutation_invariance', [(1, False), (1, True), - (2, False), (2, True), (8, False)]) - def test_analytical_score_network(self, score_network, batch, batch_size, number_of_atoms, spatial_dimension): + @pytest.mark.parametrize( + "number_of_atoms, use_permutation_invariance", + [(1, False), (1, True), (2, False), (2, True), (8, False)], + ) + def test_analytical_score_network( + self, score_network, batch, batch_size, number_of_atoms, spatial_dimension + ): normalized_scores = score_network.forward(batch) - assert normalized_scores.shape == (batch_size, number_of_atoms, spatial_dimension) - - @pytest.mark.parametrize('use_permutation_invariance', [False]) - @pytest.mark.parametrize('number_of_atoms', [1, 2, 8]) - def test_compare_score_networks(self, score_network, target_score_based_score_network, batch): + assert normalized_scores.shape == ( + batch_size, + number_of_atoms, + spatial_dimension, + ) + + @pytest.mark.parametrize("use_permutation_invariance", [False]) + @pytest.mark.parametrize("number_of_atoms", [1, 2, 8]) + def test_compare_score_networks( + self, score_network, target_score_based_score_network, batch + ): normalized_scores1 = score_network.forward(batch) normalized_scores2 = target_score_based_score_network.forward(batch) diff --git a/tests/models/test_diffusion_mace.py b/tests/models/test_diffusion_mace.py index db03e61b..4f43f356 100644 --- a/tests/models/test_diffusion_mace.py +++ b/tests/models/test_diffusion_mace.py @@ -1,18 +1,17 @@ import pytest import torch -from crystal_diffusion.models.diffusion_mace import (DiffusionMACE, - LinearVectorReadoutBlock, - input_to_diffusion_mace) -from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, - NOISY_CARTESIAN_POSITIONS, - NOISY_RELATIVE_COORDINATES, TIME, - UNIT_CELL) -from crystal_diffusion.utils.basis_transformations import ( +from e3nn import o3 +from mace.modules import gate_dict, interaction_classes + +from diffusion_for_multi_scale_molecular_dynamics.models.diffusion_mace import ( + DiffusionMACE, LinearVectorReadoutBlock, input_to_diffusion_mace) +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, NOISE, NOISY_CARTESIAN_POSITIONS, + NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import ( get_positions_from_coordinates, get_reciprocal_basis_vectors, get_relative_coordinates_from_cartesian_positions, map_relative_coordinates_to_unit_cell) -from e3nn import o3 -from mace.modules import gate_dict, interaction_classes def test_linear_vector_readout_block(): @@ -44,73 +43,95 @@ def set_seed(self): """Set the random seed.""" torch.manual_seed(234233) - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def batch_size(self): return 4 - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def number_of_atoms(self): return 8 - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def spatial_dimension(self): return 3 - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def basis_vectors(self, batch_size, spatial_dimension): # orthogonal boxes with dimensions between 5 and 10. - orthogonal_boxes = torch.stack([torch.diag(5. + 5. * torch.rand(spatial_dimension)) - for _ in range(batch_size)]) + orthogonal_boxes = torch.stack( + [ + torch.diag(5.0 + 5.0 * torch.rand(spatial_dimension)) + for _ in range(batch_size) + ] + ) # add a bit of noise to make the vectors not quite orthogonal - basis_vectors = orthogonal_boxes + 0.1 * torch.randn(batch_size, spatial_dimension, spatial_dimension) + basis_vectors = orthogonal_boxes + 0.1 * torch.randn( + batch_size, spatial_dimension, spatial_dimension + ) return basis_vectors - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def reciprocal_basis_vectors(self, basis_vectors): return get_reciprocal_basis_vectors(basis_vectors) - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def relative_coordinates(self, batch_size, number_of_atoms, spatial_dimension): - relative_coordinates = torch.rand(batch_size, number_of_atoms, spatial_dimension) + relative_coordinates = torch.rand( + batch_size, number_of_atoms, spatial_dimension + ) return relative_coordinates - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def cartesian_positions(self, relative_coordinates, basis_vectors): return get_positions_from_coordinates(relative_coordinates, basis_vectors) - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def times(self, batch_size): return torch.rand(batch_size, 1) - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def noises(self, batch_size): return 0.5 * torch.rand(batch_size, 1) - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def forces(self, batch_size, spatial_dimension): return 0.5 * torch.rand(batch_size, spatial_dimension) - @pytest.fixture(scope='class') - def batch(self, relative_coordinates, cartesian_positions, basis_vectors, times, noises, forces): - batch = {NOISY_RELATIVE_COORDINATES: relative_coordinates, - NOISY_CARTESIAN_POSITIONS: cartesian_positions, - TIME: times, - NOISE: noises, - UNIT_CELL: basis_vectors, - CARTESIAN_FORCES: forces} + @pytest.fixture(scope="class") + def batch( + self, + relative_coordinates, + cartesian_positions, + basis_vectors, + times, + noises, + forces, + ): + batch = { + NOISY_RELATIVE_COORDINATES: relative_coordinates, + NOISY_CARTESIAN_POSITIONS: cartesian_positions, + TIME: times, + NOISE: noises, + UNIT_CELL: basis_vectors, + CARTESIAN_FORCES: forces, + } return batch - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def cartesian_rotations(self, batch_size): return o3.rand_matrix(batch_size) - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def permutations(self, batch_size, number_of_atoms): return torch.stack([torch.randperm(number_of_atoms) for _ in range(batch_size)]) - @pytest.fixture(scope='class') - def cartesian_translations(self, batch_size, number_of_atoms, spatial_dimension, basis_vectors): - batch_relative_coordinates_translations = torch.rand(batch_size, spatial_dimension) + @pytest.fixture(scope="class") + def cartesian_translations( + self, batch_size, number_of_atoms, spatial_dimension, basis_vectors + ): + batch_relative_coordinates_translations = torch.rand( + batch_size, spatial_dimension + ) batch_cartesian_translations = [] for t, cell in zip(batch_relative_coordinates_translations, basis_vectors): @@ -118,8 +139,9 @@ def cartesian_translations(self, batch_size, number_of_atoms, spatial_dimension, batch_cartesian_translations = torch.stack(batch_cartesian_translations) - cartesian_translations = torch.repeat_interleave(batch_cartesian_translations.unsqueeze(1), - number_of_atoms, dim=1) + cartesian_translations = torch.repeat_interleave( + batch_cartesian_translations.unsqueeze(1), number_of_atoms, dim=1 + ) return cartesian_translations @pytest.fixture() @@ -129,26 +151,27 @@ def r_max(self): @pytest.fixture() def hyperparameters(self, r_max): - hps = dict(r_max=r_max, - num_bessel=8, - num_polynomial_cutoff=5, - num_edge_hidden_layers=0, - edge_hidden_irreps=o3.Irreps("8x0e"), - max_ell=2, - num_elements=1, - atomic_numbers=[14], - interaction_cls=interaction_classes["RealAgnosticResidualInteractionBlock"], - interaction_cls_first=interaction_classes["RealAgnosticInteractionBlock"], - num_interactions=2, - hidden_irreps=o3.Irreps("8x0e + 8x1o + 8x2e"), - mlp_irreps=o3.Irreps("8x0e"), - number_of_mlp_layers=2, - avg_num_neighbors=1, - correlation=2, - gate=gate_dict["silu"], - radial_MLP=[8, 8, 8], - radial_type="bessel", - ) + hps = dict( + r_max=r_max, + num_bessel=8, + num_polynomial_cutoff=5, + num_edge_hidden_layers=0, + edge_hidden_irreps=o3.Irreps("8x0e"), + max_ell=2, + num_elements=1, + atomic_numbers=[14], + interaction_cls=interaction_classes["RealAgnosticResidualInteractionBlock"], + interaction_cls_first=interaction_classes["RealAgnosticInteractionBlock"], + num_interactions=2, + hidden_irreps=o3.Irreps("8x0e + 8x1o + 8x2e"), + mlp_irreps=o3.Irreps("8x0e"), + number_of_mlp_layers=2, + avg_num_neighbors=1, + correlation=2, + gate=gate_dict["silu"], + radial_MLP=[8, 8, 8], + radial_type="bessel", + ) return hps @pytest.fixture() @@ -162,23 +185,44 @@ def graph_input(self, batch, r_max): return input_to_diffusion_mace(batch, radial_cutoff=r_max) @pytest.fixture() - def cartesian_scores(self, graph_input, diffusion_mace, batch_size, number_of_atoms, spatial_dimension): + def cartesian_scores( + self, + graph_input, + diffusion_mace, + batch_size, + number_of_atoms, + spatial_dimension, + ): flat_cartesian_scores = diffusion_mace(graph_input) - return flat_cartesian_scores.reshape(batch_size, number_of_atoms, spatial_dimension) + return flat_cartesian_scores.reshape( + batch_size, number_of_atoms, spatial_dimension + ) @pytest.fixture() - def translated_graph_input(self, batch, r_max, basis_vectors, reciprocal_basis_vectors, cartesian_translations): + def translated_graph_input( + self, + batch, + r_max, + basis_vectors, + reciprocal_basis_vectors, + cartesian_translations, + ): translated_batch = dict(batch) original_cartesian_positions = translated_batch[NOISY_CARTESIAN_POSITIONS] - translated_cartesian_positions = original_cartesian_positions + cartesian_translations + translated_cartesian_positions = ( + original_cartesian_positions + cartesian_translations + ) - rel_coords = get_relative_coordinates_from_cartesian_positions(translated_cartesian_positions, - reciprocal_basis_vectors) + rel_coords = get_relative_coordinates_from_cartesian_positions( + translated_cartesian_positions, reciprocal_basis_vectors + ) new_relative_coordinates = map_relative_coordinates_to_unit_cell(rel_coords) - new_cartesian_positions = get_positions_from_coordinates(new_relative_coordinates, basis_vectors) + new_cartesian_positions = get_positions_from_coordinates( + new_relative_coordinates, basis_vectors + ) translated_batch[NOISY_CARTESIAN_POSITIONS] = new_cartesian_positions translated_batch[NOISY_RELATIVE_COORDINATES] = new_relative_coordinates @@ -186,28 +230,47 @@ def translated_graph_input(self, batch, r_max, basis_vectors, reciprocal_basis_v return input_to_diffusion_mace(translated_batch, radial_cutoff=r_max) @pytest.fixture() - def translated_cartesian_scores(self, diffusion_mace, batch_size, number_of_atoms, - spatial_dimension, basis_vectors, translated_graph_input): + def translated_cartesian_scores( + self, + diffusion_mace, + batch_size, + number_of_atoms, + spatial_dimension, + basis_vectors, + translated_graph_input, + ): flat_translated_cartesian_scores = diffusion_mace(translated_graph_input) - return flat_translated_cartesian_scores.reshape(batch_size, number_of_atoms, spatial_dimension) + return flat_translated_cartesian_scores.reshape( + batch_size, number_of_atoms, spatial_dimension + ) @pytest.fixture() - def rotated_graph_input(self, batch, r_max, basis_vectors, reciprocal_basis_vectors, cartesian_rotations): + def rotated_graph_input( + self, batch, r_max, basis_vectors, reciprocal_basis_vectors, cartesian_rotations + ): rotated_batch = dict(batch) original_cartesian_positions = rotated_batch[NOISY_CARTESIAN_POSITIONS] original_basis_vectors = rotated_batch[UNIT_CELL] - rotated_cartesian_positions = torch.matmul(original_cartesian_positions, - cartesian_rotations.transpose(2, 1)) + rotated_cartesian_positions = torch.matmul( + original_cartesian_positions, cartesian_rotations.transpose(2, 1) + ) - rotated_basis_vectors = torch.matmul(original_basis_vectors, cartesian_rotations.transpose(2, 1)) - rotated_reciprocal_basis_vectors = get_reciprocal_basis_vectors(rotated_basis_vectors) + rotated_basis_vectors = torch.matmul( + original_basis_vectors, cartesian_rotations.transpose(2, 1) + ) + rotated_reciprocal_basis_vectors = get_reciprocal_basis_vectors( + rotated_basis_vectors + ) - rel_coords = get_relative_coordinates_from_cartesian_positions(rotated_cartesian_positions, - rotated_reciprocal_basis_vectors) + rel_coords = get_relative_coordinates_from_cartesian_positions( + rotated_cartesian_positions, rotated_reciprocal_basis_vectors + ) new_relative_coordinates = map_relative_coordinates_to_unit_cell(rel_coords) - new_cartesian_positions = get_positions_from_coordinates(new_relative_coordinates, rotated_basis_vectors) + new_cartesian_positions = get_positions_from_coordinates( + new_relative_coordinates, rotated_basis_vectors + ) rotated_batch[NOISY_CARTESIAN_POSITIONS] = new_cartesian_positions rotated_batch[NOISY_RELATIVE_COORDINATES] = new_relative_coordinates @@ -216,10 +279,18 @@ def rotated_graph_input(self, batch, r_max, basis_vectors, reciprocal_basis_vect return input_to_diffusion_mace(rotated_batch, radial_cutoff=r_max) @pytest.fixture() - def rotated_cartesian_scores(self, diffusion_mace, batch_size, number_of_atoms, - spatial_dimension, rotated_graph_input): + def rotated_cartesian_scores( + self, + diffusion_mace, + batch_size, + number_of_atoms, + spatial_dimension, + rotated_graph_input, + ): flat_rotated_cartesian_scores = diffusion_mace(rotated_graph_input) - return flat_rotated_cartesian_scores.reshape(batch_size, number_of_atoms, spatial_dimension) + return flat_rotated_cartesian_scores.reshape( + batch_size, number_of_atoms, spatial_dimension + ) @pytest.fixture() def permuted_graph_input(self, batch_size, batch, r_max, permutations): @@ -227,34 +298,62 @@ def permuted_graph_input(self, batch_size, batch, r_max, permutations): for position_key in [NOISY_CARTESIAN_POSITIONS, NOISY_RELATIVE_COORDINATES]: pos = permuted_batch[position_key] - permuted_pos = torch.stack([pos[batch_idx, permutations[batch_idx], :] - for batch_idx in range(batch_size)]) + permuted_pos = torch.stack( + [ + pos[batch_idx, permutations[batch_idx], :] + for batch_idx in range(batch_size) + ] + ) permuted_batch[position_key] = permuted_pos return input_to_diffusion_mace(permuted_batch, radial_cutoff=r_max) @pytest.fixture() - def permuted_cartesian_scores(self, diffusion_mace, batch_size, number_of_atoms, - spatial_dimension, permuted_graph_input): + def permuted_cartesian_scores( + self, + diffusion_mace, + batch_size, + number_of_atoms, + spatial_dimension, + permuted_graph_input, + ): flat_permuted_cartesian_scores = diffusion_mace(permuted_graph_input) - return flat_permuted_cartesian_scores.reshape(batch_size, number_of_atoms, spatial_dimension) + return flat_permuted_cartesian_scores.reshape( + batch_size, number_of_atoms, spatial_dimension + ) - def test_translation_invariance(self, cartesian_scores, translated_cartesian_scores): + def test_translation_invariance( + self, cartesian_scores, translated_cartesian_scores + ): torch.testing.assert_close(translated_cartesian_scores, cartesian_scores) - def test_rotation_equivariance(self, cartesian_scores, rotated_cartesian_scores, cartesian_rotations): - vector_irreps = o3.Irreps('1o') + def test_rotation_equivariance( + self, cartesian_scores, rotated_cartesian_scores, cartesian_rotations + ): + vector_irreps = o3.Irreps("1o") d_matrices = vector_irreps.D_from_matrix(cartesian_rotations) - expected_rotated_cartesian_scores = torch.matmul(cartesian_scores, d_matrices.transpose(2, 1)) - torch.testing.assert_close(expected_rotated_cartesian_scores, rotated_cartesian_scores) + expected_rotated_cartesian_scores = torch.matmul( + cartesian_scores, d_matrices.transpose(2, 1) + ) + torch.testing.assert_close( + expected_rotated_cartesian_scores, rotated_cartesian_scores + ) - def test_permutation_equivariance(self, cartesian_scores, permuted_cartesian_scores, batch_size, permutations): + def test_permutation_equivariance( + self, cartesian_scores, permuted_cartesian_scores, batch_size, permutations + ): - expected_permuted_cartesian_scores = torch.stack([cartesian_scores[batch_idx, permutations[batch_idx], :] - for batch_idx in range(batch_size)]) + expected_permuted_cartesian_scores = torch.stack( + [ + cartesian_scores[batch_idx, permutations[batch_idx], :] + for batch_idx in range(batch_size) + ] + ) - torch.testing.assert_close(expected_permuted_cartesian_scores, permuted_cartesian_scores) + torch.testing.assert_close( + expected_permuted_cartesian_scores, permuted_cartesian_scores + ) def test_time_dependence(self, batch, r_max, diffusion_mace): @@ -273,4 +372,6 @@ def test_time_dependence(self, batch, r_max, diffusion_mace): # Different times, different results? with pytest.raises(AssertionError): - torch.testing.assert_close(new_flat_cartesian_scores, flat_cartesian_scores1) + torch.testing.assert_close( + new_flat_cartesian_scores, flat_cartesian_scores1 + ) diff --git a/tests/models/test_egnn.py b/tests/models/test_egnn.py index c7e11a67..be93f0d4 100644 --- a/tests/models/test_egnn.py +++ b/tests/models/test_egnn.py @@ -3,7 +3,9 @@ import pytest import torch -from crystal_diffusion.models.egnn import E_GCL, EGNN + +from diffusion_for_multi_scale_molecular_dynamics.models.egnn import (E_GCL, + EGNN) class TestEGNN: @@ -21,77 +23,85 @@ def set_seed(self): """Set the random seed.""" torch.manual_seed(234233) - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def batch_size(self): return 4 - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def number_of_atoms(self): return 8 - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def spatial_dimension(self): return 3 - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def relative_coordinates(self, batch_size, number_of_atoms, spatial_dimension): - relative_coordinates = torch.rand(batch_size, number_of_atoms, spatial_dimension) + relative_coordinates = torch.rand( + batch_size, number_of_atoms, spatial_dimension + ) return relative_coordinates - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def node_features_size(self): return 5 - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def node_features(self, batch_size, number_of_atoms, node_features_size): node_features = torch.randn(batch_size, number_of_atoms, node_features_size) return node_features - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def num_edges(self, number_of_atoms): return math.floor(number_of_atoms * 1.5) - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def edges(self, batch_size, number_of_atoms, num_edges): all_edges = [] for b in range(batch_size): - batch_edges = torch.Tensor([(i, j) for i in range(number_of_atoms) for j in range(number_of_atoms)]) + batch_edges = torch.Tensor( + [(i, j) for i in range(number_of_atoms) for j in range(number_of_atoms)] + ) # select num_edges randomly indices = torch.randperm(len(batch_edges)) shuffled_edges = batch_edges[indices] + b * number_of_atoms all_edges.append(shuffled_edges[:num_edges]) return torch.cat(all_edges, dim=0).long() - @pytest.fixture(scope='class') - def batch(self, relative_coordinates, node_features, edges, batch_size, number_of_atoms): - batch = {'coord': relative_coordinates.view(batch_size * number_of_atoms, -1), - 'node_features': node_features.view(batch_size * number_of_atoms, -1), - 'edges': edges - } + @pytest.fixture(scope="class") + def batch( + self, relative_coordinates, node_features, edges, batch_size, number_of_atoms + ): + batch = { + "coord": relative_coordinates.view(batch_size * number_of_atoms, -1), + "node_features": node_features.view(batch_size * number_of_atoms, -1), + "edges": edges, + } return batch - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def generic_hyperparameters(self, node_features_size): - hps = dict(input_size=node_features_size, - message_n_hidden_dimensions=1, - message_hidden_dimensions_size=4, - node_n_hidden_dimensions=1, - node_hidden_dimensions_size=4, - coordinate_n_hidden_dimensions=1, - coordinate_hidden_dimensions_size=4, - ) + hps = dict( + input_size=node_features_size, + message_n_hidden_dimensions=1, + message_hidden_dimensions_size=4, + node_n_hidden_dimensions=1, + node_hidden_dimensions_size=4, + coordinate_n_hidden_dimensions=1, + coordinate_hidden_dimensions_size=4, + ) return hps @pytest.fixture() def egnn_hyperparameters(self, generic_hyperparameters): hps = copy(generic_hyperparameters) - hps['n_layers'] = 2 + hps["n_layers"] = 2 return hps @pytest.fixture() def egcl_hyperparameters(self, generic_hyperparameters, node_features_size): hps = copy(generic_hyperparameters) - hps['output_size'] = node_features_size + hps["output_size"] = node_features_size return hps @pytest.fixture() @@ -108,38 +118,48 @@ def egnn(self, egnn_hyperparameters): @pytest.fixture() def egnn_scores(self, batch, egnn, batch_size, number_of_atoms, spatial_dimension): - egnn_scores = egnn(batch['node_features'], batch['edges'], batch['coord']) + egnn_scores = egnn(batch["node_features"], batch["edges"], batch["coord"]) return egnn_scores.reshape(batch_size, number_of_atoms, spatial_dimension) @pytest.fixture() def egcl_scores(self, batch, egcl, batch_size, number_of_atoms): - egcl_h, egcl_x = egcl(batch['node_features'], batch['edges'], batch['coord']) - return egcl_h.reshape(batch_size, number_of_atoms, -1), egcl_x.reshape(batch_size, number_of_atoms, -1) + egcl_h, egcl_x = egcl(batch["node_features"], batch["edges"], batch["coord"]) + return egcl_h.reshape(batch_size, number_of_atoms, -1), egcl_x.reshape( + batch_size, number_of_atoms, -1 + ) - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def permutations(self, batch_size, number_of_atoms): return torch.stack([torch.randperm(number_of_atoms) for _ in range(batch_size)]) - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def permuted_coordinates(self, batch_size, number_of_atoms, batch, permutations): permuted_batch = batch - pos = permuted_batch['coord'].view(batch_size, number_of_atoms, -1) - permuted_pos = torch.stack([pos[batch_idx, permutations[batch_idx], :] - for batch_idx in range(batch_size)]) + pos = permuted_batch["coord"].view(batch_size, number_of_atoms, -1) + permuted_pos = torch.stack( + [ + pos[batch_idx, permutations[batch_idx], :] + for batch_idx in range(batch_size) + ] + ) return permuted_pos.view(batch_size * number_of_atoms, -1) - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def permuted_node_features(self, batch_size, number_of_atoms, batch, permutations): permuted_batch = batch - h = permuted_batch['node_features'].view(batch_size, number_of_atoms, -1) - permuted_h = torch.stack([h[batch_idx, permutations[batch_idx], :] - for batch_idx in range(batch_size)]) + h = permuted_batch["node_features"].view(batch_size, number_of_atoms, -1) + permuted_h = torch.stack( + [ + h[batch_idx, permutations[batch_idx], :] + for batch_idx in range(batch_size) + ] + ) return permuted_h.view(batch_size * number_of_atoms, -1) - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def permuted_edges(self, batch_size, batch, permutations, number_of_atoms): - edges = batch['edges'] + edges = batch["edges"] permuted_edges = edges.clone() for b in range(batch_size): for atom in range(number_of_atoms): @@ -148,58 +168,95 @@ def permuted_edges(self, batch_size, batch, permutations, number_of_atoms): return permuted_edges.long() @pytest.fixture() - def permuted_batch(self, permuted_coordinates, permuted_edges, permuted_node_features): - permuted_batch = {'coord': permuted_coordinates, - 'node_features': permuted_node_features, - 'edges': permuted_edges - } + def permuted_batch( + self, permuted_coordinates, permuted_edges, permuted_node_features + ): + permuted_batch = { + "coord": permuted_coordinates, + "node_features": permuted_node_features, + "edges": permuted_edges, + } return permuted_batch @pytest.fixture() - def permuted_egnn_scores(self, permuted_batch, egnn, batch_size, number_of_atoms, spatial_dimension): - egnn_scores = egnn(permuted_batch['node_features'], permuted_batch['edges'], permuted_batch['coord']) + def permuted_egnn_scores( + self, permuted_batch, egnn, batch_size, number_of_atoms, spatial_dimension + ): + egnn_scores = egnn( + permuted_batch["node_features"], + permuted_batch["edges"], + permuted_batch["coord"], + ) return egnn_scores.reshape(batch_size, number_of_atoms, spatial_dimension) @pytest.fixture() def permuted_egcl_scores(self, permuted_batch, egcl, batch_size, number_of_atoms): - egcl_h, egcl_x = egcl(permuted_batch['node_features'], permuted_batch['edges'], permuted_batch['coord']) - return egcl_h.reshape(batch_size, number_of_atoms, -1), egcl_x.reshape(batch_size, number_of_atoms, -1) - - def test_egcl_permutation_equivariance(self, egcl_scores, permuted_egcl_scores, batch_size, permutations): + egcl_h, egcl_x = egcl( + permuted_batch["node_features"], + permuted_batch["edges"], + permuted_batch["coord"], + ) + return egcl_h.reshape(batch_size, number_of_atoms, -1), egcl_x.reshape( + batch_size, number_of_atoms, -1 + ) + + def test_egcl_permutation_equivariance( + self, egcl_scores, permuted_egcl_scores, batch_size, permutations + ): permuted_egcl_h, permuted_egcl_x = permuted_egcl_scores egcl_h, egcl_x = egcl_scores - expected_permuted_h = torch.stack([egcl_h[batch_idx, permutations[batch_idx], :] - for batch_idx in range(batch_size)]) + expected_permuted_h = torch.stack( + [ + egcl_h[batch_idx, permutations[batch_idx], :] + for batch_idx in range(batch_size) + ] + ) torch.testing.assert_close(expected_permuted_h, permuted_egcl_h) - expected_permuted_x = torch.stack([egcl_x[batch_idx, permutations[batch_idx], :] - for batch_idx in range(batch_size)]) + expected_permuted_x = torch.stack( + [ + egcl_x[batch_idx, permutations[batch_idx], :] + for batch_idx in range(batch_size) + ] + ) torch.testing.assert_close(expected_permuted_x, permuted_egcl_x) - def test_egnn_permutation_equivariance(self, egnn_scores, permuted_egnn_scores, batch_size, permutations): - expected_permuted_scores = torch.stack([egnn_scores[batch_idx, permutations[batch_idx], :] - for batch_idx in range(batch_size)]) + def test_egnn_permutation_equivariance( + self, egnn_scores, permuted_egnn_scores, batch_size, permutations + ): + expected_permuted_scores = torch.stack( + [ + egnn_scores[batch_idx, permutations[batch_idx], :] + for batch_idx in range(batch_size) + ] + ) torch.testing.assert_close(expected_permuted_scores, permuted_egnn_scores) - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def single_edge(self): return torch.Tensor([1, 0]).unsqueeze(0).long() - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def fixed_distance(self): return 0.4 - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def simple_pair_coord(self, fixed_distance, spatial_dimension): coord = torch.zeros(2, spatial_dimension) coord[1, 0] = fixed_distance return coord - def test_egcl_coord2radial(self, single_edge, fixed_distance, simple_pair_coord, egcl): - computed_distance_squared, computed_displacement = egcl.coord2radial(single_edge, simple_pair_coord) - torch.testing.assert_close(computed_distance_squared.item(), fixed_distance ** 2) - torch.testing.assert_close(computed_displacement, simple_pair_coord[1, :].unsqueeze(0)) + def test_egcl_coord2radial( + self, single_edge, fixed_distance, simple_pair_coord, egcl + ): + computed_distance_squared, computed_displacement = egcl.coord2radial( + single_edge, simple_pair_coord + ) + torch.testing.assert_close(computed_distance_squared.item(), fixed_distance**2) + torch.testing.assert_close( + computed_displacement, simple_pair_coord[1, :].unsqueeze(0) + ) diff --git a/tests/models/test_egnn_utils.py b/tests/models/test_egnn_utils.py index 8dc211ff..3e15e8bf 100644 --- a/tests/models/test_egnn_utils.py +++ b/tests/models/test_egnn_utils.py @@ -1,7 +1,8 @@ import pytest import torch -from crystal_diffusion.models.egnn_utils import (unsorted_segment_mean, - unsorted_segment_sum) + +from diffusion_for_multi_scale_molecular_dynamics.models.egnn_utils import ( + unsorted_segment_mean, unsorted_segment_sum) @pytest.fixture() @@ -29,7 +30,9 @@ def messages(num_messages, num_message_features): return torch.randn(num_messages, num_message_features) -def test_unsorted_segment_sum(num_messages, num_ids, message_ids, num_message_features, messages): +def test_unsorted_segment_sum( + num_messages, num_ids, message_ids, num_message_features, messages +): expected_message_sums = torch.zeros(num_ids, num_message_features) for i in range(num_messages): m_id = message_ids[i] @@ -41,7 +44,9 @@ def test_unsorted_segment_sum(num_messages, num_ids, message_ids, num_message_fe assert torch.allclose(message_summed, expected_message_sums) -def test_unsorted_segment_mean(num_messages, num_ids, message_ids, num_message_features, messages): +def test_unsorted_segment_mean( + num_messages, num_ids, message_ids, num_message_features, messages +): expected_message_sums = torch.zeros(num_ids, num_message_features) expected_counts = torch.zeros(num_ids, 1) for i in range(num_messages): @@ -49,7 +54,9 @@ def test_unsorted_segment_mean(num_messages, num_ids, message_ids, num_message_f message = messages[i] expected_message_sums[m_id] += message expected_counts[m_id] += 1 - expected_message_average = expected_message_sums / torch.maximum(expected_counts, torch.ones_like(expected_counts)) + expected_message_average = expected_message_sums / torch.maximum( + expected_counts, torch.ones_like(expected_counts) + ) message_averaged = unsorted_segment_mean(messages, message_ids, num_ids) assert message_averaged.size() == torch.Size((num_ids, num_message_features)) diff --git a/tests/models/test_loss.py b/tests/models/test_loss.py index 674ee48e..d130e616 100644 --- a/tests/models/test_loss.py +++ b/tests/models/test_loss.py @@ -1,9 +1,9 @@ import pytest import torch -from crystal_diffusion.models.loss import (MSELossParameters, - WeightedMSELossParameters, - create_loss_calculator) -from crystal_diffusion.utils.tensor_utils import \ + +from diffusion_for_multi_scale_molecular_dynamics.models.loss import ( + MSELossParameters, WeightedMSELossParameters, create_loss_calculator) +from diffusion_for_multi_scale_molecular_dynamics.utils.tensor_utils import \ broadcast_batch_tensor_to_all_dimensions @@ -43,7 +43,9 @@ def predicted_normalized_scores(batch_size, number_of_atoms, spatial_dimension): @pytest.fixture() -def target_normalized_conditional_scores(batch_size, number_of_atoms, spatial_dimension): +def target_normalized_conditional_scores( + batch_size, number_of_atoms, spatial_dimension +): return torch.rand(batch_size, number_of_atoms, spatial_dimension) @@ -51,7 +53,9 @@ def target_normalized_conditional_scores(batch_size, number_of_atoms, spatial_di def sigmas(batch_size, number_of_atoms, spatial_dimension): batch_sigmas = torch.rand(batch_size) shape = (batch_size, number_of_atoms, spatial_dimension) - sigmas = broadcast_batch_tensor_to_all_dimensions(batch_values=batch_sigmas, final_shape=shape) + sigmas = broadcast_batch_tensor_to_all_dimensions( + batch_values=batch_sigmas, final_shape=shape + ) return sigmas @@ -60,7 +64,7 @@ def weights(sigmas, sigma0, exponent): return 1.0 + torch.exp(exponent * (sigmas - sigma0)) -@pytest.fixture(params=['mse', 'weighted_mse']) +@pytest.fixture(params=["mse", "weighted_mse"]) def algorithm(request): return request.param @@ -68,12 +72,12 @@ def algorithm(request): @pytest.fixture() def loss_parameters(algorithm, sigma0, exponent): match algorithm: - case 'mse': + case "mse": parameters = MSELossParameters() - case 'weighted_mse': + case "weighted_mse": parameters = WeightedMSELossParameters(sigma0=sigma0, exponent=exponent) case _: - raise ValueError(f'Unknown loss algorithm {algorithm}') + raise ValueError(f"Unknown loss algorithm {algorithm}") return parameters @@ -83,24 +87,41 @@ def loss_calculator(loss_parameters): @pytest.fixture() -def computed_loss(loss_calculator, predicted_normalized_scores, target_normalized_conditional_scores, sigmas): - unreduced_loss = loss_calculator.calculate_unreduced_loss(predicted_normalized_scores, - target_normalized_conditional_scores, - sigmas) +def computed_loss( + loss_calculator, + predicted_normalized_scores, + target_normalized_conditional_scores, + sigmas, +): + unreduced_loss = loss_calculator.calculate_unreduced_loss( + predicted_normalized_scores, target_normalized_conditional_scores, sigmas + ) return torch.mean(unreduced_loss) @pytest.fixture() -def expected_loss(algorithm, weights, predicted_normalized_scores, target_normalized_conditional_scores, sigmas): +def expected_loss( + algorithm, + weights, + predicted_normalized_scores, + target_normalized_conditional_scores, + sigmas, +): match algorithm: - case 'mse': + case "mse": loss = torch.nn.functional.mse_loss( - predicted_normalized_scores, target_normalized_conditional_scores, reduction="mean" + predicted_normalized_scores, + target_normalized_conditional_scores, + reduction="mean", + ) + case "weighted_mse": + loss = torch.mean( + weights + * (predicted_normalized_scores - target_normalized_conditional_scores) + ** 2 ) - case 'weighted_mse': - loss = torch.mean(weights * (predicted_normalized_scores - target_normalized_conditional_scores)**2) case _: - raise ValueError(f'Unknown loss algorithm {algorithm}') + raise ValueError(f"Unknown loss algorithm {algorithm}") return loss diff --git a/tests/models/test_mace_utils.py b/tests/models/test_mace_utils.py index 71c50751..5cb25480 100644 --- a/tests/models/test_mace_utils.py +++ b/tests/models/test_mace_utils.py @@ -4,17 +4,18 @@ import numpy as np import pytest import torch -from crystal_diffusion.models.mace_utils import ( - get_normalized_irreps_permutation_indices, get_pretrained_mace, - input_to_mace, reshape_from_e3nn_to_mace, reshape_from_mace_to_e3nn) -from crystal_diffusion.namespace import NOISY_CARTESIAN_POSITIONS, UNIT_CELL -from crystal_diffusion.utils.basis_transformations import \ - get_positions_from_coordinates from e3nn import o3 from mace.data import AtomicData, Configuration from mace.tools import get_atomic_number_table_from_zs from mace.tools.torch_geometric.dataloader import Collater +from diffusion_for_multi_scale_molecular_dynamics.models.mace_utils import ( + get_normalized_irreps_permutation_indices, get_pretrained_mace, + input_to_mace, reshape_from_e3nn_to_mace, reshape_from_mace_to_e3nn) +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + NOISY_CARTESIAN_POSITIONS, UNIT_CELL) +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ + get_positions_from_coordinates from tests.fake_data_utils import find_aligning_permutation @@ -50,36 +51,60 @@ def radial_cutoff(self): return 1.1 @pytest.fixture() - def mace_graph(self, cell_size, spatial_dim, atomic_positions, n_atoms, batch_size, radial_cutoff): - unit_cell = np.eye(spatial_dim) * cell_size # box as a spatial_dim x spatial_dim array + def mace_graph( + self, + cell_size, + spatial_dim, + atomic_positions, + n_atoms, + batch_size, + radial_cutoff, + ): + unit_cell = ( + np.eye(spatial_dim) * cell_size + ) # box as a spatial_dim x spatial_dim array atom_type = np.ones(n_atoms) * 14 pbc = np.array([True] * spatial_dim) # periodic boundary conditions - graph_config = Configuration(atomic_numbers=atom_type, - positions=atomic_positions.numpy(), - cell=unit_cell, - pbc=pbc) + graph_config = Configuration( + atomic_numbers=atom_type, + positions=atomic_positions.numpy(), + cell=unit_cell, + pbc=pbc, + ) z_table = get_atomic_number_table_from_zs(list(range(89))) - graph_data = AtomicData.from_config(graph_config, z_table=z_table, cutoff=radial_cutoff) + graph_data = AtomicData.from_config( + graph_config, z_table=z_table, cutoff=radial_cutoff + ) collate_fn = Collater(follow_batch=[None], exclude_keys=[None]) mace_batch = collate_fn([graph_data] * batch_size) return mace_batch @pytest.fixture() def score_network_input(self, batch_size, spatial_dim, cell_size, atomic_positions): - score_network_input = {NOISY_CARTESIAN_POSITIONS: atomic_positions.unsqueeze(0).repeat(batch_size, 1, 1), - UNIT_CELL: torch.eye(spatial_dim).repeat(batch_size, 1, 1) * cell_size} + score_network_input = { + NOISY_CARTESIAN_POSITIONS: atomic_positions.unsqueeze(0).repeat( + batch_size, 1, 1 + ), + UNIT_CELL: torch.eye(spatial_dim).repeat(batch_size, 1, 1) * cell_size, + } return score_network_input def test_input_to_mace(self, score_network_input, radial_cutoff, mace_graph): - crystal_diffusion_graph = input_to_mace(score_network_input, - radial_cutoff=radial_cutoff) + diffusion_for_multi_scale_molecular_dynamics_graph = input_to_mace( + score_network_input, radial_cutoff=radial_cutoff + ) # check the features used in MACE as the same in both our homemade graph and the one native to mace library - for k in ['edge_index', 'node_attrs', 'positions', 'ptr', 'batch', 'cell']: - assert k in crystal_diffusion_graph.keys() - assert crystal_diffusion_graph[k].size() == mace_graph[k].size() - assert torch.equal(crystal_diffusion_graph[k], mace_graph[k]) + for k in ["edge_index", "node_attrs", "positions", "ptr", "batch", "cell"]: + assert k in diffusion_for_multi_scale_molecular_dynamics_graph.keys() + assert ( + diffusion_for_multi_scale_molecular_dynamics_graph[k].size() + == mace_graph[k].size() + ) + assert torch.equal( + diffusion_for_multi_scale_molecular_dynamics_graph[k], mace_graph[k] + ) class TestInputToMaceRandom(TestInputToMaceChain): @@ -95,7 +120,9 @@ def n_atoms(self): @pytest.fixture def basis_vectors(self, batch_size): # orthogonal boxes with dimensions between 5 and 10. - orthogonal_boxes = torch.stack([torch.diag(5. + 5. * torch.rand(3)) for _ in range(batch_size)]) + orthogonal_boxes = torch.stack( + [torch.diag(5.0 + 5.0 * torch.rand(3)) for _ in range(batch_size)] + ) # add a bit of noise to make the vectors not quite orthogonal basis_vectors = orthogonal_boxes + 0.1 * torch.randn(batch_size, 3, 3) return basis_vectors @@ -107,18 +134,24 @@ def cartesian_positions(self, batch_size, n_atoms, spatial_dim, basis_vectors): return positions @pytest.fixture() - def mace_graph(self, basis_vectors, cartesian_positions, spatial_dim, n_atoms, radial_cutoff): + def mace_graph( + self, basis_vectors, cartesian_positions, spatial_dim, n_atoms, radial_cutoff + ): pbc = np.array([True] * spatial_dim) # periodic boundary conditions atom_type = np.ones(n_atoms) * 14 list_graphs = [] for unit_cell, atomic_positions in zip(basis_vectors, cartesian_positions): - graph_config = Configuration(atomic_numbers=atom_type, - positions=atomic_positions.numpy(), - cell=unit_cell, - pbc=pbc) + graph_config = Configuration( + atomic_numbers=atom_type, + positions=atomic_positions.numpy(), + cell=unit_cell, + pbc=pbc, + ) z_table = get_atomic_number_table_from_zs(list(range(89))) - graph_data = AtomicData.from_config(graph_config, z_table=z_table, cutoff=radial_cutoff) + graph_data = AtomicData.from_config( + graph_config, z_table=z_table, cutoff=radial_cutoff + ) list_graphs.append(graph_data) collate_fn = Collater(follow_batch=[None], exclude_keys=[None]) @@ -127,36 +160,55 @@ def mace_graph(self, basis_vectors, cartesian_positions, spatial_dim, n_atoms, r @pytest.fixture() def score_network_input(self, cartesian_positions, basis_vectors): - score_network_input = {NOISY_CARTESIAN_POSITIONS: cartesian_positions, UNIT_CELL: basis_vectors} + score_network_input = { + NOISY_CARTESIAN_POSITIONS: cartesian_positions, + UNIT_CELL: basis_vectors, + } return score_network_input @pytest.mark.parametrize("radial_cutoff", [1.1, 2.2, 4.4]) def test_input_to_mace(self, score_network_input, radial_cutoff, mace_graph): - computed_mace_graph = input_to_mace(score_network_input, - radial_cutoff=radial_cutoff) + computed_mace_graph = input_to_mace( + score_network_input, radial_cutoff=radial_cutoff + ) - for feature_name in ['node_attrs', 'positions', 'ptr', 'batch', 'cell']: - torch.testing.assert_close(mace_graph[feature_name], computed_mace_graph[feature_name]) + for feature_name in ["node_attrs", "positions", "ptr", "batch", "cell"]: + torch.testing.assert_close( + mace_graph[feature_name], computed_mace_graph[feature_name] + ) # The EDGES might not be in the same order. Compute permutations of edges - expected_src_idx = mace_graph['edge_index'][0, :] - expected_dst_idx = mace_graph['edge_index'][1, :] + expected_src_idx = mace_graph["edge_index"][0, :] + expected_dst_idx = mace_graph["edge_index"][1, :] - expected_displacements = (mace_graph['positions'][expected_dst_idx] + mace_graph['shifts'] - - mace_graph['positions'][expected_src_idx]) + expected_displacements = ( + mace_graph["positions"][expected_dst_idx] + + mace_graph["shifts"] + - mace_graph["positions"][expected_src_idx] + ) - computed_src_idx = computed_mace_graph['edge_index'][0, :] - computed_dst_idx = computed_mace_graph['edge_index'][1, :] - computed_displacements = (computed_mace_graph['positions'][computed_dst_idx] + computed_mace_graph['shifts'] - - computed_mace_graph['positions'][computed_src_idx]) + computed_src_idx = computed_mace_graph["edge_index"][0, :] + computed_dst_idx = computed_mace_graph["edge_index"][1, :] + computed_displacements = ( + computed_mace_graph["positions"][computed_dst_idx] + + computed_mace_graph["shifts"] + - computed_mace_graph["positions"][computed_src_idx] + ) # The edge order can be different between the two graphs - edge_permutation_indices = find_aligning_permutation(expected_displacements, computed_displacements) + edge_permutation_indices = find_aligning_permutation( + expected_displacements, computed_displacements + ) - torch.testing.assert_close(computed_mace_graph['shifts'][edge_permutation_indices], mace_graph['shifts']) - torch.testing.assert_close(computed_mace_graph['edge_index'][:, edge_permutation_indices], - mace_graph['edge_index']) + torch.testing.assert_close( + computed_mace_graph["shifts"][edge_permutation_indices], + mace_graph["shifts"], + ) + torch.testing.assert_close( + computed_mace_graph["edge_index"][:, edge_permutation_indices], + mace_graph["edge_index"], + ) @pytest.fixture() @@ -166,26 +218,31 @@ def scrampled_and_expected_irreps(seed): list_irrep_strings = [] for ell in range(4): - for parity in ['o', 'e']: - list_irrep_strings.append(f'{ell}{parity}') + for parity in ["o", "e"]: + list_irrep_strings.append(f"{ell}{parity}") n_irreps = len(list_irrep_strings) list_repetitions = torch.randint(0, 4, (n_irreps,)) list_irreps = [] - expected_irreps = o3.Irreps('') + expected_irreps = o3.Irreps("") for irrep_string, repetitions in zip(list_irrep_strings, list_repetitions): if repetitions == 0: continue multiplicities = torch.randint(1, 8, (repetitions,)) - list_irreps.extend([o3.Irreps(f'{multiplicity}x{irrep_string}') for multiplicity in multiplicities]) + list_irreps.extend( + [ + o3.Irreps(f"{multiplicity}x{irrep_string}") + for multiplicity in multiplicities + ] + ) total_multiplicity = int(multiplicities.sum()) - expected_irreps += o3.Irreps(f'{total_multiplicity}x{irrep_string}') + expected_irreps += o3.Irreps(f"{total_multiplicity}x{irrep_string}") scrambled_indices = torch.randperm(len(list_irreps)) - scrambled_irreps = o3.Irreps('') + scrambled_irreps = o3.Irreps("") for index in scrambled_indices: scrambled_irreps += list_irreps[index] @@ -196,7 +253,9 @@ def scrampled_and_expected_irreps(seed): def test_get_normalized_irreps_permutation_indices(scrampled_and_expected_irreps): scrambled_irreps, expected_irreps = scrampled_and_expected_irreps - normalized_irreps, column_permutation_indices = get_normalized_irreps_permutation_indices(scrambled_irreps) + normalized_irreps, column_permutation_indices = ( + get_normalized_irreps_permutation_indices(scrambled_irreps) + ) assert expected_irreps == normalized_irreps @@ -227,10 +286,16 @@ def mock_model_savedir(self, tmp_path): # Test correctly downloading a small model @patch("os.makedirs") @patch("os.path.isfile", return_value=False) - @patch("urllib.request.urlretrieve", return_value=(None, 'abc')) + @patch("urllib.request.urlretrieve", return_value=(None, "abc")) @patch("torch.load") - def test_download_pretrained_mace_small(self, mock_load, mock_urlretrieve, mock_isfile, mock_makedirs, - mock_model_savedir): + def test_download_pretrained_mace_small( + self, + mock_load, + mock_urlretrieve, + mock_isfile, + mock_makedirs, + mock_model_savedir, + ): mock_model = MagicMock(spec=torch.nn.Module) mock_model.float.return_value = mock_model mock_load.return_value = mock_model @@ -245,7 +310,9 @@ def test_download_pretrained_mace_small(self, mock_load, mock_urlretrieve, mock_ def test_download_pretrained_mace_invalid_model_name(self, mock_model_savedir): with pytest.raises(AssertionError) as e: get_pretrained_mace("invalid_name", mock_model_savedir) - assert "Model name should be small, medium or large. Got invalid_name" in str(e.value) + assert "Model name should be small, medium or large. Got invalid_name" in str( + e.value + ) class TestReshapes: @@ -277,25 +344,36 @@ def mace_format_tensor(self, num_nodes, num_channels, ell_max): def e3nn_format_tensor(self, num_nodes, num_channels, ell_max): return torch.rand(num_nodes, num_channels * (ell_max + 1) ** 2) - def test_reshape_from_mace_to_e3nn(self, mace_format_tensor, irrep, ell_max, num_channels): + def test_reshape_from_mace_to_e3nn( + self, mace_format_tensor, irrep, ell_max, num_channels + ): converted_tensor = reshape_from_mace_to_e3nn(mace_format_tensor, irrep) # mace_format_tensor: (node, channel, (lmax + 1) ** 2) # converted: (node, channel * (lmax +1)**2) # check for each ell that the values match for ell in range(ell_max + 1): - start_idx = ell ** 2 + start_idx = ell**2 end_idx = (ell + 1) ** 2 - expected_values = mace_format_tensor[:, :, start_idx:end_idx].reshape(-1, num_channels * (2 * ell + 1)) - assert torch.allclose(expected_values, - converted_tensor[:, num_channels * start_idx: num_channels * end_idx]) - - def test_reshape_from_e3nn_to_mace(self, e3nn_format_tensor, irrep, ell_max, num_channels): + expected_values = mace_format_tensor[:, :, start_idx:end_idx].reshape( + -1, num_channels * (2 * ell + 1) + ) + assert torch.allclose( + expected_values, + converted_tensor[:, num_channels * start_idx: num_channels * end_idx], + ) + + def test_reshape_from_e3nn_to_mace( + self, e3nn_format_tensor, irrep, ell_max, num_channels + ): converted_tensor = reshape_from_e3nn_to_mace(e3nn_format_tensor, irrep) # e3nn_format_tensor: (node, channel * (lmax +1)**2) # converted: (node, channel, (lmax + 1) ** 2) for ell in range(ell_max + 1): - start_idx = num_channels * (ell ** 2) + start_idx = num_channels * (ell**2) end_idx = num_channels * ((ell + 1) ** 2) - expected_values = e3nn_format_tensor[:, start_idx:end_idx].reshape(-1, num_channels, 2 * ell + 1) - assert torch.allclose(expected_values, - converted_tensor[:, :, ell ** 2:(ell + 1) ** 2]) + expected_values = e3nn_format_tensor[:, start_idx:end_idx].reshape( + -1, num_channels, 2 * ell + 1 + ) + assert torch.allclose( + expected_values, converted_tensor[:, :, ell**2: (ell + 1) ** 2] + ) diff --git a/tests/models/test_mtp.py b/tests/models/test_mtp.py index 0b31c655..be2b26ff 100644 --- a/tests/models/test_mtp.py +++ b/tests/models/test_mtp.py @@ -5,17 +5,20 @@ import pandas as pd import pytest import yaml -from crystal_diffusion.mlip.mtp_utils import ( +from pymatgen.core import Structure +from sklearn.metrics import mean_absolute_error + +from diffusion_for_multi_scale_molecular_dynamics.mlip.mtp_utils import ( MTPInputs, extract_energy_from_thermo_log, extract_structure_and_forces_from_file, get_metrics_from_pred, prepare_mtp_inputs_from_lammps) -from crystal_diffusion.models.mlip.mtp import MTPArguments, MTPWithMLIP3 -from pymatgen.core import Structure -from sklearn.metrics import mean_absolute_error +from diffusion_for_multi_scale_molecular_dynamics.models.mlip.mtp import ( + MTPArguments, MTPWithMLIP3) class FakeStructure: """Mock a pymatgen structure""" + def __init__(self, species): self.species = species @@ -32,7 +35,10 @@ def passthrough(*args, **kwargs): def mock_popen(mocker): # mock subprocess calling mlp mock_popen = mocker.patch("subprocess.Popen") - mock_popen.return_value.__enter__.return_value.communicate.return_value = (b'', b'') # stdout, stderr + mock_popen.return_value.__enter__.return_value.communicate.return_value = ( + b"", + b"", + ) # stdout, stderr mock_popen.return_value.__enter__.return_value.returncode = 0 return mock_popen @@ -43,10 +49,16 @@ def test_train(mocker, mock_popen, tmpdir): mocker.patch("os.path.exists", return_value=True) # Mock check_structures_forces_stresses to return a value without needing real input - mocker.patch("crystal_diffusion.models.mlip.mtp.check_structures_forces_stresses", side_effect=passthrough) + mocker.patch( + "diffusion_for_multi_scale_molecular_dynamics.models.mlip.mtp.check_structures_forces_stresses", + side_effect=passthrough, + ) # Mock pool_from to return a simplified pool object - mocker.patch("crystal_diffusion.models.mlip.mtp.pool_from", return_value="simple_pool_object") + mocker.patch( + "diffusion_for_multi_scale_molecular_dynamics.models.mlip.mtp.pool_from", + return_value="simple_pool_object", + ) # Mock self.write_cfg to simulate creating a config file without file operations mocker.patch.object(MTPWithMLIP3, "write_cfg", return_value="mock_filename.cfg") @@ -58,14 +70,14 @@ def test_train(mocker, mock_popen, tmpdir): mlip_path="/mock/path", name="test_model", unfitted_mtp="08.almtp", - fitted_mtp_savedir=tmpdir + fitted_mtp_savedir=tmpdir, ) model = MTPWithMLIP3(mtp_args) # Call the train method mtp_inputs = MTPInputs( - structure=[FakeStructure(['H', 'O']), FakeStructure(['Si'])], + structure=[FakeStructure(["H", "O"]), FakeStructure(["Si"])], forces=[], - energy=[1, 2] + energy=[1, 2], ) _ = model.train( @@ -80,13 +92,15 @@ def test_train(mocker, mock_popen, tmpdir): def fake_structure(): # Setup a simple mock structure object # Replace with appropriate structure setup for your use case - return Structure(lattice=[1, 0, 0, 0, 1, 0, 0, 0, 1], species=[""], coords=[[0, 0, 0]]) + return Structure( + lattice=[1, 0, 0, 0, 1, 0, 0, 0, 1], species=[""], coords=[[0, 0, 0]] + ) @pytest.fixture def mtp_instance(mocker): # Mock __init__ to not execute its original behavior - mocker.patch.object(MTPWithMLIP3, '__init__', lambda x, y: None) + mocker.patch.object(MTPWithMLIP3, "__init__", lambda x, y: None) # Setup a mocked instance with necessary attributes instance = MTPWithMLIP3("mock_path") instance.mlp_command = "mock_mlp_command" @@ -101,11 +115,16 @@ def test_evaluate(mocker, fake_structure, mtp_instance, mock_popen): test_forces = [[[0, 0, 0]]] # Mock check_structures_forces_stresses to return the arguments unmodified - mocker.patch("crystal_diffusion.models.mlip.mtp.check_structures_forces_stresses", - side_effect=lambda s, f, st: (s, f, st)) + mocker.patch( + "diffusion_for_multi_scale_molecular_dynamics.models.mlip.mtp.check_structures_forces_stresses", + side_effect=lambda s, f, st: (s, f, st), + ) # Mock pool_from to return a mocked value - mocker.patch("crystal_diffusion.models.mlip.mtp.pool_from", return_value="mock_pool") + mocker.patch( + "diffusion_for_multi_scale_molecular_dynamics.models.mlip.mtp.pool_from", + return_value="mock_pool", + ) # Mock self.write_cfg to simulate creating a config file without file operations mocker.patch.object(MTPWithMLIP3, "write_cfg", return_value="mock_filename.cfg") @@ -119,9 +138,7 @@ def test_evaluate(mocker, fake_structure, mtp_instance, mock_popen): mocker.patch("os.path.exists", return_value=True) mtp_inputs = MTPInputs( - structure=test_structures, - forces=test_forces, - energy=test_energies + structure=test_structures, forces=test_forces, energy=test_energies ) # Perform the test @@ -129,21 +146,22 @@ def test_evaluate(mocker, fake_structure, mtp_instance, mock_popen): # Assertions can vary based on the real output of `read_cfgs` # Here's an example assertion assuming `read_cfgs` returns a string in this mocked scenario - assert df_orig == "mock_dataframe" and df_predict == "mock_dataframe", "Evaluate method should return mock" + \ - "dataframes" + assert df_orig == "mock_dataframe" and df_predict == "mock_dataframe", ( + "Evaluate method should return mock" + "dataframes" + ) def test_read_cfgs(mtp_instance): cfg_path = Path(__file__).parent.joinpath("mtp_cfg_examples.txt") df = mtp_instance.read_cfgs(cfg_path, True) print(df.keys()) - assert np.array_equal(df['x'], [0.1, 0.2, 0.3]) - assert np.array_equal(df['y'], [1.1, 1.2, 1.3]) - assert np.array_equal(df['z'], [2.1, 2.2, 2.3]) - assert np.array_equal(df['fx'], [3.1, 3.2, 3.3]) - assert np.array_equal(df['fy'], [4.1, 4.2, 4.3]) - assert np.array_equal(df['fz'], [5.1, 5.2, 5.3]) - assert np.array_equal(df['nbh_grades'], [6.1, 6.2, 6.3]) + assert np.array_equal(df["x"], [0.1, 0.2, 0.3]) + assert np.array_equal(df["y"], [1.1, 1.2, 1.3]) + assert np.array_equal(df["z"], [2.1, 2.2, 2.3]) + assert np.array_equal(df["fx"], [3.1, 3.2, 3.3]) + assert np.array_equal(df["fy"], [4.1, 4.2, 4.3]) + assert np.array_equal(df["fz"], [5.1, 5.2, 5.3]) + assert np.array_equal(df["nbh_grades"], [6.1, 6.2, 6.3]) def test_extract_structure_and_forces_from_file(tmpdir): @@ -151,16 +169,16 @@ def test_extract_structure_and_forces_from_file(tmpdir): # TODO refactor to move to data/ # Create a mock LAMMPS output yaml_content = { - 'box': [[0, 10], [0, 10], [0, 10]], # x_lim, y_lim, z_lim - 'keywords': ['x', 'y', 'z', 'type', 'fx', 'fy', 'fz'], - 'data': [[1, 1, 1, 1, 0.1, 0.2, 0.3], [2, 2, 2, 2, 0.4, 0.5, 0.6]] + "box": [[0, 10], [0, 10], [0, 10]], # x_lim, y_lim, z_lim + "keywords": ["x", "y", "z", "type", "fx", "fy", "fz"], + "data": [[1, 1, 1, 1, 0.1, 0.2, 0.3], [2, 2, 2, 2, 0.4, 0.5, 0.6]], } yaml_file = os.path.join(tmpdir, "lammps.yaml") with open(yaml_file, "w") as f: yaml.dump(yaml_content, f, sort_keys=False) # Mock atom dict that the function expects - atom_dict = {1: 'H', 2: 'He'} + atom_dict = {1: "H", 2: "He"} # Call the function structures, forces = extract_structure_and_forces_from_file(yaml_file, atom_dict) @@ -175,11 +193,12 @@ def test_extract_structure_and_forces_from_file(tmpdir): # Verify species and positions species = structures[0].species - assert [str(s) for s in species] == ['H', 'He'] + assert [str(s) for s in species] == ["H", "He"] # frac coordinates are reduced coordinates - the values in data are cartesian coordinates # divide by the box length (10) to convert - np.testing.assert_array_almost_equal(structures[0].frac_coords, - [[1 / 10, 1 / 10, 1 / 10], [2 / 10, 2 / 10, 2 / 10]]) + np.testing.assert_array_almost_equal( + structures[0].frac_coords, [[1 / 10, 1 / 10, 1 / 10], [2 / 10, 2 / 10, 2 / 10]] + ) # Verify forces assert isinstance(forces, list) @@ -217,29 +236,46 @@ def test_extract_energy_from_thermo_log(tmpdir): @pytest.fixture def mock_extract_energy_from_thermo_log(mocker): - return mocker.patch('crystal_diffusion.mlip.mtp_utils.extract_energy_from_thermo_log', return_value=[]) + return mocker.patch( + "diffusion_for_multi_scale_molecular_dynamics.mlip.mtp_utils.extract_energy_from_thermo_log", + return_value=[], + ) @pytest.fixture def mock_extract_structure_and_forces(mocker): - return mocker.patch('crystal_diffusion.mlip.mtp_utils.extract_structure_and_forces_from_file', - return_value=([], [])) + return mocker.patch( + "diffusion_for_multi_scale_molecular_dynamics.mlip.mtp_utils.extract_structure_and_forces_from_file", + return_value=([], []), + ) -def test_prepare_mtp_inputs_from_lammps(mock_extract_structure_and_forces, mock_extract_energy_from_thermo_log, tmpdir): +def test_prepare_mtp_inputs_from_lammps( + mock_extract_structure_and_forces, mock_extract_energy_from_thermo_log, tmpdir +): # Create mock file paths - output_yaml_files = [os.path.join(tmpdir, "output1.yaml"), os.path.join(tmpdir, "output2.yaml")] - thermo_yaml_files = [os.path.join(tmpdir, "thermo1.yaml"), os.path.join(tmpdir, "thermo2.yaml")] + output_yaml_files = [ + os.path.join(tmpdir, "output1.yaml"), + os.path.join(tmpdir, "output2.yaml"), + ] + thermo_yaml_files = [ + os.path.join(tmpdir, "thermo1.yaml"), + os.path.join(tmpdir, "thermo2.yaml"), + ] # Mock atom dictionary - atom_dict = {1: 'H', 2: 'He'} + atom_dict = {1: "H", 2: "He"} # Call the function - mtp_inputs = prepare_mtp_inputs_from_lammps(output_yaml_files, thermo_yaml_files, atom_dict) + mtp_inputs = prepare_mtp_inputs_from_lammps( + output_yaml_files, thermo_yaml_files, atom_dict + ) # Verify that the mocks were called correctly assert mock_extract_structure_and_forces.call_count == 2 - mock_extract_structure_and_forces.assert_called_with(output_yaml_files[1], atom_dict, True) + mock_extract_structure_and_forces.assert_called_with( + output_yaml_files[1], atom_dict, True + ) assert mock_extract_energy_from_thermo_log.call_count == 2 mock_extract_energy_from_thermo_log.assert_called_with(thermo_yaml_files[1]) @@ -250,42 +286,67 @@ def test_prepare_mtp_inputs_from_lammps(mock_extract_structure_and_forces, mock_ assert isinstance(mtp_inputs.forces, list) # Verify that the data from the mocks is aggregated into the results correctly - assert mtp_inputs.structure == mock_extract_structure_and_forces.return_value[0] * len(output_yaml_files) - assert mtp_inputs.forces == mock_extract_structure_and_forces.return_value[1] * len(output_yaml_files) - assert mtp_inputs.energy == mock_extract_energy_from_thermo_log.return_value * len(thermo_yaml_files) + assert mtp_inputs.structure == mock_extract_structure_and_forces.return_value[ + 0 + ] * len(output_yaml_files) + assert mtp_inputs.forces == mock_extract_structure_and_forces.return_value[1] * len( + output_yaml_files + ) + assert mtp_inputs.energy == mock_extract_energy_from_thermo_log.return_value * len( + thermo_yaml_files + ) def test_get_metrics_from_pred(): # test function from train_mtp # TODO get better metrics and refactor the script # Assuming there are 2 structures, each with 2 atoms (Total 4 readings) - df_orig = pd.DataFrame({ - 'structure_index': [0, 0, 1, 1], - 'atom_index': [0, 1, 0, 1], - 'energy': [1, 1, 3, 3], # Total energy for the structure is the same for both atoms - 'fx': [0.1, -0.1, 0.2, -0.2], - 'fy': [0.1, -0.1, 0.2, -0.2], - 'fz': [0.1, -0.1, 0.2, -0.2] - }) + df_orig = pd.DataFrame( + { + "structure_index": [0, 0, 1, 1], + "atom_index": [0, 1, 0, 1], + "energy": [ + 1, + 1, + 3, + 3, + ], # Total energy for the structure is the same for both atoms + "fx": [0.1, -0.1, 0.2, -0.2], + "fy": [0.1, -0.1, 0.2, -0.2], + "fz": [0.1, -0.1, 0.2, -0.2], + } + ) # Predicted data with some error introduced - df_predict = pd.DataFrame({ - 'structure_index': [0, 0, 1, 1], - 'atom_index': [0, 1, 0, 1], - 'energy': [1.1, 1.1, 3.3, 3.3], # energy cannot be different per atom for a given structure - 'fx': [0.12, -0.08, 0.23, -0.17], - 'fy': [0.09, -0.11, 0.19, -0.21], - 'fz': [0.11, -0.09, 0.18, -0.22] - }) + df_predict = pd.DataFrame( + { + "structure_index": [0, 0, 1, 1], + "atom_index": [0, 1, 0, 1], + "energy": [ + 1.1, + 1.1, + 3.3, + 3.3, + ], # energy cannot be different per atom for a given structure + "fx": [0.12, -0.08, 0.23, -0.17], + "fy": [0.09, -0.11, 0.19, -0.21], + "fz": [0.11, -0.09, 0.18, -0.22], + } + ) # Calculate expected MAE for energy and forces # 1 value per structure - here, we take indices 0 and 2 - expected_mae_energy = np.abs(df_orig['energy'].iloc[[0, 2]] - df_predict['energy'].iloc[[0, 2]]).mean() / 2 + expected_mae_energy = ( + np.abs( + df_orig["energy"].iloc[[0, 2]] - df_predict["energy"].iloc[[0, 2]] + ).mean() + / 2 + ) # we take the energy per atom. 2 atoms per structure here, so we can simply divide by 2 expected_mae_forces = mean_absolute_error( - df_orig[['fx', 'fy', 'fz']].values.flatten(), - df_predict[['fx', 'fy', 'fz']].values.flatten() + df_orig[["fx", "fy", "fz"]].values.flatten(), + df_predict[["fx", "fy", "fz"]].values.flatten(), ) # Call the function under test diff --git a/tests/models/test_optimizer.py b/tests/models/test_optimizer.py index 11e3e98b..384f0305 100644 --- a/tests/models/test_optimizer.py +++ b/tests/models/test_optimizer.py @@ -1,11 +1,13 @@ import pytest import torch -from crystal_diffusion.models.optimizer import (OptimizerParameters, - load_optimizer) + +from diffusion_for_multi_scale_molecular_dynamics.models.optimizer import ( + OptimizerParameters, load_optimizer) class FakeNeuralNet(torch.nn.Module): """A fake neural net for testing that we can attach an optimizer.""" + def __init__(self): super(FakeNeuralNet, self).__init__() self.linear_layer = torch.nn.Linear(in_features=4, out_features=1) @@ -24,7 +26,7 @@ def weight_decay(request): return request.param -@pytest.fixture(params=['adam', 'adamw']) +@pytest.fixture(params=["adam", "adamw"]) def optimizer_name(request): return request.param @@ -32,7 +34,9 @@ def optimizer_name(request): @pytest.fixture() def optimizer_parameters(optimizer_name, weight_decay): if weight_decay: - return OptimizerParameters(name=optimizer_name, learning_rate=0.01, weight_decay=weight_decay) + return OptimizerParameters( + name=optimizer_name, learning_rate=0.01, weight_decay=weight_decay + ) else: return OptimizerParameters(name=optimizer_name, learning_rate=0.01) diff --git a/tests/models/test_position_diffusion_lightning_model.py b/tests/models/test_position_diffusion_lightning_model.py index 6d3bd49c..06314532 100644 --- a/tests/models/test_position_diffusion_lightning_model.py +++ b/tests/models/test_position_diffusion_lightning_model.py @@ -1,27 +1,32 @@ import pytest import torch -from crystal_diffusion.generators.predictor_corrector_position_generator import \ +from pytorch_lightning import LightningDataModule, Trainer +from torch.utils.data import DataLoader, random_split + +from diffusion_for_multi_scale_molecular_dynamics.generators.predictor_corrector_position_generator import \ PredictorCorrectorSamplingParameters -from crystal_diffusion.metrics.sampling_metrics_parameters import \ +from diffusion_for_multi_scale_molecular_dynamics.metrics.sampling_metrics_parameters import \ SamplingMetricsParameters -from crystal_diffusion.models.loss import create_loss_parameters -from crystal_diffusion.models.optimizer import OptimizerParameters -from crystal_diffusion.models.position_diffusion_lightning_model import ( +from diffusion_for_multi_scale_molecular_dynamics.models.loss import \ + create_loss_parameters +from diffusion_for_multi_scale_molecular_dynamics.models.optimizer import \ + OptimizerParameters +from diffusion_for_multi_scale_molecular_dynamics.models.position_diffusion_lightning_model import ( PositionDiffusionLightningModel, PositionDiffusionParameters) -from crystal_diffusion.models.scheduler import ( +from diffusion_for_multi_scale_molecular_dynamics.models.scheduler import ( CosineAnnealingLRSchedulerParameters, ReduceLROnPlateauSchedulerParameters) -from crystal_diffusion.models.score_networks.mlp_score_network import \ +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.mlp_score_network import \ MLPScoreNetworkParameters -from crystal_diffusion.namespace import CARTESIAN_FORCES, RELATIVE_COORDINATES -from crystal_diffusion.samples.diffusion_sampling_parameters import \ +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, RELATIVE_COORDINATES) +from diffusion_for_multi_scale_molecular_dynamics.samples.diffusion_sampling_parameters import \ DiffusionSamplingParameters -from crystal_diffusion.score.wrapped_gaussian_score import \ +from diffusion_for_multi_scale_molecular_dynamics.score.wrapped_gaussian_score import \ get_sigma_normalized_score_brute_force -from crystal_diffusion.utils.tensor_utils import \ +from diffusion_for_multi_scale_molecular_dynamics.utils.tensor_utils import \ broadcast_batch_tensor_to_all_dimensions -from pytorch_lightning import LightningDataModule, Trainer -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters -from torch.utils.data import DataLoader, random_split +from src.diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters class FakePositionsDataModule(LightningDataModule): @@ -34,10 +39,16 @@ def __init__( ): super().__init__() self.batch_size = batch_size - all_relative_coordinates = torch.rand(dataset_size, number_of_atoms, spatial_dimension) + all_relative_coordinates = torch.rand( + dataset_size, number_of_atoms, spatial_dimension + ) box = torch.rand(spatial_dimension) self.data = [ - {RELATIVE_COORDINATES: configuration, 'box': box, CARTESIAN_FORCES: torch.zeros_like(configuration)} + { + RELATIVE_COORDINATES: configuration, + "box": box, + CARTESIAN_FORCES: torch.zeros_like(configuration), + } for configuration in all_relative_coordinates ] self.train_data, self.val_data, self.test_data = None, None, None @@ -75,25 +86,31 @@ def number_of_atoms(self): def unit_cell_size(self): return 10.1 - @pytest.fixture(params=['adam', 'adamw']) + @pytest.fixture(params=["adam", "adamw"]) def optimizer_parameters(self, request): - return OptimizerParameters(name=request.param, learning_rate=0.01, weight_decay=1e-6) + return OptimizerParameters( + name=request.param, learning_rate=0.01, weight_decay=1e-6 + ) - @pytest.fixture(params=[None, 'ReduceLROnPlateau', 'CosineAnnealingLR']) + @pytest.fixture(params=[None, "ReduceLROnPlateau", "CosineAnnealingLR"]) def scheduler_parameters(self, request): match request.param: case None: scheduler_parameters = None - case 'ReduceLROnPlateau': - scheduler_parameters = ReduceLROnPlateauSchedulerParameters(factor=0.5, patience=2) - case 'CosineAnnealingLR': - scheduler_parameters = CosineAnnealingLRSchedulerParameters(T_max=5, eta_min=1e-5) + case "ReduceLROnPlateau": + scheduler_parameters = ReduceLROnPlateauSchedulerParameters( + factor=0.5, patience=2 + ) + case "CosineAnnealingLR": + scheduler_parameters = CosineAnnealingLRSchedulerParameters( + T_max=5, eta_min=1e-5 + ) case _: raise ValueError(f"Untested case {request.param}") return scheduler_parameters - @pytest.fixture(params=['mse', 'weighted_mse']) + @pytest.fixture(params=["mse", "weighted_mse"]) def loss_parameters(self, request): model_dict = dict(loss=dict(algorithm=request.param)) return create_loss_parameters(model_dictionary=model_dict) @@ -107,27 +124,41 @@ def cell_dimensions(self, unit_cell_size, spatial_dimension): return spatial_dimension * [unit_cell_size] @pytest.fixture() - def sampling_parameters(self, number_of_atoms, spatial_dimension, number_of_samples, cell_dimensions): - sampling_parameters = PredictorCorrectorSamplingParameters(number_of_atoms=number_of_atoms, - spatial_dimension=spatial_dimension, - number_of_samples=number_of_samples, - cell_dimensions=cell_dimensions) + def sampling_parameters( + self, number_of_atoms, spatial_dimension, number_of_samples, cell_dimensions + ): + sampling_parameters = PredictorCorrectorSamplingParameters( + number_of_atoms=number_of_atoms, + spatial_dimension=spatial_dimension, + number_of_samples=number_of_samples, + cell_dimensions=cell_dimensions, + ) return sampling_parameters @pytest.fixture() def diffusion_sampling_parameters(self, sampling_parameters): noise_parameters = NoiseParameters(total_time_steps=5) - metrics_parameters = SamplingMetricsParameters(structure_factor_max_distance=1.) + metrics_parameters = SamplingMetricsParameters( + structure_factor_max_distance=1.0 + ) diffusion_sampling_parameters = DiffusionSamplingParameters( sampling_parameters=sampling_parameters, noise_parameters=noise_parameters, - metrics_parameters=metrics_parameters) + metrics_parameters=metrics_parameters, + ) return diffusion_sampling_parameters @pytest.fixture() - def hyper_params(self, number_of_atoms, spatial_dimension, - optimizer_parameters, scheduler_parameters, - loss_parameters, sampling_parameters, diffusion_sampling_parameters): + def hyper_params( + self, + number_of_atoms, + spatial_dimension, + optimizer_parameters, + scheduler_parameters, + loss_parameters, + sampling_parameters, + diffusion_sampling_parameters, + ): score_network_parameters = MLPScoreNetworkParameters( number_of_atoms=number_of_atoms, n_hidden_dimensions=3, @@ -150,11 +181,15 @@ def hyper_params(self, number_of_atoms, spatial_dimension, @pytest.fixture() def real_relative_coordinates(self, batch_size, number_of_atoms, spatial_dimension): - relative_coordinates = torch.rand(batch_size, number_of_atoms, spatial_dimension) + relative_coordinates = torch.rand( + batch_size, number_of_atoms, spatial_dimension + ) return relative_coordinates @pytest.fixture() - def noisy_relative_coordinates(self, batch_size, number_of_atoms, spatial_dimension): + def noisy_relative_coordinates( + self, batch_size, number_of_atoms, spatial_dimension + ): noisy_relative_coordinates = torch.rand( batch_size, number_of_atoms, spatial_dimension ) @@ -216,7 +251,9 @@ def brute_force_target_normalized_score( @pytest.fixture() def unit_cell_sample(self, unit_cell_size, spatial_dimension, batch_size): - return torch.diag(torch.Tensor([unit_cell_size] * spatial_dimension)).repeat(batch_size, 1, 1) + return torch.diag(torch.Tensor([unit_cell_size] * spatial_dimension)).repeat( + batch_size, 1, 1 + ) # The brute force target normalized scores are *fragile*; they can return NaNs easily. # There is no point in running this test for all possible component combinations. @@ -230,7 +267,7 @@ def test_get_target_normalized_score( real_relative_coordinates, sigmas, brute_force_target_normalized_score, - unit_cell_sample + unit_cell_sample, ): computed_target_normalized_scores = ( lightning_model._get_target_normalized_score( @@ -238,16 +275,24 @@ def test_get_target_normalized_score( ) ) - torch.testing.assert_close(computed_target_normalized_scores, - brute_force_target_normalized_score, - atol=1e-7, - rtol=1e-4) + torch.testing.assert_close( + computed_target_normalized_scores, + brute_force_target_normalized_score, + atol=1e-7, + rtol=1e-4, + ) def test_smoke_test(self, lightning_model, fake_datamodule, accelerator): trainer = Trainer(fast_dev_run=3, accelerator=accelerator) trainer.fit(lightning_model, fake_datamodule) trainer.test(lightning_model, fake_datamodule) - def test_generate_sample(self, lightning_model, number_of_samples, number_of_atoms, spatial_dimension): + def test_generate_sample( + self, lightning_model, number_of_samples, number_of_atoms, spatial_dimension + ): samples_batch = lightning_model.generate_samples() - assert samples_batch[RELATIVE_COORDINATES].shape == (number_of_samples, number_of_atoms, spatial_dimension) + assert samples_batch[RELATIVE_COORDINATES].shape == ( + number_of_samples, + number_of_atoms, + spatial_dimension, + ) diff --git a/tests/models/test_scheduler.py b/tests/models/test_scheduler.py index 57b141eb..a4e75af5 100644 --- a/tests/models/test_scheduler.py +++ b/tests/models/test_scheduler.py @@ -1,14 +1,16 @@ import pytest import torch -from crystal_diffusion.models.optimizer import (OptimizerParameters, - load_optimizer) -from crystal_diffusion.models.scheduler import ( + +from diffusion_for_multi_scale_molecular_dynamics.models.optimizer import ( + OptimizerParameters, load_optimizer) +from diffusion_for_multi_scale_molecular_dynamics.models.scheduler import ( CosineAnnealingLRSchedulerParameters, ReduceLROnPlateauSchedulerParameters, load_scheduler_dictionary) class FakeNeuralNet(torch.nn.Module): """A fake neural net for testing that we can attach an optimizer.""" + def __init__(self): super(FakeNeuralNet, self).__init__() self.linear_layer = torch.nn.Linear(in_features=4, out_features=1) @@ -20,12 +22,14 @@ def forward(self, x: torch.Tensor) -> torch.Tensor: @pytest.fixture def optimizer(): model = FakeNeuralNet() - optimizer_parameters = OptimizerParameters(name='adam', learning_rate=0.001, weight_decay=1e-6) + optimizer_parameters = OptimizerParameters( + name="adam", learning_rate=0.001, weight_decay=1e-6 + ) optimizer = load_optimizer(optimizer_parameters, model) return optimizer -@pytest.fixture(params=['CosineAnnealingLR', 'ReduceLROnPlateau']) +@pytest.fixture(params=["CosineAnnealingLR", "ReduceLROnPlateau"]) def scheduler_name(request): return request.param @@ -33,14 +37,20 @@ def scheduler_name(request): @pytest.fixture def scheduler_parameters(scheduler_name): match scheduler_name: - case 'CosineAnnealingLR': - parameters = CosineAnnealingLRSchedulerParameters(name=scheduler_name, T_max=1000, eta_min=0.001) - case 'ReduceLROnPlateau': - parameters = ReduceLROnPlateauSchedulerParameters(name=scheduler_name, factor=0.243, patience=17) + case "CosineAnnealingLR": + parameters = CosineAnnealingLRSchedulerParameters( + name=scheduler_name, T_max=1000, eta_min=0.001 + ) + case "ReduceLROnPlateau": + parameters = ReduceLROnPlateauSchedulerParameters( + name=scheduler_name, factor=0.243, patience=17 + ) case _: raise ValueError(f"Untested case: {scheduler_name}") return parameters def test_load_scheduler(optimizer, scheduler_parameters): - _ = load_scheduler_dictionary(scheduler_parameters=scheduler_parameters, optimizer=optimizer) + _ = load_scheduler_dictionary( + scheduler_parameters=scheduler_parameters, optimizer=optimizer + ) diff --git a/tests/models/test_score_fokker_planck_error.py b/tests/models/test_score_fokker_planck_error.py index 672d5a90..095a7c2a 100644 --- a/tests/models/test_score_fokker_planck_error.py +++ b/tests/models/test_score_fokker_planck_error.py @@ -3,16 +3,19 @@ import einops import pytest import torch -from crystal_diffusion.models.score_networks.egnn_score_network import \ + +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.egnn_score_network import \ EGNNScoreNetworkParameters -from crystal_diffusion.models.score_networks.score_network_factory import \ +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_network_factory import \ create_score_network -from crystal_diffusion.namespace import (NOISE, NOISY_RELATIVE_COORDINATES, - TIME, UNIT_CELL) -from crystal_diffusion.samplers.exploding_variance import ExplodingVariance -from src.crystal_diffusion.models.normalized_score_fokker_planck_error import \ +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) +from diffusion_for_multi_scale_molecular_dynamics.samplers.exploding_variance import \ + ExplodingVariance +from src.diffusion_for_multi_scale_molecular_dynamics.models.normalized_score_fokker_planck_error import \ NormalizedScoreFokkerPlanckError -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters +from src.diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters def get_finite_difference_time_derivative( diff --git a/tests/oracle/test_lammps.py b/tests/oracle/test_lammps.py index cb342d90..77947b49 100644 --- a/tests/oracle/test_lammps.py +++ b/tests/oracle/test_lammps.py @@ -1,6 +1,8 @@ import numpy as np import pytest -from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps + +from diffusion_for_multi_scale_molecular_dynamics.oracle.lammps import \ + get_energy_and_forces_from_lammps @pytest.fixture @@ -18,10 +20,11 @@ def high_symmetry_positions(): # do not run on github because no lammps @pytest.mark.not_on_github def test_high_symmetry(high_symmetry_positions, high_symmetry_lattice): - energy, forces = get_energy_and_forces_from_lammps(high_symmetry_positions, high_symmetry_lattice, - atom_types=np.array([1, 1])) - for x in ['x', 'y', 'z']: - assert np.allclose(forces[f'f{x}'], [0, 0]) + energy, forces = get_energy_and_forces_from_lammps( + high_symmetry_positions, high_symmetry_lattice, atom_types=np.array([1, 1]) + ) + for x in ["x", "y", "z"]: + assert np.allclose(forces[f"f{x}"], [0, 0]) assert energy < 0 @@ -33,8 +36,9 @@ def low_symmetry_positions(): @pytest.mark.not_on_github def test_low_symmetry(low_symmetry_positions, high_symmetry_lattice): - energy, forces = get_energy_and_forces_from_lammps(low_symmetry_positions, high_symmetry_lattice, - atom_types=np.array([1, 1])) - for x in ['x', 'y', 'z']: - assert not np.allclose(forces[f'f{x}'], [0, 0]) + energy, forces = get_energy_and_forces_from_lammps( + low_symmetry_positions, high_symmetry_lattice, atom_types=np.array([1, 1]) + ) + for x in ["x", "y", "z"]: + assert not np.allclose(forces[f"f{x}"], [0, 0]) assert energy < 0 diff --git a/tests/samplers/test_exploding_variance.py b/tests/samplers/test_exploding_variance.py index 551471fb..e588e31a 100644 --- a/tests/samplers/test_exploding_variance.py +++ b/tests/samplers/test_exploding_variance.py @@ -1,7 +1,10 @@ import pytest import torch -from crystal_diffusion.samplers.exploding_variance import ExplodingVariance -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters + +from diffusion_for_multi_scale_molecular_dynamics.samplers.exploding_variance import \ + ExplodingVariance +from src.diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters class TestExplodingVariance: @@ -20,9 +23,9 @@ def sigma_max(self): @pytest.fixture() def noise_parameters(self, sigma_min, sigma_max): - return NoiseParameters(total_time_steps=10, - sigma_min=sigma_min, - sigma_max=sigma_max) + return NoiseParameters( + total_time_steps=10, sigma_min=sigma_min, sigma_max=sigma_max + ) @pytest.fixture() def times(self): @@ -36,7 +39,9 @@ def exploding_variance(self, noise_parameters): def expected_sigmas(self, noise_parameters, times): expected_sigmas = [] for t in times: - sigma = noise_parameters.sigma_min**(1. - t) * noise_parameters.sigma_max**t + sigma = ( + noise_parameters.sigma_min ** (1.0 - t) * noise_parameters.sigma_max**t + ) expected_sigmas.append(sigma) return torch.tensor(expected_sigmas) @@ -63,7 +68,7 @@ def test_get_g_squared(self, exploding_variance, times): t = torch.tensor(times, requires_grad=True) sigma = exploding_variance.get_sigma(t) - sigma_squared = sigma ** 2 + sigma_squared = sigma**2 gradients = torch.ones_like(sigma_squared) sigma_squared.backward(gradients) diff --git a/tests/samplers/test_noisy_relative_coordinates_sampler.py b/tests/samplers/test_noisy_relative_coordinates_sampler.py index a087b8c6..b18b5f76 100644 --- a/tests/samplers/test_noisy_relative_coordinates_sampler.py +++ b/tests/samplers/test_noisy_relative_coordinates_sampler.py @@ -1,7 +1,8 @@ import numpy as np import pytest import torch -from crystal_diffusion.samplers.noisy_relative_coordinates_sampler import \ + +from diffusion_for_multi_scale_molecular_dynamics.samplers.noisy_relative_coordinates_sampler import \ NoisyRelativeCoordinatesSampler @@ -47,8 +48,10 @@ def test_get_noisy_relative_coordinates_sample( return_value=fake_gaussian_sample, ) - computed_samples = NoisyRelativeCoordinatesSampler.get_noisy_relative_coordinates_sample( - real_relative_coordinates, sigmas + computed_samples = ( + NoisyRelativeCoordinatesSampler.get_noisy_relative_coordinates_sample( + real_relative_coordinates, sigmas + ) ) flat_sigmas = sigmas.flatten() diff --git a/tests/samplers/test_variance_sampler.py b/tests/samplers/test_variance_sampler.py index 50779272..bfe4c5ea 100644 --- a/tests/samplers/test_variance_sampler.py +++ b/tests/samplers/test_variance_sampler.py @@ -1,6 +1,7 @@ import pytest import torch -from src.crystal_diffusion.samplers.variance_sampler import ( + +from src.diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import ( ExplodingVarianceSampler, NoiseParameters) @@ -10,11 +11,15 @@ @pytest.mark.parametrize("corrector_step_epsilon", [2e-5, 0.1]) class TestExplodingVarianceSampler: @pytest.fixture() - def noise_parameters(self, total_time_steps, time_delta, sigma_min, corrector_step_epsilon): - return NoiseParameters(total_time_steps=total_time_steps, - time_delta=time_delta, - sigma_min=sigma_min, - corrector_step_epsilon=corrector_step_epsilon) + def noise_parameters( + self, total_time_steps, time_delta, sigma_min, corrector_step_epsilon + ): + return NoiseParameters( + total_time_steps=total_time_steps, + time_delta=time_delta, + sigma_min=sigma_min, + corrector_step_epsilon=corrector_step_epsilon, + ) @pytest.fixture() def variance_sampler(self, noise_parameters): @@ -24,7 +29,7 @@ def variance_sampler(self, noise_parameters): def expected_times(self, total_time_steps, time_delta): times = [] for i in range(total_time_steps): - t = i / (total_time_steps - 1) * (1. - time_delta) + time_delta + t = i / (total_time_steps - 1) * (1.0 - time_delta) + time_delta times.append(t) times = torch.tensor(times) return times @@ -60,7 +65,9 @@ def test_time_array(self, variance_sampler, expected_times): def test_sigma_and_sigma_squared_arrays(self, variance_sampler, expected_sigmas): torch.testing.assert_close(variance_sampler._sigma_array, expected_sigmas) - torch.testing.assert_close(variance_sampler._sigma_squared_array, expected_sigmas**2) + torch.testing.assert_close( + variance_sampler._sigma_squared_array, expected_sigmas**2 + ) def test_g_and_g_square_array(self, variance_sampler, expected_sigmas, sigma_min): expected_sigmas_square = expected_sigmas**2 @@ -78,11 +85,16 @@ def test_g_and_g_square_array(self, variance_sampler, expected_sigmas, sigma_min expected_g_array = torch.sqrt(expected_g_squared_array) torch.testing.assert_close(variance_sampler._g_array, expected_g_array) - torch.testing.assert_close(variance_sampler._g_squared_array, expected_g_squared_array) + torch.testing.assert_close( + variance_sampler._g_squared_array, expected_g_squared_array + ) def test_epsilon_arrays(self, variance_sampler, expected_epsilons): torch.testing.assert_close(variance_sampler._epsilon_array, expected_epsilons) - torch.testing.assert_close(variance_sampler._sqrt_two_epsilon_array, torch.sqrt(2. * expected_epsilons)) + torch.testing.assert_close( + variance_sampler._sqrt_two_epsilon_array, + torch.sqrt(2.0 * expected_epsilons), + ) def test_get_random_time_step_indices(self, variance_sampler, total_time_steps): random_indices = variance_sampler._get_random_time_step_indices(shape=(1000,)) @@ -120,9 +132,15 @@ def test_get_all_sampling_parameters(self, variance_sampler): noise, langevin_dynamics = variance_sampler.get_all_sampling_parameters() torch.testing.assert_close(noise.time, variance_sampler._time_array) torch.testing.assert_close(noise.sigma, variance_sampler._sigma_array) - torch.testing.assert_close(noise.sigma_squared, variance_sampler._sigma_squared_array) + torch.testing.assert_close( + noise.sigma_squared, variance_sampler._sigma_squared_array + ) torch.testing.assert_close(noise.g, variance_sampler._g_array) torch.testing.assert_close(noise.g_squared, variance_sampler._g_squared_array) - torch.testing.assert_close(langevin_dynamics.epsilon, variance_sampler._epsilon_array) - torch.testing.assert_close(langevin_dynamics.sqrt_2_epsilon, variance_sampler._sqrt_two_epsilon_array) + torch.testing.assert_close( + langevin_dynamics.epsilon, variance_sampler._epsilon_array + ) + torch.testing.assert_close( + langevin_dynamics.sqrt_2_epsilon, variance_sampler._sqrt_two_epsilon_array + ) diff --git a/tests/samples_and_metrics/test_sampling.py b/tests/samples_and_metrics/test_sampling.py index fb939520..c7cdc993 100644 --- a/tests/samples_and_metrics/test_sampling.py +++ b/tests/samples_and_metrics/test_sampling.py @@ -1,13 +1,15 @@ import einops import pytest import torch -from crystal_diffusion.namespace import (CARTESIAN_POSITIONS, - RELATIVE_COORDINATES, UNIT_CELL) -from crystal_diffusion.utils.basis_transformations import \ + +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_POSITIONS, RELATIVE_COORDINATES, UNIT_CELL) +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ get_positions_from_coordinates -from src.crystal_diffusion.generators.position_generator import ( +from src.diffusion_for_multi_scale_molecular_dynamics.generators.position_generator import ( PositionGenerator, SamplingParameters) -from src.crystal_diffusion.samples.sampling import create_batch_of_samples +from src.diffusion_for_multi_scale_molecular_dynamics.samples.sampling import \ + create_batch_of_samples class DummyGenerator(PositionGenerator): @@ -22,7 +24,9 @@ def sample( self, number_of_samples: int, device: torch.device, unit_cell: torch.Tensor ) -> torch.Tensor: self._counter += number_of_samples - return self._relative_coordinates[self._counter - number_of_samples:self._counter] + return self._relative_coordinates[ + self._counter - number_of_samples: self._counter + ] @pytest.fixture diff --git a/tests/score/test_wrapped_gaussian_score.py b/tests/score/test_wrapped_gaussian_score.py index 0787bede..adb77f34 100644 --- a/tests/score/test_wrapped_gaussian_score.py +++ b/tests/score/test_wrapped_gaussian_score.py @@ -1,7 +1,8 @@ import numpy as np import pytest import torch -from crystal_diffusion.score.wrapped_gaussian_score import ( + +from diffusion_for_multi_scale_molecular_dynamics.score.wrapped_gaussian_score import ( SIGMA_THRESHOLD, _get_large_sigma_mask, _get_s1a_exponential, _get_s1b_exponential, _get_sigma_normalized_s2, _get_sigma_square_times_score_1_from_exponential, @@ -44,7 +45,9 @@ def expected_sigma_normalized_scores(relative_coordinates, sigmas): shape = relative_coordinates.shape list_sigma_normalized_scores = [] - for u, sigma in zip(relative_coordinates.numpy().flatten(), sigmas.numpy().flatten()): + for u, sigma in zip( + relative_coordinates.numpy().flatten(), sigmas.numpy().flatten() + ): s = get_sigma_normalized_score_brute_force(u, sigma) list_sigma_normalized_scores.append(s) @@ -162,10 +165,14 @@ def test_get_sigma_normalized_s2(list_u, list_sigma, list_k, numerical_type): deriv_z2 = torch.tensor(0.0, dtype=numerical_type) for k in list_k: - g_term = torch.sqrt(2.0 * pi) * sigma * (-2.0 * pi**2 * sigma**2 * k**2).exp() - (-pi * k**2).exp() + g_term = ( + torch.sqrt(2.0 * pi) * sigma * (-2.0 * pi**2 * sigma**2 * k**2).exp() + - (-pi * k**2).exp() + ) z2 += (-pi * (u + k) ** 2).exp() + g_term * torch.cos(2 * pi * k * u) - deriv_z2 += (-2.0 * pi * (u + k) * (-pi * (u + k) ** 2).exp() - - 2.0 * pi * k * g_term * torch.sin(2.0 * pi * k * u)) + deriv_z2 += -2.0 * pi * (u + k) * ( + -pi * (u + k) ** 2 + ).exp() - 2.0 * pi * k * g_term * torch.sin(2.0 * pi * k * u) expected_value = sigma * deriv_z2 / z2 list_expected_s2.append(expected_value) @@ -178,11 +185,13 @@ def test_get_sigma_normalized_s2(list_u, list_sigma, list_k, numerical_type): @pytest.mark.parametrize("kmax", [4]) @pytest.mark.parametrize("shape", test_shapes) def test_get_sigma_normalized_score( - relative_coordinates, sigmas, kmax, expected_sigma_normalized_scores + relative_coordinates, sigmas, kmax, expected_sigma_normalized_scores ): sigma_normalized_score_small_sigma = get_sigma_normalized_score( relative_coordinates, sigmas, kmax ) torch.testing.assert_close( - sigma_normalized_score_small_sigma, expected_sigma_normalized_scores, check_dtype=False + sigma_normalized_score_small_sigma, + expected_sigma_normalized_scores, + check_dtype=False, ) diff --git a/tests/test_hp_utils.py b/tests/test_hp_utils.py index d35c8b71..d136417f 100644 --- a/tests/test_hp_utils.py +++ b/tests/test_hp_utils.py @@ -1,28 +1,30 @@ import pytest -from crystal_diffusion.utils.hp_utils import check_hp + +from diffusion_for_multi_scale_molecular_dynamics.utils.hp_utils import \ + check_hp def test_check_hp__all_params_are_there(): - names = ['a', 'b'] - hps = {'a': 0, 'b': 1} + names = ["a", "b"] + hps = {"a": 0, "b": 1} check_hp(names, hps) def test_check_hp__param_is_missing(): - names = ['a', 'b'] - hps = {'a': 0} + names = ["a", "b"] + hps = {"a": 0} with pytest.raises(ValueError): check_hp(names, hps) def test_check_hp__extra_param_allowed(): - names = ['a'] - hps = {'a': 0, 'b': 1} + names = ["a"] + hps = {"a": 0, "b": 1} check_hp(names, hps) def test_check_hp__extra_param_not_allowed(): - names = ['a'] - hps = {'a': 0, 'b': 1} + names = ["a"] + hps = {"a": 0, "b": 1} with pytest.raises(ValueError): check_hp(names, hps, allow_extra=False) diff --git a/tests/test_sample_diffusion.py b/tests/test_sample_diffusion.py index 56c6e07a..d6f8d0ab 100644 --- a/tests/test_sample_diffusion.py +++ b/tests/test_sample_diffusion.py @@ -3,17 +3,22 @@ import pytest import torch import yaml -from crystal_diffusion import sample_diffusion -from crystal_diffusion.generators.predictor_corrector_position_generator import \ + +from diffusion_for_multi_scale_molecular_dynamics import sample_diffusion +from diffusion_for_multi_scale_molecular_dynamics.generators.predictor_corrector_position_generator import \ PredictorCorrectorSamplingParameters -from crystal_diffusion.models.loss import MSELossParameters -from crystal_diffusion.models.optimizer import OptimizerParameters -from crystal_diffusion.models.position_diffusion_lightning_model import ( +from diffusion_for_multi_scale_molecular_dynamics.models.loss import \ + MSELossParameters +from diffusion_for_multi_scale_molecular_dynamics.models.optimizer import \ + OptimizerParameters +from diffusion_for_multi_scale_molecular_dynamics.models.position_diffusion_lightning_model import ( PositionDiffusionLightningModel, PositionDiffusionParameters) -from crystal_diffusion.models.score_networks.mlp_score_network import \ +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.mlp_score_network import \ MLPScoreNetworkParameters -from crystal_diffusion.namespace import RELATIVE_COORDINATES -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters +from diffusion_for_multi_scale_molecular_dynamics.namespace import \ + RELATIVE_COORDINATES +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters @pytest.fixture() @@ -139,7 +144,7 @@ def test_sample_diffusion( record_samples, ): mocker.patch( - "crystal_diffusion.sample_diffusion.get_sigma_normalized_score_network", + "diffusion_for_multi_scale_molecular_dynamics.sample_diffusion.get_sigma_normalized_score_network", return_value=sigma_normalized_score_network, ) diff --git a/tests/test_train_diffusion.py b/tests/test_train_diffusion.py index e7866b6f..16f10d23 100644 --- a/tests/test_train_diffusion.py +++ b/tests/test_train_diffusion.py @@ -4,6 +4,7 @@ to make sure that the code can run without crashing when given appropriate inputs: we are not testing that the results are correct. """ + import glob import os import re @@ -12,10 +13,10 @@ import numpy as np import pytest import yaml -from crystal_diffusion import train_diffusion -from crystal_diffusion.callbacks.standard_callbacks import (BEST_MODEL_NAME, - LAST_MODEL_NAME) +from diffusion_for_multi_scale_molecular_dynamics import train_diffusion +from diffusion_for_multi_scale_molecular_dynamics.callbacks.standard_callbacks import ( + BEST_MODEL_NAME, LAST_MODEL_NAME) from tests.conftest import TestDiffusionDataBase best_model_regex = re.compile(r"best_model-epoch=(?P(\d+)).*.ckpt") @@ -24,6 +25,7 @@ class DelayedInterrupt: """This class will raise a KeyboardInterrupt when its method is called a certain number of times.""" + def __init__(self, how_many_epochs_before_interrupt: int): self.how_many_epochs_before_interrupt = how_many_epochs_before_interrupt self.counter = 0 @@ -36,130 +38,174 @@ def delayed_interrupt(self): def get_prediction_head_parameters(name: str): - if name == 'mlp': - head_parameters = dict(name='mlp', - hidden_dimensions_size=16, - n_hidden_dimensions=3) - - elif name == 'equivariant': - head_parameters = dict(name='equivariant', - time_embedding_irreps="16x0e", - number_of_layers=2, - gate="silu") + if name == "mlp": + head_parameters = dict( + name="mlp", hidden_dimensions_size=16, n_hidden_dimensions=3 + ) + + elif name == "equivariant": + head_parameters = dict( + name="equivariant", + time_embedding_irreps="16x0e", + number_of_layers=2, + gate="silu", + ) else: raise NotImplementedError("This score network is not implemented") return head_parameters -def get_score_network(architecture: str, head_name: Union[str, None], number_of_atoms: int): - if architecture == 'mlp': +def get_score_network( + architecture: str, head_name: Union[str, None], number_of_atoms: int +): + if architecture == "mlp": assert head_name is None, "There are no head options for a MLP score network." - score_network = dict(architecture='mlp', - number_of_atoms=number_of_atoms, - embedding_dimensions_size=8, - n_hidden_dimensions=2, - hidden_dimensions_size=16) - elif architecture == 'mace': - score_network = dict(architecture='mace', - r_max=3.0, - num_bessel=4, - hidden_irreps="8x0e + 8x1o", - number_of_atoms=number_of_atoms, - radial_MLP=[4, 4, 4], - prediction_head_parameters=get_prediction_head_parameters(head_name)) - - elif architecture == 'diffusion_mace': + score_network = dict( + architecture="mlp", + number_of_atoms=number_of_atoms, + embedding_dimensions_size=8, + n_hidden_dimensions=2, + hidden_dimensions_size=16, + ) + elif architecture == "mace": + score_network = dict( + architecture="mace", + r_max=3.0, + num_bessel=4, + hidden_irreps="8x0e + 8x1o", + number_of_atoms=number_of_atoms, + radial_MLP=[4, 4, 4], + prediction_head_parameters=get_prediction_head_parameters(head_name), + ) + + elif architecture == "diffusion_mace": assert head_name is None, "There are no head options for a MLP score network." - score_network = dict(architecture='diffusion_mace', - r_max=3.0, - num_bessel=4, - hidden_irreps="8x0e + 8x1o", - mlp_irreps="8x0e", - number_of_mlp_layers=1, - number_of_atoms=number_of_atoms, - radial_MLP=[4, 4, 4]) - - elif architecture == 'egnn': - score_network = dict(architecture='egnn') + score_network = dict( + architecture="diffusion_mace", + r_max=3.0, + num_bessel=4, + hidden_irreps="8x0e + 8x1o", + mlp_irreps="8x0e", + number_of_mlp_layers=1, + number_of_atoms=number_of_atoms, + radial_MLP=[4, 4, 4], + ) + + elif architecture == "egnn": + score_network = dict(architecture="egnn") else: raise NotImplementedError("This score network is not implemented") return score_network -def get_config(number_of_atoms: int, max_epoch: int, architecture: str, head_name: Union[str, None], - sampling_algorithm: str): +def get_config( + number_of_atoms: int, + max_epoch: int, + architecture: str, + head_name: Union[str, None], + sampling_algorithm: str, +): data_config = dict(batch_size=4, num_workers=0, max_atom=number_of_atoms) - model_config = dict(score_network=get_score_network(architecture, head_name, number_of_atoms), - loss={'algorithm': 'mse'}, - noise={'total_time_steps': 10}) - - optimizer_config = dict(name='adam', learning_rate=0.001) - scheduler_config = dict(name='ReduceLROnPlateau', factor=0.6, patience=2) - - sampling_dict = dict(algorithm=sampling_algorithm, - spatial_dimension=3, - number_of_atoms=number_of_atoms, - number_of_samples=4, - record_samples=True, - cell_dimensions=[10., 10., 10.]) - if sampling_algorithm == 'predictor_corrector': + model_config = dict( + score_network=get_score_network(architecture, head_name, number_of_atoms), + loss={"algorithm": "mse"}, + noise={"total_time_steps": 10}, + ) + + optimizer_config = dict(name="adam", learning_rate=0.001) + scheduler_config = dict(name="ReduceLROnPlateau", factor=0.6, patience=2) + + sampling_dict = dict( + algorithm=sampling_algorithm, + spatial_dimension=3, + number_of_atoms=number_of_atoms, + number_of_samples=4, + record_samples=True, + cell_dimensions=[10.0, 10.0, 10.0], + ) + if sampling_algorithm == "predictor_corrector": sampling_dict["number_of_corrector_steps"] = 1 - early_stopping_config = dict(metric='validation_epoch_loss', mode='min', patience=max_epoch) - model_checkpoint_config = dict(monitor='validation_epoch_loss', mode='min') - diffusion_sampling_config = dict(noise={'total_time_steps': 10}, - sampling=sampling_dict, - metrics={'compute_energies': False, - 'compute_structure_factor': True, - 'structure_factor_max_distance': 5.0}) - - config = dict(max_epoch=max_epoch, - exp_name='smoke_test', - seed=9999, - spatial_dimension=3, - data=data_config, - model=model_config, - optimizer=optimizer_config, - scheduler=scheduler_config, - early_stopping=early_stopping_config, - model_checkpoint=model_checkpoint_config, - loss_monitoring=dict(number_of_bins=10, sample_every_n_epochs=3), - diffusion_sampling=diffusion_sampling_config, - logging=['csv', 'tensorboard']) + early_stopping_config = dict( + metric="validation_epoch_loss", mode="min", patience=max_epoch + ) + model_checkpoint_config = dict(monitor="validation_epoch_loss", mode="min") + diffusion_sampling_config = dict( + noise={"total_time_steps": 10}, + sampling=sampling_dict, + metrics={ + "compute_energies": False, + "compute_structure_factor": True, + "structure_factor_max_distance": 5.0, + }, + ) + + config = dict( + max_epoch=max_epoch, + exp_name="smoke_test", + seed=9999, + spatial_dimension=3, + data=data_config, + model=model_config, + optimizer=optimizer_config, + scheduler=scheduler_config, + early_stopping=early_stopping_config, + model_checkpoint=model_checkpoint_config, + loss_monitoring=dict(number_of_bins=10, sample_every_n_epochs=3), + diffusion_sampling=diffusion_sampling_config, + logging=["csv", "tensorboard"], + ) return config @pytest.mark.parametrize("sampling_algorithm", ["ode", "predictor_corrector"]) -@pytest.mark.parametrize("architecture, head_name", - [('egnn', None), - ('diffusion_mace', None), - ('mlp', None), - ('mace', 'equivariant'), - ('mace', 'mlp')]) +@pytest.mark.parametrize( + "architecture, head_name", + [ + ("egnn", None), + ("diffusion_mace", None), + ("mlp", None), + ("mace", "equivariant"), + ("mace", "mlp"), + ], +) class TestTrainDiffusion(TestDiffusionDataBase): @pytest.fixture() def max_epoch(self): return 5 @pytest.fixture() - def config(self, number_of_atoms, max_epoch, architecture, head_name, sampling_algorithm): - return get_config(number_of_atoms, max_epoch=max_epoch, - architecture=architecture, head_name=head_name, sampling_algorithm=sampling_algorithm) + def config( + self, number_of_atoms, max_epoch, architecture, head_name, sampling_algorithm + ): + return get_config( + number_of_atoms, + max_epoch=max_epoch, + architecture=architecture, + head_name=head_name, + sampling_algorithm=sampling_algorithm, + ) @pytest.fixture() def all_paths(self, paths, tmpdir, config): - all_paths = dict(data=paths['raw_data_dir'], processed_datadir=paths['processed_data_dir']) + all_paths = dict( + data=paths["raw_data_dir"], processed_datadir=paths["processed_data_dir"] + ) - for directory_name in ['dataset_working_dir', 'output']: + for directory_name in ["dataset_working_dir", "output"]: # use random names to make sure we didn't accidentally hardcode a folder name. - directory = os.path.join(tmpdir, f"{directory_name}_{np.random.randint(99999999)}") + directory = os.path.join( + tmpdir, f"{directory_name}_{np.random.randint(99999999)}" + ) all_paths[directory_name] = directory - all_paths['config'] = os.path.join(tmpdir, f"config_{np.random.randint(99999999)}.yaml") + all_paths["config"] = os.path.join( + tmpdir, f"config_{np.random.randint(99999999)}.yaml" + ) - with open(all_paths['config'], 'w') as fd: + with open(all_paths["config"], "w") as fd: yaml.dump(config, fd) return all_paths @@ -167,58 +213,71 @@ def all_paths(self, paths, tmpdir, config): @pytest.fixture() def args(self, all_paths, accelerator): """Input arguments for main.""" - input_args = [f"--config={all_paths['config']}", - f"--data={all_paths['data']}", - f"--processed_datadir={all_paths['processed_datadir']}", - f"--dataset_working_dir={all_paths['dataset_working_dir']}", - f"--output={all_paths['output']}", - f"--accelerator={accelerator}", - f"--devices={1}"] + input_args = [ + f"--config={all_paths['config']}", + f"--data={all_paths['data']}", + f"--processed_datadir={all_paths['processed_datadir']}", + f"--dataset_working_dir={all_paths['dataset_working_dir']}", + f"--output={all_paths['output']}", + f"--accelerator={accelerator}", + f"--devices={1}", + ] return input_args @pytest.mark.slow def test_checkpoint_callback(self, args, all_paths, max_epoch): train_diffusion.main(args) - best_model_path = os.path.join(all_paths['output'], BEST_MODEL_NAME) - last_model_path = os.path.join(all_paths['output'], LAST_MODEL_NAME) + best_model_path = os.path.join(all_paths["output"], BEST_MODEL_NAME) + last_model_path = os.path.join(all_paths["output"], LAST_MODEL_NAME) model_paths = [best_model_path, last_model_path] regexes = [best_model_regex, last_model_regex] - should_test_epoch_number = [False, True] # the 'best' model epoch is ill-defined. Don't test! + should_test_epoch_number = [ + False, + True, + ] # the 'best' model epoch is ill-defined. Don't test! - for model_path, regex, test_epoch_number in zip(model_paths, regexes, should_test_epoch_number): - paths_in_directory = glob.glob(model_path + '/*.ckpt') + for model_path, regex, test_epoch_number in zip( + model_paths, regexes, should_test_epoch_number + ): + paths_in_directory = glob.glob(model_path + "/*.ckpt") assert len(paths_in_directory) == 1 model_filename = os.path.basename(paths_in_directory[0]) match_object = regex.match(model_filename) assert match_object is not None if test_epoch_number: - model_epoch = int(match_object.group('epoch')) + model_epoch = int(match_object.group("epoch")) assert model_epoch == max_epoch - 1 # the epoch counter starts at zero! - @pytest.mark.skip(reason="This test fails because of some obscure change in the Pytorch-Lightning library. " - "'Restart' is such a low value proposition at this time that it is not worth the " - "time and effort to fight with a subtle library issue.") + @pytest.mark.skip( + reason="This test fails because of some obscure change in the Pytorch-Lightning library. " + "'Restart' is such a low value proposition at this time that it is not worth the " + "time and effort to fight with a subtle library issue." + ) def test_restart(self, args, all_paths, max_epoch, mocker): - last_model_path = os.path.join(all_paths['output'], LAST_MODEL_NAME) + last_model_path = os.path.join(all_paths["output"], LAST_MODEL_NAME) - method_to_patch = ("crystal_diffusion.models.position_diffusion_lightning_model." - "PositionDiffusionLightningModel.on_train_epoch_start") + method_to_patch = ( + "diffusion_for_multi_scale_molecular_dynamics.models.position_diffusion_lightning_model." + "PositionDiffusionLightningModel.on_train_epoch_start" + ) interruption_epoch = max_epoch // 2 - interruptor = DelayedInterrupt(how_many_epochs_before_interrupt=interruption_epoch) + interruptor = DelayedInterrupt( + how_many_epochs_before_interrupt=interruption_epoch + ) mocker.patch(method_to_patch, side_effect=interruptor.delayed_interrupt) train_diffusion.main(args) # Check that there is only one last model, and that it fits expectation - paths_in_directory = glob.glob(last_model_path + '/*.ckpt') + paths_in_directory = glob.glob(last_model_path + "/*.ckpt") last_model_filename = os.path.basename(paths_in_directory[0]) match_object = last_model_regex.match(last_model_filename) assert match_object is not None - model_epoch = int(match_object.group('epoch')) + model_epoch = int(match_object.group("epoch")) assert model_epoch == interruption_epoch - 1 # Restart training @@ -233,10 +292,10 @@ def test_restart(self, args, all_paths, max_epoch, mocker): # deleting all checkpoints. Interruptions are rare: it is best to deal with # multiple checkpoints by trusting the largest epoch number. list_epoch_numbers = [] - for path in glob.glob(last_model_path + '/*.ckpt'): + for path in glob.glob(last_model_path + "/*.ckpt"): last_model_filename = os.path.basename(path) match_object = last_model_regex.match(last_model_filename) assert match_object is not None - model_epoch = int(match_object.group('epoch')) + model_epoch = int(match_object.group("epoch")) list_epoch_numbers.append(model_epoch) assert np.max(list_epoch_numbers) == max_epoch - 1 diff --git a/tests/utils/test_basis_transformations.py b/tests/utils/test_basis_transformations.py index 62541103..e14bc7a8 100644 --- a/tests/utils/test_basis_transformations.py +++ b/tests/utils/test_basis_transformations.py @@ -1,6 +1,7 @@ import pytest import torch -from crystal_diffusion.utils.basis_transformations import ( + +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import ( get_positions_from_coordinates, get_reciprocal_basis_vectors, get_relative_coordinates_from_cartesian_positions, map_relative_coordinates_to_unit_cell) @@ -32,9 +33,13 @@ def test_get_reciprocal_basis_vectors(basis_vectors): torch.testing.assert_allclose(b_matrix @ a_matrix, identity) -def test_get_positions_from_coordinates(batch_size, relative_coordinates, basis_vectors): +def test_get_positions_from_coordinates( + batch_size, relative_coordinates, basis_vectors +): - computed_positions = get_positions_from_coordinates(relative_coordinates, basis_vectors) + computed_positions = get_positions_from_coordinates( + relative_coordinates, basis_vectors + ) expected_positions = torch.empty(relative_coordinates.shape, dtype=torch.float32) for batch_idx, (a1, a2, a3) in enumerate(basis_vectors): @@ -44,12 +49,17 @@ def test_get_positions_from_coordinates(batch_size, relative_coordinates, basis_ torch.testing.assert_close(expected_positions, computed_positions) -def test_get_relative_coordinates_from_cartesian_positions(relative_coordinates, basis_vectors): - cartesian_positions = get_positions_from_coordinates(relative_coordinates, basis_vectors) +def test_get_relative_coordinates_from_cartesian_positions( + relative_coordinates, basis_vectors +): + cartesian_positions = get_positions_from_coordinates( + relative_coordinates, basis_vectors + ) reciprocal_basis_vectors = get_reciprocal_basis_vectors(basis_vectors) - computed_relative_coordinates = get_relative_coordinates_from_cartesian_positions(cartesian_positions, - reciprocal_basis_vectors) + computed_relative_coordinates = get_relative_coordinates_from_cartesian_positions( + cartesian_positions, reciprocal_basis_vectors + ) torch.testing.assert_close(computed_relative_coordinates, relative_coordinates) @@ -57,7 +67,7 @@ def test_get_relative_coordinates_from_cartesian_positions(relative_coordinates, def test_remainder_failure(): # This test demonstrates how torch.remainder does not do what we want, which is why we need # to define the function "map_relative_coordinates_to_unit_cell". - epsilon = -torch.tensor(1.e-8) + epsilon = -torch.tensor(1.0e-8) relative_coordinates_not_in_unit_cell = torch.remainder(epsilon, 1.0) assert relative_coordinates_not_in_unit_cell == 1.0 @@ -65,19 +75,27 @@ def test_remainder_failure(): @pytest.mark.parametrize("shape", [(10,), (10, 20), (3, 4, 5)]) def test_map_relative_coordinates_to_unit_cell_hard(shape): relative_coordinates = 1e-8 * (torch.rand((10,)) - 0.5) - computed_relative_coordinates = map_relative_coordinates_to_unit_cell(relative_coordinates) - - positive_relative_coordinates_mask = relative_coordinates >= 0. - assert torch.all(relative_coordinates[positive_relative_coordinates_mask] - == computed_relative_coordinates[positive_relative_coordinates_mask]) - torch.testing.assert_close(computed_relative_coordinates[~positive_relative_coordinates_mask], - torch.zeros_like(computed_relative_coordinates[~positive_relative_coordinates_mask])) + computed_relative_coordinates = map_relative_coordinates_to_unit_cell( + relative_coordinates + ) + + positive_relative_coordinates_mask = relative_coordinates >= 0.0 + assert torch.all( + relative_coordinates[positive_relative_coordinates_mask] + == computed_relative_coordinates[positive_relative_coordinates_mask] + ) + torch.testing.assert_close( + computed_relative_coordinates[~positive_relative_coordinates_mask], + torch.zeros_like( + computed_relative_coordinates[~positive_relative_coordinates_mask] + ), + ) @pytest.mark.parametrize("shape", [(100, 8, 16)]) def test_map_relative_coordinates_to_unit_cell_easy(shape): # Very unlikely to hit the edge cases. - relative_coordinates = 10. * (torch.rand((10,)) - 0.5) - expected_values = torch.remainder(relative_coordinates, 1.) + relative_coordinates = 10.0 * (torch.rand((10,)) - 0.5) + expected_values = torch.remainder(relative_coordinates, 1.0) computed_values = map_relative_coordinates_to_unit_cell(relative_coordinates) torch.testing.assert_close(computed_values, expected_values) diff --git a/tests/utils/test_configuration_parsing.py b/tests/utils/test_configuration_parsing.py index c72fc92c..f74b6a62 100644 --- a/tests/utils/test_configuration_parsing.py +++ b/tests/utils/test_configuration_parsing.py @@ -1,28 +1,32 @@ from dataclasses import asdict, dataclass import pytest -from crystal_diffusion.utils.configuration_parsing import \ + +from diffusion_for_multi_scale_molecular_dynamics.utils.configuration_parsing import \ create_parameters_from_configuration_dictionary @dataclass(kw_only=True) class DummyParameters: """Base dataclass for some set of parameters""" + name: str @dataclass(kw_only=True) class FirstKindDummyParameters(DummyParameters): """Base dataclass for some set of parameters""" - name: str = 'first_kind' - a: float = 1. - b: float = 2. + + name: str = "first_kind" + a: float = 1.0 + b: float = 2.0 @dataclass(kw_only=True) class SecondKindDummyParameters(DummyParameters): """Base dataclass for some set of parameters""" - name: str = 'second_kind' + + name: str = "second_kind" x: float = 0.1 y: float = 0.2 z: float = 0.3 @@ -40,12 +44,16 @@ def configuration(parameters): @pytest.fixture() def options(): - return dict(first_kind=FirstKindDummyParameters, second_kind=SecondKindDummyParameters) + return dict( + first_kind=FirstKindDummyParameters, second_kind=SecondKindDummyParameters + ) -def test_create_parameters_from_configuration_dictionary(configuration, options, parameters): +def test_create_parameters_from_configuration_dictionary( + configuration, options, parameters +): - computed_parameters = create_parameters_from_configuration_dictionary(configuration=configuration, - identifier='name', - options=options) + computed_parameters = create_parameters_from_configuration_dictionary( + configuration=configuration, identifier="name", options=options + ) assert computed_parameters == parameters diff --git a/tests/utils/test_neighbors.py b/tests/utils/test_neighbors.py index aee6dc0e..b58c1f8e 100644 --- a/tests/utils/test_neighbors.py +++ b/tests/utils/test_neighbors.py @@ -3,18 +3,20 @@ import numpy as np import pytest import torch -from crystal_diffusion.utils.basis_transformations import \ +from pymatgen.core import Lattice, Structure + +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ get_positions_from_coordinates -from crystal_diffusion.utils.neighbors import ( +from diffusion_for_multi_scale_molecular_dynamics.utils.neighbors import ( AdjacencyInfo, _get_relative_coordinates_lattice_vectors, _get_shifted_positions, _get_shortest_distance_that_crosses_unit_cell, _get_vectors_from_multiple_indices, get_periodic_adjacency_information, shift_adjacency_matrix_indices_for_graph_batching) -from pymatgen.core import Lattice, Structure - from tests.fake_data_utils import find_aligning_permutation -Neighbors = namedtuple("Neighbors", ["source_index", "destination_index", "displacement", "shift"]) +Neighbors = namedtuple( + "Neighbors", ["source_index", "destination_index", "displacement", "shift"] +) @pytest.fixture(scope="module", autouse=True) @@ -46,9 +48,13 @@ def positions(relative_coordinates, basis_vectors): @pytest.fixture def lattice_vectors(batch_size, basis_vectors, number_of_shells): - relative_lattice_vectors = _get_relative_coordinates_lattice_vectors(number_of_shells) + relative_lattice_vectors = _get_relative_coordinates_lattice_vectors( + number_of_shells + ) batched_relative_lattice_vectors = relative_lattice_vectors.repeat(batch_size, 1, 1) - lattice_vectors = get_positions_from_coordinates(batched_relative_lattice_vectors, basis_vectors) + lattice_vectors = get_positions_from_coordinates( + batched_relative_lattice_vectors, basis_vectors + ) return lattice_vectors @@ -58,14 +64,18 @@ def structures(basis_vectors, relative_coordinates): list_structures = [] for basis, coordinates in zip(basis_vectors, relative_coordinates): number_of_atoms = coordinates.shape[0] - species = number_of_atoms * ["Si"] # this is a dummy variable. It doesn't matter what the atom types are... + species = number_of_atoms * [ + "Si" + ] # this is a dummy variable. It doesn't matter what the atom types are... lattice = Lattice(matrix=basis.cpu().numpy(), pbc=(True, True, True)) - structure = Structure(lattice=lattice, - species=species, - coords=coordinates.cpu().numpy(), - to_unit_cell=False, # already in [0, 1) range. - coords_are_cartesian=False) + structure = Structure( + lattice=lattice, + species=species, + coords=coordinates.cpu().numpy(), + to_unit_cell=False, # already in [0, 1) range. + coords_are_cartesian=False, + ) list_structures.append(structure) return list_structures @@ -83,18 +93,21 @@ def expected_neighbors(structures, radial_cutoff): source_site = structure.sites[src_idx] for neighbor_site in neighbor_sites: # Make sure the neighbors are no more than one shell away. - assert np.all(np.abs(np.array(neighbor_site.image)) <= 1.), \ - "A neighbor site is out of range. Review test code!" + assert np.all( + np.abs(np.array(neighbor_site.image)) <= 1.0 + ), "A neighbor site is out of range. Review test code!" dst_idx = neighbor_site.index displacement = neighbor_site.coords - source_site.coords shift = np.dot(np.array(neighbor_site.image), structure.lattice.matrix) - neighbors = Neighbors(source_index=src_idx, - destination_index=dst_idx, - displacement=displacement, - shift=shift) + neighbors = Neighbors( + source_index=src_idx, + destination_index=dst_idx, + displacement=displacement, + shift=shift, + ) list_neighbors.append(neighbors) structure_neighbors.append(list_neighbors) @@ -109,74 +122,123 @@ def expected_adjacency_info(structures, expected_neighbors): shifts = [] adj_matrix = [] number_of_edges = [] - for batch_index, (structure, list_neighbors) in enumerate(zip(structures, expected_neighbors)): + for batch_index, (structure, list_neighbors) in enumerate( + zip(structures, expected_neighbors) + ): number_of_atoms = len(structure) - batch_node_batch_indices = torch.tensor(number_of_atoms * [batch_index], dtype=torch.long) + batch_node_batch_indices = torch.tensor( + number_of_atoms * [batch_index], dtype=torch.long + ) node_batch_indices.append(batch_node_batch_indices) - batch_shifts = torch.stack([torch.from_numpy(neigh.shift) for neigh in list_neighbors]) + batch_shifts = torch.stack( + [torch.from_numpy(neigh.shift) for neigh in list_neighbors] + ) shifts.append(batch_shifts) source_indices = torch.tensor([neigh.source_index for neigh in list_neighbors]) - dest_indices = torch.tensor([neigh.destination_index for neigh in list_neighbors]) + dest_indices = torch.tensor( + [neigh.destination_index for neigh in list_neighbors] + ) batch_adj_matrix = torch.stack([source_indices, dest_indices]) adj_matrix.append(batch_adj_matrix) number_of_edges.append(len(source_indices)) - batch_edge_batch_indices = torch.tensor(len(source_indices) * [batch_index], dtype=torch.long) + batch_edge_batch_indices = torch.tensor( + len(source_indices) * [batch_index], dtype=torch.long + ) edge_batch_indices.append(batch_edge_batch_indices) - return AdjacencyInfo(adjacency_matrix=torch.cat(adj_matrix, dim=1), - shifts=torch.cat(shifts, dim=0), - node_batch_indices=torch.cat(node_batch_indices), - edge_batch_indices=torch.cat(edge_batch_indices), - number_of_edges=torch.tensor(number_of_edges)) + return AdjacencyInfo( + adjacency_matrix=torch.cat(adj_matrix, dim=1), + shifts=torch.cat(shifts, dim=0), + node_batch_indices=torch.cat(node_batch_indices), + edge_batch_indices=torch.cat(edge_batch_indices), + number_of_edges=torch.tensor(number_of_edges), + ) @pytest.mark.parametrize("radial_cutoff", [1.1, 2.2, 3.3]) -def test_get_periodic_adjacency_information(basis_vectors, positions, radial_cutoff, - batch_size, expected_adjacency_info): - computed_adjacency_info = get_periodic_adjacency_information(positions, basis_vectors, radial_cutoff) - - torch.testing.assert_close(expected_adjacency_info.number_of_edges, computed_adjacency_info.number_of_edges) - torch.testing.assert_close(expected_adjacency_info.node_batch_indices, computed_adjacency_info.node_batch_indices) - torch.testing.assert_close(expected_adjacency_info.edge_batch_indices, computed_adjacency_info.edge_batch_indices) +def test_get_periodic_adjacency_information( + basis_vectors, positions, radial_cutoff, batch_size, expected_adjacency_info +): + computed_adjacency_info = get_periodic_adjacency_information( + positions, basis_vectors, radial_cutoff + ) + + torch.testing.assert_close( + expected_adjacency_info.number_of_edges, computed_adjacency_info.number_of_edges + ) + torch.testing.assert_close( + expected_adjacency_info.node_batch_indices, + computed_adjacency_info.node_batch_indices, + ) + torch.testing.assert_close( + expected_adjacency_info.edge_batch_indices, + computed_adjacency_info.edge_batch_indices, + ) # The edges might not be in the same order; build the corresponding permutation. for batch_idx in range(batch_size): expected_mask = expected_adjacency_info.edge_batch_indices == batch_idx - expected_src_idx, expected_dst_idx = expected_adjacency_info.adjacency_matrix[:, expected_mask] + expected_src_idx, expected_dst_idx = expected_adjacency_info.adjacency_matrix[ + :, expected_mask + ] expected_shifts = expected_adjacency_info.shifts[expected_mask, :] - expected_displacements = (positions[batch_idx, expected_dst_idx] + expected_shifts - - positions[batch_idx, expected_src_idx]) + expected_displacements = ( + positions[batch_idx, expected_dst_idx] + + expected_shifts + - positions[batch_idx, expected_src_idx] + ) computed_mask = computed_adjacency_info.edge_batch_indices == batch_idx - computed_src_idx, computed_dst_idx = computed_adjacency_info.adjacency_matrix[:, computed_mask] + computed_src_idx, computed_dst_idx = computed_adjacency_info.adjacency_matrix[ + :, computed_mask + ] computed_shifts = computed_adjacency_info.shifts[computed_mask, :] - computed_displacements = (positions[batch_idx, computed_dst_idx] + computed_shifts - - positions[batch_idx, computed_src_idx]) - - permutation_indices = find_aligning_permutation(expected_displacements, computed_displacements, tol=1e-5) - - torch.testing.assert_close(computed_shifts[permutation_indices], expected_shifts, check_dtype=False) - torch.testing.assert_close(computed_src_idx[permutation_indices], expected_src_idx) - torch.testing.assert_close(computed_dst_idx[permutation_indices], expected_dst_idx) - - -def test_get_periodic_neighbour_indices_and_displacements_large_cutoff(basis_vectors, relative_coordinates): + computed_displacements = ( + positions[batch_idx, computed_dst_idx] + + computed_shifts + - positions[batch_idx, computed_src_idx] + ) + + permutation_indices = find_aligning_permutation( + expected_displacements, computed_displacements, tol=1e-5 + ) + + torch.testing.assert_close( + computed_shifts[permutation_indices], expected_shifts, check_dtype=False + ) + torch.testing.assert_close( + computed_src_idx[permutation_indices], expected_src_idx + ) + torch.testing.assert_close( + computed_dst_idx[permutation_indices], expected_dst_idx + ) + + +def test_get_periodic_neighbour_indices_and_displacements_large_cutoff( + basis_vectors, relative_coordinates +): # Check that the code crashes if the radial cutoff is too big! - shortest_cell_crossing_distances = _get_shortest_distance_that_crosses_unit_cell(basis_vectors).min() + shortest_cell_crossing_distances = _get_shortest_distance_that_crosses_unit_cell( + basis_vectors + ).min() large_radial_cutoff = shortest_cell_crossing_distances + 0.1 small_radial_cutoff = shortest_cell_crossing_distances - 0.1 # Should run - get_periodic_adjacency_information(relative_coordinates, basis_vectors, small_radial_cutoff) + get_periodic_adjacency_information( + relative_coordinates, basis_vectors, small_radial_cutoff + ) with pytest.raises(AssertionError): # Should crash - get_periodic_adjacency_information(relative_coordinates, basis_vectors, large_radial_cutoff) + get_periodic_adjacency_information( + relative_coordinates, basis_vectors, large_radial_cutoff + ) @pytest.mark.parametrize("number_of_shells", [1, 2, 3]) @@ -190,8 +252,12 @@ def test_get_relative_coordinates_lattice_vectors(number_of_shells): lattice_vector = torch.tensor([nx, ny, nz]) expected_lattice_vectors.append(lattice_vector) - expected_lattice_vectors = torch.stack(expected_lattice_vectors).to(dtype=torch.float32) - computed_lattice_vectors = _get_relative_coordinates_lattice_vectors(number_of_shells) + expected_lattice_vectors = torch.stack(expected_lattice_vectors).to( + dtype=torch.float32 + ) + computed_lattice_vectors = _get_relative_coordinates_lattice_vectors( + number_of_shells + ) torch.testing.assert_close(expected_lattice_vectors, computed_lattice_vectors) @@ -214,9 +280,13 @@ def test_get_shifted_positions(positions, lattice_vectors): lattice_vector = lattice_vectors[batch_idx, lat_idx, :] expected_cell_shift_positions = position + lattice_vector - computed_cell_shift_positions = computed_shifted_positions[batch_idx, lat_idx, atom_idx, :] + computed_cell_shift_positions = computed_shifted_positions[ + batch_idx, lat_idx, atom_idx, : + ] - torch.testing.assert_close(expected_cell_shift_positions, computed_cell_shift_positions) + torch.testing.assert_close( + expected_cell_shift_positions, computed_cell_shift_positions + ) def test_get_shortest_distance_that_crosses_unit_cell(basis_vectors): @@ -235,7 +305,9 @@ def test_get_shortest_distance_that_crosses_unit_cell(basis_vectors): expected_shortest_distances.append(np.min([d1, d2, d3])) expected_shortest_distances = torch.Tensor(expected_shortest_distances) - computed_shortest_distances = _get_shortest_distance_that_crosses_unit_cell(basis_vectors) + computed_shortest_distances = _get_shortest_distance_that_crosses_unit_cell( + basis_vectors + ) torch.testing.assert_close(expected_shortest_distances, computed_shortest_distances) @@ -250,12 +322,16 @@ def test_get_vectors_from_multiple_indices(batch_size, number_of_atoms): batch_indices = torch.randint(0, batch_size, (number_of_indices,)) vector_indices = torch.randint(0, number_of_atoms, (number_of_indices,)) - expected_vectors = torch.empty(number_of_indices, spatial_dimension, dtype=torch.float32) + expected_vectors = torch.empty( + number_of_indices, spatial_dimension, dtype=torch.float32 + ) for idx, (batch_idx, vector_idx) in enumerate(zip(batch_indices, vector_indices)): expected_vectors[idx] = vectors[batch_idx, vector_idx] - computed_vectors = _get_vectors_from_multiple_indices(vectors, batch_indices, vector_indices) + computed_vectors = _get_vectors_from_multiple_indices( + vectors, batch_indices, vector_indices + ) torch.testing.assert_close(expected_vectors, computed_vectors) @@ -273,8 +349,8 @@ def test_shift_adjacency_matrix_indices_for_graph_batching(batch_size, number_of expected_shifted_adj[:, bottom_idx:top_idx] += index_shift index_shift += number_of_atoms - computed_shifted_adj = shift_adjacency_matrix_indices_for_graph_batching(adjacency_matrix, - num_edges, - number_of_atoms) + computed_shifted_adj = shift_adjacency_matrix_indices_for_graph_batching( + adjacency_matrix, num_edges, number_of_atoms + ) torch.testing.assert_close(expected_shifted_adj, computed_shifted_adj) diff --git a/tests/utils/test_sample_trajectory.py b/tests/utils/test_sample_trajectory.py index bef3e266..e8699dee 100644 --- a/tests/utils/test_sample_trajectory.py +++ b/tests/utils/test_sample_trajectory.py @@ -3,165 +3,250 @@ import einops import pytest import torch -from crystal_diffusion.utils.sample_trajectory import \ + +from diffusion_for_multi_scale_molecular_dynamics.utils.sample_trajectory import \ PredictorCorrectorSampleTrajectory -@pytest.fixture(autouse=True, scope='module') +@pytest.fixture(autouse=True, scope="module") def set_seed(): torch.manual_seed(32434) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def number_of_predictor_steps(): return 8 -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def number_of_corrector_steps(): return 3 -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def batch_size(): return 4 -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def number_of_atoms(): return 8 -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def spatial_dimension(): return 3 -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def basis_vectors(batch_size): # orthogonal boxes with dimensions between 5 and 10. - orthogonal_boxes = torch.stack([torch.diag(5. + 5. * torch.rand(3)) for _ in range(batch_size)]) + orthogonal_boxes = torch.stack( + [torch.diag(5.0 + 5.0 * torch.rand(3)) for _ in range(batch_size)] + ) # add a bit of noise to make the vectors not quite orthogonal basis_vectors = orthogonal_boxes + 0.1 * torch.randn(batch_size, 3, 3) return basis_vectors -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def list_i_indices(number_of_predictor_steps): return torch.arange(number_of_predictor_steps - 1, -1, -1) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def list_sigmas(number_of_predictor_steps): return torch.rand(number_of_predictor_steps) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def list_times(number_of_predictor_steps): return torch.rand(number_of_predictor_steps) -@pytest.fixture(scope='module') -def predictor_scores(number_of_predictor_steps, batch_size, number_of_atoms, spatial_dimension): - return torch.rand(number_of_predictor_steps, batch_size, number_of_atoms, spatial_dimension) +@pytest.fixture(scope="module") +def predictor_scores( + number_of_predictor_steps, batch_size, number_of_atoms, spatial_dimension +): + return torch.rand( + number_of_predictor_steps, batch_size, number_of_atoms, spatial_dimension + ) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def list_x_i(number_of_predictor_steps, batch_size, number_of_atoms, spatial_dimension): - return torch.rand(number_of_predictor_steps, batch_size, number_of_atoms, spatial_dimension) - - -@pytest.fixture(scope='module') -def list_x_im1(number_of_predictor_steps, batch_size, number_of_atoms, spatial_dimension): - return torch.rand(number_of_predictor_steps, batch_size, number_of_atoms, spatial_dimension) - - -@pytest.fixture(scope='module') -def corrector_scores(number_of_predictor_steps, number_of_corrector_steps, batch_size, - number_of_atoms, spatial_dimension): + return torch.rand( + number_of_predictor_steps, batch_size, number_of_atoms, spatial_dimension + ) + + +@pytest.fixture(scope="module") +def list_x_im1( + number_of_predictor_steps, batch_size, number_of_atoms, spatial_dimension +): + return torch.rand( + number_of_predictor_steps, batch_size, number_of_atoms, spatial_dimension + ) + + +@pytest.fixture(scope="module") +def corrector_scores( + number_of_predictor_steps, + number_of_corrector_steps, + batch_size, + number_of_atoms, + spatial_dimension, +): number_of_scores = number_of_predictor_steps * number_of_corrector_steps return torch.rand(number_of_scores, batch_size, number_of_atoms, spatial_dimension) -@pytest.fixture(scope='module') -def list_x_i_corr(number_of_predictor_steps, number_of_corrector_steps, batch_size, number_of_atoms, spatial_dimension): +@pytest.fixture(scope="module") +def list_x_i_corr( + number_of_predictor_steps, + number_of_corrector_steps, + batch_size, + number_of_atoms, + spatial_dimension, +): number_of_scores = number_of_predictor_steps * number_of_corrector_steps return torch.rand(number_of_scores, batch_size, number_of_atoms, spatial_dimension) -@pytest.fixture(scope='module') -def list_corrected_x_i(number_of_predictor_steps, number_of_corrector_steps, batch_size, - number_of_atoms, spatial_dimension): +@pytest.fixture(scope="module") +def list_corrected_x_i( + number_of_predictor_steps, + number_of_corrector_steps, + batch_size, + number_of_atoms, + spatial_dimension, +): number_of_scores = number_of_predictor_steps * number_of_corrector_steps return torch.rand(number_of_scores, batch_size, number_of_atoms, spatial_dimension) -@pytest.fixture(scope='module') -def sample_trajectory(number_of_corrector_steps, list_i_indices, list_times, list_sigmas, basis_vectors, - list_x_i, list_x_im1, predictor_scores, list_x_i_corr, list_corrected_x_i, corrector_scores): +@pytest.fixture(scope="module") +def sample_trajectory( + number_of_corrector_steps, + list_i_indices, + list_times, + list_sigmas, + basis_vectors, + list_x_i, + list_x_im1, + predictor_scores, + list_x_i_corr, + list_corrected_x_i, + corrector_scores, +): sample_trajectory = PredictorCorrectorSampleTrajectory() sample_trajectory.record_unit_cell(basis_vectors) total_corrector_index = 0 - for i_index, time, sigma, x_i, x_im1, scores in zip(list_i_indices, list_times, list_sigmas, - list_x_i, list_x_im1, predictor_scores): - sample_trajectory.record_predictor_step(i_index=i_index, time=time, sigma=sigma, - x_i=x_i, x_im1=x_im1, scores=scores) + for i_index, time, sigma, x_i, x_im1, scores in zip( + list_i_indices, list_times, list_sigmas, list_x_i, list_x_im1, predictor_scores + ): + sample_trajectory.record_predictor_step( + i_index=i_index, time=time, sigma=sigma, x_i=x_i, x_im1=x_im1, scores=scores + ) for _ in range(number_of_corrector_steps): x_i = list_x_i_corr[total_corrector_index] corrected_x_i = list_corrected_x_i[total_corrector_index] scores = corrector_scores[total_corrector_index] - sample_trajectory.record_corrector_step(i_index=i_index, time=time, sigma=sigma, - x_i=x_i, corrected_x_i=corrected_x_i, scores=scores) + sample_trajectory.record_corrector_step( + i_index=i_index, + time=time, + sigma=sigma, + x_i=x_i, + corrected_x_i=corrected_x_i, + scores=scores, + ) total_corrector_index += 1 return sample_trajectory def test_sample_trajectory_unit_cell(sample_trajectory, basis_vectors): - torch.testing.assert_close(sample_trajectory.data['unit_cell'], basis_vectors) - - -def test_record_predictor(sample_trajectory, list_times, list_sigmas, list_x_i, list_x_im1, predictor_scores): - torch.testing.assert_close(torch.tensor(sample_trajectory.data['predictor_time']), list_times) - torch.testing.assert_close(torch.tensor(sample_trajectory.data['predictor_sigma']), list_sigmas) - torch.testing.assert_close(torch.stack(sample_trajectory.data['predictor_x_i'], dim=0), list_x_i) - torch.testing.assert_close(torch.stack(sample_trajectory.data['predictor_x_im1'], dim=0), list_x_im1) - torch.testing.assert_close(torch.stack(sample_trajectory.data['predictor_scores'], dim=0), predictor_scores) - - -def test_record_corrector(sample_trajectory, number_of_corrector_steps, list_times, list_sigmas, list_x_i_corr, - list_corrected_x_i, corrector_scores): - - torch.testing.assert_close(torch.tensor(sample_trajectory.data['corrector_time']), - torch.repeat_interleave(list_times, number_of_corrector_steps)) - torch.testing.assert_close(torch.tensor(sample_trajectory.data['corrector_sigma']), - torch.repeat_interleave(list_sigmas, number_of_corrector_steps)) - torch.testing.assert_close(torch.stack(sample_trajectory.data['corrector_x_i'], dim=0), - list_x_i_corr) - torch.testing.assert_close(torch.stack(sample_trajectory.data['corrector_corrected_x_i'], dim=0), - list_corrected_x_i) - torch.testing.assert_close(torch.stack(sample_trajectory.data['corrector_scores'], dim=0), corrector_scores) - - -def test_standardize_data_and_write_pickle(sample_trajectory, basis_vectors, list_times, list_sigmas, - list_x_i, predictor_scores, tmp_path): - pickle_path = str(tmp_path / 'test_pickle_path.pkl') + torch.testing.assert_close(sample_trajectory.data["unit_cell"], basis_vectors) + + +def test_record_predictor( + sample_trajectory, list_times, list_sigmas, list_x_i, list_x_im1, predictor_scores +): + torch.testing.assert_close( + torch.tensor(sample_trajectory.data["predictor_time"]), list_times + ) + torch.testing.assert_close( + torch.tensor(sample_trajectory.data["predictor_sigma"]), list_sigmas + ) + torch.testing.assert_close( + torch.stack(sample_trajectory.data["predictor_x_i"], dim=0), list_x_i + ) + torch.testing.assert_close( + torch.stack(sample_trajectory.data["predictor_x_im1"], dim=0), list_x_im1 + ) + torch.testing.assert_close( + torch.stack(sample_trajectory.data["predictor_scores"], dim=0), predictor_scores + ) + + +def test_record_corrector( + sample_trajectory, + number_of_corrector_steps, + list_times, + list_sigmas, + list_x_i_corr, + list_corrected_x_i, + corrector_scores, +): + + torch.testing.assert_close( + torch.tensor(sample_trajectory.data["corrector_time"]), + torch.repeat_interleave(list_times, number_of_corrector_steps), + ) + torch.testing.assert_close( + torch.tensor(sample_trajectory.data["corrector_sigma"]), + torch.repeat_interleave(list_sigmas, number_of_corrector_steps), + ) + torch.testing.assert_close( + torch.stack(sample_trajectory.data["corrector_x_i"], dim=0), list_x_i_corr + ) + torch.testing.assert_close( + torch.stack(sample_trajectory.data["corrector_corrected_x_i"], dim=0), + list_corrected_x_i, + ) + torch.testing.assert_close( + torch.stack(sample_trajectory.data["corrector_scores"], dim=0), corrector_scores + ) + + +def test_standardize_data_and_write_pickle( + sample_trajectory, + basis_vectors, + list_times, + list_sigmas, + list_x_i, + predictor_scores, + tmp_path, +): + pickle_path = str(tmp_path / "test_pickle_path.pkl") sample_trajectory.write_to_pickle(pickle_path) - with open(pickle_path, 'rb') as fd: + with open(pickle_path, "rb") as fd: standardized_data = torch.load(fd) reordered_scores = einops.rearrange(predictor_scores, "t b n d -> b t n d") reordered_relative_coordinates = einops.rearrange(list_x_i, "t b n d -> b t n d") - torch.testing.assert_close(standardized_data['unit_cell'], basis_vectors) - torch.testing.assert_close(standardized_data['time'], list_times) - torch.testing.assert_close(standardized_data['sigma'], list_sigmas) - torch.testing.assert_close(standardized_data['relative_coordinates'], reordered_relative_coordinates) - torch.testing.assert_close(standardized_data['normalized_scores'], reordered_scores) + torch.testing.assert_close(standardized_data["unit_cell"], basis_vectors) + torch.testing.assert_close(standardized_data["time"], list_times) + torch.testing.assert_close(standardized_data["sigma"], list_sigmas) + torch.testing.assert_close( + standardized_data["relative_coordinates"], reordered_relative_coordinates + ) + torch.testing.assert_close(standardized_data["normalized_scores"], reordered_scores) def test_reset(sample_trajectory, tmp_path): diff --git a/tests/utils/test_structure_utils.py b/tests/utils/test_structure_utils.py index d73cab76..250b54a5 100644 --- a/tests/utils/test_structure_utils.py +++ b/tests/utils/test_structure_utils.py @@ -1,8 +1,9 @@ import pytest import torch -from crystal_diffusion.utils.basis_transformations import \ + +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ get_positions_from_coordinates -from crystal_diffusion.utils.structure_utils import ( +from diffusion_for_multi_scale_molecular_dynamics.utils.structure_utils import ( compute_distances, compute_distances_in_batch, get_orthogonal_basis_vectors) diff --git a/tests/utils/test_tensor_utils.py b/tests/utils/test_tensor_utils.py index dee8184c..4d2d5253 100644 --- a/tests/utils/test_tensor_utils.py +++ b/tests/utils/test_tensor_utils.py @@ -1,6 +1,7 @@ import pytest import torch -from crystal_diffusion.utils.tensor_utils import \ + +from diffusion_for_multi_scale_molecular_dynamics.utils.tensor_utils import \ broadcast_batch_tensor_to_all_dimensions @@ -23,8 +24,12 @@ def final_shape(batch_size, number_of_dimensions): @pytest.mark.parametrize("batch_size", [4, 8]) @pytest.mark.parametrize("number_of_dimensions", [1, 2, 3]) -def test_broadcast_batch_tensor_to_all_dimensions(batch_size, batch_values, final_shape): - broadcast_values = broadcast_batch_tensor_to_all_dimensions(batch_values, final_shape) +def test_broadcast_batch_tensor_to_all_dimensions( + batch_size, batch_values, final_shape +): + broadcast_values = broadcast_batch_tensor_to_all_dimensions( + batch_values, final_shape + ) value_arrays = broadcast_values.reshape(batch_size, -1) From 4abda7f0b8cde2539b5478e31d7a883f9e7dcde7 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Tue, 22 Oct 2024 07:35:27 -0400 Subject: [PATCH 20/29] Fixed imports in the experiments folder. --- .../analysis/analytic_score/__init__.py | 2 +- .../analytical_score_sampling_and_plotting.py | 301 ++++++++++-------- .../generate_sample_energies.py | 190 ++++++----- .../plot_sample_energies.py | 53 +-- .../perfect_score_loss_analysis.py | 169 ++++++---- ...t_repaint_analytical_score_trajectories.py | 134 +++++--- .../repaint/repaint_with_analytic_score.py | 105 +++--- .../score_convergence_analysis.py | 82 +++-- experiments/analysis/analytic_score/utils.py | 127 +++++--- .../diffusion_sample_position_analysis.py | 127 ++++++-- .../analysis/exploding_variance_analysis.py | 40 ++- .../analysis/perturbation_kernel_analysis.py | 41 ++- experiments/analysis/plot_repulsive_force.py | 11 +- .../plotting_gaussian_vs_wrapped_gaussian.py | 36 ++- experiments/analysis/target_score_analysis.py | 89 ++++-- experiments/analysis_utils.py | 13 +- .../dataset_analysis/dataset_covariance.py | 36 ++- .../energy_consistency_analysis.py | 59 ++-- .../dataset_analysis/plot_si_phonon_DOS.py | 7 +- .../dataset_analysis/si_diffusion_analysis.py | 162 ++++++---- .../si_diffusion_v1_analysis.py | 100 ++++-- ...experiments_with_various_score_networks.py | 244 ++++++++------ .../analysis_callbacks.py | 53 +-- .../overfit_diffusion_mace.py | 121 ++++--- .../generators/sde_generator_sanity_check.py | 167 ++++++---- .../plot_repaint_score_trajectories.py | 118 ++++--- .../repaint_with_sota_score.py | 77 +++-- .../sota_score_sampling_and_plotting.py | 256 ++++++++------- .../draw_samples_from_equilibrium.py | 28 +- .../plot_hessian_eigenvalues.py | 16 +- .../plot_score_norm.py | 28 +- experiments/score_stability_analysis/util.py | 15 +- 32 files changed, 1887 insertions(+), 1120 deletions(-) diff --git a/experiments/analysis/analytic_score/__init__.py b/experiments/analysis/analytic_score/__init__.py index edadef72..d97a9bed 100644 --- a/experiments/analysis/analytic_score/__init__.py +++ b/experiments/analysis/analytic_score/__init__.py @@ -1,4 +1,4 @@ -from crystal_diffusion import ANALYSIS_RESULTS_DIR +from diffusion_for_multi_scale_molecular_dynamics import ANALYSIS_RESULTS_DIR ANALYTIC_SCORE_RESULTS_DIR = ANALYSIS_RESULTS_DIR / "ANALYTIC_SCORE" ANALYTIC_SCORE_RESULTS_DIR.mkdir(exist_ok=True) diff --git a/experiments/analysis/analytic_score/analytical_score_sampling_and_plotting.py b/experiments/analysis/analytic_score/analytical_score_sampling_and_plotting.py index 44f2b452..c28bd3b1 100644 --- a/experiments/analysis/analytic_score/analytical_score_sampling_and_plotting.py +++ b/experiments/analysis/analytic_score/analytical_score_sampling_and_plotting.py @@ -11,18 +11,22 @@ import matplotlib.pyplot as plt import numpy as np import torch -from crystal_diffusion.generators.langevin_generator import LangevinGenerator -from crystal_diffusion.generators.ode_position_generator import ( +from einops import einops + +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) +from diffusion_for_multi_scale_molecular_dynamics.generators.langevin_generator import \ + LangevinGenerator +from diffusion_for_multi_scale_molecular_dynamics.generators.ode_position_generator import ( ExplodingVarianceODEPositionGenerator, ODESamplingParameters) -from crystal_diffusion.generators.predictor_corrector_position_generator import \ +from diffusion_for_multi_scale_molecular_dynamics.generators.predictor_corrector_position_generator import \ PredictorCorrectorSamplingParameters -from crystal_diffusion.models.score_networks.analytical_score_network import ( +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.analytical_score_network import ( AnalyticalScoreNetwork, AnalyticalScoreNetworkParameters) -from crystal_diffusion.utils.logging_utils import setup_analysis_logger -from einops import einops -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters - +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters +from diffusion_for_multi_scale_molecular_dynamics.utils.logging_utils import \ + setup_analysis_logger from experiments.analysis.analytic_score import ANALYTIC_SCORE_RESULTS_DIR from experiments.analysis.analytic_score.utils import ( get_exact_samples, get_samples_harmonic_energy, get_silicon_supercell, @@ -35,13 +39,13 @@ plt.style.use(PLOT_STYLE_PATH) if torch.cuda.is_available(): - device = torch.device('cuda') + device = torch.device("cuda") else: - device = torch.device('cpu') + device = torch.device("cpu") # Change these parameters as needed! # sampling_algorithm = 'ode' -sampling_algorithm = 'langevin' +sampling_algorithm = "langevin" supercell_factor = 1 kmax = 1 @@ -51,66 +55,75 @@ total_time_steps = 101 number_of_corrector_steps = 1 -if __name__ == '__main__': +if __name__ == "__main__": logger.info("Setting up parameters") equilibrium_relative_coordinates = torch.from_numpy( - get_silicon_supercell(supercell_factor=supercell_factor)).to(device) + get_silicon_supercell(supercell_factor=supercell_factor) + ).to(device) number_of_atoms, spatial_dimension = equilibrium_relative_coordinates.shape nd = number_of_atoms * spatial_dimension - noise_parameters = NoiseParameters(total_time_steps=total_time_steps, - sigma_min=0.001, - sigma_max=0.5) + noise_parameters = NoiseParameters( + total_time_steps=total_time_steps, sigma_min=0.001, sigma_max=0.5 + ) score_network_parameters = AnalyticalScoreNetworkParameters( number_of_atoms=number_of_atoms, spatial_dimension=spatial_dimension, kmax=kmax, equilibrium_relative_coordinates=equilibrium_relative_coordinates, - variance_parameter=variance_parameter) + variance_parameter=variance_parameter, + ) sigma_normalized_score_network = AnalyticalScoreNetwork(score_network_parameters) - if sampling_algorithm == 'ode': - ode_sampling_parameters = ODESamplingParameters(spatial_dimension=spatial_dimension, - number_of_atoms=number_of_atoms, - number_of_samples=number_of_samples, - cell_dimensions=[1., 1., 1.], - record_samples=True, - absolute_solver_tolerance=1.0e-5, - relative_solver_tolerance=1.0e-5) - - position_generator = ( - ExplodingVarianceODEPositionGenerator(noise_parameters=noise_parameters, - sampling_parameters=ode_sampling_parameters, - sigma_normalized_score_network=sigma_normalized_score_network)) - - elif sampling_algorithm == 'langevin': + if sampling_algorithm == "ode": + ode_sampling_parameters = ODESamplingParameters( + spatial_dimension=spatial_dimension, + number_of_atoms=number_of_atoms, + number_of_samples=number_of_samples, + cell_dimensions=[1.0, 1.0, 1.0], + record_samples=True, + absolute_solver_tolerance=1.0e-5, + relative_solver_tolerance=1.0e-5, + ) + + position_generator = ExplodingVarianceODEPositionGenerator( + noise_parameters=noise_parameters, + sampling_parameters=ode_sampling_parameters, + sigma_normalized_score_network=sigma_normalized_score_network, + ) + + elif sampling_algorithm == "langevin": pc_sampling_parameters = PredictorCorrectorSamplingParameters( number_of_corrector_steps=number_of_corrector_steps, spatial_dimension=spatial_dimension, number_of_atoms=number_of_atoms, number_of_samples=number_of_samples, - cell_dimensions=[1., 1., 1.], - record_samples=True) + cell_dimensions=[1.0, 1.0, 1.0], + record_samples=True, + ) position_generator = LangevinGenerator( noise_parameters=noise_parameters, sampling_parameters=pc_sampling_parameters, - sigma_normalized_score_network=sigma_normalized_score_network) + sigma_normalized_score_network=sigma_normalized_score_network, + ) # Draw some samples, create some plots - unit_cell = get_unit_cells(acell=1., - spatial_dimension=spatial_dimension, - number_of_samples=number_of_samples).to(device) + unit_cell = get_unit_cells( + acell=1.0, + spatial_dimension=spatial_dimension, + number_of_samples=number_of_samples, + ).to(device) logger.info("Drawing samples") - samples = position_generator.sample(number_of_samples=number_of_samples, - device=device, - unit_cell=unit_cell).detach() + samples = position_generator.sample( + number_of_samples=number_of_samples, device=device, unit_cell=unit_cell + ).detach() - if sampling_algorithm == 'ode': + if sampling_algorithm == "ode": # Plot the ODE parameters logger.info("Plotting ODE parameters") times = torch.linspace(0, 1, 1001) @@ -118,19 +131,19 @@ ode_prefactor = position_generator._get_ode_prefactor(sigmas) fig0 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig0.suptitle('ODE parameters') + fig0.suptitle("ODE parameters") ax1 = fig0.add_subplot(121) ax2 = fig0.add_subplot(122) - ax1.set_title('$\\sigma$ Parameter') - ax2.set_title('$\\gamma$ Parameter') - ax1.plot(times, sigmas, '-') - ax2.plot(times, ode_prefactor, '-') + ax1.set_title("$\\sigma$ Parameter") + ax2.set_title("$\\gamma$ Parameter") + ax1.plot(times, sigmas, "-") + ax2.plot(times, ode_prefactor, "-") - ax1.set_ylabel('$\\sigma(t)$') - ax2.set_ylabel('$\\gamma(t)$') + ax1.set_ylabel("$\\sigma(t)$") + ax2.set_ylabel("$\\gamma(t)$") for ax in [ax1, ax2]: - ax.set_xlabel('Diffusion Time') + ax.set_xlabel("Diffusion Time") ax.set_xlim([-0.01, 1.01]) fig0.tight_layout() @@ -139,74 +152,86 @@ logger.info("Extracting data artifacts") raw_data = position_generator.sample_trajectory_recorder.data - recorded_data = position_generator.sample_trajectory_recorder.standardize_data(raw_data) + recorded_data = position_generator.sample_trajectory_recorder.standardize_data( + raw_data + ) - sampling_times = recorded_data['time'] - relative_coordinates = recorded_data['relative_coordinates'] - batch_flat_relative_coordinates = einops.rearrange(relative_coordinates, "b t n d -> b t (n d)") + sampling_times = recorded_data["time"] + relative_coordinates = recorded_data["relative_coordinates"] + batch_flat_relative_coordinates = einops.rearrange( + relative_coordinates, "b t n d -> b t (n d)" + ) - normalized_scores = recorded_data['normalized_scores'] - batch_flat_normalized_scores = einops.rearrange(normalized_scores, "b t n d -> b t (n d)") + normalized_scores = recorded_data["normalized_scores"] + batch_flat_normalized_scores = einops.rearrange( + normalized_scores, "b t n d -> b t (n d)" + ) # ============================================================================ logger.info("Creating samples from the exact distribution") - inverse_covariance = (torch.diag(torch.ones(nd)) / variance_parameter).to(equilibrium_relative_coordinates) - inverse_covariance = inverse_covariance.reshape(number_of_atoms, spatial_dimension, - number_of_atoms, spatial_dimension) - exact_samples = get_exact_samples(equilibrium_relative_coordinates, - inverse_covariance, - number_of_samples).cpu() + inverse_covariance = (torch.diag(torch.ones(nd)) / variance_parameter).to( + equilibrium_relative_coordinates + ) + inverse_covariance = inverse_covariance.reshape( + number_of_atoms, spatial_dimension, number_of_atoms, spatial_dimension + ) + exact_samples = get_exact_samples( + equilibrium_relative_coordinates, inverse_covariance, number_of_samples + ).cpu() logger.info("Computing harmonic energies") - exact_harmonic_energies = get_samples_harmonic_energy(equilibrium_relative_coordinates, - inverse_covariance, - exact_samples) + exact_harmonic_energies = get_samples_harmonic_energy( + equilibrium_relative_coordinates, inverse_covariance, exact_samples + ) - sampled_harmonic_energies = get_samples_harmonic_energy(equilibrium_relative_coordinates, - inverse_covariance, - samples) + sampled_harmonic_energies = get_samples_harmonic_energy( + equilibrium_relative_coordinates, inverse_covariance, samples + ) # ======================== Figure 1 ====================================== logger.info("Plotting relative coordinates trajectories") fig1 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig1.suptitle('Sampling Trajectories') + fig1.suptitle("Sampling Trajectories") ax = fig1.add_subplot(111) - ax.set_xlabel('Diffusion Time') - ax.set_ylabel('Raw Relative Coordinate') + ax.set_xlabel("Diffusion Time") + ax.set_ylabel("Raw Relative Coordinate") ax.yaxis.tick_right() - ax.spines['top'].set_visible(True) - ax.spines['right'].set_visible(True) - ax.spines['bottom'].set_visible(True) - ax.spines['left'].set_visible(True) + ax.spines["top"].set_visible(True) + ax.spines["right"].set_visible(True) + ax.spines["bottom"].set_visible(True) + ax.spines["left"].set_visible(True) alpha = 1.0 for flat_relative_coordinates in batch_flat_relative_coordinates[::100]: for i in range(number_of_atoms * spatial_dimension): coordinate = flat_relative_coordinates[:, i] - ax.plot(sampling_times.cpu(), coordinate.cpu(), '-', color='b', alpha=alpha) + ax.plot(sampling_times.cpu(), coordinate.cpu(), "-", color="b", alpha=alpha) alpha = 0.05 ax.set_xlim([1.01, -0.01]) - fig1.savefig(ANALYTIC_SCORE_RESULTS_DIR / f"sampling_trajectories_{sampling_algorithm}_{number_of_atoms}_atoms.png") + fig1.savefig( + ANALYTIC_SCORE_RESULTS_DIR + / f"sampling_trajectories_{sampling_algorithm}_{number_of_atoms}_atoms.png" + ) plt.close(fig1) # ======================== Figure 2 ====================================== logger.info("Plotting scores along trajectories") fig2 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig2.suptitle('Root Mean Squared Normalized Scores Along Sample Trajectories') - rms_norm_score = (batch_flat_normalized_scores ** 2).mean(dim=-1).sqrt().numpy() + fig2.suptitle("Root Mean Squared Normalized Scores Along Sample Trajectories") + rms_norm_score = (batch_flat_normalized_scores**2).mean(dim=-1).sqrt().numpy() ax1 = fig2.add_subplot(121) ax2 = fig2.add_subplot(122) for y in rms_norm_score[::10]: - ax1.plot(sampling_times, y, '-', color='gray', alpha=0.2, label='__nolabel__') - ax2.plot(sampling_times, y, '-', color='gray', alpha=0.2, label='__nolabel__') + ax1.plot(sampling_times, y, "-", color="gray", alpha=0.2, label="__nolabel__") + ax2.plot(sampling_times, y, "-", color="gray", alpha=0.2, label="__nolabel__") - ax2.set_yscale('log') + ax2.set_yscale("log") list_quantiles = [0.0, 0.10, 0.5, 1.0] - list_colors = ['green', 'yellow', 'orange', 'red'] + list_colors = ["green", "yellow", "orange", "red"] energies = sampled_harmonic_energies.numpy() @@ -215,12 +240,18 @@ idx = np.argmin(np.abs(energies - energy_quantile)) e = energies[idx] for ax in [ax1, ax2]: - ax.plot(sampling_times, rms_norm_score[idx], '-', - color=c, alpha=1., label=f'{100 * q:2.0f}% Percentile Energy: {e:5.1f}') + ax.plot( + sampling_times, + rms_norm_score[idx], + "-", + color=c, + alpha=1.0, + label=f"{100 * q:2.0f}% Percentile Energy: {e:5.1f}", + ) for ax in [ax1, ax2]: - ax.set_xlabel('Diffusion Time') - ax.set_ylabel(r'$\langle \sqrt{(\sigma(t) S_{\theta})^2} \rangle$') + ax.set_xlabel("Diffusion Time") + ax.set_ylabel(r"$\langle \sqrt{(\sigma(t) S_{\theta})^2} \rangle$") ax.set_xlim(1, 0) ax1.legend(loc=0, fontsize=6) ax1.set_title("Normal Scale") @@ -229,41 +260,45 @@ fig2.tight_layout() fig2.savefig( - ANALYTIC_SCORE_RESULTS_DIR / f"sampling_score_trajectories_{sampling_algorithm}_{number_of_atoms}_atoms.png") + ANALYTIC_SCORE_RESULTS_DIR + / f"sampling_score_trajectories_{sampling_algorithm}_{number_of_atoms}_atoms.png" + ) plt.close(fig2) # ======================== Figure 3 ====================================== logger.info("Plotting Marginal distribution in 2D") fig3 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig3.suptitle('Comparing Sampling and Expected Marginal Distributions') - ax1 = fig3.add_subplot(131, aspect='equal') - ax2 = fig3.add_subplot(132, aspect='equal') - ax3 = fig3.add_subplot(133, aspect='equal') + fig3.suptitle("Comparing Sampling and Expected Marginal Distributions") + ax1 = fig3.add_subplot(131, aspect="equal") + ax2 = fig3.add_subplot(132, aspect="equal") + ax3 = fig3.add_subplot(133, aspect="equal") - xs = einops.rearrange(samples, 'b n d -> (b n) d').cpu() - zs = einops.rearrange(exact_samples, 'b n d -> (b n) d').cpu() - ax1.set_title('XY Projection') - ax1.plot(xs[:, 0], xs[:, 1], 'ro', alpha=0.5, mew=0, label='ODE Solver') - ax1.plot(zs[:, 0], zs[:, 1], 'go', alpha=0.05, mew=0, label='Exact Samples') + xs = einops.rearrange(samples, "b n d -> (b n) d").cpu() + zs = einops.rearrange(exact_samples, "b n d -> (b n) d").cpu() + ax1.set_title("XY Projection") + ax1.plot(xs[:, 0], xs[:, 1], "ro", alpha=0.5, mew=0, label="ODE Solver") + ax1.plot(zs[:, 0], zs[:, 1], "go", alpha=0.05, mew=0, label="Exact Samples") - ax2.set_title('XZ Projection') - ax2.plot(xs[:, 0], xs[:, 2], 'ro', alpha=0.5, mew=0, label='ODE Solver') - ax2.plot(zs[:, 0], zs[:, 2], 'go', alpha=0.05, mew=0, label='Exact Samples') + ax2.set_title("XZ Projection") + ax2.plot(xs[:, 0], xs[:, 2], "ro", alpha=0.5, mew=0, label="ODE Solver") + ax2.plot(zs[:, 0], zs[:, 2], "go", alpha=0.05, mew=0, label="Exact Samples") - ax3.set_title('YZ Projection') - ax3.plot(xs[:, 1], xs[:, 2], 'ro', alpha=0.5, mew=0, label='ODE Solver') - ax3.plot(zs[:, 1], zs[:, 2], 'go', alpha=0.05, mew=0, label='Exact Samples') + ax3.set_title("YZ Projection") + ax3.plot(xs[:, 1], xs[:, 2], "ro", alpha=0.5, mew=0, label="ODE Solver") + ax3.plot(zs[:, 1], zs[:, 2], "go", alpha=0.05, mew=0, label="Exact Samples") for ax in [ax1, ax2, ax3]: ax.set_xlim(-0.01, 1.01) ax.set_ylim(-0.01, 1.01) - ax.vlines(x=[0, 1], ymin=0, ymax=1, color='k', lw=2) - ax.hlines(y=[0, 1], xmin=0, xmax=1, color='k', lw=2) + ax.vlines(x=[0, 1], ymin=0, ymax=1, color="k", lw=2) + ax.hlines(y=[0, 1], xmin=0, xmax=1, color="k", lw=2) ax1.legend(loc=0) fig3.tight_layout() fig3.savefig( - ANALYTIC_SCORE_RESULTS_DIR / f"marginal_2D_distributions_{sampling_algorithm}_{number_of_atoms}_atoms.png") + ANALYTIC_SCORE_RESULTS_DIR + / f"marginal_2D_distributions_{sampling_algorithm}_{number_of_atoms}_atoms.png" + ) plt.close(fig3) # ======================== Figure 4 ====================================== @@ -272,21 +307,21 @@ ax1 = fig4.add_subplot(131) ax2 = fig4.add_subplot(132) ax3 = fig4.add_subplot(133) - fig4.suptitle('Comparing Sampling and Expected Marginal Distributions') + fig4.suptitle("Comparing Sampling and Expected Marginal Distributions") - common_params = dict(histtype='stepfilled', alpha=0.5, bins=50) + common_params = dict(histtype="stepfilled", alpha=0.5, bins=50) - ax1.hist(xs[:, 0], **common_params, facecolor='r', label='ODE solver') - ax2.hist(xs[:, 1], **common_params, facecolor='r', label='ODE solver') - ax3.hist(xs[:, 2], **common_params, facecolor='r', label='ODE solver') + ax1.hist(xs[:, 0], **common_params, facecolor="r", label="ODE solver") + ax2.hist(xs[:, 1], **common_params, facecolor="r", label="ODE solver") + ax3.hist(xs[:, 2], **common_params, facecolor="r", label="ODE solver") - ax1.hist(zs[:, 0], **common_params, facecolor='g', label='Exact') - ax2.hist(zs[:, 1], **common_params, facecolor='g', label='Exact') - ax3.hist(zs[:, 2], **common_params, facecolor='g', label='Exact') + ax1.hist(zs[:, 0], **common_params, facecolor="g", label="Exact") + ax2.hist(zs[:, 1], **common_params, facecolor="g", label="Exact") + ax3.hist(zs[:, 2], **common_params, facecolor="g", label="Exact") - ax1.set_xlabel('X') - ax2.set_xlabel('Y') - ax3.set_xlabel('Z') + ax1.set_xlabel("X") + ax2.set_xlabel("Y") + ax3.set_xlabel("Z") for ax in [ax1, ax2, ax3]: ax.set_xlim(-0.01, 1.01) @@ -294,27 +329,41 @@ ax1.legend(loc=0) fig4.tight_layout() fig4.savefig( - ANALYTIC_SCORE_RESULTS_DIR / f"marginal_1D_distributions_{sampling_algorithm}_{number_of_atoms}_atoms.png") + ANALYTIC_SCORE_RESULTS_DIR + / f"marginal_1D_distributions_{sampling_algorithm}_{number_of_atoms}_atoms.png" + ) plt.close(fig4) # ======================== Figure 5 ====================================== logger.info("Plotting harmonic energy distributions") fig5 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig5.suptitle(f'Energy Distribution, sampling algorithm {sampling_algorithm}') + fig5.suptitle(f"Energy Distribution, sampling algorithm {sampling_algorithm}") common_params = dict(density=True, bins=50, histtype="stepfilled", alpha=0.25) ax1 = fig5.add_subplot(111) - ax1.hist(sampled_harmonic_energies, **common_params, label='Sampled Energies', color='red') - ax1.hist(exact_harmonic_energies, **common_params, label='Theoretical Energies', color='green') + ax1.hist( + sampled_harmonic_energies, + **common_params, + label="Sampled Energies", + color="red", + ) + ax1.hist( + exact_harmonic_energies, + **common_params, + label="Theoretical Energies", + color="green", + ) ax1.set_xlim(xmin=-0.01) - ax1.set_xlabel('Unitless Harmonic Energy') - ax1.set_ylabel('Density') - ax1.legend(loc='upper right', fancybox=True, shadow=True, ncol=1, fontsize=12) + ax1.set_xlabel("Unitless Harmonic Energy") + ax1.set_ylabel("Density") + ax1.legend(loc="upper right", fancybox=True, shadow=True, ncol=1, fontsize=12) fig5.tight_layout() fig5.savefig( - ANALYTIC_SCORE_RESULTS_DIR / f"harmonic_energy_samples_{sampling_algorithm}_{number_of_atoms}_atoms.png") + ANALYTIC_SCORE_RESULTS_DIR + / f"harmonic_energy_samples_{sampling_algorithm}_{number_of_atoms}_atoms.png" + ) plt.close(fig5) logger.info("Done!") diff --git a/experiments/analysis/analytic_score/exploring_langevin_generator/generate_sample_energies.py b/experiments/analysis/analytic_score/exploring_langevin_generator/generate_sample_energies.py index 17ae45ed..2a016f5f 100644 --- a/experiments/analysis/analytic_score/exploring_langevin_generator/generate_sample_energies.py +++ b/experiments/analysis/analytic_score/exploring_langevin_generator/generate_sample_energies.py @@ -5,18 +5,22 @@ import einops import numpy as np import torch -from crystal_diffusion.callbacks.sampling_visualization_callback import logger -from crystal_diffusion.generators.langevin_generator import LangevinGenerator -from crystal_diffusion.generators.predictor_corrector_position_generator import \ +from tqdm import tqdm + +from diffusion_for_multi_scale_molecular_dynamics.callbacks.sampling_visualization_callback import \ + logger +from diffusion_for_multi_scale_molecular_dynamics.generators.langevin_generator import \ + LangevinGenerator +from diffusion_for_multi_scale_molecular_dynamics.generators.predictor_corrector_position_generator import \ PredictorCorrectorSamplingParameters -from crystal_diffusion.models.score_networks.analytical_score_network import ( +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.analytical_score_network import ( AnalyticalScoreNetworkParameters, TargetScoreBasedAnalyticalScoreNetwork) -from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps -from crystal_diffusion.utils.basis_transformations import ( +from diffusion_for_multi_scale_molecular_dynamics.oracle.lammps import \ + get_energy_and_forces_from_lammps +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import ( get_positions_from_coordinates, map_relative_coordinates_to_unit_cell) -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters -from tqdm import tqdm - from experiments.analysis.analytic_score.exploring_langevin_generator import \ LANGEVIN_EXPLORATION_DIRECTORY from experiments.analysis.analytic_score.utils import (get_exact_samples, @@ -37,45 +41,59 @@ def _get_basis_vectors(self, batch_size: int) -> torch.Tensor: basis_vectors = self.unit_cell.unsqueeze(0).repeat(batch_size, 1, 1) return basis_vectors - def compute_oracle_energies(self, batch_relative_coordinates: torch.Tensor) -> np.ndarray: + def compute_oracle_energies( + self, batch_relative_coordinates: torch.Tensor + ) -> np.ndarray: """Compute oracle energies.""" batch_size = batch_relative_coordinates.shape[0] - batched_unit_cells = einops.repeat(self.unit_cell, "d1 d2 -> b d1 d2", b=batch_size) + batched_unit_cells = einops.repeat( + self.unit_cell, "d1 d2 -> b d1 d2", b=batch_size + ) - batch_cartesian_positions = get_positions_from_coordinates(batch_relative_coordinates, - batched_unit_cells) + batch_cartesian_positions = get_positions_from_coordinates( + batch_relative_coordinates, batched_unit_cells + ) list_energy = [] logger.info("Compute energy from Oracle") with tempfile.TemporaryDirectory() as tmp_work_dir: - for positions, box in zip(batch_cartesian_positions.cpu().numpy(), batched_unit_cells.cpu().numpy()): - energy, forces = get_energy_and_forces_from_lammps(positions, - box, - self.atom_types, - tmp_work_dir=tmp_work_dir) + for positions, box in zip( + batch_cartesian_positions.cpu().numpy(), + batched_unit_cells.cpu().numpy(), + ): + energy, forces = get_energy_and_forces_from_lammps( + positions, box, self.atom_types, tmp_work_dir=tmp_work_dir + ) list_energy.append(energy) return np.array(list_energy) -def generate_exact_samples(equilibrium_relative_coordinates: torch.Tensor, sigma_d: float, number_of_samples: int): +def generate_exact_samples( + equilibrium_relative_coordinates: torch.Tensor, + sigma_d: float, + number_of_samples: int, +): """Generate Gaussian samples about the equilibrium relative coordinates.""" - variance_parameter = sigma_d ** 2 + variance_parameter = sigma_d**2 number_of_atoms, spatial_dimension = equilibrium_relative_coordinates.shape nd = number_of_atoms * spatial_dimension inverse_covariance = torch.diag(torch.ones(nd)) / variance_parameter - inverse_covariance = inverse_covariance.reshape(number_of_atoms, spatial_dimension, - number_of_atoms, spatial_dimension) + inverse_covariance = inverse_covariance.reshape( + number_of_atoms, spatial_dimension, number_of_atoms, spatial_dimension + ) - exact_samples = get_exact_samples(equilibrium_relative_coordinates, inverse_covariance, number_of_samples) + exact_samples = get_exact_samples( + equilibrium_relative_coordinates, inverse_covariance, number_of_samples + ) exact_samples = map_relative_coordinates_to_unit_cell(exact_samples) return exact_samples -device = torch.device('cpu') +device = torch.device("cpu") number_of_atoms = 64 spatial_dimension = 3 @@ -84,7 +102,10 @@ def generate_exact_samples(equilibrium_relative_coordinates: torch.Tensor, sigma supercell_factor = 2 cell_dimensions = 3 * [acell * supercell_factor] -pickle_directory = LANGEVIN_EXPLORATION_DIRECTORY / f"Si_{supercell_factor}x{supercell_factor}x{supercell_factor}" +pickle_directory = ( + LANGEVIN_EXPLORATION_DIRECTORY + / f"Si_{supercell_factor}x{supercell_factor}x{supercell_factor}" +) pickle_directory.mkdir(exist_ok=True, parents=True) number_of_samples = 1000 @@ -95,37 +116,53 @@ def generate_exact_samples(equilibrium_relative_coordinates: torch.Tensor, sigma list_total_time_steps = [10, 100, 1000] -if __name__ == '__main__': +if __name__ == "__main__": unit_cell = torch.diag(torch.tensor(cell_dimensions)) - batched_unit_cells = einops.repeat(unit_cell, "d1 d2 -> b d1 d2", b=number_of_samples) + batched_unit_cells = einops.repeat( + unit_cell, "d1 d2 -> b d1 d2", b=number_of_samples + ) - energy_calculator = EnergyCalculator(unit_cell=unit_cell, number_of_atoms=number_of_atoms) + energy_calculator = EnergyCalculator( + unit_cell=unit_cell, number_of_atoms=number_of_atoms + ) equilibrium_relative_coordinates = torch.from_numpy( - get_silicon_supercell(supercell_factor=supercell_factor)).to(torch.float32) + get_silicon_supercell(supercell_factor=supercell_factor) + ).to(torch.float32) for sigma_d in tqdm(list_sigma_d, "exact energies"): name = f"Sd={sigma_d}" - output_path = pickle_directory / f'exact_energies_{name}.pkl' + output_path = pickle_directory / f"exact_energies_{name}.pkl" if output_path.is_file(): logger.info(f"file {name} already exists. Moving on.") continue - exact_samples = generate_exact_samples(equilibrium_relative_coordinates, - sigma_d=sigma_d, - number_of_samples=number_of_samples) + exact_samples = generate_exact_samples( + equilibrium_relative_coordinates, + sigma_d=sigma_d, + number_of_samples=number_of_samples, + ) - exact_samples_energies = energy_calculator.compute_oracle_energies(exact_samples) + exact_samples_energies = energy_calculator.compute_oracle_energies( + exact_samples + ) result = dict(sigma_d=sigma_d, energies=exact_samples_energies) - with open(output_path, 'wb') as fd: + with open(output_path, "wb") as fd: pickle.dump(result, fd) - prod = itertools.product(list_number_of_corrector_steps, list_sigma_d, list_sigma_min, list_total_time_steps) - for number_of_corrector_steps, sigma_d, sigma_min, total_time_steps in tqdm(prod, "sample energies"): + prod = itertools.product( + list_number_of_corrector_steps, + list_sigma_d, + list_sigma_min, + list_total_time_steps, + ) + for number_of_corrector_steps, sigma_d, sigma_min, total_time_steps in tqdm( + prod, "sample energies" + ): name = f"Sd={sigma_d}_Sm={sigma_min}_S={total_time_steps}_C={number_of_corrector_steps}" - output_path = pickle_directory / f'sampled_energies_{name}.pkl' + output_path = pickle_directory / f"sampled_energies_{name}.pkl" if output_path.is_file(): logger.info(f"file {name} already exists. Moving on.") continue @@ -136,39 +173,50 @@ def generate_exact_samples(equilibrium_relative_coordinates: torch.Tensor, sigma use_permutation_invariance=False, kmax=kmax, equilibrium_relative_coordinates=equilibrium_relative_coordinates, - variance_parameter=sigma_d**2) - - sigma_normalized_score_network = TargetScoreBasedAnalyticalScoreNetwork(score_network_parameters) - - sampling_parameters = PredictorCorrectorSamplingParameters(number_of_atoms=number_of_atoms, - number_of_corrector_steps=number_of_corrector_steps, - number_of_samples=number_of_samples, - sample_batchsize=number_of_samples, - sample_every_n_epochs=1, - first_sampling_epoch=0, - cell_dimensions=cell_dimensions, - record_samples=False) - - noise_parameters = NoiseParameters(total_time_steps=total_time_steps, - sigma_min=sigma_min, - sigma_max=0.5) - - generator = LangevinGenerator(noise_parameters=noise_parameters, - sampling_parameters=sampling_parameters, - sigma_normalized_score_network=sigma_normalized_score_network) - - generated_samples = generator.sample(number_of_samples, - device=device, - unit_cell=batched_unit_cells) - - generated_samples_energies = energy_calculator.compute_oracle_energies(generated_samples) - - result = dict(sigma_d=sigma_d, - sigma_min=sigma_min, - total_time_steps=total_time_steps, - number_of_corrector_steps=number_of_corrector_steps, - energies=generated_samples_energies) + variance_parameter=sigma_d**2, + ) + + sigma_normalized_score_network = TargetScoreBasedAnalyticalScoreNetwork( + score_network_parameters + ) + + sampling_parameters = PredictorCorrectorSamplingParameters( + number_of_atoms=number_of_atoms, + number_of_corrector_steps=number_of_corrector_steps, + number_of_samples=number_of_samples, + sample_batchsize=number_of_samples, + sample_every_n_epochs=1, + first_sampling_epoch=0, + cell_dimensions=cell_dimensions, + record_samples=False, + ) + + noise_parameters = NoiseParameters( + total_time_steps=total_time_steps, sigma_min=sigma_min, sigma_max=0.5 + ) + + generator = LangevinGenerator( + noise_parameters=noise_parameters, + sampling_parameters=sampling_parameters, + sigma_normalized_score_network=sigma_normalized_score_network, + ) + + generated_samples = generator.sample( + number_of_samples, device=device, unit_cell=batched_unit_cells + ) + + generated_samples_energies = energy_calculator.compute_oracle_energies( + generated_samples + ) + + result = dict( + sigma_d=sigma_d, + sigma_min=sigma_min, + total_time_steps=total_time_steps, + number_of_corrector_steps=number_of_corrector_steps, + energies=generated_samples_energies, + ) name = f"Sd={sigma_d}_Sm={sigma_min}_S={total_time_steps}_C={number_of_corrector_steps}" # Pickle a lot of intermediate things to avoid losing everything in a crash. - with open(output_path, 'wb') as fd: + with open(output_path, "wb") as fd: pickle.dump(result, fd) diff --git a/experiments/analysis/analytic_score/exploring_langevin_generator/plot_sample_energies.py b/experiments/analysis/analytic_score/exploring_langevin_generator/plot_sample_energies.py index 4d897836..0a943507 100644 --- a/experiments/analysis/analytic_score/exploring_langevin_generator/plot_sample_energies.py +++ b/experiments/analysis/analytic_score/exploring_langevin_generator/plot_sample_energies.py @@ -2,24 +2,28 @@ import matplotlib.pyplot as plt import numpy as np -from src.crystal_diffusion.analysis import PLOT_STYLE_PATH +from diffusion_for_multi_scale_molecular_dynamics.analysis import \ + PLOT_STYLE_PATH from experiments.analysis.analytic_score.exploring_langevin_generator import \ LANGEVIN_EXPLORATION_DIRECTORY plt.style.use(PLOT_STYLE_PATH) supercell_factor = 2 -pickle_directory = LANGEVIN_EXPLORATION_DIRECTORY / f"Si_{supercell_factor}x{supercell_factor}x{supercell_factor}" +pickle_directory = ( + LANGEVIN_EXPLORATION_DIRECTORY + / f"Si_{supercell_factor}x{supercell_factor}x{supercell_factor}" +) title = f"Si {supercell_factor}x{supercell_factor}x{supercell_factor}" sigma_d = 0.001 sigma_min = 0.001 -if __name__ == '__main__': +if __name__ == "__main__": list_q = np.linspace(0, 1, 101) - with open(pickle_directory / f'exact_energies_Sd={sigma_d}.pkl', 'rb') as fd: + with open(pickle_directory / f"exact_energies_Sd={sigma_d}.pkl", "rb") as fd: exact_results = pickle.load(fd) FIG_SIZE = (7.2, 7.2) @@ -30,9 +34,16 @@ fig.suptitle(f"{title} : $\\sigma_d$ = {sigma_d}, $\\sigma_m$ = {sigma_min}") - list_energy_quantiles = np.quantile(exact_results['energies'], list_q) + list_energy_quantiles = np.quantile(exact_results["energies"], list_q) for ax in [ax1, ax2, ax3]: - ax.plot(100 * list_q, list_energy_quantiles, '-', lw=10, color='green', label='Exact') + ax.plot( + 100 * list_q, + list_energy_quantiles, + "-", + lw=10, + color="green", + label="Exact", + ) min = list_energy_quantiles[0] max = list_energy_quantiles[-1] @@ -45,27 +56,31 @@ ax.set_title(f"Corrector steps: {number_of_corrector_steps}") lw = 8 - for total_time_steps, color in zip([10, 100, 1000], ['r', 'y', 'b']): - name = (f"sampled_energies_Sd={sigma_d}_Sm={sigma_min}" - f"_S={total_time_steps}_C={number_of_corrector_steps}.pkl") + for total_time_steps, color in zip([10, 100, 1000], ["r", "y", "b"]): + name = ( + f"sampled_energies_Sd={sigma_d}_Sm={sigma_min}" + f"_S={total_time_steps}_C={number_of_corrector_steps}.pkl" + ) filepath = pickle_directory / name if not filepath.is_file(): print(f"File {name} is missing. Moving on.") continue - with open(filepath, 'rb') as fd: + with open(filepath, "rb") as fd: sampled_results = pickle.load(fd) - energies = sampled_results.pop('energies') + energies = sampled_results.pop("energies") list_energy_quantiles = np.quantile(energies, list_q) - ax.plot(100 * list_q, - list_energy_quantiles, - '-.', - lw=lw, - c=color, - label=f'total time steps = {total_time_steps}') + ax.plot( + 100 * list_q, + list_energy_quantiles, + "-.", + lw=lw, + c=color, + label=f"total time steps = {total_time_steps}", + ) lw = lw / 1.5 - ax.set_xlabel('Quantile (%)') - ax.set_ylabel('Energy Quantile (eV)') + ax.set_xlabel("Quantile (%)") + ax.set_ylabel("Energy Quantile (eV)") ax.set_xlim([-0.01, 100.01]) ax.set_ylim([min, max]) diff --git a/experiments/analysis/analytic_score/perfect_score_loss_analysis.py b/experiments/analysis/analytic_score/perfect_score_loss_analysis.py index d9cdf106..085e7c3b 100644 --- a/experiments/analysis/analytic_score/perfect_score_loss_analysis.py +++ b/experiments/analysis/analytic_score/perfect_score_loss_analysis.py @@ -6,36 +6,40 @@ import numpy as np import pytorch_lightning as pl import torch -from crystal_diffusion import ANALYSIS_RESULTS_DIR -from crystal_diffusion.callbacks.loss_monitoring_callback import \ +from pytorch_lightning.loggers import CSVLogger +from torch.utils.data import DataLoader +from tqdm import tqdm + +from diffusion_for_multi_scale_molecular_dynamics import ANALYSIS_RESULTS_DIR +from diffusion_for_multi_scale_molecular_dynamics.analysis import \ + PLOT_STYLE_PATH +from diffusion_for_multi_scale_molecular_dynamics.callbacks.loss_monitoring_callback import \ LossMonitoringCallback -from crystal_diffusion.callbacks.sampling_visualization_callback import \ +from diffusion_for_multi_scale_molecular_dynamics.callbacks.sampling_visualization_callback import \ PredictorCorrectorDiffusionSamplingCallback -from crystal_diffusion.generators.predictor_corrector_position_generator import \ +from diffusion_for_multi_scale_molecular_dynamics.generators.predictor_corrector_position_generator import \ PredictorCorrectorSamplingParameters -from crystal_diffusion.models.loss import (MSELossParameters, - create_loss_calculator) -from crystal_diffusion.models.optimizer import OptimizerParameters -from crystal_diffusion.models.position_diffusion_lightning_model import ( +from diffusion_for_multi_scale_molecular_dynamics.models.loss import ( + MSELossParameters, create_loss_calculator) +from diffusion_for_multi_scale_molecular_dynamics.models.optimizer import \ + OptimizerParameters +from diffusion_for_multi_scale_molecular_dynamics.models.position_diffusion_lightning_model import ( PositionDiffusionLightningModel, PositionDiffusionParameters) -from crystal_diffusion.models.scheduler import \ +from diffusion_for_multi_scale_molecular_dynamics.models.scheduler import \ CosineAnnealingLRSchedulerParameters -from crystal_diffusion.models.score_networks.analytical_score_network import ( +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.analytical_score_network import ( AnalyticalScoreNetwork, AnalyticalScoreNetworkParameters, TargetScoreBasedAnalyticalScoreNetwork) -from crystal_diffusion.namespace import CARTESIAN_FORCES, RELATIVE_COORDINATES -from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps -from crystal_diffusion.samplers.noisy_relative_coordinates_sampler import \ +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, RELATIVE_COORDINATES) +from diffusion_for_multi_scale_molecular_dynamics.oracle.lammps import \ + get_energy_and_forces_from_lammps +from diffusion_for_multi_scale_molecular_dynamics.samplers.noisy_relative_coordinates_sampler import \ NoisyRelativeCoordinatesSampler -from crystal_diffusion.utils.basis_transformations import \ - map_relative_coordinates_to_unit_cell -from pytorch_lightning.loggers import CSVLogger -from src.crystal_diffusion.analysis import PLOT_STYLE_PATH -from src.crystal_diffusion.samplers.variance_sampler import ( +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import ( ExplodingVarianceSampler, NoiseParameters) -from torch.utils.data import DataLoader -from tqdm import tqdm - +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ + map_relative_coordinates_to_unit_cell from experiments.analysis.analytic_score.utils import (get_exact_samples, get_silicon_supercell) @@ -59,13 +63,20 @@ def __init__(self, hyper_params: PositionDiffusionParameters): self.hyper_params = hyper_params self.save_hyperparameters(logger=False) - self.use_permutation_invariance = hyper_params.score_network_parameters.use_permutation_invariance + self.use_permutation_invariance = ( + hyper_params.score_network_parameters.use_permutation_invariance + ) if self.use_permutation_invariance: - self.sigma_normalized_score_network = AnalyticalScoreNetwork(hyper_params.score_network_parameters) + self.sigma_normalized_score_network = AnalyticalScoreNetwork( + hyper_params.score_network_parameters + ) else: - self.sigma_normalized_score_network = TargetScoreBasedAnalyticalScoreNetwork( - hyper_params.score_network_parameters) + self.sigma_normalized_score_network = ( + TargetScoreBasedAnalyticalScoreNetwork( + hyper_params.score_network_parameters + ) + ) self.loss_calculator = create_loss_calculator(hyper_params.loss_parameters) self.noisy_relative_coordinates_sampler = NoisyRelativeCoordinatesSampler() @@ -80,7 +91,7 @@ def on_validation_start(self) -> None: plt.style.use(PLOT_STYLE_PATH) -device = torch.device('cpu') +device = torch.device("cpu") dataset_size = 1024 batch_size = 1024 @@ -101,82 +112,98 @@ def on_validation_start(self) -> None: else: # samples will be created sigma_d = 0.0025 / np.sqrt(supercell_factor) - variance_parameter = sigma_d ** 2 + variance_parameter = sigma_d**2 model_variance_parameter = 1.0 * variance_parameter -noise_parameters = NoiseParameters(total_time_steps=1000, - sigma_min=0.0001, - sigma_max=0.5) +noise_parameters = NoiseParameters( + total_time_steps=1000, sigma_min=0.0001, sigma_max=0.5 +) # We will not optimize, so this doesn't matter -dummy_optimizer_parameters = OptimizerParameters(name='adam', learning_rate=0.001, weight_decay=0.0) +dummy_optimizer_parameters = OptimizerParameters( + name="adam", learning_rate=0.001, weight_decay=0.0 +) dummy_scheduler_parameters = CosineAnnealingLRSchedulerParameters(T_max=10) -loss_monitoring_callback = LossMonitoringCallback(number_of_bins=100, - sample_every_n_epochs=1, - spatial_dimension=spatial_dimension) +loss_monitoring_callback = LossMonitoringCallback( + number_of_bins=100, sample_every_n_epochs=1, spatial_dimension=spatial_dimension +) -experiment_name = (f"cell_{supercell_factor}x{supercell_factor}x{supercell_factor}" - f"_permutation_{use_permutation_invariance}" - f"_sigma_d={np.sqrt(model_variance_parameter):5.4f}_super_noise") +experiment_name = ( + f"cell_{supercell_factor}x{supercell_factor}x{supercell_factor}" + f"_permutation_{use_permutation_invariance}" + f"_sigma_d={np.sqrt(model_variance_parameter):5.4f}_super_noise" +) -output_dir = ANALYSIS_RESULTS_DIR / 'PERFECT_SCORE_LOSS' +output_dir = ANALYSIS_RESULTS_DIR / "PERFECT_SCORE_LOSS" csv_logger = CSVLogger(save_dir=str(output_dir), name=experiment_name) -if __name__ == '__main__': +if __name__ == "__main__": box = torch.diag(torch.tensor(cell_dimensions)) equilibrium_relative_coordinates = torch.from_numpy( - get_silicon_supercell(supercell_factor=supercell_factor)).to(torch.float32) + get_silicon_supercell(supercell_factor=supercell_factor) + ).to(torch.float32) number_of_atoms, _ = equilibrium_relative_coordinates.shape nd = number_of_atoms * spatial_dimension atom_types = np.array(number_of_atoms * [1]) - sampling_parameters = PredictorCorrectorSamplingParameters(number_of_atoms=number_of_atoms, - number_of_corrector_steps=10, - number_of_samples=batch_size, - sample_batchsize=batch_size, - sample_every_n_epochs=1, - first_sampling_epoch=0, - cell_dimensions=cell_dimensions, - record_samples=False) + sampling_parameters = PredictorCorrectorSamplingParameters( + number_of_atoms=number_of_atoms, + number_of_corrector_steps=10, + number_of_samples=batch_size, + sample_batchsize=batch_size, + sample_every_n_epochs=1, + first_sampling_epoch=0, + cell_dimensions=cell_dimensions, + record_samples=False, + ) diffusion_sampling_callback = PredictorCorrectorDiffusionSamplingCallback( noise_parameters=noise_parameters, sampling_parameters=sampling_parameters, - output_directory=output_dir / experiment_name + output_directory=output_dir / experiment_name, ) if use_equilibrium: - exact_samples = einops.repeat(equilibrium_relative_coordinates, "n d -> b n d", b=dataset_size) + exact_samples = einops.repeat( + equilibrium_relative_coordinates, "n d -> b n d", b=dataset_size + ) else: inverse_covariance = torch.diag(torch.ones(nd)) / variance_parameter - inverse_covariance = inverse_covariance.reshape(number_of_atoms, spatial_dimension, - number_of_atoms, spatial_dimension) + inverse_covariance = inverse_covariance.reshape( + number_of_atoms, spatial_dimension, number_of_atoms, spatial_dimension + ) # Create a dataloader - exact_samples = get_exact_samples(equilibrium_relative_coordinates, inverse_covariance, dataset_size) + exact_samples = get_exact_samples( + equilibrium_relative_coordinates, inverse_covariance, dataset_size + ) exact_samples = map_relative_coordinates_to_unit_cell(exact_samples) list_oracle_energies = [] list_positions = (exact_samples @ box).cpu().numpy() with tempfile.TemporaryDirectory() as tmp_work_dir: for positions in tqdm(list_positions, "LAMMPS energies"): - energy, forces = get_energy_and_forces_from_lammps(positions, - box.numpy(), - atom_types, - tmp_work_dir=tmp_work_dir) + energy, forces = get_energy_and_forces_from_lammps( + positions, box.numpy(), atom_types, tmp_work_dir=tmp_work_dir + ) list_oracle_energies.append(energy) - dataset = [{RELATIVE_COORDINATES: x, - CARTESIAN_FORCES: torch.zeros_like(x), - 'potential_energy': energy, - 'box': torch.tensor(cell_dimensions)} for x, energy in zip(exact_samples, list_oracle_energies)] + dataset = [ + { + RELATIVE_COORDINATES: x, + CARTESIAN_FORCES: torch.zeros_like(x), + "potential_energy": energy, + "box": torch.tensor(cell_dimensions), + } + for x, energy in zip(exact_samples, list_oracle_energies) + ] dataloader = DataLoader(dataset, batch_size=batch_size) @@ -186,7 +213,8 @@ def on_validation_start(self) -> None: use_permutation_invariance=use_permutation_invariance, kmax=kmax, equilibrium_relative_coordinates=equilibrium_relative_coordinates, - variance_parameter=model_variance_parameter) + variance_parameter=model_variance_parameter, + ) diffusion_params = PositionDiffusionParameters( score_network_parameters=score_network_parameters, @@ -194,18 +222,19 @@ def on_validation_start(self) -> None: optimizer_parameters=dummy_optimizer_parameters, scheduler_parameters=dummy_scheduler_parameters, noise_parameters=noise_parameters, - kmax_target_score=kmax_target_score + kmax_target_score=kmax_target_score, ) model = AnalyticalScorePositionDiffusionLightningModel(diffusion_params) - trainer = pl.Trainer(callbacks=[loss_monitoring_callback, diffusion_sampling_callback], - max_epochs=1, - log_every_n_steps=1, - fast_dev_run=False, - logger=csv_logger, - inference_mode=not use_equilibrium # Do this or else the gradients don't work! - ) + trainer = pl.Trainer( + callbacks=[loss_monitoring_callback, diffusion_sampling_callback], + max_epochs=1, + log_every_n_steps=1, + fast_dev_run=False, + logger=csv_logger, + inference_mode=not use_equilibrium, # Do this or else the gradients don't work! + ) # Run a validation epoch trainer.validate(model, dataloaders=[dataloader]) diff --git a/experiments/analysis/analytic_score/repaint/plot_repaint_analytical_score_trajectories.py b/experiments/analysis/analytic_score/repaint/plot_repaint_analytical_score_trajectories.py index aa23103e..f1043a42 100644 --- a/experiments/analysis/analytic_score/repaint/plot_repaint_analytical_score_trajectories.py +++ b/experiments/analysis/analytic_score/repaint/plot_repaint_analytical_score_trajectories.py @@ -5,14 +5,15 @@ import matplotlib.pyplot as plt import numpy as np import torch -from crystal_diffusion import ANALYSIS_RESULTS_DIR -from crystal_diffusion.utils.logging_utils import setup_analysis_logger from einops import einops -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from experiments.analysis.analytic_score import (get_exact_samples, - get_samples_harmonic_energy, - get_silicon_supercell) +from diffusion_for_multi_scale_molecular_dynamics import ANALYSIS_RESULTS_DIR +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) +from diffusion_for_multi_scale_molecular_dynamics.utils.logging_utils import \ + setup_analysis_logger +from experiments.analysis.analytic_score.utils import ( + get_exact_samples, get_samples_harmonic_energy, get_silicon_supercell) logger = logging.getLogger(__name__) setup_analysis_logger() @@ -34,99 +35,140 @@ number_of_atoms, spatial_dimension = 8, 3 nd = number_of_atoms * spatial_dimension -if __name__ == '__main__': +if __name__ == "__main__": - inverse_covariance = (torch.diag(torch.ones(nd)) / variance_parameter) - inverse_covariance = inverse_covariance.reshape(number_of_atoms, spatial_dimension, - number_of_atoms, spatial_dimension) + inverse_covariance = torch.diag(torch.ones(nd)) / variance_parameter + inverse_covariance = inverse_covariance.reshape( + number_of_atoms, spatial_dimension, number_of_atoms, spatial_dimension + ) equilibrium_relative_coordinates = torch.from_numpy( - get_silicon_supercell(supercell_factor=supercell_factor).astype(np.float32)) - exact_samples = get_exact_samples(equilibrium_relative_coordinates, - inverse_covariance, - number_of_samples).cpu() + get_silicon_supercell(supercell_factor=supercell_factor).astype(np.float32) + ) + exact_samples = get_exact_samples( + equilibrium_relative_coordinates, inverse_covariance, number_of_samples + ).cpu() logger.info("Computing harmonic energies") - exact_harmonic_energies = get_samples_harmonic_energy(equilibrium_relative_coordinates, - inverse_covariance, - exact_samples) + exact_harmonic_energies = get_samples_harmonic_energy( + equilibrium_relative_coordinates, inverse_covariance, exact_samples + ) logger.info("Extracting data artifacts") - with open(data_pickle_path, 'rb') as fd: - recorded_data = torch.load(fd, map_location=torch.device('cpu')) + with open(data_pickle_path, "rb") as fd: + recorded_data = torch.load(fd, map_location=torch.device("cpu")) energies = torch.load(energy_pickle_path) # ============================================================== logger.info("Plotting energy distributions") fig0 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig0.suptitle('Energy Distribution: Constrained vs Free') + fig0.suptitle("Energy Distribution: Constrained vs Free") common_params = dict(density=True, bins=50, histtype="stepfilled", alpha=0.25) ax1 = fig0.add_subplot(111) - ax1.hist(energies, **common_params, label='Repaint Samples Energies', color='red') - ax1.hist(exact_harmonic_energies, **common_params, label='Unconstrained Samples Energies', color='green') + ax1.hist(energies, **common_params, label="Repaint Samples Energies", color="red") + ax1.hist( + exact_harmonic_energies, + **common_params, + label="Unconstrained Samples Energies", + color="green", + ) ax1.set_xlim(xmin=-0.01) - ax1.set_xlabel('Unitless Harmonic Energy') - ax1.set_ylabel('Density') - ax1.legend(loc='upper right', fancybox=True, shadow=True, ncol=1, fontsize=12) + ax1.set_xlabel("Unitless Harmonic Energy") + ax1.set_ylabel("Density") + ax1.legend(loc="upper right", fancybox=True, shadow=True, ncol=1, fontsize=12) fig0.tight_layout() fig0.savefig(base_path / "comparing_free_and_repaint_energies.png") # ============================================================== - sampling_times = recorded_data['time'] - relative_coordinates = recorded_data['relative_coordinates'] - batch_flat_relative_coordinates = einops.rearrange(relative_coordinates, "b t n d -> b t (n d)") + sampling_times = recorded_data["time"] + relative_coordinates = recorded_data["relative_coordinates"] + batch_flat_relative_coordinates = einops.rearrange( + relative_coordinates, "b t n d -> b t (n d)" + ) - normalized_scores = recorded_data['normalized_scores'] - batch_flat_normalized_scores = einops.rearrange(normalized_scores, "b t n d -> b t (n d)") + normalized_scores = recorded_data["normalized_scores"] + batch_flat_normalized_scores = einops.rearrange( + normalized_scores, "b t n d -> b t (n d)" + ) logger.info("Plotting scores along trajectories") fig = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig.suptitle('Root Mean Square Normalized Scores Along Sample Trajectories') - rms_norm_score = (batch_flat_normalized_scores ** 2).mean(dim=-1).sqrt().numpy() + fig.suptitle("Root Mean Square Normalized Scores Along Sample Trajectories") + rms_norm_score = (batch_flat_normalized_scores**2).mean(dim=-1).sqrt().numpy() constrained_rms_norm_score = ( - (batch_flat_normalized_scores[:, :, :number_of_constrained_coordinates] ** 2).mean(dim=-1).sqrt().numpy()) + (batch_flat_normalized_scores[:, :, :number_of_constrained_coordinates] ** 2) + .mean(dim=-1) + .sqrt() + .numpy() + ) free_rms_norm_score = ( - (batch_flat_normalized_scores[:, :, number_of_constrained_coordinates:] ** 2).mean(dim=-1).sqrt().numpy()) + (batch_flat_normalized_scores[:, :, number_of_constrained_coordinates:] ** 2) + .mean(dim=-1) + .sqrt() + .numpy() + ) ax1 = fig.add_subplot(221) ax2 = fig.add_subplot(222) ax3 = fig.add_subplot(223) ax4 = fig.add_subplot(224) - for ax, scores in zip([ax1, ax2, ax3, ax4], - [rms_norm_score, rms_norm_score, constrained_rms_norm_score, free_rms_norm_score]): + for ax, scores in zip( + [ax1, ax2, ax3, ax4], + [ + rms_norm_score, + rms_norm_score, + constrained_rms_norm_score, + free_rms_norm_score, + ], + ): for y in scores[::10]: - ax.plot(sampling_times, y, '-', color='gray', alpha=0.2, label='__nolabel__') + ax.plot( + sampling_times, y, "-", color="gray", alpha=0.2, label="__nolabel__" + ) list_quantiles = [0.0, 0.10, 0.5, 1.0] - list_colors = ['green', 'yellow', 'orange', 'red'] + list_colors = ["green", "yellow", "orange", "red"] for q, c in zip(list_quantiles, list_colors): energy_quantile = np.quantile(energies, q) idx = np.argmin(np.abs(energies - energy_quantile)) e = energies[idx] - for ax, scores in zip([ax1, ax2, ax3, ax4], - [rms_norm_score, rms_norm_score, constrained_rms_norm_score, free_rms_norm_score]): - ax.plot(sampling_times, scores[idx], '-', - color=c, alpha=1., label=f'Q = {100 * q:2.0f}%, Energy: {e:5.1f}') + for ax, scores in zip( + [ax1, ax2, ax3, ax4], + [ + rms_norm_score, + rms_norm_score, + constrained_rms_norm_score, + free_rms_norm_score, + ], + ): + ax.plot( + sampling_times, + scores[idx], + "-", + color=c, + alpha=1.0, + label=f"Q = {100 * q:2.0f}%, Energy: {e:5.1f}", + ) for ax in [ax1, ax2, ax3, ax4]: - ax.set_xlabel('Diffusion Time') - ax.set_ylabel(r'$\sqrt{\langle (\sigma(t) S_{\theta} )^2 \rangle}$') + ax.set_xlabel("Diffusion Time") + ax.set_ylabel(r"$\sqrt{\langle (\sigma(t) S_{\theta} )^2 \rangle}$") ax.set_xlim(1, 0) for ax in [ax2, ax3, ax4]: - ax.set_yscale('log') + ax.set_yscale("log") ax1.set_title("All Coordinates (Not Log Scale)") - ax1.set_ylim(ymin=0.) + ax1.set_ylim(ymin=0.0) ax2.set_title("All Coordinates") ax3.set_title("Restrained Coordinates") diff --git a/experiments/analysis/analytic_score/repaint/repaint_with_analytic_score.py b/experiments/analysis/analytic_score/repaint/repaint_with_analytic_score.py index c4667eb4..1ee22cc2 100644 --- a/experiments/analysis/analytic_score/repaint/repaint_with_analytic_score.py +++ b/experiments/analysis/analytic_score/repaint/repaint_with_analytic_score.py @@ -3,16 +3,20 @@ import matplotlib.pyplot as plt import numpy as np import torch -from crystal_diffusion import ANALYSIS_RESULTS_DIR -from crystal_diffusion.generators.constrained_langevin_generator import ( + +from diffusion_for_multi_scale_molecular_dynamics import ANALYSIS_RESULTS_DIR +from diffusion_for_multi_scale_molecular_dynamics.analysis import \ + PLOT_STYLE_PATH +from diffusion_for_multi_scale_molecular_dynamics.generators.constrained_langevin_generator import ( ConstrainedLangevinGenerator, ConstrainedLangevinGeneratorParameters) -from crystal_diffusion.models.score_networks.analytical_score_network import ( +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.analytical_score_network import ( AnalyticalScoreNetwork, AnalyticalScoreNetworkParameters) -from crystal_diffusion.utils.logging_utils import setup_analysis_logger -from crystal_diffusion.utils.structure_utils import create_structure -from src.crystal_diffusion.analysis import PLOT_STYLE_PATH -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters - +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters +from diffusion_for_multi_scale_molecular_dynamics.utils.logging_utils import \ + setup_analysis_logger +from diffusion_for_multi_scale_molecular_dynamics.utils.structure_utils import \ + create_structure from experiments.analysis.analytic_score import (get_samples_harmonic_energy, get_silicon_supercell, get_unit_cells) @@ -26,9 +30,9 @@ plt.style.use(PLOT_STYLE_PATH) if torch.cuda.is_available(): - device = torch.device('cuda') + device = torch.device("cuda") else: - device = torch.device('cpu') + device = torch.device("cpu") kmax = 1 supercell_factor = 1 @@ -39,40 +43,49 @@ acell = 5.43 # Angstroms. -constrained_relative_coordinates = np.array([[0.5, 0.5, 0.25], - [0.5, 0.5, 0.5], - [0.5, 0.5, 0.75]], dtype=np.float32) +constrained_relative_coordinates = np.array( + [[0.5, 0.5, 0.25], [0.5, 0.5, 0.5], [0.5, 0.5, 0.75]], dtype=np.float32 +) translation = np.array([0.125, 0.125, 0.125]).astype(np.float32) -if __name__ == '__main__': +if __name__ == "__main__": logger.info("Setting up parameters") - equilibrium_relative_coordinates = get_silicon_supercell(supercell_factor=supercell_factor).astype(np.float32) + equilibrium_relative_coordinates = get_silicon_supercell( + supercell_factor=supercell_factor + ).astype(np.float32) # Translate to avoid atoms right on the cell boundary equilibrium_relative_coordinates = equilibrium_relative_coordinates + translation number_of_atoms, spatial_dimension = equilibrium_relative_coordinates.shape logger.info("Creating samples from the exact distribution") - inverse_covariance = ((torch.diag(torch.ones(number_of_atoms * spatial_dimension)) / variance_parameter) - .to(device)) - inverse_covariance = inverse_covariance.reshape(number_of_atoms, spatial_dimension, - number_of_atoms, spatial_dimension) - - unit_cells = get_unit_cells(acell=acell, - spatial_dimension=spatial_dimension, - number_of_samples=number_of_samples) + inverse_covariance = ( + torch.diag(torch.ones(number_of_atoms * spatial_dimension)) / variance_parameter + ).to(device) + inverse_covariance = inverse_covariance.reshape( + number_of_atoms, spatial_dimension, number_of_atoms, spatial_dimension + ) + + unit_cells = get_unit_cells( + acell=acell, + spatial_dimension=spatial_dimension, + number_of_samples=number_of_samples, + ) - noise_parameters = NoiseParameters(total_time_steps=total_time_steps, - sigma_min=0.001, - sigma_max=0.5) + noise_parameters = NoiseParameters( + total_time_steps=total_time_steps, sigma_min=0.001, sigma_max=0.5 + ) score_network_parameters = AnalyticalScoreNetworkParameters( number_of_atoms=number_of_atoms, spatial_dimension=spatial_dimension, kmax=kmax, - equilibrium_relative_coordinates=torch.from_numpy(equilibrium_relative_coordinates).to(device), - variance_parameter=variance_parameter) + equilibrium_relative_coordinates=torch.from_numpy( + equilibrium_relative_coordinates + ).to(device), + variance_parameter=variance_parameter, + ) sigma_normalized_score_network = AnalyticalScoreNetwork(score_network_parameters) @@ -83,24 +96,28 @@ number_of_samples=number_of_samples, cell_dimensions=3 * [acell], constrained_relative_coordinates=constrained_relative_coordinates, - record_samples=True) + record_samples=True, + ) position_generator = ConstrainedLangevinGenerator( noise_parameters=noise_parameters, sampling_parameters=sampling_parameters, - sigma_normalized_score_network=sigma_normalized_score_network) + sigma_normalized_score_network=sigma_normalized_score_network, + ) logger.info("Drawing constrained samples") - samples = position_generator.sample(number_of_samples=number_of_samples, - device=device, - unit_cell=unit_cells).detach() + samples = position_generator.sample( + number_of_samples=number_of_samples, device=device, unit_cell=unit_cells + ).detach() - position_generator.sample_trajectory_recorder.write_to_pickle(repaint_dir / "repaint_trajectories.pkl") + position_generator.sample_trajectory_recorder.write_to_pickle( + repaint_dir / "repaint_trajectories.pkl" + ) logger.info("Computing harmonic energies") - sampled_harmonic_energies = get_samples_harmonic_energy(equilibrium_relative_coordinates, - inverse_covariance, - samples) + sampled_harmonic_energies = get_samples_harmonic_energy( + equilibrium_relative_coordinates, inverse_covariance, samples + ) with open(repaint_dir / "harmonic_energies.pt", "wb") as fd: torch.save(sampled_harmonic_energies, fd) @@ -108,14 +125,20 @@ logger.info("Creating CIF files") lattice_basis_vectors = np.diag([acell, acell, acell]) - species = number_of_atoms * ['Si'] + species = number_of_atoms * ["Si"] relative_coordinates = equilibrium_relative_coordinates - equilibrium_structure = create_structure(lattice_basis_vectors, relative_coordinates, species) + equilibrium_structure = create_structure( + lattice_basis_vectors, relative_coordinates, species + ) equilibrium_structure.to_file(str(repaint_dir / "equilibrium_positions.cif")) - relative_coordinates[:len(constrained_relative_coordinates)] = constrained_relative_coordinates - forced_structure = create_structure(lattice_basis_vectors, relative_coordinates, species) + relative_coordinates[: len(constrained_relative_coordinates)] = ( + constrained_relative_coordinates + ) + forced_structure = create_structure( + lattice_basis_vectors, relative_coordinates, species + ) forced_structure.to_file(str(repaint_dir / "forced_constraint_positions.cif")) samples_dir = repaint_dir / "samples" diff --git a/experiments/analysis/analytic_score/score_convergence_analysis.py b/experiments/analysis/analytic_score/score_convergence_analysis.py index 1e61ff45..8fecb26f 100644 --- a/experiments/analysis/analytic_score/score_convergence_analysis.py +++ b/experiments/analysis/analytic_score/score_convergence_analysis.py @@ -2,17 +2,19 @@ This module seeks to estimate the "dynamical matrix" for Si 1x1x1 from data. """ + import logging import matplotlib.pyplot as plt import torch -from crystal_diffusion.models.score_networks.analytical_score_network import ( - AnalyticalScoreNetwork, AnalyticalScoreNetworkParameters) -from crystal_diffusion.namespace import (NOISE, NOISY_RELATIVE_COORDINATES, - TIME, UNIT_CELL) from einops import einops -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.analytical_score_network import ( + AnalyticalScoreNetwork, AnalyticalScoreNetworkParameters) +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) from experiments.analysis.analytic_score import ANALYTIC_SCORE_RESULTS_DIR from experiments.analysis.analytic_score.utils import get_unit_cells @@ -20,23 +22,31 @@ plt.style.use(PLOT_STYLE_PATH) -device = torch.device('cpu') +device = torch.device("cpu") number_of_atoms = 1 spatial_dimension = 1 -variance_parameter = 1. / 250. +variance_parameter = 1.0 / 250.0 x_eq = 0.5 -if __name__ == '__main__': +if __name__ == "__main__": - grid = torch.linspace(0., 1., 201)[:-1] + grid = torch.linspace(0.0, 1.0, 201)[:-1] batch_size = len(grid) - relative_coordinates = einops.repeat(grid, 'b -> b n d', n=number_of_atoms, d=spatial_dimension) + relative_coordinates = einops.repeat( + grid, "b -> b n d", n=number_of_atoms, d=spatial_dimension + ) times = torch.rand(batch_size, 1) # doesn't matter. Not used by analytical score. - unit_cell = get_unit_cells(acell=1., spatial_dimension=spatial_dimension, number_of_samples=batch_size) - batch = {NOISY_RELATIVE_COORDINATES: relative_coordinates, TIME: times, UNIT_CELL: unit_cell} + unit_cell = get_unit_cells( + acell=1.0, spatial_dimension=spatial_dimension, number_of_samples=batch_size + ) + batch = { + NOISY_RELATIVE_COORDINATES: relative_coordinates, + TIME: times, + UNIT_CELL: unit_cell, + } equilibrium_relative_coordinates = torch.tensor([[x_eq]]) @@ -46,33 +56,40 @@ spatial_dimension=spatial_dimension, kmax=kmax, equilibrium_relative_coordinates=equilibrium_relative_coordinates, - variance_parameter=variance_parameter) + variance_parameter=variance_parameter, + ) sigma_normalized_score_network = AnalyticalScoreNetwork(score_network_parameters) fig = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig.suptitle(f'Sigma normalized Score, 1 atom in 1D\n Equilibrium Position: ({x_eq}), ' - f'$\\sigma_d^2$ = {variance_parameter}, kmax = {kmax}') + fig.suptitle( + f"Sigma normalized Score, 1 atom in 1D\n Equilibrium Position: ({x_eq}), " + f"$\\sigma_d^2$ = {variance_parameter}, kmax = {kmax}" + ) ax1 = fig.add_subplot(111) for sigma in [0.001, 0.01, 0.1, 0.2, 0.3, 0.4, 0.5]: batch[NOISE] = sigma * torch.ones(batch_size, 1) - sigma_normalized_scores = sigma_normalized_score_network(batch).detach().flatten() + sigma_normalized_scores = ( + sigma_normalized_score_network(batch).detach().flatten() + ) x = relative_coordinates.detach().flatten() - ax1.plot(x, sigma_normalized_scores, label=f'$\\sigma$ = {sigma}') + ax1.plot(x, sigma_normalized_scores, label=f"$\\sigma$ = {sigma}") - ax1.set_xlabel('Relative Coordinates') - ax1.set_ylabel('$\\sigma \\times$ Score') - ax1.legend(loc='upper right', fancybox=True, shadow=True, ncol=1, fontsize=9) + ax1.set_xlabel("Relative Coordinates") + ax1.set_ylabel("$\\sigma \\times$ Score") + ax1.legend(loc="upper right", fancybox=True, shadow=True, ncol=1, fontsize=9) ax1.set_xlim([-0.01, 1.01]) fig.tight_layout() fig.savefig(ANALYTIC_SCORE_RESULTS_DIR / "analytical_score_network_1D.png") plt.close(fig) fig2 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig2.suptitle(f'Sigma normalized Score, 1 atom in 1D\n Equilibrium Position: ({x_eq}), ' - f'$\\sigma_d^2$ = {variance_parameter}') + fig2.suptitle( + f"Sigma normalized Score, 1 atom in 1D\n Equilibrium Position: ({x_eq}), " + f"$\\sigma_d^2$ = {variance_parameter}" + ) ax2 = fig2.add_subplot(131) ax3 = fig2.add_subplot(132) @@ -86,20 +103,27 @@ spatial_dimension=spatial_dimension, kmax=kmax, equilibrium_relative_coordinates=equilibrium_relative_coordinates, - variance_parameter=variance_parameter) - sigma_normalized_score_network = AnalyticalScoreNetwork(score_network_parameters) + variance_parameter=variance_parameter, + ) + sigma_normalized_score_network = AnalyticalScoreNetwork( + score_network_parameters + ) for sigma, ax in zip([0.001, 0.1, 0.5], [ax2, ax3, ax4]): batch[NOISE] = sigma * torch.ones(batch_size, 1) - sigma_normalized_scores = sigma_normalized_score_network(batch).detach().flatten() + sigma_normalized_scores = ( + sigma_normalized_score_network(batch).detach().flatten() + ) - ax.plot(x, sigma_normalized_scores, label=f'kmax = {kmax}', lw=lw) + ax.plot(x, sigma_normalized_scores, label=f"kmax = {kmax}", lw=lw) lw = 0.5 * lw - ax2.legend(loc='upper right', fancybox=True, shadow=True, ncol=1, fontsize=9) + ax2.legend(loc="upper right", fancybox=True, shadow=True, ncol=1, fontsize=9) for sigma, ax in zip([0.001, 0.1, 0.5], [ax2, ax3, ax4]): - ax.set_ylabel('$\\sigma \\times$ Score') + ax.set_ylabel("$\\sigma \\times$ Score") ax.set_xlim([-0.01, 1.01]) ax.set_title(f"$\\sigma$={sigma}") fig2.tight_layout() - fig2.savefig(ANALYTIC_SCORE_RESULTS_DIR / "analytical_score_network_1D_convergence.png") + fig2.savefig( + ANALYTIC_SCORE_RESULTS_DIR / "analytical_score_network_1D_convergence.png" + ) plt.close(fig2) diff --git a/experiments/analysis/analytic_score/utils.py b/experiments/analysis/analytic_score/utils.py index d166a71f..17a4736f 100644 --- a/experiments/analysis/analytic_score/utils.py +++ b/experiments/analysis/analytic_score/utils.py @@ -5,7 +5,9 @@ from pymatgen.symmetry.analyzer import SpacegroupAnalyzer -def get_unit_cells(acell: float, spatial_dimension: int, number_of_samples: int) -> torch.Tensor: +def get_unit_cells( + acell: float, spatial_dimension: int, number_of_samples: int +) -> torch.Tensor: """Generate cubic unit cells.""" unit_cell = torch.diag(acell * torch.ones(spatial_dimension)) return unit_cell.repeat(number_of_samples, 1, 1) @@ -14,33 +16,45 @@ def get_unit_cells(acell: float, spatial_dimension: int, number_of_samples: int) def get_silicon_supercell(supercell_factor: int): """Get silicon supercell.""" primitive_cell_a = 3.84 - lattice = Lattice.from_parameters(a=primitive_cell_a, - b=primitive_cell_a, - c=primitive_cell_a, - alpha=60., beta=60., gamma=60.) - - species = ['Si', 'Si'] + lattice = Lattice.from_parameters( + a=primitive_cell_a, + b=primitive_cell_a, + c=primitive_cell_a, + alpha=60.0, + beta=60.0, + gamma=60.0, + ) + + species = ["Si", "Si"] coordinates = np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]) - primitize_structure = Structure(lattice=lattice, - species=species, - coords=coordinates, - coords_are_cartesian=False) - conventional_structure = SpacegroupAnalyzer(primitize_structure).get_symmetrized_structure().to_conventional() + primitize_structure = Structure( + lattice=lattice, species=species, coords=coordinates, coords_are_cartesian=False + ) + conventional_structure = ( + SpacegroupAnalyzer(primitize_structure) + .get_symmetrized_structure() + .to_conventional() + ) - super_structure = conventional_structure.make_supercell([supercell_factor, supercell_factor, supercell_factor]) + super_structure = conventional_structure.make_supercell( + [supercell_factor, supercell_factor, supercell_factor] + ) return super_structure.frac_coords -def get_random_equilibrium_relative_coordinates(number_of_atoms: int, spatial_dimension: int) -> torch.Tensor: +def get_random_equilibrium_relative_coordinates( + number_of_atoms: int, spatial_dimension: int +) -> torch.Tensor: """Random equilibrium coordinates inside the unit cell.""" # Make sure the positions are far from the edges. return 0.6 * torch.rand(number_of_atoms, spatial_dimension) + 0.2 -def get_random_inverse_covariance(spring_constant_scale: float, number_of_atoms: int, - spatial_dimension: int) -> torch.Tensor: +def get_random_inverse_covariance( + spring_constant_scale: float, number_of_atoms: int, spatial_dimension: int +) -> torch.Tensor: """Get a random inverse covariance.""" flat_dim = number_of_atoms * spatial_dimension @@ -49,23 +63,35 @@ def get_random_inverse_covariance(spring_constant_scale: float, number_of_atoms: random_matrix = torch.rand(flat_dim, flat_dim) orthogonal_matrix, _, _ = torch.svd(random_matrix) - flat_inverse_covariance = orthogonal_matrix @ (torch.diag(diagonal_values) @ orthogonal_matrix.T) + flat_inverse_covariance = orthogonal_matrix @ ( + torch.diag(diagonal_values) @ orthogonal_matrix.T + ) - inverse_covariance = einops.rearrange(flat_inverse_covariance, "(n1 d1) (n2 d2) -> n1 d1 n2 d2", - n1=number_of_atoms, n2=number_of_atoms, - d1=spatial_dimension, d2=spatial_dimension) + inverse_covariance = einops.rearrange( + flat_inverse_covariance, + "(n1 d1) (n2 d2) -> n1 d1 n2 d2", + n1=number_of_atoms, + n2=number_of_atoms, + d1=spatial_dimension, + d2=spatial_dimension, + ) return inverse_covariance -def get_exact_samples(equilibrium_relative_coordinates: torch.Tensor, inverse_covariance: torch.Tensor, - number_of_samples: int) -> torch.Tensor: +def get_exact_samples( + equilibrium_relative_coordinates: torch.Tensor, + inverse_covariance: torch.Tensor, + number_of_samples: int, +) -> torch.Tensor: """Sample the exact harmonic energy.""" device = equilibrium_relative_coordinates.device natom, spatial_dimension, _, _ = inverse_covariance.shape flat_dim = natom * spatial_dimension - flat_equilibrium_relative_coordinates = einops.rearrange(equilibrium_relative_coordinates, "n d -> (n d)") + flat_equilibrium_relative_coordinates = einops.rearrange( + equilibrium_relative_coordinates, "n d -> (n d)" + ) matrix = einops.rearrange(inverse_covariance, "n1 d1 n2 d2 -> (n1 d1) (n2 d2)") @@ -73,7 +99,7 @@ def get_exact_samples(equilibrium_relative_coordinates: torch.Tensor, inverse_co eigenvalues = eigen.eigenvalues eigenvectors_as_columns = eigen.eigenvectors - sigmas = 1. / torch.sqrt(eigenvalues) + sigmas = 1.0 / torch.sqrt(eigenvalues) z_scores = torch.randn(number_of_samples, flat_dim).to(device) @@ -81,16 +107,25 @@ def get_exact_samples(equilibrium_relative_coordinates: torch.Tensor, inverse_co flat_displacements = sigma_z_scores @ eigenvectors_as_columns.T - flat_relative_coordinates = flat_equilibrium_relative_coordinates.unsqueeze(0) + flat_displacements + flat_relative_coordinates = ( + flat_equilibrium_relative_coordinates.unsqueeze(0) + flat_displacements + ) - relative_coordinates_sample = einops.rearrange(flat_relative_coordinates, - "batch (n d) -> batch n d", n=natom, d=spatial_dimension) + relative_coordinates_sample = einops.rearrange( + flat_relative_coordinates, + "batch (n d) -> batch n d", + n=natom, + d=spatial_dimension, + ) return relative_coordinates_sample -def get_samples_harmonic_energy(equilibrium_relative_coordinates: torch.Tensor, inverse_covariance: torch.Tensor, - samples: torch.Tensor) -> torch.Tensor: +def get_samples_harmonic_energy( + equilibrium_relative_coordinates: torch.Tensor, + inverse_covariance: torch.Tensor, + samples: torch.Tensor, +) -> torch.Tensor: """Get samples harmonic energy.""" flat_x_eq = einops.rearrange(equilibrium_relative_coordinates, "n d -> (n d)") flat_x = einops.rearrange(samples, "batch n d -> batch (n d)") @@ -100,27 +135,41 @@ def get_samples_harmonic_energy(equilibrium_relative_coordinates: torch.Tensor, # Account for periodicity: the closest equilibrium position might be a lattice vector away from the origin. flat_displacements = flat_displacements - flat_displacements.round() - flat_inverse_covariance = einops.rearrange(inverse_covariance, "n1 d1 n2 d2 -> (n1 d1) (n2 d2)") + flat_inverse_covariance = einops.rearrange( + inverse_covariance, "n1 d1 n2 d2 -> (n1 d1) (n2 d2)" + ) - m_u = einops.einsum(flat_inverse_covariance, flat_displacements, "flat1 flat2, batch flat2 -> batch flat1") + m_u = einops.einsum( + flat_inverse_covariance, + flat_displacements, + "flat1 flat2, batch flat2 -> batch flat1", + ) u_m_u = einops.einsum(flat_displacements, m_u, "batch flat, batch flat-> batch") energies = 0.5 * u_m_u return energies -def get_relative_harmonic_energy(batch_relative_coordinates: torch.Tensor, - equilibrium_relative_coordinates: torch.Tensor, - spring_constant: float): +def get_relative_harmonic_energy( + batch_relative_coordinates: torch.Tensor, + equilibrium_relative_coordinates: torch.Tensor, + spring_constant: float, +): """Get relative harmonic energy. This is the harmonic energy without the center of mass term when there are only two atoms and an isotropic dynamical matrix described by a spring constant. """ - assert batch_relative_coordinates.shape[1] == 2, "This method is specialized to 2 atoms only." - assert equilibrium_relative_coordinates.shape[0] == 2, "This method is specialized to 2 atoms only." - - batch_displacements = batch_relative_coordinates[:, 1, :] - batch_relative_coordinates[:, 0, :] - energies = spring_constant * (batch_displacements ** 2).sum(dim=1) + assert ( + batch_relative_coordinates.shape[1] == 2 + ), "This method is specialized to 2 atoms only." + assert ( + equilibrium_relative_coordinates.shape[0] == 2 + ), "This method is specialized to 2 atoms only." + + batch_displacements = ( + batch_relative_coordinates[:, 1, :] - batch_relative_coordinates[:, 0, :] + ) + energies = spring_constant * (batch_displacements**2).sum(dim=1) return energies diff --git a/experiments/analysis/diffusion_sample_position_analysis.py b/experiments/analysis/diffusion_sample_position_analysis.py index 1986ed48..58c3302e 100644 --- a/experiments/analysis/diffusion_sample_position_analysis.py +++ b/experiments/analysis/diffusion_sample_position_analysis.py @@ -3,13 +3,16 @@ A simple script to extract the diffusion positions from a pickle on disk and generate some distributions. """ + import matplotlib.pyplot as plt import numpy as np import torch -from crystal_diffusion import TOP_DIR -from crystal_diffusion.utils.sample_trajectory import \ + +from diffusion_for_multi_scale_molecular_dynamics import TOP_DIR +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) +from diffusion_for_multi_scale_molecular_dynamics.utils.sample_trajectory import \ PredictorCorrectorSampleTrajectory -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH plt.style.use(PLOT_STYLE_PATH) @@ -21,25 +24,35 @@ # experiment_name = "mace/run1/" -trajectory_top_output_directory = TOP_DIR / "experiments/diffusion_mace_harmonic_data/output/" +trajectory_top_output_directory = ( + TOP_DIR / "experiments/diffusion_mace_harmonic_data/output/" +) -trajectory_data_directory = trajectory_top_output_directory / experiment_name / "diffusion_position_samples" -energy_sample_directory = trajectory_top_output_directory / experiment_name / "energy_samples" +trajectory_data_directory = ( + trajectory_top_output_directory / experiment_name / "diffusion_position_samples" +) +energy_sample_directory = ( + trajectory_top_output_directory / experiment_name / "energy_samples" +) output_top_dir = trajectory_data_directory.parent / "visualization" -if __name__ == '__main__': +if __name__ == "__main__": energies = torch.load(energy_sample_directory / f"energies_sample_epoch={epoch}.pt") - pickle_path = trajectory_data_directory / f"diffusion_position_sample_epoch={epoch}.pt" + pickle_path = ( + trajectory_data_directory / f"diffusion_position_sample_epoch={epoch}.pt" + ) sample_trajectory = PredictorCorrectorSampleTrajectory.read_from_pickle(pickle_path) - pickle_path = trajectory_data_directory / f"diffusion_position_sample_epoch={epoch}.pt" + pickle_path = ( + trajectory_data_directory / f"diffusion_position_sample_epoch={epoch}.pt" + ) sample_trajectory = PredictorCorrectorSampleTrajectory.read_from_pickle(pickle_path) - list_predictor_coordinates = sample_trajectory.data['predictor_x_i'] + list_predictor_coordinates = sample_trajectory.data["predictor_x_i"] float_datatype = list_predictor_coordinates[0].dtype fig = plt.figure(figsize=PLEASANT_FIG_SIZE) @@ -57,37 +70,85 @@ list_indices = [0, 40, 80, 99] equilibrium_delta_squared = 0.75 - default_orientation = torch.tensor([1., 1., 1.], dtype=float_datatype) / np.sqrt(3.) + default_orientation = torch.tensor([1.0, 1.0, 1.0], dtype=float_datatype) / np.sqrt( + 3.0 + ) for idx in list_indices: time_idx = list_t[idx] relative_coordinates = list_predictor_coordinates[idx] displacements = relative_coordinates[:, 1, :] - relative_coordinates[:, 0, :] - center_of_mass = 0.5 * (relative_coordinates[:, 1, :] + relative_coordinates[:, 0, :]) - delta_squared = (displacements ** 2).sum(dim=1) - - normalized_angle = torch.arccos(displacements @ default_orientation / torch.sqrt(delta_squared)) / (np.pi) - - ax1.hist(delta_squared, bins=nbins, alpha=0.5, histtype="stepfilled", label=f't = {time_idx}') - ax2.hist(normalized_angle, bins=nbins, alpha=0.5, histtype="stepfilled", label=f't = {time_idx}') - - ax3.hist(center_of_mass[:, 0], bins=nbins, alpha=0.5, histtype="stepfilled", label=f't = {time_idx}') - ax4.hist(center_of_mass[:, 1], bins=nbins, alpha=0.5, histtype="stepfilled", label=f't = {time_idx}') - ax5.hist(center_of_mass[:, 2], bins=nbins, alpha=0.5, histtype="stepfilled", label=f't = {time_idx}') - - ax1.vlines(x=equilibrium_delta_squared, ymin=0, ymax=75, - linestyle='--', color='black', label='Equilibrium Distance') - ax2.vlines(x=torch.arccos(torch.tensor([-1, -1 / 3, 1 / 3, 1])) / np.pi, ymin=0, ymax=180, - linestyle='--', color='black', label='Equilibrium Angles') + center_of_mass = 0.5 * ( + relative_coordinates[:, 1, :] + relative_coordinates[:, 0, :] + ) + delta_squared = (displacements**2).sum(dim=1) + + normalized_angle = torch.arccos( + displacements @ default_orientation / torch.sqrt(delta_squared) + ) / (np.pi) + + ax1.hist( + delta_squared, + bins=nbins, + alpha=0.5, + histtype="stepfilled", + label=f"t = {time_idx}", + ) + ax2.hist( + normalized_angle, + bins=nbins, + alpha=0.5, + histtype="stepfilled", + label=f"t = {time_idx}", + ) + + ax3.hist( + center_of_mass[:, 0], + bins=nbins, + alpha=0.5, + histtype="stepfilled", + label=f"t = {time_idx}", + ) + ax4.hist( + center_of_mass[:, 1], + bins=nbins, + alpha=0.5, + histtype="stepfilled", + label=f"t = {time_idx}", + ) + ax5.hist( + center_of_mass[:, 2], + bins=nbins, + alpha=0.5, + histtype="stepfilled", + label=f"t = {time_idx}", + ) + + ax1.vlines( + x=equilibrium_delta_squared, + ymin=0, + ymax=75, + linestyle="--", + color="black", + label="Equilibrium Distance", + ) + ax2.vlines( + x=torch.arccos(torch.tensor([-1, -1 / 3, 1 / 3, 1])) / np.pi, + ymin=0, + ymax=180, + linestyle="--", + color="black", + label="Equilibrium Angles", + ) for ax in [ax1, ax2, ax3, ax4, ax5]: - ax.set_ylabel('Counts') + ax.set_ylabel("Counts") ax1.legend(loc=0, fontsize=8) - ax1.set_xlabel('$\\delta^2$') - ax2.set_xlabel('Angle ($\\pi$)') - ax3.set_xlabel('CM x direction') - ax4.set_xlabel('CM y direction') - ax5.set_xlabel('CM z direction') + ax1.set_xlabel("$\\delta^2$") + ax2.set_xlabel("Angle ($\\pi$)") + ax3.set_xlabel("CM x direction") + ax4.set_xlabel("CM y direction") + ax5.set_xlabel("CM z direction") ax1.set_xlim(xmin=-0.01) for ax in [ax2, ax3, ax4, ax5]: diff --git a/experiments/analysis/exploding_variance_analysis.py b/experiments/analysis/exploding_variance_analysis.py index 0520683b..197df4d6 100644 --- a/experiments/analysis/exploding_variance_analysis.py +++ b/experiments/analysis/exploding_variance_analysis.py @@ -3,18 +3,21 @@ This script computes and plots the variance schedule used to noise and denoise the relative positions. """ + import matplotlib.pyplot as plt import torch -from crystal_diffusion import ANALYSIS_RESULTS_DIR -from crystal_diffusion.score.wrapped_gaussian_score import \ - get_sigma_normalized_score -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from src.crystal_diffusion.samplers.variance_sampler import ( + +from diffusion_for_multi_scale_molecular_dynamics import ANALYSIS_RESULTS_DIR +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import ( ExplodingVarianceSampler, NoiseParameters) +from diffusion_for_multi_scale_molecular_dynamics.score.wrapped_gaussian_score import \ + get_sigma_normalized_score plt.style.use(PLOT_STYLE_PATH) -if __name__ == '__main__': +if __name__ == "__main__": noise_parameters = NoiseParameters(total_time_steps=1000) variance_sampler = ExplodingVarianceSampler(noise_parameters=noise_parameters) @@ -27,14 +30,21 @@ ax1 = fig1.add_subplot(121) ax2 = fig1.add_subplot(122) - ax1.plot(noise.time, noise.sigma, '-', c='b', lw=2, label='$\\sigma(t)$') - ax1.plot(noise.time, noise.g, '-', c='g', lw=2, label="$g(t)$") + ax1.plot(noise.time, noise.sigma, "-", c="b", lw=2, label="$\\sigma(t)$") + ax1.plot(noise.time, noise.g, "-", c="g", lw=2, label="$g(t)$") shifted_time = torch.cat([torch.tensor([0]), noise.time[:-1]]) - ax1.plot(shifted_time, langevin_dynamics.epsilon, '-', c='r', lw=2, label="$\\epsilon(t)$") + ax1.plot( + shifted_time, + langevin_dynamics.epsilon, + "-", + c="r", + lw=2, + label="$\\epsilon(t)$", + ) ax1.legend(loc=0) - ax1.set_xlabel('time') + ax1.set_xlabel("time") ax1.set_xlim([-0.01, 1.01]) ax1.set_title("$\\sigma, g, \\epsilon$ schedules") @@ -49,10 +59,12 @@ gs_squared = noise.g_squared.take(indices) for t, sigma in zip(times, sigmas): - target_sigma_normalized_scores = get_sigma_normalized_score(relative_positions, - torch.ones_like(relative_positions) * sigma, - kmax=kmax) - ax2.plot(relative_positions, target_sigma_normalized_scores, label=f"t = {t:3.2f}") + target_sigma_normalized_scores = get_sigma_normalized_score( + relative_positions, torch.ones_like(relative_positions) * sigma, kmax=kmax + ) + ax2.plot( + relative_positions, target_sigma_normalized_scores, label=f"t = {t:3.2f}" + ) ax2.set_title("Target Normalized Score") ax2.set_xlabel("relative position, u") diff --git a/experiments/analysis/perturbation_kernel_analysis.py b/experiments/analysis/perturbation_kernel_analysis.py index 25d2de3a..d4cff243 100644 --- a/experiments/analysis/perturbation_kernel_analysis.py +++ b/experiments/analysis/perturbation_kernel_analysis.py @@ -3,23 +3,26 @@ This script computes and plots the conditional target score based on the perturbation kernel K for various values of sigma, showing the behavior for different normalizations. """ + import matplotlib.pyplot as plt import torch -from crystal_diffusion import ANALYSIS_RESULTS_DIR -from crystal_diffusion.score.wrapped_gaussian_score import \ + +from diffusion_for_multi_scale_molecular_dynamics import ANALYSIS_RESULTS_DIR +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) +from diffusion_for_multi_scale_molecular_dynamics.score.wrapped_gaussian_score import \ get_sigma_normalized_score -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH plt.style.use(PLOT_STYLE_PATH) sigma_max = 0.5 -if __name__ == '__main__': +if __name__ == "__main__": coordinates = torch.linspace(start=0, end=1, steps=1001)[:-1] sigmas = torch.linspace(start=0, end=sigma_max, steps=101)[1:] - X, SIG = torch.meshgrid(coordinates, sigmas, indexing='xy') + X, SIG = torch.meshgrid(coordinates, sigmas, indexing="xy") # Avoid non-contiguous bjorks X = X.clone() @@ -33,7 +36,7 @@ fig = plt.figure(figsize=fig_size) fig.suptitle(r"$\alpha$ Component of Conditional Score") - left, right, bottom, top = 0, 1., 0., sigma_max + left, right, bottom, top = 0, 1.0, 0.0, sigma_max extent = [left, right, bottom, top] ax1 = fig.add_subplot(221) @@ -49,18 +52,30 @@ xlabel = r"$x^\alpha - x_0^\alpha$" - for label, scores, ax in zip([label1, label2], [sigma_normalized_scores, sigma2_normalized_scores], [ax1, ax3]): + for label, scores, ax in zip( + [label1, label2], + [sigma_normalized_scores, sigma2_normalized_scores], + [ax1, ax3], + ): ax.set_title(label) - image = ax.imshow(scores, origin="lower", cmap='jet', extent=extent) + image = ax.imshow(scores, origin="lower", cmap="jet", extent=extent) ax.set_xlabel(xlabel) ax.set_ylabel(r"$\sigma$") _ = fig.colorbar(image, shrink=0.75, ax=ax) for index in [19, 39, 99]: sigma = sigmas[index] - for ax, scores in zip([ax2, ax4], [sigma_normalized_scores, sigma2_normalized_scores]): - ax.plot(coordinates, scores[index], ls='-', lw=2, label=f'$\\sigma = {sigma:4.3f}$') + for ax, scores in zip( + [ax2, ax4], [sigma_normalized_scores, sigma2_normalized_scores] + ): + ax.plot( + coordinates, + scores[index], + ls="-", + lw=2, + label=f"$\\sigma = {sigma:4.3f}$", + ) for ax in [ax2, ax4]: ax.legend(loc=0) @@ -70,4 +85,8 @@ ax4.set_ylabel(label2) fig.tight_layout() - fig.savefig(ANALYSIS_RESULTS_DIR.joinpath("perturbation_kernel_with_different_normalizations.png")) + fig.savefig( + ANALYSIS_RESULTS_DIR.joinpath( + "perturbation_kernel_with_different_normalizations.png" + ) + ) diff --git a/experiments/analysis/plot_repulsive_force.py b/experiments/analysis/plot_repulsive_force.py index d5e50b97..1d16572a 100644 --- a/experiments/analysis/plot_repulsive_force.py +++ b/experiments/analysis/plot_repulsive_force.py @@ -1,9 +1,12 @@ import torch -from crystal_diffusion.models.score_networks.force_field_augmented_score_network import ( - ForceFieldAugmentedScoreNetwork, ForceFieldParameters) -from crystal_diffusion.namespace import NOISY_RELATIVE_COORDINATES, UNIT_CELL from matplotlib import pyplot as plt -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH + +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.force_field_augmented_score_network import ( + ForceFieldAugmentedScoreNetwork, ForceFieldParameters) +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + NOISY_RELATIVE_COORDINATES, UNIT_CELL) plt.style.use(PLOT_STYLE_PATH) diff --git a/experiments/analysis/plotting_gaussian_vs_wrapped_gaussian.py b/experiments/analysis/plotting_gaussian_vs_wrapped_gaussian.py index 7b13971a..6edba2ec 100644 --- a/experiments/analysis/plotting_gaussian_vs_wrapped_gaussian.py +++ b/experiments/analysis/plotting_gaussian_vs_wrapped_gaussian.py @@ -1,24 +1,26 @@ - """Plotting Gaussians vs. Wrapped Gaussians. This script plots a comparison between a 1D Gaussian centered at 0.5 and the sum of its periodic images. """ + import matplotlib.pyplot as plt import numpy as np -from crystal_diffusion import ANALYSIS_RESULTS_DIR from scipy.stats import norm -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH + +from diffusion_for_multi_scale_molecular_dynamics import ANALYSIS_RESULTS_DIR +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) plt.style.use(PLOT_STYLE_PATH) sigma_max = 0.5 list_sigmas = [0.1, 0.25, 0.5] -list_colors = ['r', 'b', 'g'] +list_colors = ["r", "b", "g"] imin = -1 imax = 2 -if __name__ == '__main__': +if __name__ == "__main__": fig1 = plt.figure(figsize=PLEASANT_FIG_SIZE) fig2 = plt.figure(figsize=PLEASANT_FIG_SIZE) @@ -36,28 +38,36 @@ ax = fig.add_subplot(111) in_cell_gaussian = norm(loc=0.5, scale=sigma).pdf(x_in_cell) ymax1 = np.max(in_cell_gaussian) + 0.1 - ax.plot(x_in_cell, in_cell_gaussian, lw=2, color=color, label='Gaussian') + ax.plot(x_in_cell, in_cell_gaussian, lw=2, color=color, label="Gaussian") wrapped_gaussians = np.zeros(len(x)) - label = 'Periodic Images' + label = "Periodic Images" for loc in list_locs: gaussian = norm(loc=loc, scale=sigma).pdf(x) wrapped_gaussians += gaussian - ax.plot(x, gaussian, color=color, lw=1, ls='-', alpha=0.5, label=label) - label = '__nolabel__' + ax.plot(x, gaussian, color=color, lw=1, ls="-", alpha=0.5, label=label) + label = "__nolabel__" ymax2 = np.max(wrapped_gaussians) + 0.1 ymax = np.max([ymax1, ymax2]) - ax.plot(x, wrapped_gaussians, color=color, lw=2, ls='--', label='Wrapped Gaussian') + ax.plot( + x, wrapped_gaussians, color=color, lw=2, ls="--", label="Wrapped Gaussian" + ) - ax.vlines(np.arange(imin, imax + 1), 0, ymax, color='k') + ax.vlines(np.arange(imin, imax + 1), 0, ymax, color="k") - ax.fill_betweenx([0, ymax], x1=0, x2=1, color='grey', alpha=0.25, label='Unit Cell') + ax.fill_betweenx( + [0, ymax], x1=0, x2=1, color="grey", alpha=0.25, label="Unit Cell" + ) ax.set_ylim(0, ymax) ax.set_xlim(imin - 0.1, imax + 0.1) ax.legend(loc=0) fig.tight_layout() - fig.savefig(ANALYSIS_RESULTS_DIR.joinpath(f"Gaussian_vs_Wrapped_Gaussian_sigma={sigma}.png")) + fig.savefig( + ANALYSIS_RESULTS_DIR.joinpath( + f"Gaussian_vs_Wrapped_Gaussian_sigma={sigma}.png" + ) + ) diff --git a/experiments/analysis/target_score_analysis.py b/experiments/analysis/target_score_analysis.py index 0bca3889..a3d5e736 100644 --- a/experiments/analysis/target_score_analysis.py +++ b/experiments/analysis/target_score_analysis.py @@ -3,18 +3,21 @@ This script computes and plots the target score for various values of sigma, showing that the 'smart' implementation converges quickly and is equal to the expected brute force value. """ + import matplotlib.pyplot as plt import numpy as np import torch -from crystal_diffusion import ANALYSIS_RESULTS_DIR -from crystal_diffusion.score.wrapped_gaussian_score import ( + +from diffusion_for_multi_scale_molecular_dynamics import ANALYSIS_RESULTS_DIR +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) +from diffusion_for_multi_scale_molecular_dynamics.score.wrapped_gaussian_score import ( SIGMA_THRESHOLD, get_sigma_normalized_score, get_sigma_normalized_score_brute_force) -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH plt.style.use(PLOT_STYLE_PATH) -if __name__ == '__main__': +if __name__ == "__main__": list_u = np.linspace(0, 1, 101)[:-1] relative_positions = torch.from_numpy(list_u) @@ -27,25 +30,35 @@ ax1 = fig1.add_subplot(121) ax2 = fig1.add_subplot(122) - for sigma_factor, color in zip([0.1, 1., 2.], ['r', 'g', 'b']): + for sigma_factor, color in zip([0.1, 1.0, 2.0], ["r", "g", "b"]): sigma = sigma_factor * SIGMA_THRESHOLD sigmas = torch.ones_like(relative_positions) * sigma - list_scores_brute = np.array([get_sigma_normalized_score_brute_force(u, sigma) for u in list_u]) - list_scores = get_sigma_normalized_score(relative_positions, sigmas, kmax=kmax).numpy() + list_scores_brute = np.array( + [get_sigma_normalized_score_brute_force(u, sigma) for u in list_u] + ) + list_scores = get_sigma_normalized_score( + relative_positions, sigmas, kmax=kmax + ).numpy() error = list_scores - list_scores_brute - ax1.plot(list_u, list_scores_brute, '--', c=color, lw=4, label='brute force') - ax1.plot(list_u, list_scores, '-', c=color, lw=2, label='smart') + ax1.plot(list_u, list_scores_brute, "--", c=color, lw=4, label="brute force") + ax1.plot(list_u, list_scores, "-", c=color, lw=2, label="smart") - ax2.plot(list_u, error, '-', c=color, label=f'$\\sigma$ = {sigma_factor} $\\sigma_{{th}}$') + ax2.plot( + list_u, + error, + "-", + c=color, + label=f"$\\sigma$ = {sigma_factor} $\\sigma_{{th}}$", + ) - ax1.set_ylabel('$\\sigma^2\\times S$') - ax2.set_xlabel('u') - ax2.set_ylabel('Error') + ax1.set_ylabel("$\\sigma^2\\times S$") + ax2.set_xlabel("u") + ax2.set_ylabel("Error") for ax in [ax1, ax2]: - ax.set_xlabel('u') + ax.set_xlabel("u") ax.set_xlim([0, 1]) ax.legend(loc=0) @@ -59,29 +72,51 @@ ax3 = fig2.add_subplot(121) ax4 = fig2.add_subplot(122) - sigma_factors = torch.linspace(0.001, 4., 40).to(torch.double) + sigma_factors = torch.linspace(0.001, 4.0, 40).to(torch.double) sigmas = sigma_factors * SIGMA_THRESHOLD u = 0.6 relative_positions = torch.ones_like(sigmas).to(torch.double) * u ms = 8 - for kmax, color in zip([1, 2, 3, 4, 5], ['y', 'r', 'g', 'b', 'k']): - list_scores = get_sigma_normalized_score(relative_positions, sigmas, kmax=kmax).numpy() - ax3.semilogy(sigma_factors, list_scores, 'o-', - ms=ms, c=color, lw=2, alpha=0.25, label=f'kmax = {kmax}') - - list_scores_brute = np.array([ - get_sigma_normalized_score_brute_force(u, sigma, kmax=4 * kmax) for sigma in sigmas]) - ax4.semilogy(sigma_factors, list_scores_brute, 'o-', - ms=ms, c=color, lw=2, alpha=0.25, label=f'kmax = {4 * kmax}') + for kmax, color in zip([1, 2, 3, 4, 5], ["y", "r", "g", "b", "k"]): + list_scores = get_sigma_normalized_score( + relative_positions, sigmas, kmax=kmax + ).numpy() + ax3.semilogy( + sigma_factors, + list_scores, + "o-", + ms=ms, + c=color, + lw=2, + alpha=0.25, + label=f"kmax = {kmax}", + ) + + list_scores_brute = np.array( + [ + get_sigma_normalized_score_brute_force(u, sigma, kmax=4 * kmax) + for sigma in sigmas + ] + ) + ax4.semilogy( + sigma_factors, + list_scores_brute, + "o-", + ms=ms, + c=color, + lw=2, + alpha=0.25, + label=f"kmax = {4 * kmax}", + ) ms = 0.75 * ms for ax in [ax3, ax4]: - ax.set_xlabel('$\\sigma$ ($\\sigma_{th}$)') - ax.set_ylabel(f'$\\sigma^2 \\times S(u={u})$') - ax.set_xlim([0., 1.1 * sigma_factors.max()]) + ax.set_xlabel("$\\sigma$ ($\\sigma_{th}$)") + ax.set_ylabel(f"$\\sigma^2 \\times S(u={u})$") + ax.set_xlim([0.0, 1.1 * sigma_factors.max()]) ax.legend(loc=0) ax3.set_title("Smart implementation") diff --git a/experiments/analysis_utils.py b/experiments/analysis_utils.py index 3f92569f..3a5c38d8 100644 --- a/experiments/analysis_utils.py +++ b/experiments/analysis_utils.py @@ -31,8 +31,9 @@ def get_thermo_dataset(dataset_name: str) -> Tuple[pd.DataFrame, pd.DataFrame]: """ lammps_dataset_dir = DATA_DIR.joinpath(dataset_name) - assert lammps_dataset_dir.is_dir(), \ - f"The folder {lammps_dataset_dir} does not exist! Data must be present to execute this function." + assert ( + lammps_dataset_dir.is_dir() + ), f"The folder {lammps_dataset_dir} does not exist! Data must be present to execute this function." cache_dir = EXPERIMENT_ANALYSIS_DIR.joinpath(f"cache/{dataset_name}") cache_dir.mkdir(parents=True, exist_ok=True) @@ -41,7 +42,7 @@ def get_thermo_dataset(dataset_name: str) -> Tuple[pd.DataFrame, pd.DataFrame]: list_valid_df = [] logging.info("Parsing the thermo logs") - run_directories = glob.glob(str(lammps_dataset_dir.joinpath('*_run_*'))) + run_directories = glob.glob(str(lammps_dataset_dir.joinpath("*_run_*"))) for run_directory in run_directories: basename = os.path.basename(run_directory) @@ -50,13 +51,15 @@ def get_thermo_dataset(dataset_name: str) -> Tuple[pd.DataFrame, pd.DataFrame]: logging.info(f"Pickle file {pickle_path} exists. Reading in...") else: logging.info(f"Pickle file {pickle_path} does not exist: creating...") - lammps_thermo_log = lammps_dataset_dir.joinpath(f"{basename}/lammps_thermo.yaml") + lammps_thermo_log = lammps_dataset_dir.joinpath( + f"{basename}/lammps_thermo.yaml" + ) df = pd.DataFrame(parse_lammps_thermo_log(lammps_thermo_log)) df.to_pickle(pickle_path) logging.info("Done creating pickle file") df = pd.read_pickle(pickle_path) - if 'train' in basename: + if "train" in basename: list_train_df.append(df) else: list_valid_df.append(df) diff --git a/experiments/dataset_analysis/dataset_covariance.py b/experiments/dataset_analysis/dataset_covariance.py index 83b95dc6..70994a7e 100644 --- a/experiments/dataset_analysis/dataset_covariance.py +++ b/experiments/dataset_analysis/dataset_covariance.py @@ -4,30 +4,34 @@ actual datasets, that is, the standard deviation of the displacement from equilibrium, in fractional coordinates. """ + import logging import einops import torch -from crystal_diffusion import ANALYSIS_RESULTS_DIR, DATA_DIR -from crystal_diffusion.data.diffusion.data_loader import ( +from tqdm import tqdm + +from diffusion_for_multi_scale_molecular_dynamics import (ANALYSIS_RESULTS_DIR, + DATA_DIR) +from diffusion_for_multi_scale_molecular_dynamics.data.diffusion.data_loader import ( LammpsForDiffusionDataModule, LammpsLoaderParameters) -from crystal_diffusion.utils.basis_transformations import \ +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell -from crystal_diffusion.utils.logging_utils import setup_analysis_logger -from tqdm import tqdm +from diffusion_for_multi_scale_molecular_dynamics.utils.logging_utils import \ + setup_analysis_logger logger = logging.getLogger(__name__) -dataset_name = 'si_diffusion_2x2x2' +dataset_name = "si_diffusion_2x2x2" # dataset_name = 'si_diffusion_1x1x1' output_dir = ANALYSIS_RESULTS_DIR / "covariances" output_dir.mkdir(exist_ok=True) -if dataset_name == 'si_diffusion_1x1x1': +if dataset_name == "si_diffusion_1x1x1": max_atom = 8 translation = torch.tensor([0.125, 0.125, 0.125]) -elif dataset_name == 'si_diffusion_2x2x2': +elif dataset_name == "si_diffusion_2x2x2": max_atom = 64 translation = torch.tensor([0.0625, 0.0625, 0.0625]) @@ -38,7 +42,7 @@ data_params = LammpsLoaderParameters(batch_size=2048, max_atom=max_atom) -if __name__ == '__main__': +if __name__ == "__main__": setup_analysis_logger() logger.info(f"Computing the covariance matrix for {dataset_name}") @@ -54,7 +58,9 @@ list_means = [] for batch in tqdm(datamodule.train_dataloader(), "Mean"): - x = map_relative_coordinates_to_unit_cell(batch['relative_coordinates'] + translation) + x = map_relative_coordinates_to_unit_cell( + batch["relative_coordinates"] + translation + ) list_means.append(x.mean(dim=0)) # Drop the last batch, which might not have dimension batch_size @@ -63,9 +69,13 @@ list_covariances = [] list_sizes = [] for batch in tqdm(datamodule.train_dataloader(), "displacements"): - x = map_relative_coordinates_to_unit_cell(batch['relative_coordinates'] + translation) + x = map_relative_coordinates_to_unit_cell( + batch["relative_coordinates"] + translation + ) list_sizes.append(x.shape[0]) - displacements = einops.rearrange(x - x0, "batch natoms space -> batch (natoms space)") + displacements = einops.rearrange( + x - x0, "batch natoms space -> batch (natoms space)" + ) covariance = (displacements[:, None, :] * displacements[:, :, None]).sum(dim=0) list_covariances.append(covariance) @@ -73,5 +83,5 @@ output_file = output_dir / f"covariance_{dataset_name}.pkl" logger.info(f"Writing to file {output_file}...") - with open(output_file, 'wb') as fd: + with open(output_file, "wb") as fd: torch.save(covariance, fd) diff --git a/experiments/dataset_analysis/energy_consistency_analysis.py b/experiments/dataset_analysis/energy_consistency_analysis.py index 4c32c45d..35762c04 100644 --- a/experiments/dataset_analysis/energy_consistency_analysis.py +++ b/experiments/dataset_analysis/energy_consistency_analysis.py @@ -6,31 +6,35 @@ This is to ensure that the configurations are exactly equivalent between these two distinct ways of calling LAMMPS. """ + import logging import tempfile import matplotlib.pyplot as plt import numpy as np -from crystal_diffusion import DATA_DIR -from crystal_diffusion.callbacks.sampling_visualization_callback import ( - LOGGER_FIGSIZE, SamplingVisualizationCallback) -from crystal_diffusion.data.diffusion.data_loader import ( - LammpsForDiffusionDataModule, LammpsLoaderParameters) -from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps -from crystal_diffusion.utils.logging_utils import setup_analysis_logger -from src.crystal_diffusion.analysis import PLOT_STYLE_PATH from tqdm import tqdm +from diffusion_for_multi_scale_molecular_dynamics import DATA_DIR +from diffusion_for_multi_scale_molecular_dynamics.analysis import \ + PLOT_STYLE_PATH +from diffusion_for_multi_scale_molecular_dynamics.callbacks.sampling_visualization_callback import ( + LOGGER_FIGSIZE, SamplingVisualizationCallback) +from diffusion_for_multi_scale_molecular_dynamics.data.diffusion.data_loader import ( + LammpsForDiffusionDataModule, LammpsLoaderParameters) +from diffusion_for_multi_scale_molecular_dynamics.oracle.lammps import \ + get_energy_and_forces_from_lammps +from diffusion_for_multi_scale_molecular_dynamics.utils.logging_utils import \ + setup_analysis_logger from experiments import EXPERIMENT_ANALYSIS_DIR from experiments.analysis_utils import get_thermo_dataset plt.style.use(PLOT_STYLE_PATH) logger = logging.getLogger(__name__) -dataset_name = 'si_diffusion_1x1x1' +dataset_name = "si_diffusion_1x1x1" lammps_run_dir = str(DATA_DIR / dataset_name) -processed_dataset_dir = str(DATA_DIR / dataset_name / 'processed') +processed_dataset_dir = str(DATA_DIR / dataset_name / "processed") cache_dir = str(EXPERIMENT_ANALYSIS_DIR / "cache" / dataset_name) @@ -38,7 +42,7 @@ sample_size = 1000 -if __name__ == '__main__': +if __name__ == "__main__": setup_analysis_logger() logging.info(f"Starting {dataset_name} analysis") @@ -57,32 +61,35 @@ train_dataset = datamodule.train_dataset data = train_dataset[0] - box = np.diag(data['box'].cpu().numpy()) - atom_types = data['type'].cpu().numpy() + box = np.diag(data["box"].cpu().numpy()) + atom_types = data["type"].cpu().numpy() - list_relative_positions = train_dataset[:sample_size]['relative_positions'].cpu().numpy() - list_positions = train_dataset[:sample_size]['position'].cpu().numpy() + list_relative_positions = ( + train_dataset[:sample_size]["relative_positions"].cpu().numpy() + ) + list_positions = train_dataset[:sample_size]["position"].cpu().numpy() list_positions_translated_back_in_box = list_positions % box[0, 0] list_expected_positions = np.dot(list_relative_positions, box) - list_dataset_potential_energies = train_df['potential_energy'][:sample_size].values + list_dataset_potential_energies = train_df["potential_energy"][:sample_size].values logger.info("Compute energy from Oracle") list_oracle_energies = [] with tempfile.TemporaryDirectory() as tmp_work_dir: for positions in tqdm(list_positions_translated_back_in_box, "LAMMPS energies"): - energy, forces = get_energy_and_forces_from_lammps(positions, - box, - atom_types, - tmp_work_dir=tmp_work_dir) + energy, forces = get_energy_and_forces_from_lammps( + positions, box, atom_types, tmp_work_dir=tmp_work_dir + ) list_oracle_energies.append(energy) list_oracle_energies = np.array(list_oracle_energies) - fig = SamplingVisualizationCallback._plot_energy_histogram(list_oracle_energies, list_dataset_potential_energies) + fig = SamplingVisualizationCallback._plot_energy_histogram( + list_oracle_energies, list_dataset_potential_energies + ) plt.show() fig2 = plt.figure(figsize=LOGGER_FIGSIZE) @@ -90,9 +97,11 @@ errors = list_oracle_energies - list_dataset_potential_energies - fig2.suptitle('Error Distribution between Dataset and Oracle') - ax2.hist(errors, density=True, bins=20, histtype="stepfilled", alpha=0.25, color='red') - ax2.set_xlabel('Energy (eV)') - ax2.set_ylabel('Density') + fig2.suptitle("Error Distribution between Dataset and Oracle") + ax2.hist( + errors, density=True, bins=20, histtype="stepfilled", alpha=0.25, color="red" + ) + ax2.set_xlabel("Energy (eV)") + ax2.set_ylabel("Density") fig2.tight_layout() plt.show() diff --git a/experiments/dataset_analysis/plot_si_phonon_DOS.py b/experiments/dataset_analysis/plot_si_phonon_DOS.py index efda2862..14fc4e86 100644 --- a/experiments/dataset_analysis/plot_si_phonon_DOS.py +++ b/experiments/dataset_analysis/plot_si_phonon_DOS.py @@ -4,10 +4,13 @@ Here we extract the corresponding phonon density of state, based on this covariance, to see if the energy scales match up. """ + import matplotlib.pyplot as plt import torch -from crystal_diffusion import ANALYSIS_RESULTS_DIR -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH + +from diffusion_for_multi_scale_molecular_dynamics import ANALYSIS_RESULTS_DIR +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) plt.style.use(PLOT_STYLE_PATH) diff --git a/experiments/dataset_analysis/si_diffusion_analysis.py b/experiments/dataset_analysis/si_diffusion_analysis.py index 3bb92a53..1deb3916 100644 --- a/experiments/dataset_analysis/si_diffusion_analysis.py +++ b/experiments/dataset_analysis/si_diffusion_analysis.py @@ -9,16 +9,20 @@ It is still early days in the project, so the starting point format for these kinds of analyses is still in flux. """ + import logging import matplotlib.pyplot as plt import numpy as np import pandas as pd import scipy -from crystal_diffusion import ANALYSIS_RESULTS_DIR, DATA_DIR -from crystal_diffusion.utils.logging_utils import setup_analysis_logger -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH +from diffusion_for_multi_scale_molecular_dynamics import (ANALYSIS_RESULTS_DIR, + DATA_DIR) +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) +from diffusion_for_multi_scale_molecular_dynamics.utils.logging_utils import \ + setup_analysis_logger from experiments.analysis_utils import get_thermo_dataset plt.style.use(PLOT_STYLE_PATH) @@ -28,25 +32,25 @@ # Taken from this table: http://wild.life.nctu.edu.tw/class/common/energy-unit-conv-table.html kelvin_in_ev = 0.0000861705 -dataset_name = 'si_diffusion_1x1x1' +dataset_name = "si_diffusion_1x1x1" -if dataset_name == 'si_diffusion_1x1x1': +if dataset_name == "si_diffusion_1x1x1": number_of_atoms = 8 -elif dataset_name == 'si_diffusion_2x2x2': +elif dataset_name == "si_diffusion_2x2x2": number_of_atoms = 64 -elif dataset_name == 'si_diffusion_3x3x3': +elif dataset_name == "si_diffusion_3x3x3": number_of_atoms = 216 lammps_dataset_dir = DATA_DIR.joinpath(dataset_name) -if __name__ == '__main__': +if __name__ == "__main__": setup_analysis_logger() logging.info(f"Starting {dataset_name} analysis") train_df, valid_df = get_thermo_dataset(dataset_name) - temperatures = train_df['temperature'].values # Kelvin + temperatures = train_df["temperature"].values # Kelvin mu_temp = np.mean(temperatures) kB_T = mu_temp * kelvin_in_ev sigma_temp = np.std(temperatures) @@ -54,123 +58,157 @@ list_t = np.linspace(100, 500, 1001) list_pt = scipy.stats.norm(loc=mu_temp, scale=sigma_temp).pdf(list_t) - beta = 1. / kB_T + beta = 1.0 / kB_T - relative_energies = train_df['energy'].values + relative_energies = train_df["energy"].values mu_e = np.mean(relative_energies) - e0 = mu_e - 3. * number_of_atoms * kB_T + e0 = mu_e - 3.0 * number_of_atoms * kB_T - expected_sigma_e = np.sqrt((3. * number_of_atoms) / beta**2) # in eV + expected_sigma_e = np.sqrt((3.0 * number_of_atoms) / beta**2) # in eV computed_sigma_e = np.std(relative_energies) std_relative_error = (expected_sigma_e - computed_sigma_e) / expected_sigma_e - list_de = np.linspace(mu_e - 5 * computed_sigma_e, mu_e + 5 * computed_sigma_e, 1001) + list_de = np.linspace( + mu_e - 5 * computed_sigma_e, mu_e + 5 * computed_sigma_e, 1001 + ) list_pe = scipy.stats.norm(loc=mu_e, scale=computed_sigma_e).pdf(list_de) fig1 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig1.suptitle(f'Distributions for dataset {dataset_name}') + fig1.suptitle(f"Distributions for dataset {dataset_name}") ax1 = fig1.add_subplot(211) ax2 = fig1.add_subplot(212) common_params = dict(density=True, bins=50, histtype="stepfilled", alpha=0.25) - ax1.set_title('Energy distribution') - ax1.hist(train_df['energy'], **common_params, label='Train', color='green') - ax1.hist(valid_df['energy'], **common_params, label='Valid', color='yellow') - ax1.vlines(e0, ymin=0, ymax=1, color='k', linestyle='--', label=f'$E_0$ = {e0:5.3f} eV') - ax1.vlines(e0 + 3 * number_of_atoms * kB_T, ymin=0, ymax=1, color='r', - linestyle='--', label=f'$E_0 + 3 N k_B T$ = {np.mean(relative_energies):5.3f} eV') - - label = f'Normal Fit: $\\sigma_{{emp}}$ = {computed_sigma_e:5.3f} eV, $\\sigma_{{th}}$ = {expected_sigma_e:5.3f} eV' - ax1.plot(list_de, list_pe, '-', color='blue', label=label) - - ax1.set_xlabel('Energy (eV)') - ax1.set_ylabel('Density') + ax1.set_title("Energy distribution") + ax1.hist(train_df["energy"], **common_params, label="Train", color="green") + ax1.hist(valid_df["energy"], **common_params, label="Valid", color="yellow") + ax1.vlines( + e0, ymin=0, ymax=1, color="k", linestyle="--", label=f"$E_0$ = {e0:5.3f} eV" + ) + ax1.vlines( + e0 + 3 * number_of_atoms * kB_T, + ymin=0, + ymax=1, + color="r", + linestyle="--", + label=f"$E_0 + 3 N k_B T$ = {np.mean(relative_energies):5.3f} eV", + ) + + label = f"Normal Fit: $\\sigma_{{emp}}$ = {computed_sigma_e:5.3f} eV, $\\sigma_{{th}}$ = {expected_sigma_e:5.3f} eV" + ax1.plot(list_de, list_pe, "-", color="blue", label=label) + + ax1.set_xlabel("Energy (eV)") + ax1.set_ylabel("Density") ax1.legend(loc=0) - ax2.set_title('Temperature Distribution') - ax2.hist(train_df['temperature'], **common_params, label='Train', color='green') - ax2.hist(valid_df['temperature'], **common_params, label='Valid', color='yellow') - label = f'Normal Fit: $\\sigma_T$ = {sigma_temp:5.1f} K, $k_B \\sigma_T$ = {kelvin_in_ev * sigma_temp:5.4f} eV' - ax2.plot(list_t, list_pt, '-', color='blue', label=label) - ax2.set_xlabel('Temperature (K)') - ax2.set_ylabel('Density') + ax2.set_title("Temperature Distribution") + ax2.hist(train_df["temperature"], **common_params, label="Train", color="green") + ax2.hist(valid_df["temperature"], **common_params, label="Valid", color="yellow") + label = f"Normal Fit: $\\sigma_T$ = {sigma_temp:5.1f} K, $k_B \\sigma_T$ = {kelvin_in_ev * sigma_temp:5.4f} eV" + ax2.plot(list_t, list_pt, "-", color="blue", label=label) + ax2.set_xlabel("Temperature (K)") + ax2.set_ylabel("Density") ax2.legend(loc=0) fig1.tight_layout() - fig1.savefig(ANALYSIS_RESULTS_DIR.joinpath(f"{dataset_name}_distribution_analysis.png")) + fig1.savefig( + ANALYSIS_RESULTS_DIR.joinpath(f"{dataset_name}_distribution_analysis.png") + ) common_params = dict(density=True, bins=50, histtype="stepfilled", alpha=0.25) fig2 = plt.figure(figsize=PLEASANT_FIG_SIZE) ax3 = fig2.add_subplot(121) ax4 = fig2.add_subplot(122) - ax3.set_title('Energy distribution') - ax4.set_title('Average Energy') + ax3.set_title("Energy distribution") + ax4.set_title("Average Energy") - ax3.set_xlabel('$E- E_0$ (eV)') - ax3.set_ylabel('Density') + ax3.set_xlabel("$E- E_0$ (eV)") + ax3.set_ylabel("Density") - ax4.set_xlabel('$k_B T$ (eV)') - ax4.set_ylabel('Energy (eV)') + ax4.set_xlabel("$k_B T$ (eV)") + ax4.set_ylabel("Energy (eV)") - df = train_df[['temperature', 'energy']] - temperature_bins = pd.cut(df['temperature'], bins=5) + df = train_df[["temperature", "energy"]] + temperature_bins = pd.cut(df["temperature"], bins=5) list_kt = [] list_kt_err = [] list_de = [] list_de_err = [] for temp_bin, group_df in df.groupby(temperature_bins): - relative_energies = group_df['energy'].values - e0 - kB_temperatures = kelvin_in_ev * group_df['temperature'].values - ax3.hist(relative_energies, **common_params, label=f'$T \\in$ {temp_bin} K') + relative_energies = group_df["energy"].values - e0 + kB_temperatures = kelvin_in_ev * group_df["temperature"].values + ax3.hist(relative_energies, **common_params, label=f"$T \\in$ {temp_bin} K") list_kt.append(np.mean(kB_temperatures)) list_kt_err.append(np.std(kB_temperatures)) list_de.append(np.mean(relative_energies)) list_de_err.append(np.std(relative_energies)) - ax4.errorbar(list_kt, list_de, xerr=list_kt_err, yerr=list_de_err, fmt='o', label='Data') + ax4.errorbar( + list_kt, list_de, xerr=list_kt_err, yerr=list_de_err, fmt="o", label="Data" + ) list_kt_fit = np.linspace(0.01, 0.04, 101) - list_de_ideal_fit = 3. * number_of_atoms * list_kt_fit - ax4.plot(list_kt_fit, list_de_ideal_fit, 'r-', label='$E-E_0 = 3 N k_B T$') + list_de_ideal_fit = 3.0 * number_of_atoms * list_kt_fit + ax4.plot(list_kt_fit, list_de_ideal_fit, "r-", label="$E-E_0 = 3 N k_B T$") fit = np.polyfit(list_kt, list_de, deg=1) m = fit[0] b = fit[1] list_de_fit = np.poly1d(fit)(list_kt_fit) - ax4.plot(list_kt_fit, list_de_fit, 'g-', label=f'$E-E_0 = m k_B T + b$\n$m = {m:4.2f}, b={b:4.2f}$ eV') + ax4.plot( + list_kt_fit, + list_de_fit, + "g-", + label=f"$E-E_0 = m k_B T + b$\n$m = {m:4.2f}, b={b:4.2f}$ eV", + ) ax3.legend(loc=0) ax4.legend(loc=0) fig2.tight_layout() - fig2.savefig(ANALYSIS_RESULTS_DIR.joinpath(f"{dataset_name}_temperature_bins_analysis.png")) + fig2.savefig( + ANALYSIS_RESULTS_DIR.joinpath(f"{dataset_name}_temperature_bins_analysis.png") + ) fig3 = plt.figure(figsize=PLEASANT_FIG_SIZE) ax5 = fig3.add_subplot(111) - ax5.hist2d(kelvin_in_ev * df['temperature'], df['energy'], bins=100) - ax5.set_xlabel('$k_B T$ (eV)') - ax5.set_ylabel('$E$ (eV)') + ax5.hist2d(kelvin_in_ev * df["temperature"], df["energy"], bins=100) + ax5.set_xlabel("$k_B T$ (eV)") + ax5.set_ylabel("$E$ (eV)") list_kb_T = kelvin_in_ev * np.linspace(100, 500, 101) list_e = e0 + 3 * number_of_atoms * list_kb_T - ax5.plot(list_kb_T, list_e, 'r-', label='$E = E_0 + 3 N k_B T$') + ax5.plot(list_kb_T, list_e, "r-", label="$E = E_0 + 3 N k_B T$") ax5.legend(loc=0) fig3.tight_layout() - fig3.savefig(ANALYSIS_RESULTS_DIR.joinpath(f"{dataset_name}_energy_temperature_2D_histogram.png")) + fig3.savefig( + ANALYSIS_RESULTS_DIR.joinpath( + f"{dataset_name}_energy_temperature_2D_histogram.png" + ) + ) fig4 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig4.suptitle('Total Energy and Potential Energy') + fig4.suptitle("Total Energy and Potential Energy") ax6 = fig4.add_subplot(111) - ax6.hist(train_df['potential_energy'].values, **common_params, color='green', label='Potential Energy') - ax6.hist(train_df['energy'].values, **common_params, color='red', label='Total Energy') - ax6.set_xlabel('Energy (eV)') - ax6.set_ylabel('Density') + ax6.hist( + train_df["potential_energy"].values, + **common_params, + color="green", + label="Potential Energy", + ) + ax6.hist( + train_df["energy"].values, **common_params, color="red", label="Total Energy" + ) + ax6.set_xlabel("Energy (eV)") + ax6.set_ylabel("Density") ax6.legend(loc=0) fig4.tight_layout() - fig4.savefig(ANALYSIS_RESULTS_DIR.joinpath(f"{dataset_name}_potential_vs_total_energy.png")) + fig4.savefig( + ANALYSIS_RESULTS_DIR.joinpath(f"{dataset_name}_potential_vs_total_energy.png") + ) diff --git a/experiments/dataset_analysis/si_diffusion_v1_analysis.py b/experiments/dataset_analysis/si_diffusion_v1_analysis.py index 81c1e1f4..a1be7ad4 100644 --- a/experiments/dataset_analysis/si_diffusion_v1_analysis.py +++ b/experiments/dataset_analysis/si_diffusion_v1_analysis.py @@ -2,17 +2,22 @@ This script computes and plots different features of a dataset used to train a diffusion model. """ + import os from typing import Dict import matplotlib.pyplot as plt import numpy as np import pandas as pd -from crystal_diffusion import ANALYSIS_RESULTS_DIR, DATA_DIR -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from src.crystal_diffusion.data.parse_lammps_outputs import parse_lammps_output -DATASET_NAME = 'si_diffusion_v1' +from diffusion_for_multi_scale_molecular_dynamics import (ANALYSIS_RESULTS_DIR, + DATA_DIR) +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) +from diffusion_for_multi_scale_molecular_dynamics.data.parse_lammps_outputs import \ + parse_lammps_output + +DATASET_NAME = "si_diffusion_v1" def read_lammps_run(run_path: str) -> pd.DataFrame: @@ -24,10 +29,14 @@ def read_lammps_run(run_path: str) -> pd.DataFrame: Returns: output as """ - dump_file = [d for d in os.listdir(run_path) if 'dump' in d] - thermo_file = [d for d in os.listdir(run_path) if 'thermo' in d] + dump_file = [d for d in os.listdir(run_path) if "dump" in d] + thermo_file = [d for d in os.listdir(run_path) if "thermo" in d] - df = parse_lammps_output(os.path.join(run_path, dump_file[0]), os.path.join(run_path, thermo_file[0]), None) + df = parse_lammps_output( + os.path.join(run_path, dump_file[0]), + os.path.join(run_path, thermo_file[0]), + None, + ) return df @@ -42,26 +51,40 @@ def compute_metrics_for_a_run(df: pd.DataFrame) -> Dict[str, pd.Series]: metrics evaluated at each MD step organized in a dict """ metrics = {} - metrics['energy'] = df['energy'] - force_norm_mean = df.apply(lambda row: np.mean([np.sqrt(fx**2 + fy**2 + fz**2) for fx, fy, fz in - zip(row['fx'], row['fy'], row['fz'])]), axis=1) - metrics['force_norm_average'] = force_norm_mean - - x0s = df['x'][0] - y0s = df['y'][0] - z0s = df['z'][0] - - square_displacement = df.apply(lambda row: [(x - x0) ** 2 + (y - y0) ** 2 + (z - z0) ** 2 for x, y, z, x0, y0, z0 in - zip(row['x'], row['y'], row['z'], x0s, y0s, z0s)], axis=1) - - metrics['root_mean_square_displacement'] = square_displacement.apply(lambda row: np.sqrt(np.mean(row))) - - metrics['std_displacement'] = np.std(square_displacement.apply(np.sqrt)) + metrics["energy"] = df["energy"] + force_norm_mean = df.apply( + lambda row: np.mean( + [ + np.sqrt(fx**2 + fy**2 + fz**2) + for fx, fy, fz in zip(row["fx"], row["fy"], row["fz"]) + ] + ), + axis=1, + ) + metrics["force_norm_average"] = force_norm_mean + + x0s = df["x"][0] + y0s = df["y"][0] + z0s = df["z"][0] + + square_displacement = df.apply( + lambda row: [ + (x - x0) ** 2 + (y - y0) ** 2 + (z - z0) ** 2 + for x, y, z, x0, y0, z0 in zip(row["x"], row["y"], row["z"], x0s, y0s, z0s) + ], + axis=1, + ) + + metrics["root_mean_square_displacement"] = square_displacement.apply( + lambda row: np.sqrt(np.mean(row)) + ) + + metrics["std_displacement"] = np.std(square_displacement.apply(np.sqrt)) return metrics -def plot_metrics_runs(dataset_name: str, mode: str = 'train'): +def plot_metrics_runs(dataset_name: str, mode: str = "train"): """Compute and plot metrics for a dataset made up of several MD runs. Args: @@ -71,8 +94,13 @@ def plot_metrics_runs(dataset_name: str, mode: str = 'train'): assert mode in ["train", "valid"], f"Mode should be train or valid. Got {mode}" dataset_path = os.path.join(DATA_DIR, dataset_name) - list_runs = [d for d in os.listdir(dataset_path) if os.path.isdir(os.path.join(dataset_path, d)) - and d.startswith(f"{mode}_run") and not d.endswith('backup')] + list_runs = [ + d + for d in os.listdir(dataset_path) + if os.path.isdir(os.path.join(dataset_path, d)) + and d.startswith(f"{mode}_run") + and not d.endswith("backup") + ] metrics = {} for run in list_runs: @@ -82,7 +110,9 @@ def plot_metrics_runs(dataset_name: str, mode: str = 'train'): plt.style.use(PLOT_STYLE_PATH) - fig, axs = plt.subplots(4, 1, figsize=(PLEASANT_FIG_SIZE[0], 4 * PLEASANT_FIG_SIZE[1])) + fig, axs = plt.subplots( + 4, 1, figsize=(PLEASANT_FIG_SIZE[0], 4 * PLEASANT_FIG_SIZE[1]) + ) fig.suptitle("MD runs properties") # energy @@ -100,10 +130,10 @@ def plot_metrics_runs(dataset_name: str, mode: str = 'train'): legend = [] for k, m in metrics.items(): - axs[0].plot(m['energy'], '-', lw=2) - axs[1].plot(m['force_norm_average'], ':', lw=2) - axs[2].plot(m['root_mean_square_displacement'], lw=2) - axs[3].plot(m['std_displacement'], lw=2) + axs[0].plot(m["energy"], "-", lw=2) + axs[1].plot(m["force_norm_average"], ":", lw=2) + axs[2].plot(m["root_mean_square_displacement"], lw=2) + axs[3].plot(m["std_displacement"], lw=2) legend.append(k) for ax in axs: @@ -112,14 +142,16 @@ def plot_metrics_runs(dataset_name: str, mode: str = 'train'): fig.tight_layout() - fig.savefig(ANALYSIS_RESULTS_DIR.joinpath(f"{dataset_name}_{mode}_analysis.png"), dpi=300) + fig.savefig( + ANALYSIS_RESULTS_DIR.joinpath(f"{dataset_name}_{mode}_analysis.png"), dpi=300 + ) def main(): """Analyze training and validation set of a dataset.""" - plot_metrics_runs(DATASET_NAME, mode='train') - plot_metrics_runs(DATASET_NAME, mode='valid') + plot_metrics_runs(DATASET_NAME, mode="train") + plot_metrics_runs(DATASET_NAME, mode="valid") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/experiments/diffusion_mace_harmonic_data/ad_hoc_experiments_with_various_score_networks.py b/experiments/diffusion_mace_harmonic_data/ad_hoc_experiments_with_various_score_networks.py index 209cd31e..1403ffa4 100644 --- a/experiments/diffusion_mace_harmonic_data/ad_hoc_experiments_with_various_score_networks.py +++ b/experiments/diffusion_mace_harmonic_data/ad_hoc_experiments_with_various_score_networks.py @@ -4,28 +4,33 @@ import matplotlib.pyplot as plt import pytorch_lightning as pl import torch -from crystal_diffusion.callbacks.callback_loader import create_all_callbacks -from crystal_diffusion.models.optimizer import OptimizerParameters -from crystal_diffusion.models.position_diffusion_lightning_model import ( +from pytorch_lightning.loggers import TensorBoardLogger +from torch.utils.data import DataLoader + +from diffusion_for_multi_scale_molecular_dynamics.analysis import \ + PLOT_STYLE_PATH +from diffusion_for_multi_scale_molecular_dynamics.callbacks.callback_loader import \ + create_all_callbacks +from diffusion_for_multi_scale_molecular_dynamics.generators.position_generator import \ + SamplingParameters +from diffusion_for_multi_scale_molecular_dynamics.models.optimizer import \ + OptimizerParameters +from diffusion_for_multi_scale_molecular_dynamics.models.position_diffusion_lightning_model import ( PositionDiffusionLightningModel, PositionDiffusionParameters) -from crystal_diffusion.models.scheduler import \ +from diffusion_for_multi_scale_molecular_dynamics.models.scheduler import \ ReduceLROnPlateauSchedulerParameters -from crystal_diffusion.models.score_networks.diffusion_mace_score_network import \ +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.diffusion_mace_score_network import \ DiffusionMACEScoreNetworkParameters -from crystal_diffusion.models.score_networks.mace_score_network import \ +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.mace_score_network import \ MACEScoreNetworkParameters -from crystal_diffusion.models.score_networks.mlp_score_network import \ +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.mlp_score_network import \ MLPScoreNetworkParameters -from crystal_diffusion.models.score_networks.score_prediction_head import \ +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.score_prediction_head import \ MaceEquivariantScorePredictionHeadParameters -from crystal_diffusion.namespace import CARTESIAN_FORCES, RELATIVE_COORDINATES -from pytorch_lightning.loggers import TensorBoardLogger -from src.crystal_diffusion.analysis import PLOT_STYLE_PATH -from src.crystal_diffusion.generators.position_generator import \ - SamplingParameters -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters -from torch.utils.data import DataLoader - +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, RELATIVE_COORDINATES) +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters from experiments.analysis.analytic_score import (get_exact_samples, get_relative_harmonic_energy) from experiments.diffusion_mace_harmonic_data.analysis_callbacks import \ @@ -37,7 +42,7 @@ plt.style.use(PLOT_STYLE_PATH) # model = 'mlp' -model = 'diffusion_mace' +model = "diffusion_mace" # model = 'mace_plus_prediction_head' run_id = 1 @@ -48,50 +53,56 @@ batch_size = 1000 gradient_clip_val = 0.0 -spring_constant = 1000. +spring_constant = 1000.0 dim = 16 num_interactions = 2 correlation = 2 number_of_mlp_layers = 0 -common_mace_parameters = dict(number_of_atoms=number_of_atoms, - r_max=5.0, - num_bessel=8, - num_polynomial_cutoff=5, - max_ell=2, - interaction_cls="RealAgnosticResidualInteractionBlock", - interaction_cls_first="RealAgnosticInteractionBlock", - num_interactions=num_interactions, - hidden_irreps=f"{dim}x0e + {dim}x1o + {dim}x2e", - avg_num_neighbors=1, - correlation=correlation, - gate="silu", - radial_MLP=[dim, dim, dim], - radial_type="gaussian") - - -prediction_head_parameters = MaceEquivariantScorePredictionHeadParameters(time_embedding_irreps="32x0e", - gate="silu", - number_of_layers=3) - -mace_score_network_parameters = MACEScoreNetworkParameters(prediction_head_parameters=prediction_head_parameters, - MLP_irreps=f"{dim}x0e", - **common_mace_parameters) - -diffusion_mace_score_network_parameters = DiffusionMACEScoreNetworkParameters(mlp_irreps=f"{dim}x0e", - number_of_mlp_layers=number_of_mlp_layers, - **common_mace_parameters) - -mlp_score_network_parameters = MLPScoreNetworkParameters(number_of_atoms=number_of_atoms, - n_hidden_dimensions=3, - hidden_dimensions_size=64) - -if model == 'diffusion_mace': +common_mace_parameters = dict( + number_of_atoms=number_of_atoms, + r_max=5.0, + num_bessel=8, + num_polynomial_cutoff=5, + max_ell=2, + interaction_cls="RealAgnosticResidualInteractionBlock", + interaction_cls_first="RealAgnosticInteractionBlock", + num_interactions=num_interactions, + hidden_irreps=f"{dim}x0e + {dim}x1o + {dim}x2e", + avg_num_neighbors=1, + correlation=correlation, + gate="silu", + radial_MLP=[dim, dim, dim], + radial_type="gaussian", +) + + +prediction_head_parameters = MaceEquivariantScorePredictionHeadParameters( + time_embedding_irreps="32x0e", gate="silu", number_of_layers=3 +) + +mace_score_network_parameters = MACEScoreNetworkParameters( + prediction_head_parameters=prediction_head_parameters, + MLP_irreps=f"{dim}x0e", + **common_mace_parameters, +) + +diffusion_mace_score_network_parameters = DiffusionMACEScoreNetworkParameters( + mlp_irreps=f"{dim}x0e", + number_of_mlp_layers=number_of_mlp_layers, + **common_mace_parameters, +) + +mlp_score_network_parameters = MLPScoreNetworkParameters( + number_of_atoms=number_of_atoms, n_hidden_dimensions=3, hidden_dimensions_size=64 +) + +if model == "diffusion_mace": score_network_parameters = diffusion_mace_score_network_parameters -elif model == 'mace_plus_prediction_head': +elif model == "mace_plus_prediction_head": score_network_parameters = mace_score_network_parameters -elif model == 'mlp': +elif model == "mlp": score_network_parameters = mlp_score_network_parameters @@ -99,19 +110,25 @@ acell = 5.5 noise_parameters = NoiseParameters(total_time_steps=100, sigma_min=0.001, sigma_max=0.5) -sampling_parameters = SamplingParameters(spatial_dimension=3, - number_of_corrector_steps=1, - number_of_atoms=number_of_atoms, - number_of_samples=1000, - sample_every_n_epochs=1, - record_samples=True, - cell_dimensions=[acell, acell, acell]) - - -optimizer_parameters = OptimizerParameters(name='adamw', learning_rate=0.001, weight_decay=0.0) +sampling_parameters = SamplingParameters( + spatial_dimension=3, + number_of_corrector_steps=1, + number_of_atoms=number_of_atoms, + number_of_samples=1000, + sample_every_n_epochs=1, + record_samples=True, + cell_dimensions=[acell, acell, acell], +) + + +optimizer_parameters = OptimizerParameters( + name="adamw", learning_rate=0.001, weight_decay=0.0 +) scheduler_parameters = ReduceLROnPlateauSchedulerParameters(factor=0.5, patience=10) -loss_monitoring_parameters = dict(number_of_bins=50, sample_every_n_epochs=1, spatial_dimension=spatial_dimension) +loss_monitoring_parameters = dict( + number_of_bins=50, sample_every_n_epochs=1, spatial_dimension=spatial_dimension +) callback_parameters = dict(loss_monitoring=loss_monitoring_parameters) @@ -120,49 +137,79 @@ output_directory = str(Path(__file__).parent / "output" / experiment_name) -tensorboard_logger = TensorBoardLogger(save_dir=output_directory, - default_hp_metric=False, - name=experiment_name, - version=0, - ) +tensorboard_logger = TensorBoardLogger( + save_dir=output_directory, + default_hp_metric=False, + name=experiment_name, + version=0, +) -if __name__ == '__main__': +if __name__ == "__main__": torch.manual_seed(42) # torch.set_default_dtype(torch.float64) box = acell * torch.ones(spatial_dimension) if number_of_atoms == 1: - equilibrium_relative_coordinates = (0.5 * torch.ones(spatial_dimension)).unsqueeze(0) + equilibrium_relative_coordinates = ( + 0.5 * torch.ones(spatial_dimension) + ).unsqueeze(0) elif number_of_atoms == 2: - equilibrium_relative_coordinates = torch.stack([0.25 * torch.ones(spatial_dimension), - 0.75 * torch.ones(spatial_dimension)]) + equilibrium_relative_coordinates = torch.stack( + [0.25 * torch.ones(spatial_dimension), 0.75 * torch.ones(spatial_dimension)] + ) - inverse_covariance = torch.zeros(number_of_atoms, spatial_dimension, number_of_atoms, spatial_dimension) + inverse_covariance = torch.zeros( + number_of_atoms, spatial_dimension, number_of_atoms, spatial_dimension + ) for atom_i in range(number_of_atoms): for alpha in range(spatial_dimension): inverse_covariance[atom_i, alpha, atom_i, alpha] = spring_constant - diffusion_sampling_callback = HarmonicEnergyDiffusionSamplingCallback(noise_parameters, - sampling_parameters, - equilibrium_relative_coordinates, - inverse_covariance, - output_directory) + diffusion_sampling_callback = HarmonicEnergyDiffusionSamplingCallback( + noise_parameters, + sampling_parameters, + equilibrium_relative_coordinates, + inverse_covariance, + output_directory, + ) # Create a dataloader - train_samples = get_exact_samples(equilibrium_relative_coordinates, inverse_covariance, dataset_size) + train_samples = get_exact_samples( + equilibrium_relative_coordinates, inverse_covariance, dataset_size + ) # train_energies = get_samples_harmonic_energy(equilibrium_relative_coordinates, inverse_covariance, train_samples) - train_energies = get_relative_harmonic_energy(train_samples, equilibrium_relative_coordinates, spring_constant) - train_dataset = [{RELATIVE_COORDINATES: x, 'box': box, 'potential_energy': e, CARTESIAN_FORCES: torch.zeros_like(x)} - for x, e in zip(train_samples, train_energies)] + train_energies = get_relative_harmonic_energy( + train_samples, equilibrium_relative_coordinates, spring_constant + ) + train_dataset = [ + { + RELATIVE_COORDINATES: x, + "box": box, + "potential_energy": e, + CARTESIAN_FORCES: torch.zeros_like(x), + } + for x, e in zip(train_samples, train_energies) + ] train_dataloader = DataLoader(train_dataset, batch_size=batch_size) - valid_samples = get_exact_samples(equilibrium_relative_coordinates, inverse_covariance, dataset_size) - valid_energies = get_relative_harmonic_energy(valid_samples, equilibrium_relative_coordinates, spring_constant) + valid_samples = get_exact_samples( + equilibrium_relative_coordinates, inverse_covariance, dataset_size + ) + valid_energies = get_relative_harmonic_energy( + valid_samples, equilibrium_relative_coordinates, spring_constant + ) # valid_energies = get_samples_harmonic_energy(equilibrium_relative_coordinates, inverse_covariance, valid_samples) - valid_dataset = [{RELATIVE_COORDINATES: x, 'box': box, 'potential_energy': e, CARTESIAN_FORCES: torch.zeros_like(x)} - for x, e in zip(valid_samples, valid_energies)] + valid_dataset = [ + { + RELATIVE_COORDINATES: x, + "box": box, + "potential_energy": e, + CARTESIAN_FORCES: torch.zeros_like(x), + } + for x, e in zip(valid_samples, valid_energies) + ] valid_dataloader = DataLoader(valid_dataset, batch_size=batch_size) diffusion_params = PositionDiffusionParameters( @@ -174,17 +221,22 @@ model = PositionDiffusionLightningModel(diffusion_params) - callbacks_dict = create_all_callbacks(callback_parameters, output_directory=output_directory, verbose=True) + callbacks_dict = create_all_callbacks( + callback_parameters, output_directory=output_directory, verbose=True + ) callbacks = list(callbacks_dict.values()) callbacks.append(diffusion_sampling_callback) - trainer = pl.Trainer(callbacks=callbacks, - max_epochs=max_epochs, - log_every_n_steps=1, - fast_dev_run=False, - gradient_clip_val=gradient_clip_val, - logger=tensorboard_logger, - ) + trainer = pl.Trainer( + callbacks=callbacks, + max_epochs=max_epochs, + log_every_n_steps=1, + fast_dev_run=False, + gradient_clip_val=gradient_clip_val, + logger=tensorboard_logger, + ) - trainer.fit(model, train_dataloaders=train_dataloader, val_dataloaders=valid_dataloader) + trainer.fit( + model, train_dataloaders=train_dataloader, val_dataloaders=valid_dataloader + ) diff --git a/experiments/diffusion_mace_harmonic_data/analysis_callbacks.py b/experiments/diffusion_mace_harmonic_data/analysis_callbacks.py index 4bdb5e24..a7576c66 100644 --- a/experiments/diffusion_mace_harmonic_data/analysis_callbacks.py +++ b/experiments/diffusion_mace_harmonic_data/analysis_callbacks.py @@ -3,18 +3,21 @@ The callbacks in this module are not designed for 'production'. They are to be used in ad hoc / debugging / analysis experiments. """ + import logging import matplotlib.pyplot as plt import numpy as np import torch -from crystal_diffusion.callbacks.sampling_visualization_callback import \ + +from diffusion_for_multi_scale_molecular_dynamics.analysis import \ + PLOT_STYLE_PATH +from diffusion_for_multi_scale_molecular_dynamics.callbacks.sampling_visualization_callback import \ SamplingVisualizationCallback -from src.crystal_diffusion.analysis import PLOT_STYLE_PATH -from src.crystal_diffusion.generators.position_generator import \ +from diffusion_for_multi_scale_molecular_dynamics.generators.position_generator import \ SamplingParameters -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters - +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters from experiments.analysis.analytic_score import get_relative_harmonic_energy logger = logging.getLogger(__name__) @@ -25,26 +28,33 @@ class HarmonicEnergyDiffusionSamplingCallback(SamplingVisualizationCallback): """Callback class to periodically generate samples and log their energies.""" - def __init__(self, noise_parameters: NoiseParameters, - sampling_parameters: SamplingParameters, - equilibrium_relative_coordinates: torch.Tensor, - inverse_covariance: torch.Tensor, - output_directory: str): + def __init__( + self, + noise_parameters: NoiseParameters, + sampling_parameters: SamplingParameters, + equilibrium_relative_coordinates: torch.Tensor, + inverse_covariance: torch.Tensor, + output_directory: str, + ): """Init method.""" super().__init__(noise_parameters, sampling_parameters, output_directory) self.equilibrium_relative_coordinates = equilibrium_relative_coordinates self.inverse_covariance = inverse_covariance - def _compute_oracle_energies(self, batch_relative_coordinates: torch.Tensor) -> np.ndarray: + def _compute_oracle_energies( + self, batch_relative_coordinates: torch.Tensor + ) -> np.ndarray: """Compute energies from samples.""" logger.info("Compute harmonic energy from Oracle") spring_constant = self.inverse_covariance[0, 0, 0, 0] - energies = get_relative_harmonic_energy(batch_relative_coordinates, - self.equilibrium_relative_coordinates, - spring_constant) + energies = get_relative_harmonic_energy( + batch_relative_coordinates, + self.equilibrium_relative_coordinates, + spring_constant, + ) # energies = get_samples_harmonic_energy(self.equilibrium_relative_coordinates, # self.inverse_covariance, @@ -52,11 +62,16 @@ def _compute_oracle_energies(self, batch_relative_coordinates: torch.Tensor) -> return energies.cpu().detach().numpy() @staticmethod - def _plot_energy_histogram(sample_energies: np.ndarray, validation_dataset_energies: np.array, - epoch: int) -> plt.figure: - fig = SamplingVisualizationCallback._plot_energy_histogram(sample_energies, validation_dataset_energies, epoch) + def _plot_energy_histogram( + sample_energies: np.ndarray, validation_dataset_energies: np.array, epoch: int + ) -> plt.figure: + fig = SamplingVisualizationCallback._plot_energy_histogram( + sample_energies, validation_dataset_energies, epoch + ) - fig.suptitle(f'Sampling Unitless Harmonic Potential Energy Distributions\nEpoch {epoch}') + fig.suptitle( + f"Sampling Unitless Harmonic Potential Energy Distributions\nEpoch {epoch}" + ) ax1 = fig.axes[0] - ax1.set_xlabel('Unitless Harmonic Energy') + ax1.set_xlabel("Unitless Harmonic Energy") return fig diff --git a/experiments/diffusion_mace_harmonic_data/overfit_diffusion_mace.py b/experiments/diffusion_mace_harmonic_data/overfit_diffusion_mace.py index b9d5cbc4..03b2e519 100644 --- a/experiments/diffusion_mace_harmonic_data/overfit_diffusion_mace.py +++ b/experiments/diffusion_mace_harmonic_data/overfit_diffusion_mace.py @@ -3,30 +3,31 @@ This script helps in the exploration of the Diffusion MACE architecture, trying different ideas quickly to see if we can overfit the analytical score for a single example. """ + from pathlib import Path import pytorch_lightning as pl import torch -from crystal_diffusion.callbacks.standard_callbacks import CustomProgressBar -from crystal_diffusion.models.score_networks.analytical_score_network import ( +from pytorch_lightning.loggers import TensorBoardLogger +from torch import optim +from torch.utils.data import DataLoader + +from diffusion_for_multi_scale_molecular_dynamics.callbacks.standard_callbacks import \ + CustomProgressBar +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.analytical_score_network import ( AnalyticalScoreNetwork, AnalyticalScoreNetworkParameters) -from crystal_diffusion.models.score_networks.diffusion_mace_score_network import ( +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.diffusion_mace_score_network import ( DiffusionMACEScoreNetwork, DiffusionMACEScoreNetworkParameters) -from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, - NOISY_RELATIVE_COORDINATES, TIME, - UNIT_CELL) -from crystal_diffusion.samplers.noisy_relative_coordinates_sampler import \ +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) +from diffusion_for_multi_scale_molecular_dynamics.samplers.noisy_relative_coordinates_sampler import \ NoisyRelativeCoordinatesSampler -from crystal_diffusion.utils.basis_transformations import \ +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import ( + ExplodingVarianceSampler, NoiseParameters) +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell -from crystal_diffusion.utils.tensor_utils import \ +from diffusion_for_multi_scale_molecular_dynamics.utils.tensor_utils import \ broadcast_batch_tensor_to_all_dimensions -from pytorch_lightning.loggers import TensorBoardLogger -from src.crystal_diffusion.samplers.variance_sampler import ( - ExplodingVarianceSampler, NoiseParameters) -from torch import optim -from torch.utils.data import DataLoader - from experiments.analysis.analytic_score import (get_exact_samples, get_unit_cells) @@ -44,21 +45,27 @@ output_directory = root_dir / experiment_name -tensorboard_logger = TensorBoardLogger(save_dir=str(output_directory), - default_hp_metric=False, - name=experiment_name, - version=0, - ) +tensorboard_logger = TensorBoardLogger( + save_dir=str(output_directory), + default_hp_metric=False, + name=experiment_name, + version=0, +) class DevDiffusionMaceLightningModel(pl.LightningModule): """This is a stub lightning module for the purpose of trying to overfit Diffusion Mace to the analytical score.""" - def __init__(self, diffusion_mace_score_network_parameters: DiffusionMACEScoreNetworkParameters): + def __init__( + self, + diffusion_mace_score_network_parameters: DiffusionMACEScoreNetworkParameters, + ): """Init method.""" super().__init__() self.save_hyperparameters(logger=True) - self.sigma_normalized_score_network = DiffusionMACEScoreNetwork(diffusion_mace_score_network_parameters) + self.sigma_normalized_score_network = DiffusionMACEScoreNetwork( + diffusion_mace_score_network_parameters + ) def configure_optimizers(self): """Configure optimizers.""" @@ -69,8 +76,10 @@ def configure_optimizers(self): def training_step(self, batch, batch_idx): """Training step.""" predicted_normalized_scores = self.sigma_normalized_score_network(batch) - target_normalized_scores = batch['TARGET'] - loss = torch.nn.functional.mse_loss(predicted_normalized_scores, target_normalized_scores, reduction="mean") + target_normalized_scores = batch["TARGET"] + loss = torch.nn.functional.mse_loss( + predicted_normalized_scores, target_normalized_scores, reduction="mean" + ) output = dict(loss=loss) self.log("train_step_loss", loss, on_step=True, on_epoch=False, prog_bar=True) return output @@ -98,7 +107,8 @@ def training_step(self, batch, batch_idx): correlation=2, gate="silu", radial_MLP=[dim, dim, dim], - radial_type="gaussian") + radial_type="gaussian", +) max_epochs = 1000 @@ -110,30 +120,39 @@ def training_step(self, batch, batch_idx): variance_sampler = ExplodingVarianceSampler(noise_parameters) -if __name__ == '__main__': +if __name__ == "__main__": torch.manual_seed(42) # ====================== Generate Harmonic potential samples ==================================== - spring_constant = 1000. + spring_constant = 1000.0 box = acell * torch.ones(spatial_dimension) - equilibrium_relative_coordinates = torch.stack([0.25 * torch.ones(spatial_dimension), - 0.75 * torch.ones(spatial_dimension)]) + equilibrium_relative_coordinates = torch.stack( + [0.25 * torch.ones(spatial_dimension), 0.75 * torch.ones(spatial_dimension)] + ) - inverse_covariance = torch.zeros(number_of_atoms, spatial_dimension, number_of_atoms, spatial_dimension) + inverse_covariance = torch.zeros( + number_of_atoms, spatial_dimension, number_of_atoms, spatial_dimension + ) for atom_i in range(number_of_atoms): for alpha in range(spatial_dimension): inverse_covariance[atom_i, alpha, atom_i, alpha] = spring_constant # ====================== Generate Random Data ==================================== - x0 = get_exact_samples(equilibrium_relative_coordinates, inverse_covariance, dataset_size) + x0 = get_exact_samples( + equilibrium_relative_coordinates, inverse_covariance, dataset_size + ) noise_sample = variance_sampler.get_random_noise_sample(dataset_size) - sigmas = broadcast_batch_tensor_to_all_dimensions(batch_values=noise_sample.sigma, final_shape=x0.shape) + sigmas = broadcast_batch_tensor_to_all_dimensions( + batch_values=noise_sample.sigma, final_shape=x0.shape + ) sigmas = torch.ones_like(sigmas) - xt = noisy_relative_coordinates_sampler.get_noisy_relative_coordinates_sample(x0, sigmas) + xt = noisy_relative_coordinates_sampler.get_noisy_relative_coordinates_sample( + x0, sigmas + ) cm = 0.5 * xt.sum(dim=1) xt = map_relative_coordinates_to_unit_cell(xt - cm) @@ -145,19 +164,26 @@ def training_step(self, batch, batch_idx): spatial_dimension=spatial_dimension, kmax=1, equilibrium_relative_coordinates=equilibrium_relative_coordinates, - inverse_covariance=inverse_covariance) + inverse_covariance=inverse_covariance, + ) - analytical_score_network = AnalyticalScoreNetwork(analytical_score_network_parameters) + analytical_score_network = AnalyticalScoreNetwork( + analytical_score_network_parameters + ) - unit_cells = get_unit_cells(acell, spatial_dimension, number_of_samples=dataset_size) + unit_cells = get_unit_cells( + acell, spatial_dimension, number_of_samples=dataset_size + ) sigma = noise_sample.sigma.reshape(-1, 1) sigma = sigma_value * torch.ones_like(sigma) - augmented_batch = {NOISY_RELATIVE_COORDINATES: xt, - TIME: noise_sample.time.reshape(-1, 1), - NOISE: sigma, - UNIT_CELL: unit_cells, - CARTESIAN_FORCES: torch.zeros_like(xt)} + augmented_batch = { + NOISY_RELATIVE_COORDINATES: xt, + TIME: noise_sample.time.reshape(-1, 1), + NOISE: sigma, + UNIT_CELL: unit_cells, + CARTESIAN_FORCES: torch.zeros_like(xt), + } analytical_scores = analytical_score_network(augmented_batch) @@ -176,11 +202,12 @@ def training_step(self, batch, batch_idx): callbacks = [CustomProgressBar()] - trainer = pl.Trainer(callbacks=callbacks, - max_epochs=max_epochs, - log_every_n_steps=1, - fast_dev_run=False, - logger=tensorboard_logger, - ) + trainer = pl.Trainer( + callbacks=callbacks, + max_epochs=max_epochs, + log_every_n_steps=1, + fast_dev_run=False, + logger=tensorboard_logger, + ) trainer.fit(model, train_dataloaders=train_dataloader) diff --git a/experiments/generators/sde_generator_sanity_check.py b/experiments/generators/sde_generator_sanity_check.py index 075f441b..ff140d37 100644 --- a/experiments/generators/sde_generator_sanity_check.py +++ b/experiments/generators/sde_generator_sanity_check.py @@ -6,16 +6,18 @@ import einops import numpy as np import torch -from crystal_diffusion.generators.sde_position_generator import ( +from matplotlib import pyplot as plt + +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) +from diffusion_for_multi_scale_molecular_dynamics.generators.sde_position_generator import ( ExplodingVarianceSDEPositionGenerator, SDESamplingParameters) -from crystal_diffusion.models.score_networks.analytical_score_network import ( +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks.analytical_score_network import ( AnalyticalScoreNetworkParameters, TargetScoreBasedAnalyticalScoreNetwork) -from crystal_diffusion.utils.basis_transformations import \ +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ map_relative_coordinates_to_unit_cell -from matplotlib import pyplot as plt -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters - from experiments.analysis.analytic_score.utils import get_exact_samples from experiments.generators import GENERATOR_SANITY_CHECK_DIRECTORY @@ -29,33 +31,42 @@ def __init__(self, equilibrium_relative_coordinates: torch.Tensor): """Init method.""" self.equilibrium_relative_coordinates = equilibrium_relative_coordinates - def compute_displacements(self, batch_relative_coordinates: torch.Tensor) -> np.ndarray: + def compute_displacements( + self, batch_relative_coordinates: torch.Tensor + ) -> np.ndarray: """Compute displacements.""" return (batch_relative_coordinates - equilibrium_relative_coordinates).flatten() -def generate_exact_samples(equilibrium_relative_coordinates: torch.Tensor, sigma_d: float, number_of_samples: int): +def generate_exact_samples( + equilibrium_relative_coordinates: torch.Tensor, + sigma_d: float, + number_of_samples: int, +): """Generate Gaussian samples about the equilibrium relative coordinates.""" - variance_parameter = sigma_d ** 2 + variance_parameter = sigma_d**2 number_of_atoms, spatial_dimension = equilibrium_relative_coordinates.shape nd = number_of_atoms * spatial_dimension inverse_covariance = torch.diag(torch.ones(nd)) / variance_parameter - inverse_covariance = inverse_covariance.reshape(number_of_atoms, spatial_dimension, - number_of_atoms, spatial_dimension) + inverse_covariance = inverse_covariance.reshape( + number_of_atoms, spatial_dimension, number_of_atoms, spatial_dimension + ) - exact_samples = get_exact_samples(equilibrium_relative_coordinates, inverse_covariance, number_of_samples) + exact_samples = get_exact_samples( + equilibrium_relative_coordinates, inverse_covariance, number_of_samples + ) exact_samples = map_relative_coordinates_to_unit_cell(exact_samples) return exact_samples -device = torch.device('cpu') +device = torch.device("cpu") number_of_atoms = 1 spatial_dimension = 1 kmax = 8 -acell = 1. +acell = 1.0 cell_dimensions = [acell] output_dir = GENERATOR_SANITY_CHECK_DIRECTORY / "figures" @@ -67,21 +78,29 @@ def generate_exact_samples(equilibrium_relative_coordinates: torch.Tensor, sigma sigma_min = 0.001 total_time_steps = 101 -if __name__ == '__main__': +if __name__ == "__main__": unit_cell = torch.diag(torch.tensor(cell_dimensions)) - batched_unit_cells = einops.repeat(unit_cell, "d1 d2 -> b d1 d2", b=number_of_samples) + batched_unit_cells = einops.repeat( + unit_cell, "d1 d2 -> b d1 d2", b=number_of_samples + ) equilibrium_relative_coordinates = torch.tensor([[0.5]]) - displacement_calculator = DisplacementCalculator(equilibrium_relative_coordinates=equilibrium_relative_coordinates) + displacement_calculator = DisplacementCalculator( + equilibrium_relative_coordinates=equilibrium_relative_coordinates + ) for sigma_d in list_sigma_d: - exact_samples = generate_exact_samples(equilibrium_relative_coordinates, - sigma_d=sigma_d, - number_of_samples=number_of_samples) + exact_samples = generate_exact_samples( + equilibrium_relative_coordinates, + sigma_d=sigma_d, + number_of_samples=number_of_samples, + ) - exact_samples_displacements = displacement_calculator.compute_displacements(exact_samples) + exact_samples_displacements = displacement_calculator.compute_displacements( + exact_samples + ) score_network_parameters = AnalyticalScoreNetworkParameters( number_of_atoms=number_of_atoms, @@ -89,63 +108,85 @@ def generate_exact_samples(equilibrium_relative_coordinates: torch.Tensor, sigma use_permutation_invariance=False, kmax=kmax, equilibrium_relative_coordinates=equilibrium_relative_coordinates, - variance_parameter=sigma_d**2) - - sigma_normalized_score_network = TargetScoreBasedAnalyticalScoreNetwork(score_network_parameters) - - sampling_parameters = SDESamplingParameters(method='euler', - adaptative=False, - absolute_solver_tolerance=1.0e-7, - relative_solver_tolerance=1.0e-5, - number_of_atoms=number_of_atoms, - spatial_dimension=spatial_dimension, - number_of_samples=number_of_samples, - sample_batchsize=number_of_samples, - cell_dimensions=cell_dimensions, - record_samples=True) - - noise_parameters = NoiseParameters(total_time_steps=total_time_steps, - sigma_min=sigma_min, - sigma_max=0.5) - - generator = ExplodingVarianceSDEPositionGenerator(noise_parameters=noise_parameters, - sampling_parameters=sampling_parameters, - sigma_normalized_score_network=sigma_normalized_score_network) - - generated_samples = generator.sample(number_of_samples, - device=device, - unit_cell=batched_unit_cells) + variance_parameter=sigma_d**2, + ) + + sigma_normalized_score_network = TargetScoreBasedAnalyticalScoreNetwork( + score_network_parameters + ) + + sampling_parameters = SDESamplingParameters( + method="euler", + adaptative=False, + absolute_solver_tolerance=1.0e-7, + relative_solver_tolerance=1.0e-5, + number_of_atoms=number_of_atoms, + spatial_dimension=spatial_dimension, + number_of_samples=number_of_samples, + sample_batchsize=number_of_samples, + cell_dimensions=cell_dimensions, + record_samples=True, + ) + + noise_parameters = NoiseParameters( + total_time_steps=total_time_steps, sigma_min=sigma_min, sigma_max=0.5 + ) + + generator = ExplodingVarianceSDEPositionGenerator( + noise_parameters=noise_parameters, + sampling_parameters=sampling_parameters, + sigma_normalized_score_network=sigma_normalized_score_network, + ) + + generated_samples = generator.sample( + number_of_samples, device=device, unit_cell=batched_unit_cells + ) sampled_x = generated_samples[:, 0, 0] trajectory_data = generator.sample_trajectory_recorder.standardize_data( - generator.sample_trajectory_recorder.data) + generator.sample_trajectory_recorder.data + ) - times = trajectory_data['time'] - x = trajectory_data['relative_coordinates'][:, :, 0, 0] + times = trajectory_data["time"] + x = trajectory_data["relative_coordinates"][:, :, 0, 0] fig = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig.suptitle("1 Atom in 1D, Analytical Score, SDE solver" - f"\n $\\sigma_d$ = {sigma_d:4.3f}, time steps = {total_time_steps}") + fig.suptitle( + "1 Atom in 1D, Analytical Score, SDE solver" + f"\n $\\sigma_d$ = {sigma_d:4.3f}, time steps = {total_time_steps}" + ) ax1 = fig.add_subplot(121) ax1.set_title("Subset of Sampling Trajectories") for i in range(50): - ax1.plot(times, x[i, :], '-', color='gray', alpha=0.5) + ax1.plot(times, x[i, :], "-", color="gray", alpha=0.5) ax1.set_xlim(1, 0) - ax1.set_xlabel('Diffusion Time') - ax1.set_ylabel('$x(t)$') + ax1.set_xlabel("Diffusion Time") + ax1.set_ylabel("$x(t)$") - generated_samples_displacements = displacement_calculator.compute_displacements(generated_samples) + generated_samples_displacements = displacement_calculator.compute_displacements( + generated_samples + ) ax2 = fig.add_subplot(122) ax2.set_title("Distribution of Displacements") common_params = dict(density=True, bins=101, histtype="stepfilled", alpha=0.25) - ax2.hist(exact_samples_displacements, **common_params, label='Exact Samples Displacements', color='green') - ax2.hist(generated_samples_displacements, **common_params, label='Generated Samples Displacements', color='red') - - ax2.set_xlabel('$x - x_0$') - ax2.set_ylabel('Density') - ax2.legend(loc='upper right', fancybox=True, shadow=True, ncol=1, fontsize=8) + ax2.hist( + exact_samples_displacements, + **common_params, + label="Exact Samples Displacements", + color="green", + ) + ax2.hist( + generated_samples_displacements, + **common_params, + label="Generated Samples Displacements", + color="red", + ) + + ax2.set_xlabel("$x - x_0$") + ax2.set_ylabel("Density") + ax2.legend(loc="upper right", fancybox=True, shadow=True, ncol=1, fontsize=8) fig.tight_layout() fig.savefig(output_dir / f"sde_solution_sigma_d={sigma_d}.png") diff --git a/experiments/sampling_sota_model/plot_repaint_score_trajectories.py b/experiments/sampling_sota_model/plot_repaint_score_trajectories.py index c9f6252a..c8e054ea 100644 --- a/experiments/sampling_sota_model/plot_repaint_score_trajectories.py +++ b/experiments/sampling_sota_model/plot_repaint_score_trajectories.py @@ -6,9 +6,12 @@ import matplotlib.pyplot as plt import numpy as np import torch -from crystal_diffusion.utils.logging_utils import setup_analysis_logger from einops import einops -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH + +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) +from diffusion_for_multi_scale_molecular_dynamics.utils.logging_utils import \ + setup_analysis_logger logger = logging.getLogger(__name__) setup_analysis_logger() @@ -19,17 +22,19 @@ energy_pickle_path = base_path / "energies.pt" -unconstrained_energy_pickle_path = Path("/Users/bruno/courtois/draw_sota_samples/1x1x1/LANGEVIN/energy_samples.pt") +unconstrained_energy_pickle_path = Path( + "/Users/bruno/courtois/draw_sota_samples/1x1x1/LANGEVIN/energy_samples.pt" +) number_of_constrained_coordinates = 9 plt.style.use(PLOT_STYLE_PATH) -if __name__ == '__main__': +if __name__ == "__main__": logger.info("Extracting data artifacts") - with open(data_pickle_path, 'rb') as fd: - recorded_data = torch.load(fd, map_location=torch.device('cpu')) + with open(data_pickle_path, "rb") as fd: + recorded_data = torch.load(fd, map_location=torch.device("cpu")) repaint_energies = torch.load(energy_pickle_path) unconstrained_energies = torch.load(unconstrained_energy_pickle_path) @@ -37,72 +42,113 @@ # ============================================================== logger.info("Plotting energy distributions") fig0 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig0.suptitle('Comparing Unconstrained and Repaint Sample Energy Distributions') + fig0.suptitle("Comparing Unconstrained and Repaint Sample Energy Distributions") common_params = dict(density=True, bins=80, histtype="stepfilled", alpha=0.25) ax1 = fig0.add_subplot(111) - ax1.hist(unconstrained_energies, **common_params, label='Unconstrained Energies', color='green') - ax1.hist(repaint_energies, **common_params, label='Repaint Energies', color='red') - - ax1.set_xlabel('Energy (eV)') - ax1.set_ylabel('Density') - ax1.legend(loc='upper right', fancybox=True, shadow=True, ncol=1, fontsize=12) - ax1.set_yscale('log') + ax1.hist( + unconstrained_energies, + **common_params, + label="Unconstrained Energies", + color="green", + ) + ax1.hist(repaint_energies, **common_params, label="Repaint Energies", color="red") + + ax1.set_xlabel("Energy (eV)") + ax1.set_ylabel("Density") + ax1.legend(loc="upper right", fancybox=True, shadow=True, ncol=1, fontsize=12) + ax1.set_yscale("log") fig0.tight_layout() - fig0.savefig(base_path / "comparing_energy_distributions_unconstrained_vs_repaint.png") + fig0.savefig( + base_path / "comparing_energy_distributions_unconstrained_vs_repaint.png" + ) plt.close(fig0) - sampling_times = recorded_data['time'] - relative_coordinates = recorded_data['relative_coordinates'] - batch_flat_relative_coordinates = einops.rearrange(relative_coordinates, "b t n d -> b t (n d)") + sampling_times = recorded_data["time"] + relative_coordinates = recorded_data["relative_coordinates"] + batch_flat_relative_coordinates = einops.rearrange( + relative_coordinates, "b t n d -> b t (n d)" + ) - normalized_scores = recorded_data['normalized_scores'] - batch_flat_normalized_scores = einops.rearrange(normalized_scores, "b t n d -> b t (n d)") + normalized_scores = recorded_data["normalized_scores"] + batch_flat_normalized_scores = einops.rearrange( + normalized_scores, "b t n d -> b t (n d)" + ) logger.info("Plotting scores along trajectories") fig = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig.suptitle('Root Mean Square Normalized Scores Along Sample Trajectories') - rms_norm_score = (batch_flat_normalized_scores ** 2).mean(dim=-1).sqrt().numpy() + fig.suptitle("Root Mean Square Normalized Scores Along Sample Trajectories") + rms_norm_score = (batch_flat_normalized_scores**2).mean(dim=-1).sqrt().numpy() constrained_rms_norm_score = ( - (batch_flat_normalized_scores[:, :, :number_of_constrained_coordinates] ** 2).mean(dim=-1).sqrt().numpy()) + (batch_flat_normalized_scores[:, :, :number_of_constrained_coordinates] ** 2) + .mean(dim=-1) + .sqrt() + .numpy() + ) free_rms_norm_score = ( - (batch_flat_normalized_scores[:, :, number_of_constrained_coordinates:] ** 2).mean(dim=-1).sqrt().numpy()) + (batch_flat_normalized_scores[:, :, number_of_constrained_coordinates:] ** 2) + .mean(dim=-1) + .sqrt() + .numpy() + ) ax1 = fig.add_subplot(221) ax2 = fig.add_subplot(222) ax3 = fig.add_subplot(223) ax4 = fig.add_subplot(224) - for ax, scores in zip([ax1, ax2, ax3, ax4], - [rms_norm_score, rms_norm_score, constrained_rms_norm_score, free_rms_norm_score]): + for ax, scores in zip( + [ax1, ax2, ax3, ax4], + [ + rms_norm_score, + rms_norm_score, + constrained_rms_norm_score, + free_rms_norm_score, + ], + ): for y in scores[::10]: - ax.plot(sampling_times, y, '-', color='gray', alpha=0.2, label='__nolabel__') + ax.plot( + sampling_times, y, "-", color="gray", alpha=0.2, label="__nolabel__" + ) list_quantiles = [0.0, 0.10, 0.5, 1.0] - list_colors = ['green', 'yellow', 'orange', 'red'] + list_colors = ["green", "yellow", "orange", "red"] for q, c in zip(list_quantiles, list_colors): energy_quantile = np.quantile(repaint_energies, q) idx = np.argmin(np.abs(repaint_energies - energy_quantile)) e = repaint_energies[idx] - for ax, scores in zip([ax1, ax2, ax3, ax4], - [rms_norm_score, rms_norm_score, constrained_rms_norm_score, free_rms_norm_score]): - ax.plot(sampling_times, scores[idx], '-', - color=c, alpha=1., label=f'Q = {100 * q:2.0f}%, Energy: {e:5.1f}') + for ax, scores in zip( + [ax1, ax2, ax3, ax4], + [ + rms_norm_score, + rms_norm_score, + constrained_rms_norm_score, + free_rms_norm_score, + ], + ): + ax.plot( + sampling_times, + scores[idx], + "-", + color=c, + alpha=1.0, + label=f"Q = {100 * q:2.0f}%, Energy: {e:5.1f}", + ) for ax in [ax1, ax2, ax3, ax4]: - ax.set_xlabel('Diffusion Time') - ax.set_ylabel(r'$\sqrt{\langle (\sigma(t) S_{\theta} )^2 \rangle}$') + ax.set_xlabel("Diffusion Time") + ax.set_ylabel(r"$\sqrt{\langle (\sigma(t) S_{\theta} )^2 \rangle}$") ax.set_xlim(1, 0) for ax in [ax2, ax3, ax4]: - ax.set_yscale('log') + ax.set_yscale("log") ax1.set_title("All Coordinates (Not Log Scale)") - ax1.set_ylim(ymin=0.) + ax1.set_ylim(ymin=0.0) ax2.set_title("All Coordinates") ax3.set_title("Restrained Coordinates") diff --git a/experiments/sampling_sota_model/repaint_with_sota_score.py b/experiments/sampling_sota_model/repaint_with_sota_score.py index 9a96d80d..935781b3 100644 --- a/experiments/sampling_sota_model/repaint_with_sota_score.py +++ b/experiments/sampling_sota_model/repaint_with_sota_score.py @@ -6,15 +6,20 @@ import numpy as np import torch import yaml -from crystal_diffusion import DATA_DIR -from crystal_diffusion.generators.constrained_langevin_generator import ( + +from diffusion_for_multi_scale_molecular_dynamics import DATA_DIR +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) +from diffusion_for_multi_scale_molecular_dynamics.generators.constrained_langevin_generator import ( ConstrainedLangevinGenerator, ConstrainedLangevinGeneratorParameters) -from crystal_diffusion.models.instantiate_diffusion_model import \ +from diffusion_for_multi_scale_molecular_dynamics.models.instantiate_diffusion_model import \ load_diffusion_model -from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps -from crystal_diffusion.utils.logging_utils import setup_analysis_logger -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters +from diffusion_for_multi_scale_molecular_dynamics.oracle.lammps import \ + get_energy_and_forces_from_lammps +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters +from diffusion_for_multi_scale_molecular_dynamics.utils.logging_utils import \ + setup_analysis_logger logger = logging.getLogger(__name__) setup_analysis_logger() @@ -30,7 +35,7 @@ plt.style.use(PLOT_STYLE_PATH) -device = torch.device('cuda') +device = torch.device("cuda") number_of_samples = 1000 total_time_steps = 100 @@ -42,24 +47,24 @@ number_of_atoms, spatial_dimension = 8, 3 atom_types = np.ones(number_of_atoms, dtype=int) -constrained_relative_coordinates = np.array([[0.5, 0.5, 0.25], - [0.5, 0.5, 0.5], - [0.5, 0.5, 0.75]], dtype=np.float32) +constrained_relative_coordinates = np.array( + [[0.5, 0.5, 0.25], [0.5, 0.5, 0.5], [0.5, 0.5, 0.75]], dtype=np.float32 +) -if __name__ == '__main__': +if __name__ == "__main__": logger.info("Setting up parameters") unit_cells = torch.Tensor(box).repeat(number_of_samples, 1, 1).to(device) - noise_parameters = NoiseParameters(total_time_steps=total_time_steps, - sigma_min=0.001, - sigma_max=0.5) + noise_parameters = NoiseParameters( + total_time_steps=total_time_steps, sigma_min=0.001, sigma_max=0.5 + ) logger.info("Loading state dict") - with open(str(state_dict_path), 'rb') as fd: + with open(str(state_dict_path), "rb") as fd: state_dict = torch.load(fd) - with open(str(config_path), 'r') as fd: + with open(str(config_path), "r") as fd: hyper_params = yaml.load(fd, Loader=yaml.FullLoader) logger.info("Instantiate model") pl_model = load_diffusion_model(hyper_params) @@ -76,24 +81,28 @@ number_of_samples=number_of_samples, cell_dimensions=3 * [acell], constrained_relative_coordinates=constrained_relative_coordinates, - record_samples=True) + record_samples=True, + ) position_generator = ConstrainedLangevinGenerator( noise_parameters=noise_parameters, sampling_parameters=sampling_parameters, - sigma_normalized_score_network=sigma_normalized_score_network) + sigma_normalized_score_network=sigma_normalized_score_network, + ) logger.info("Drawing constrained samples") with torch.no_grad(): - samples = position_generator.sample(number_of_samples=number_of_samples, - device=device, - unit_cell=unit_cells) + samples = position_generator.sample( + number_of_samples=number_of_samples, device=device, unit_cell=unit_cells + ) batch_relative_positions = samples.cpu().numpy() batch_positions = np.dot(batch_relative_positions, box) - position_generator.sample_trajectory_recorder.write_to_pickle(repaint_dir / "repaint_trajectories.pkl") + position_generator.sample_trajectory_recorder.write_to_pickle( + repaint_dir / "repaint_trajectories.pkl" + ) logger.info("Compute energy from Oracle") list_energy = [] @@ -102,11 +111,13 @@ lammps_work_directory.mkdir(exist_ok=True) for idx, positions in enumerate(batch_positions): - energy, forces = get_energy_and_forces_from_lammps(positions, - box, - atom_types, - tmp_work_dir=str(lammps_work_directory), - pair_coeff_dir=DATA_DIR) + energy, forces = get_energy_and_forces_from_lammps( + positions, + box, + atom_types, + tmp_work_dir=str(lammps_work_directory), + pair_coeff_dir=DATA_DIR, + ) list_energy.append(energy) src = os.path.join(lammps_work_directory, "dump.yaml") dst = os.path.join(lammps_work_directory, f"dump_{idx}.yaml") @@ -119,16 +130,16 @@ logger.info("Plotting energy distributions") fig = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig.suptitle('Energy Distribution for Repaint Structures,') + fig.suptitle("Energy Distribution for Repaint Structures,") common_params = dict(density=True, bins=50, histtype="stepfilled", alpha=0.25) ax1 = fig.add_subplot(111) - ax1.hist(energies, **common_params, label='Sampled Energies', color='red') + ax1.hist(energies, **common_params, label="Sampled Energies", color="red") - ax1.set_xlabel('Energy (eV)') - ax1.set_ylabel('Density') - ax1.legend(loc='upper right', fancybox=True, shadow=True, ncol=1, fontsize=12) + ax1.set_xlabel("Energy (eV)") + ax1.set_ylabel("Density") + ax1.legend(loc="upper right", fancybox=True, shadow=True, ncol=1, fontsize=12) fig.tight_layout() fig.savefig(repaint_dir / f"energy_samples_repaint_{number_of_atoms}_atoms.png") plt.close(fig) diff --git a/experiments/sampling_sota_model/sota_score_sampling_and_plotting.py b/experiments/sampling_sota_model/sota_score_sampling_and_plotting.py index 2867eb28..be33daab 100644 --- a/experiments/sampling_sota_model/sota_score_sampling_and_plotting.py +++ b/experiments/sampling_sota_model/sota_score_sampling_and_plotting.py @@ -8,19 +8,25 @@ import numpy as np import torch import yaml -from crystal_diffusion import DATA_DIR -from crystal_diffusion.generators.langevin_generator import LangevinGenerator -from crystal_diffusion.generators.ode_position_generator import ( +from einops import einops + +from diffusion_for_multi_scale_molecular_dynamics import DATA_DIR +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) +from diffusion_for_multi_scale_molecular_dynamics.generators.langevin_generator import \ + LangevinGenerator +from diffusion_for_multi_scale_molecular_dynamics.generators.ode_position_generator import ( ExplodingVarianceODEPositionGenerator, ODESamplingParameters) -from crystal_diffusion.generators.predictor_corrector_position_generator import \ +from diffusion_for_multi_scale_molecular_dynamics.generators.predictor_corrector_position_generator import \ PredictorCorrectorSamplingParameters -from crystal_diffusion.models.instantiate_diffusion_model import \ +from diffusion_for_multi_scale_molecular_dynamics.models.instantiate_diffusion_model import \ load_diffusion_model -from crystal_diffusion.oracle.lammps import get_energy_and_forces_from_lammps -from crystal_diffusion.utils.logging_utils import setup_analysis_logger -from einops import einops -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters +from diffusion_for_multi_scale_molecular_dynamics.oracle.lammps import \ + get_energy_and_forces_from_lammps +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters +from diffusion_for_multi_scale_molecular_dynamics.utils.logging_utils import \ + setup_analysis_logger logger = logging.getLogger(__name__) setup_analysis_logger() @@ -30,18 +36,20 @@ state_dict_path = model_dir / "last_model-epoch=199-step=019600_state_dict.ckpt" config_path = model_dir / "config_backup.yaml" -SOTA_SCORE_RESULTS_DIR = Path("/home/mila/r/rousseab/experiments/draw_sota_samples/figures") +SOTA_SCORE_RESULTS_DIR = Path( + "/home/mila/r/rousseab/experiments/draw_sota_samples/figures" +) SOTA_SCORE_RESULTS_DIR.mkdir(exist_ok=True) plt.style.use(PLOT_STYLE_PATH) -device = torch.device('cuda') +device = torch.device("cuda") # Change these parameters as needed! # sampling_algorithm = 'ode' -sampling_algorithm = 'langevin' +sampling_algorithm = "langevin" spatial_dimension = 3 number_of_atoms = 8 @@ -54,13 +62,13 @@ total_time_steps = 100 number_of_corrector_steps = 1 -if __name__ == '__main__': +if __name__ == "__main__": logger.info("Loading state dict") - with open(str(state_dict_path), 'rb') as fd: + with open(str(state_dict_path), "rb") as fd: state_dict = torch.load(fd) - with open(str(config_path), 'r') as fd: + with open(str(config_path), "r") as fd: hyper_params = yaml.load(fd, Loader=yaml.FullLoader) logger.info("Instantiate model") pl_model = load_diffusion_model(hyper_params) @@ -71,37 +79,42 @@ sigma_normalized_score_network = pl_model.sigma_normalized_score_network logger.info("Setting up parameters") - noise_parameters = NoiseParameters(total_time_steps=total_time_steps, - sigma_min=0.001, - sigma_max=0.5) - - if sampling_algorithm == 'ode': - ode_sampling_parameters = ODESamplingParameters(spatial_dimension=spatial_dimension, - number_of_atoms=number_of_atoms, - number_of_samples=number_of_samples, - cell_dimensions=[acell, acell, acell], - record_samples=True, - absolute_solver_tolerance=1.0e-5, - relative_solver_tolerance=1.0e-5) - - position_generator = ( - ExplodingVarianceODEPositionGenerator(noise_parameters=noise_parameters, - sampling_parameters=ode_sampling_parameters, - sigma_normalized_score_network=sigma_normalized_score_network)) - - elif sampling_algorithm == 'langevin': + noise_parameters = NoiseParameters( + total_time_steps=total_time_steps, sigma_min=0.001, sigma_max=0.5 + ) + + if sampling_algorithm == "ode": + ode_sampling_parameters = ODESamplingParameters( + spatial_dimension=spatial_dimension, + number_of_atoms=number_of_atoms, + number_of_samples=number_of_samples, + cell_dimensions=[acell, acell, acell], + record_samples=True, + absolute_solver_tolerance=1.0e-5, + relative_solver_tolerance=1.0e-5, + ) + + position_generator = ExplodingVarianceODEPositionGenerator( + noise_parameters=noise_parameters, + sampling_parameters=ode_sampling_parameters, + sigma_normalized_score_network=sigma_normalized_score_network, + ) + + elif sampling_algorithm == "langevin": pc_sampling_parameters = PredictorCorrectorSamplingParameters( number_of_corrector_steps=number_of_corrector_steps, spatial_dimension=spatial_dimension, number_of_atoms=number_of_atoms, number_of_samples=number_of_samples, cell_dimensions=[acell, acell, acell], - record_samples=True) + record_samples=True, + ) position_generator = LangevinGenerator( noise_parameters=noise_parameters, sampling_parameters=pc_sampling_parameters, - sigma_normalized_score_network=sigma_normalized_score_network) + sigma_normalized_score_network=sigma_normalized_score_network, + ) # Draw some samples, create some plots unit_cells = torch.Tensor(box).repeat(number_of_samples, 1, 1).to(device) @@ -109,9 +122,9 @@ logger.info("Drawing samples") with torch.no_grad(): - samples = position_generator.sample(number_of_samples=number_of_samples, - device=device, - unit_cell=unit_cells) + samples = position_generator.sample( + number_of_samples=number_of_samples, device=device, unit_cell=unit_cells + ) batch_relative_positions = samples.cpu().numpy() batch_positions = np.dot(batch_relative_positions, box) @@ -120,15 +133,17 @@ logger.info("Compute energy from Oracle") with tempfile.TemporaryDirectory() as lammps_work_directory: for idx, positions in enumerate(batch_positions): - energy, forces = get_energy_and_forces_from_lammps(positions, - box, - atom_types, - tmp_work_dir=lammps_work_directory, - pair_coeff_dir=DATA_DIR) + energy, forces = get_energy_and_forces_from_lammps( + positions, + box, + atom_types, + tmp_work_dir=lammps_work_directory, + pair_coeff_dir=DATA_DIR, + ) list_energy.append(energy) energies = np.array(list_energy) - if sampling_algorithm == 'ode': + if sampling_algorithm == "ode": # Plot the ODE parameters logger.info("Plotting ODE parameters") times = torch.linspace(0, 1, 1001) @@ -136,19 +151,19 @@ ode_prefactor = position_generator._get_ode_prefactor(sigmas) fig0 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig0.suptitle('ODE parameters') + fig0.suptitle("ODE parameters") ax1 = fig0.add_subplot(121) ax2 = fig0.add_subplot(122) - ax1.set_title('$\\sigma$ Parameter') - ax2.set_title('$\\gamma$ Parameter') - ax1.plot(times, sigmas, '-') - ax2.plot(times, ode_prefactor, '-') + ax1.set_title("$\\sigma$ Parameter") + ax2.set_title("$\\gamma$ Parameter") + ax1.plot(times, sigmas, "-") + ax2.plot(times, ode_prefactor, "-") - ax1.set_ylabel('$\\sigma(t)$') - ax2.set_ylabel('$\\gamma(t)$') + ax1.set_ylabel("$\\sigma(t)$") + ax2.set_ylabel("$\\gamma(t)$") for ax in [ax1, ax2]: - ax.set_xlabel('Diffusion Time') + ax.set_xlabel("Diffusion Time") ax.set_xlim([-0.01, 1.01]) fig0.tight_layout() @@ -157,103 +172,124 @@ logger.info("Extracting data artifacts") raw_data = position_generator.sample_trajectory_recorder.data - recorded_data = position_generator.sample_trajectory_recorder.standardize_data(raw_data) + recorded_data = position_generator.sample_trajectory_recorder.standardize_data( + raw_data + ) - sampling_times = recorded_data['time'].cpu() - relative_coordinates = recorded_data['relative_coordinates'] - batch_flat_relative_coordinates = einops.rearrange(relative_coordinates, "b t n d -> b t (n d)") + sampling_times = recorded_data["time"].cpu() + relative_coordinates = recorded_data["relative_coordinates"] + batch_flat_relative_coordinates = einops.rearrange( + relative_coordinates, "b t n d -> b t (n d)" + ) - normalized_scores = recorded_data['normalized_scores'] - batch_flat_normalized_scores = einops.rearrange(normalized_scores, "b t n d -> b t (n d)") + normalized_scores = recorded_data["normalized_scores"] + batch_flat_normalized_scores = einops.rearrange( + normalized_scores, "b t n d -> b t (n d)" + ) # ============================================================================ logger.info("Plotting relative coordinates trajectories") fig1 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig1.suptitle('Sampling Trajectories') + fig1.suptitle("Sampling Trajectories") ax = fig1.add_subplot(111) - ax.set_xlabel('Diffusion Time') - ax.set_ylabel('Raw Relative Coordinate') + ax.set_xlabel("Diffusion Time") + ax.set_ylabel("Raw Relative Coordinate") ax.yaxis.tick_right() - ax.spines['top'].set_visible(True) - ax.spines['right'].set_visible(True) - ax.spines['bottom'].set_visible(True) - ax.spines['left'].set_visible(True) + ax.spines["top"].set_visible(True) + ax.spines["right"].set_visible(True) + ax.spines["bottom"].set_visible(True) + ax.spines["left"].set_visible(True) alpha = 1.0 for flat_relative_coordinates in batch_flat_relative_coordinates[::100]: for i in range(number_of_atoms * spatial_dimension): coordinate = flat_relative_coordinates[:, i].cpu() - ax.plot(sampling_times, coordinate, '-', color='b', alpha=alpha) + ax.plot(sampling_times, coordinate, "-", color="b", alpha=alpha) alpha = 0.05 ax.set_xlim([1.01, -0.01]) - fig1.savefig(SOTA_SCORE_RESULTS_DIR / f"sampling_trajectories_{sampling_algorithm}_{number_of_atoms}_atoms.png") + fig1.savefig( + SOTA_SCORE_RESULTS_DIR + / f"sampling_trajectories_{sampling_algorithm}_{number_of_atoms}_atoms.png" + ) plt.close(fig1) # ======================== Figure 2 ====================================== logger.info("Plotting scores along trajectories") fig2 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig2.suptitle('Root Mean Squared Normalized Scores Along Sample Trajectories') - rms_norm_score = (batch_flat_normalized_scores ** 2).mean(dim=-1).sqrt().cpu().numpy() + fig2.suptitle("Root Mean Squared Normalized Scores Along Sample Trajectories") + rms_norm_score = (batch_flat_normalized_scores**2).mean(dim=-1).sqrt().cpu().numpy() ax1 = fig2.add_subplot(121) ax2 = fig2.add_subplot(122) for y in rms_norm_score[::10]: for ax in [ax1, ax2]: - ax.plot(sampling_times, y, '-', color='gray', alpha=0.2, label='__nolabel__') + ax.plot( + sampling_times, y, "-", color="gray", alpha=0.2, label="__nolabel__" + ) list_quantiles = [0.0, 0.10, 0.5, 1.0] - list_colors = ['green', 'yellow', 'orange', 'red'] + list_colors = ["green", "yellow", "orange", "red"] for q, c in zip(list_quantiles, list_colors): energy_quantile = np.quantile(energies, q) idx = np.argmin(np.abs(energies - energy_quantile)) e = energies[idx] for ax in [ax1, ax2]: - ax.plot(sampling_times, rms_norm_score[idx], '-', - color=c, alpha=1., label=f'{100 * q:2.0f}% Percentile Energy: {e:5.1f}') + ax.plot( + sampling_times, + rms_norm_score[idx], + "-", + color=c, + alpha=1.0, + label=f"{100 * q:2.0f}% Percentile Energy: {e:5.1f}", + ) for ax in [ax1, ax2]: - ax.set_xlabel('Diffusion Time') - ax.set_ylabel(r'$ \sqrt{\langle (\sigma(t) S_{\theta} )^2\rangle}$') + ax.set_xlabel("Diffusion Time") + ax.set_ylabel(r"$ \sqrt{\langle (\sigma(t) S_{\theta} )^2\rangle}$") ax.set_xlim(1, 0) ax1.legend(loc=0, fontsize=6) - ax1.set_yscale('log') + ax1.set_yscale("log") fig2.savefig( - SOTA_SCORE_RESULTS_DIR / f"sampling_score_trajectories_{sampling_algorithm}_{number_of_atoms}_atoms.png") + SOTA_SCORE_RESULTS_DIR + / f"sampling_score_trajectories_{sampling_algorithm}_{number_of_atoms}_atoms.png" + ) plt.close(fig2) # ======================== Figure 3 ====================================== logger.info("Plotting Marginal distribution in 2D") fig3 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig3.suptitle('Sampling Marginal Distributions') - ax1 = fig3.add_subplot(131, aspect='equal') - ax2 = fig3.add_subplot(132, aspect='equal') - ax3 = fig3.add_subplot(133, aspect='equal') + fig3.suptitle("Sampling Marginal Distributions") + ax1 = fig3.add_subplot(131, aspect="equal") + ax2 = fig3.add_subplot(132, aspect="equal") + ax3 = fig3.add_subplot(133, aspect="equal") - xs = einops.rearrange(samples, 'b n d -> (b n) d').cpu() - ax1.set_title('XY Projection') - ax1.plot(xs[:, 0], xs[:, 1], 'ro', alpha=0.5, mew=0, label='ODE Solver') + xs = einops.rearrange(samples, "b n d -> (b n) d").cpu() + ax1.set_title("XY Projection") + ax1.plot(xs[:, 0], xs[:, 1], "ro", alpha=0.5, mew=0, label="ODE Solver") - ax2.set_title('XZ Projection') - ax2.plot(xs[:, 0], xs[:, 2], 'ro', alpha=0.5, mew=0, label='ODE Solver') + ax2.set_title("XZ Projection") + ax2.plot(xs[:, 0], xs[:, 2], "ro", alpha=0.5, mew=0, label="ODE Solver") - ax3.set_title('YZ Projection') - ax3.plot(xs[:, 1], xs[:, 2], 'ro', alpha=0.5, mew=0, label='ODE Solver') + ax3.set_title("YZ Projection") + ax3.plot(xs[:, 1], xs[:, 2], "ro", alpha=0.5, mew=0, label="ODE Solver") for ax in [ax1, ax2, ax3]: ax.set_xlim(-0.01, 1.01) ax.set_ylim(-0.01, 1.01) - ax.vlines(x=[0, 1], ymin=0, ymax=1, color='k', lw=2) - ax.hlines(y=[0, 1], xmin=0, xmax=1, color='k', lw=2) + ax.vlines(x=[0, 1], ymin=0, ymax=1, color="k", lw=2) + ax.hlines(y=[0, 1], xmin=0, xmax=1, color="k", lw=2) ax1.legend(loc=0) fig3.tight_layout() fig3.savefig( - SOTA_SCORE_RESULTS_DIR / f"marginal_2D_distributions_{sampling_algorithm}_{number_of_atoms}_atoms.png") + SOTA_SCORE_RESULTS_DIR + / f"marginal_2D_distributions_{sampling_algorithm}_{number_of_atoms}_atoms.png" + ) plt.close(fig3) # ======================== Figure 4 ====================================== @@ -262,42 +298,46 @@ ax1 = fig4.add_subplot(131) ax2 = fig4.add_subplot(132) ax3 = fig4.add_subplot(133) - fig4.suptitle('Comparing Sampling and Expected Marginal Distributions') + fig4.suptitle("Comparing Sampling and Expected Marginal Distributions") - common_params = dict(histtype='stepfilled', alpha=0.5, bins=50) + common_params = dict(histtype="stepfilled", alpha=0.5, bins=50) - ax1.hist(xs[:, 0], **common_params, facecolor='r') - ax2.hist(xs[:, 1], **common_params, facecolor='r') - ax3.hist(xs[:, 2], **common_params, facecolor='r') + ax1.hist(xs[:, 0], **common_params, facecolor="r") + ax2.hist(xs[:, 1], **common_params, facecolor="r") + ax3.hist(xs[:, 2], **common_params, facecolor="r") - ax1.set_xlabel('X') - ax2.set_xlabel('Y') - ax3.set_xlabel('Z') + ax1.set_xlabel("X") + ax2.set_xlabel("Y") + ax3.set_xlabel("Z") for ax in [ax1, ax2, ax3]: ax.set_xlim(-0.01, 1.01) fig4.tight_layout() fig4.savefig( - SOTA_SCORE_RESULTS_DIR / f"marginal_1D_distributions_{sampling_algorithm}_{number_of_atoms}_atoms.png") + SOTA_SCORE_RESULTS_DIR + / f"marginal_1D_distributions_{sampling_algorithm}_{number_of_atoms}_atoms.png" + ) plt.close(fig4) # ======================== Figure 5 ====================================== logger.info("Plotting energy distributions") fig5 = plt.figure(figsize=PLEASANT_FIG_SIZE) - fig5.suptitle(f'Energy Distribution, sampling algorithm {sampling_algorithm}') + fig5.suptitle(f"Energy Distribution, sampling algorithm {sampling_algorithm}") common_params = dict(density=True, bins=50, histtype="stepfilled", alpha=0.25) ax1 = fig5.add_subplot(111) - ax1.hist(energies, **common_params, label='Sampled Energies', color='red') + ax1.hist(energies, **common_params, label="Sampled Energies", color="red") - ax1.set_xlabel('Energy (eV)') - ax1.set_ylabel('Density') - ax1.legend(loc='upper right', fancybox=True, shadow=True, ncol=1, fontsize=12) + ax1.set_xlabel("Energy (eV)") + ax1.set_ylabel("Density") + ax1.legend(loc="upper right", fancybox=True, shadow=True, ncol=1, fontsize=12) fig5.tight_layout() fig5.savefig( - SOTA_SCORE_RESULTS_DIR / f"energy_samples_{sampling_algorithm}_{number_of_atoms}_atoms.png") + SOTA_SCORE_RESULTS_DIR + / f"energy_samples_{sampling_algorithm}_{number_of_atoms}_atoms.png" + ) plt.close(fig5) logger.info("Done!") diff --git a/experiments/score_stability_analysis/draw_samples_from_equilibrium.py b/experiments/score_stability_analysis/draw_samples_from_equilibrium.py index 303f92fb..aa54a67a 100644 --- a/experiments/score_stability_analysis/draw_samples_from_equilibrium.py +++ b/experiments/score_stability_analysis/draw_samples_from_equilibrium.py @@ -4,20 +4,25 @@ import matplotlib.pyplot as plt import numpy as np import torch -from crystal_diffusion.generators.langevin_generator import LangevinGenerator -from crystal_diffusion.generators.ode_position_generator import ( + +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) +from diffusion_for_multi_scale_molecular_dynamics.generators.langevin_generator import \ + LangevinGenerator +from diffusion_for_multi_scale_molecular_dynamics.generators.ode_position_generator import ( ExplodingVarianceODEPositionGenerator, ODESamplingParameters) -from crystal_diffusion.generators.predictor_corrector_position_generator import \ +from diffusion_for_multi_scale_molecular_dynamics.generators.predictor_corrector_position_generator import \ PredictorCorrectorSamplingParameters -from crystal_diffusion.generators.sde_position_generator import ( +from diffusion_for_multi_scale_molecular_dynamics.generators.sde_position_generator import ( ExplodingVarianceSDEPositionGenerator, SDESamplingParameters) -from crystal_diffusion.models.position_diffusion_lightning_model import \ +from diffusion_for_multi_scale_molecular_dynamics.models.position_diffusion_lightning_model import \ PositionDiffusionLightningModel -from crystal_diffusion.models.score_networks import ScoreNetwork -from crystal_diffusion.utils.logging_utils import setup_analysis_logger -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters - +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks import \ + ScoreNetwork +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters +from diffusion_for_multi_scale_molecular_dynamics.utils.logging_utils import \ + setup_analysis_logger from experiments.analysis.analytic_score.exploring_langevin_generator.generate_sample_energies import \ EnergyCalculator from experiments.analysis.analytic_score.utils import get_silicon_supercell @@ -30,6 +35,7 @@ class ForcedStartingPointLangevinGenerator(LangevinGenerator): """Langevin Generator with Forced Starting point.""" + def __init__( self, noise_parameters: NoiseParameters, @@ -56,6 +62,7 @@ def initialize(self, number_of_samples: int): class ForcedStartingPointODEPositionGenerator(ExplodingVarianceODEPositionGenerator): """Forced starting point ODE position generator.""" + def __init__( self, noise_parameters: NoiseParameters, @@ -82,6 +89,7 @@ def initialize(self, number_of_samples: int): class ForcedStartingPointSDEPositionGenerator(ExplodingVarianceSDEPositionGenerator): """Forced Starting Point SDE position generator.""" + def __init__( self, noise_parameters: NoiseParameters, diff --git a/experiments/score_stability_analysis/plot_hessian_eigenvalues.py b/experiments/score_stability_analysis/plot_hessian_eigenvalues.py index b9508d33..543a9deb 100644 --- a/experiments/score_stability_analysis/plot_hessian_eigenvalues.py +++ b/experiments/score_stability_analysis/plot_hessian_eigenvalues.py @@ -5,15 +5,19 @@ import matplotlib.pyplot as plt import numpy as np import torch -from crystal_diffusion.models.position_diffusion_lightning_model import \ - PositionDiffusionLightningModel -from crystal_diffusion.samplers.exploding_variance import ExplodingVariance -from crystal_diffusion.utils.logging_utils import setup_analysis_logger -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters from torch.func import jacrev from tqdm import tqdm +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) +from diffusion_for_multi_scale_molecular_dynamics.models.position_diffusion_lightning_model import \ + PositionDiffusionLightningModel +from diffusion_for_multi_scale_molecular_dynamics.samplers.exploding_variance import \ + ExplodingVariance +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters +from diffusion_for_multi_scale_molecular_dynamics.utils.logging_utils import \ + setup_analysis_logger from experiments import get_normalized_score_function from experiments.analysis.analytic_score.utils import get_silicon_supercell diff --git a/experiments/score_stability_analysis/plot_score_norm.py b/experiments/score_stability_analysis/plot_score_norm.py index 1cfd8399..706e6bbd 100644 --- a/experiments/score_stability_analysis/plot_score_norm.py +++ b/experiments/score_stability_analysis/plot_score_norm.py @@ -4,16 +4,20 @@ import matplotlib.pyplot as plt import numpy as np import torch -from crystal_diffusion.models.position_diffusion_lightning_model import \ - PositionDiffusionLightningModel -from crystal_diffusion.samplers.exploding_variance import ExplodingVariance -from crystal_diffusion.utils.basis_transformations import \ - map_relative_coordinates_to_unit_cell -from crystal_diffusion.utils.logging_utils import setup_analysis_logger -from src.crystal_diffusion.analysis import PLEASANT_FIG_SIZE, PLOT_STYLE_PATH -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters from tqdm import tqdm +from diffusion_for_multi_scale_molecular_dynamics.analysis import ( + PLEASANT_FIG_SIZE, PLOT_STYLE_PATH) +from diffusion_for_multi_scale_molecular_dynamics.models.position_diffusion_lightning_model import \ + PositionDiffusionLightningModel +from diffusion_for_multi_scale_molecular_dynamics.samplers.exploding_variance import \ + ExplodingVariance +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters +from diffusion_for_multi_scale_molecular_dynamics.utils.basis_transformations import \ + map_relative_coordinates_to_unit_cell +from diffusion_for_multi_scale_molecular_dynamics.utils.logging_utils import \ + setup_analysis_logger from experiments import create_fixed_time_normalized_score_function from experiments.analysis.analytic_score.utils import get_silicon_supercell @@ -23,8 +27,10 @@ setup_analysis_logger() -checkpoint_path = ("/home/mila/r/rousseab/scratch/experiments/oct2_egnn_1x1x1/run1/" - "output/last_model/last_model-epoch=049-step=039100.ckpt") +checkpoint_path = ( + "/home/mila/r/rousseab/scratch/experiments/oct2_egnn_1x1x1/run1/" + "output/last_model/last_model-epoch=049-step=039100.ckpt" +) spatial_dimension = 3 number_of_atoms = 8 @@ -67,7 +73,7 @@ dv = equilibrium_relative_coordinates[0] - equilibrium_relative_coordinates[1] direction[0] = -0.5 * dv direction[1] = 0.5 * dv - list_delta = torch.linspace(0., 2.0, 201) + list_delta = torch.linspace(0.0, 2.0, 201) relative_coordinates = [] for delta in list_delta: diff --git a/experiments/score_stability_analysis/util.py b/experiments/score_stability_analysis/util.py index 842107de..373cc761 100644 --- a/experiments/score_stability_analysis/util.py +++ b/experiments/score_stability_analysis/util.py @@ -3,12 +3,15 @@ import einops import torch -from crystal_diffusion.models.score_networks import ScoreNetwork -from crystal_diffusion.namespace import (CARTESIAN_FORCES, NOISE, - NOISY_RELATIVE_COORDINATES, TIME, - UNIT_CELL) -from crystal_diffusion.samplers.exploding_variance import ExplodingVariance -from src.crystal_diffusion.samplers.variance_sampler import NoiseParameters + +from diffusion_for_multi_scale_molecular_dynamics.models.score_networks import \ + ScoreNetwork +from diffusion_for_multi_scale_molecular_dynamics.namespace import ( + CARTESIAN_FORCES, NOISE, NOISY_RELATIVE_COORDINATES, TIME, UNIT_CELL) +from diffusion_for_multi_scale_molecular_dynamics.samplers.exploding_variance import \ + ExplodingVariance +from diffusion_for_multi_scale_molecular_dynamics.samplers.variance_sampler import \ + NoiseParameters def get_normalized_score_function( From 87dcdcd3919d22f7f6bad4d50b189e9471d7caf0 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Tue, 22 Oct 2024 07:44:20 -0400 Subject: [PATCH 21/29] the toml file and uv lock --- pyproject.toml | 59 + uv.lock | 4932 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 4991 insertions(+) create mode 100644 pyproject.toml create mode 100644 uv.lock diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..49ab5007 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,59 @@ +[project] +name = "diffusion-for-multi-scale-molecular-dynamics" +version = "0.1.0" +description = "This project trains and samples from diffusion models adapted to periodic materials." +readme = "README.md" +requires-python = ">=3.10" +authors = [ + {name = "Simon Blackburn", email = "simon.blackburn@mila.quebec"}, + {name = "Bruno Rousseau", email = "rousseau.bruno@mila.quebec"}, +] +dependencies = [ + "black>=24.10.0", + "comet-ml>=3.47.0", + "datasets==2.17.1", + "deepdiff==7.0.1", + "einops==0.8.0", + "flake8-docstrings==1.6.0", + "flake8==4.0.1", + "gitpython==3.1.27", + "hydra-core==1.3.2", + "isort==5.13.2", + "jinja2==3.1.2", + "jupyter==1.0.0", + "kaleido==0.2.1", + "lammps>=2024.8.29.1.0", + "mace-torch==0.3.4", + "maml==2023.9.9", + "matplotlib==3.8.3", + "monty==2024.2.2", + "myst-parser==2.0.0", + "orion>=0.2.7", + "ovito==3.10.6.post2", + "pip>=24.2", + "pyarrow==15.0.1", + "pykeops==2.2.3", + "pymatgen==2024.2.23", + "pytest-cov==3.0.0", + "pytest-mock==3.12.0", + "pytest==7.1.2", + "pytorch-lightning>=2.4.0", + "pytype==2024.2.13", + "pyyaml==6.0.1", + "rich==13.7.1", + "sphinx-autoapi==3.0.0", + "sphinx-rtd-theme==2.0.0", + "sphinx==7.2.6", + "sphinxcontrib-katex==0.8.6", + "sphinxcontrib-napoleon==0.7", + "tensorboard==2.16.2", + "torch-geometric==2.5.3", + "torch==2.2.0", + "torchode==0.2.0", + "torchsde==0.2.6", + "tqdm==4.64.0", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..a4059de3 --- /dev/null +++ b/uv.lock @@ -0,0 +1,4932 @@ +version = 1 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version < '3.11'", + "python_full_version == '3.11.*'", + "python_full_version >= '3.12'", +] + +[[package]] +name = "absl-py" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/8f/fc001b92ecc467cc32ab38398bd0bfb45df46e7523bf33c2ad22a505f06e/absl-py-2.1.0.tar.gz", hash = "sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff", size = 118055 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/ad/e0d3c824784ff121c03cc031f944bc7e139a8f1870ffd2845cc2dd76f6c4/absl_py-2.1.0-py3-none-any.whl", hash = "sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308", size = 133706 }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/69/2f6d5a019bd02e920a3417689a89887b39ad1e350b562f9955693d900c40/aiohappyeyeballs-2.4.3.tar.gz", hash = "sha256:75cf88a15106a5002a8eb1dab212525c00d1f4c0fa96e551c9fbe6f09a621586", size = 21809 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/d8/120cd0fe3e8530df0539e71ba9683eade12cae103dd7543e50d15f737917/aiohappyeyeballs-2.4.3-py3-none-any.whl", hash = "sha256:8a7a83727b2756f394ab2895ea0765a0a8c475e3c71e98d43d76f22b4b435572", size = 14742 }, +] + +[[package]] +name = "aiohttp" +version = "3.10.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "async-timeout", marker = "python_full_version < '3.11'" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/17/7e/16e57e6cf20eb62481a2f9ce8674328407187950ccc602ad07c685279141/aiohttp-3.10.10.tar.gz", hash = "sha256:0631dd7c9f0822cc61c88586ca76d5b5ada26538097d0f1df510b082bad3411a", size = 7542993 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/dd/3d40c0e67e79c5c42671e3e268742f1ff96c6573ca43823563d01abd9475/aiohttp-3.10.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be7443669ae9c016b71f402e43208e13ddf00912f47f623ee5994e12fc7d4b3f", size = 586969 }, + { url = "https://files.pythonhosted.org/packages/75/64/8de41b5555e5b43ef6d4ed1261891d33fe45ecc6cb62875bfafb90b9ab93/aiohttp-3.10.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b06b7843929e41a94ea09eb1ce3927865387e3e23ebe108e0d0d09b08d25be9", size = 399367 }, + { url = "https://files.pythonhosted.org/packages/96/36/27bd62ea7ce43906d1443a73691823fc82ffb8fa03276b0e2f7e1037c286/aiohttp-3.10.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:333cf6cf8e65f6a1e06e9eb3e643a0c515bb850d470902274239fea02033e9a8", size = 390720 }, + { url = "https://files.pythonhosted.org/packages/e8/4d/d516b050d811ce0dd26325c383013c104ffa8b58bd361b82e52833f68e78/aiohttp-3.10.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:274cfa632350225ce3fdeb318c23b4a10ec25c0e2c880eff951a3842cf358ac1", size = 1228820 }, + { url = "https://files.pythonhosted.org/packages/53/94/964d9327a3e336d89aad52260836e4ec87fdfa1207176550fdf384eaffe7/aiohttp-3.10.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9e5e4a85bdb56d224f412d9c98ae4cbd032cc4f3161818f692cd81766eee65a", size = 1264616 }, + { url = "https://files.pythonhosted.org/packages/0c/20/70ce17764b685ca8f5bf4d568881b4e1f1f4ea5e8170f512fdb1a33859d2/aiohttp-3.10.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b606353da03edcc71130b52388d25f9a30a126e04caef1fd637e31683033abd", size = 1298402 }, + { url = "https://files.pythonhosted.org/packages/d1/d1/5248225ccc687f498d06c3bca5af2647a361c3687a85eb3aedcc247ee1aa/aiohttp-3.10.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab5a5a0c7a7991d90446a198689c0535be89bbd6b410a1f9a66688f0880ec026", size = 1222205 }, + { url = "https://files.pythonhosted.org/packages/f2/a3/9296b27cc5d4feadf970a14d0694902a49a985f3fae71b8322a5f77b0baa/aiohttp-3.10.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:578a4b875af3e0daaf1ac6fa983d93e0bbfec3ead753b6d6f33d467100cdc67b", size = 1193804 }, + { url = "https://files.pythonhosted.org/packages/d9/07/f3760160feb12ac51a6168a6da251a4a8f2a70733d49e6ceb9b3e6ee2f03/aiohttp-3.10.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8105fd8a890df77b76dd3054cddf01a879fc13e8af576805d667e0fa0224c35d", size = 1193544 }, + { url = "https://files.pythonhosted.org/packages/7e/4c/93a70f9a4ba1c30183a6dd68bfa79cddbf9a674f162f9c62e823a74a5515/aiohttp-3.10.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3bcd391d083f636c06a68715e69467963d1f9600f85ef556ea82e9ef25f043f7", size = 1193047 }, + { url = "https://files.pythonhosted.org/packages/ff/a3/36a1e23ff00c7a0cd696c5a28db05db25dc42bfc78c508bd78623ff62a4a/aiohttp-3.10.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fbc6264158392bad9df19537e872d476f7c57adf718944cc1e4495cbabf38e2a", size = 1247201 }, + { url = "https://files.pythonhosted.org/packages/55/ae/95399848557b98bb2c402d640b2276ce3a542b94dba202de5a5a1fe29abe/aiohttp-3.10.10-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e48d5021a84d341bcaf95c8460b152cfbad770d28e5fe14a768988c461b821bc", size = 1264102 }, + { url = "https://files.pythonhosted.org/packages/38/f5/02e5c72c1b60d7cceb30b982679a26167e84ac029fd35a93dd4da52c50a3/aiohttp-3.10.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2609e9ab08474702cc67b7702dbb8a80e392c54613ebe80db7e8dbdb79837c68", size = 1215760 }, + { url = "https://files.pythonhosted.org/packages/30/17/1463840bad10d02d0439068f37ce5af0b383884b0d5838f46fb027e233bf/aiohttp-3.10.10-cp310-cp310-win32.whl", hash = "sha256:84afcdea18eda514c25bc68b9af2a2b1adea7c08899175a51fe7c4fb6d551257", size = 362678 }, + { url = "https://files.pythonhosted.org/packages/dd/01/a0ef707d93e867a43abbffee3a2cdf30559910750b9176b891628c7ad074/aiohttp-3.10.10-cp310-cp310-win_amd64.whl", hash = "sha256:9c72109213eb9d3874f7ac8c0c5fa90e072d678e117d9061c06e30c85b4cf0e6", size = 381097 }, + { url = "https://files.pythonhosted.org/packages/72/31/3c351d17596194e5a38ef169a4da76458952b2497b4b54645b9d483cbbb0/aiohttp-3.10.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c30a0eafc89d28e7f959281b58198a9fa5e99405f716c0289b7892ca345fe45f", size = 586501 }, + { url = "https://files.pythonhosted.org/packages/a4/a8/a559d09eb08478cdead6b7ce05b0c4a133ba27fcdfa91e05d2e62867300d/aiohttp-3.10.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:258c5dd01afc10015866114e210fb7365f0d02d9d059c3c3415382ab633fcbcb", size = 398993 }, + { url = "https://files.pythonhosted.org/packages/c5/47/7736d4174613feef61d25332c3bd1a4f8ff5591fbd7331988238a7299485/aiohttp-3.10.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:15ecd889a709b0080f02721255b3f80bb261c2293d3c748151274dfea93ac871", size = 390647 }, + { url = "https://files.pythonhosted.org/packages/27/21/e9ba192a04b7160f5a8952c98a1de7cf8072ad150fa3abd454ead1ab1d7f/aiohttp-3.10.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3935f82f6f4a3820270842e90456ebad3af15810cf65932bd24da4463bc0a4c", size = 1306481 }, + { url = "https://files.pythonhosted.org/packages/cf/50/f364c01c8d0def1dc34747b2470969e216f5a37c7ece00fe558810f37013/aiohttp-3.10.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:413251f6fcf552a33c981c4709a6bba37b12710982fec8e558ae944bfb2abd38", size = 1344652 }, + { url = "https://files.pythonhosted.org/packages/1d/c2/74f608e984e9b585649e2e83883facad6fa3fc1d021de87b20cc67e8e5ae/aiohttp-3.10.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1720b4f14c78a3089562b8875b53e36b51c97c51adc53325a69b79b4b48ebcb", size = 1378498 }, + { url = "https://files.pythonhosted.org/packages/9f/a7/05a48c7c0a7a80a5591b1203bf1b64ca2ed6a2050af918d09c05852dc42b/aiohttp-3.10.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:679abe5d3858b33c2cf74faec299fda60ea9de62916e8b67e625d65bf069a3b7", size = 1292718 }, + { url = "https://files.pythonhosted.org/packages/7d/78/a925655018747e9790350180330032e27d6e0d7ed30bde545fae42f8c49c/aiohttp-3.10.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79019094f87c9fb44f8d769e41dbb664d6e8fcfd62f665ccce36762deaa0e911", size = 1251776 }, + { url = "https://files.pythonhosted.org/packages/47/9d/85c6b69f702351d1236594745a4fdc042fc43f494c247a98dac17e004026/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe2fb38c2ed905a2582948e2de560675e9dfbee94c6d5ccdb1301c6d0a5bf092", size = 1271716 }, + { url = "https://files.pythonhosted.org/packages/7f/a7/55fc805ff9b14af818903882ece08e2235b12b73b867b521b92994c52b14/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a3f00003de6eba42d6e94fabb4125600d6e484846dbf90ea8e48a800430cc142", size = 1266263 }, + { url = "https://files.pythonhosted.org/packages/1f/ec/d2be2ca7b063e4f91519d550dbc9c1cb43040174a322470deed90b3d3333/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1bbb122c557a16fafc10354b9d99ebf2f2808a660d78202f10ba9d50786384b9", size = 1321617 }, + { url = "https://files.pythonhosted.org/packages/c9/a3/b29f7920e1cd0a9a68a45dd3eb16140074d2efb1518d2e1f3e140357dc37/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:30ca7c3b94708a9d7ae76ff281b2f47d8eaf2579cd05971b5dc681db8caac6e1", size = 1339227 }, + { url = "https://files.pythonhosted.org/packages/8a/81/34b67235c47e232d807b4bbc42ba9b927c7ce9476872372fddcfd1e41b3d/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df9270660711670e68803107d55c2b5949c2e0f2e4896da176e1ecfc068b974a", size = 1299068 }, + { url = "https://files.pythonhosted.org/packages/04/1f/26a7fe11b6ad3184f214733428353c89ae9fe3e4f605a657f5245c5e720c/aiohttp-3.10.10-cp311-cp311-win32.whl", hash = "sha256:aafc8ee9b742ce75044ae9a4d3e60e3d918d15a4c2e08a6c3c3e38fa59b92d94", size = 362223 }, + { url = "https://files.pythonhosted.org/packages/10/91/85dcd93f64011434359ce2666bece981f08d31bc49df33261e625b28595d/aiohttp-3.10.10-cp311-cp311-win_amd64.whl", hash = "sha256:362f641f9071e5f3ee6f8e7d37d5ed0d95aae656adf4ef578313ee585b585959", size = 381576 }, + { url = "https://files.pythonhosted.org/packages/ae/99/4c5aefe5ad06a1baf206aed6598c7cdcbc7c044c46801cd0d1ecb758cae3/aiohttp-3.10.10-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9294bbb581f92770e6ed5c19559e1e99255e4ca604a22c5c6397b2f9dd3ee42c", size = 583536 }, + { url = "https://files.pythonhosted.org/packages/a9/36/8b3bc49b49cb6d2da40ee61ff15dbcc44fd345a3e6ab5bb20844df929821/aiohttp-3.10.10-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a8fa23fe62c436ccf23ff930149c047f060c7126eae3ccea005f0483f27b2e28", size = 395693 }, + { url = "https://files.pythonhosted.org/packages/e1/77/0aa8660dcf11fa65d61712dbb458c4989de220a844bd69778dff25f2d50b/aiohttp-3.10.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c6a5b8c7926ba5d8545c7dd22961a107526562da31a7a32fa2456baf040939f", size = 390898 }, + { url = "https://files.pythonhosted.org/packages/38/d2/b833d95deb48c75db85bf6646de0a697e7fb5d87bd27cbade4f9746b48b1/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:007ec22fbc573e5eb2fb7dec4198ef8f6bf2fe4ce20020798b2eb5d0abda6138", size = 1312060 }, + { url = "https://files.pythonhosted.org/packages/aa/5f/29fd5113165a0893de8efedf9b4737e0ba92dfcd791415a528f947d10299/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9627cc1a10c8c409b5822a92d57a77f383b554463d1884008e051c32ab1b3742", size = 1350553 }, + { url = "https://files.pythonhosted.org/packages/ad/cc/f835f74b7d344428469200105236d44606cfa448be1e7c95ca52880d9bac/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50edbcad60d8f0e3eccc68da67f37268b5144ecc34d59f27a02f9611c1d4eec7", size = 1392646 }, + { url = "https://files.pythonhosted.org/packages/bf/fe/1332409d845ca601893bbf2d76935e0b93d41686e5f333841c7d7a4a770d/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a45d85cf20b5e0d0aa5a8dca27cce8eddef3292bc29d72dcad1641f4ed50aa16", size = 1306310 }, + { url = "https://files.pythonhosted.org/packages/e4/a1/25a7633a5a513278a9892e333501e2e69c83e50be4b57a62285fb7a008c3/aiohttp-3.10.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b00807e2605f16e1e198f33a53ce3c4523114059b0c09c337209ae55e3823a8", size = 1260255 }, + { url = "https://files.pythonhosted.org/packages/f2/39/30eafe89e0e2a06c25e4762844c8214c0c0cd0fd9ffc3471694a7986f421/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f2d4324a98062be0525d16f768a03e0bbb3b9fe301ceee99611dc9a7953124e6", size = 1271141 }, + { url = "https://files.pythonhosted.org/packages/5b/fc/33125df728b48391ef1fcb512dfb02072158cc10d041414fb79803463020/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:438cd072f75bb6612f2aca29f8bd7cdf6e35e8f160bc312e49fbecab77c99e3a", size = 1280244 }, + { url = "https://files.pythonhosted.org/packages/3b/61/e42bf2c2934b5caa4e2ec0b5e5fd86989adb022b5ee60c2572a9d77cf6fe/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:baa42524a82f75303f714108fea528ccacf0386af429b69fff141ffef1c534f9", size = 1316805 }, + { url = "https://files.pythonhosted.org/packages/18/32/f52a5e2ae9ad3bba10e026a63a7a23abfa37c7d97aeeb9004eaa98df3ce3/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a7d8d14fe962153fc681f6366bdec33d4356f98a3e3567782aac1b6e0e40109a", size = 1343930 }, + { url = "https://files.pythonhosted.org/packages/05/be/6a403b464dcab3631fe8e27b0f1d906d9e45c5e92aca97ee007e5a895560/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1277cd707c465cd09572a774559a3cc7c7a28802eb3a2a9472588f062097205", size = 1306186 }, + { url = "https://files.pythonhosted.org/packages/8e/fd/bb50fe781068a736a02bf5c7ad5f3ab53e39f1d1e63110da6d30f7605edc/aiohttp-3.10.10-cp312-cp312-win32.whl", hash = "sha256:59bb3c54aa420521dc4ce3cc2c3fe2ad82adf7b09403fa1f48ae45c0cbde6628", size = 359289 }, + { url = "https://files.pythonhosted.org/packages/70/9e/5add7e240f77ef67c275c82cc1d08afbca57b77593118c1f6e920ae8ad3f/aiohttp-3.10.10-cp312-cp312-win_amd64.whl", hash = "sha256:0e1b370d8007c4ae31ee6db7f9a2fe801a42b146cec80a86766e7ad5c4a259cf", size = 379313 }, + { url = "https://files.pythonhosted.org/packages/b1/eb/618b1b76c7fe8082a71c9d62e3fe84c5b9af6703078caa9ec57850a12080/aiohttp-3.10.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ad7593bb24b2ab09e65e8a1d385606f0f47c65b5a2ae6c551db67d6653e78c28", size = 576114 }, + { url = "https://files.pythonhosted.org/packages/aa/37/3126995d7869f8b30d05381b81a2d4fb4ec6ad313db788e009bc6d39c211/aiohttp-3.10.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1eb89d3d29adaf533588f209768a9c02e44e4baf832b08118749c5fad191781d", size = 391901 }, + { url = "https://files.pythonhosted.org/packages/3e/f2/8fdfc845be1f811c31ceb797968523813f8e1263ee3e9120d61253f6848f/aiohttp-3.10.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3fe407bf93533a6fa82dece0e74dbcaaf5d684e5a51862887f9eaebe6372cd79", size = 387418 }, + { url = "https://files.pythonhosted.org/packages/60/d5/33d2061d36bf07e80286e04b7e0a4de37ce04b5ebfed72dba67659a05250/aiohttp-3.10.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aed5155f819873d23520919e16703fc8925e509abbb1a1491b0087d1cd969e", size = 1287073 }, + { url = "https://files.pythonhosted.org/packages/00/52/affb55be16a4747740bd630b4c002dac6c5eac42f9bb64202fc3cf3f1930/aiohttp-3.10.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f05e9727ce409358baa615dbeb9b969db94324a79b5a5cea45d39bdb01d82e6", size = 1323612 }, + { url = "https://files.pythonhosted.org/packages/94/f2/cddb69b975387daa2182a8442566971d6410b8a0179bb4540d81c97b1611/aiohttp-3.10.10-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dffb610a30d643983aeb185ce134f97f290f8935f0abccdd32c77bed9388b42", size = 1368406 }, + { url = "https://files.pythonhosted.org/packages/c1/e4/afba7327da4d932da8c6e29aecaf855f9d52dace53ac15bfc8030a246f1b/aiohttp-3.10.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa6658732517ddabe22c9036479eabce6036655ba87a0224c612e1ae6af2087e", size = 1282761 }, + { url = "https://files.pythonhosted.org/packages/9f/6b/364856faa0c9031ea76e24ef0f7fef79cddd9fa8e7dba9a1771c6acc56b5/aiohttp-3.10.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:741a46d58677d8c733175d7e5aa618d277cd9d880301a380fd296975a9cdd7bc", size = 1236518 }, + { url = "https://files.pythonhosted.org/packages/46/af/c382846f8356fe64a7b5908bb9b477457aa23b71be7ed551013b7b7d4d87/aiohttp-3.10.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e00e3505cd80440f6c98c6d69269dcc2a119f86ad0a9fd70bccc59504bebd68a", size = 1250344 }, + { url = "https://files.pythonhosted.org/packages/87/53/294f87fc086fd0772d0ab82497beb9df67f0f27a8b3dd5742a2656db2bc6/aiohttp-3.10.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ffe595f10566f8276b76dc3a11ae4bb7eba1aac8ddd75811736a15b0d5311414", size = 1248956 }, + { url = "https://files.pythonhosted.org/packages/86/30/7d746717fe11bdfefb88bb6c09c5fc985d85c4632da8bb6018e273899254/aiohttp-3.10.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdfcf6443637c148c4e1a20c48c566aa694fa5e288d34b20fcdc58507882fed3", size = 1293379 }, + { url = "https://files.pythonhosted.org/packages/48/b9/45d670a834458db67a24258e9139ba61fa3bd7d69b98ecf3650c22806f8f/aiohttp-3.10.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d183cf9c797a5291e8301790ed6d053480ed94070637bfaad914dd38b0981f67", size = 1320108 }, + { url = "https://files.pythonhosted.org/packages/72/8c/804bb2e837a175635d2000a0659eafc15b2e9d92d3d81c8f69e141ecd0b0/aiohttp-3.10.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:77abf6665ae54000b98b3c742bc6ea1d1fb31c394bcabf8b5d2c1ac3ebfe7f3b", size = 1281546 }, + { url = "https://files.pythonhosted.org/packages/89/c0/862e6a9de3d6eeb126cd9d9ea388243b70df9b871ce1a42b193b7a4a77fc/aiohttp-3.10.10-cp313-cp313-win32.whl", hash = "sha256:4470c73c12cd9109db8277287d11f9dd98f77fc54155fc71a7738a83ffcc8ea8", size = 357516 }, + { url = "https://files.pythonhosted.org/packages/ae/63/3e1aee3e554263f3f1011cca50d78a4894ae16ce99bf78101ac3a2f0ef74/aiohttp-3.10.10-cp313-cp313-win_amd64.whl", hash = "sha256:486f7aabfa292719a2753c016cc3a8f8172965cabb3ea2e7f7436c7f5a22a151", size = 376785 }, +] + +[[package]] +name = "aiosignal" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/67/0952ed97a9793b4958e5736f6d2b346b414a2cd63e82d05940032f45b32f/aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc", size = 19422 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/ac/a7305707cb852b7e16ff80eaf5692309bde30e2b1100a1fcacdc8f731d97/aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17", size = 7617 }, +] + +[[package]] +name = "alabaster" +version = "0.7.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511 }, +] + +[[package]] +name = "antlr4-python3-runtime" +version = "4.9.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b", size = 117034 } + +[[package]] +name = "anyascii" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/52/93b9ea99063f7cf37fb67f5e3f49480686cbe7f228c48b9d713326223b6e/anyascii-0.3.2.tar.gz", hash = "sha256:9d5d32ef844fe225b8bc7cba7f950534fae4da27a9bf3a6bea2cb0ea46ce4730", size = 214052 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/7b/a9a747e0632271d855da379532b05a62c58e979813814a57fa3b3afeb3a4/anyascii-0.3.2-py3-none-any.whl", hash = "sha256:3b3beef6fc43d9036d3b0529050b0c48bfad8bc960e9e562d7223cfb94fe45d4", size = 289923 }, +] + +[[package]] +name = "anyio" +version = "4.6.2.post1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377 }, +] + +[[package]] +name = "appdirs" +version = "1.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/d8/05696357e0311f5b5c316d7b95f46c669dd9c15aaeecbb48c7d0aeb88c40/appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", size = 13470 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128", size = 9566 }, +] + +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321 }, +] + +[[package]] +name = "argon2-cffi" +version = "23.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi-bindings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/fa/57ec2c6d16ecd2ba0cf15f3c7d1c3c2e7b5fcb83555ff56d7ab10888ec8f/argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08", size = 42798 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea", size = 15124 }, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "21.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/e9/184b8ccce6683b0aa2fbb7ba5683ea4b9c5763f1356347f1312c32e3c66e/argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", size = 1779911 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/13/838ce2620025e9666aa8f686431f67a29052241692a3dd1ae9d3692a89d3/argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", size = 29658 }, + { url = "https://files.pythonhosted.org/packages/b3/02/f7f7bb6b6af6031edb11037639c697b912e1dea2db94d436e681aea2f495/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", size = 80583 }, + { url = "https://files.pythonhosted.org/packages/ec/f7/378254e6dd7ae6f31fe40c8649eea7d4832a42243acaf0f1fff9083b2bed/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", size = 86168 }, + { url = "https://files.pythonhosted.org/packages/74/f6/4a34a37a98311ed73bb80efe422fed95f2ac25a4cacc5ae1d7ae6a144505/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", size = 82709 }, + { url = "https://files.pythonhosted.org/packages/74/2b/73d767bfdaab25484f7e7901379d5f8793cccbb86c6e0cbc4c1b96f63896/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", size = 83613 }, + { url = "https://files.pythonhosted.org/packages/4f/fd/37f86deef67ff57c76f137a67181949c2d408077e2e3dd70c6c42912c9bf/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", size = 84583 }, + { url = "https://files.pythonhosted.org/packages/6f/52/5a60085a3dae8fded8327a4f564223029f5f54b0cb0455a31131b5363a01/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", size = 88475 }, + { url = "https://files.pythonhosted.org/packages/8b/95/143cd64feb24a15fa4b189a3e1e7efbaeeb00f39a51e99b26fc62fbacabd/argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", size = 27698 }, + { url = "https://files.pythonhosted.org/packages/37/2c/e34e47c7dee97ba6f01a6203e0383e15b60fb85d78ac9a15cd066f6fe28b/argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", size = 30817 }, + { url = "https://files.pythonhosted.org/packages/5a/e4/bf8034d25edaa495da3c8a3405627d2e35758e44ff6eaa7948092646fdcc/argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", size = 53104 }, +] + +[[package]] +name = "arrow" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "types-python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419 }, +] + +[[package]] +name = "ase" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/6b/fd582d013c28aeae14df1a6fc2f057dea6c2b7e62ae1632699e59bb3f7d4/ase-3.23.0.tar.gz", hash = "sha256:91a2aa31d89bd90b0efdfe4a7e84264f32828b2abfc9f38e65e041ad76fec8ae", size = 2336462 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/81/2c339c920fb1be1caa0b7efccb14452c9f4f0dbe3837f33519610611f57b/ase-3.23.0-py3-none-any.whl", hash = "sha256:52060410e720b6c701ea1ebecfdeb5ec6f9c1c63edc7cee68c15bd66d226dd43", size = 2877073 }, +] + +[[package]] +name = "astroid" +version = "3.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/1e/326fb1d3d83a3bb77c9f9be29d31f2901e35acb94b0605c3f2e5085047f9/astroid-3.3.5.tar.gz", hash = "sha256:5cfc40ae9f68311075d27ef68a4841bdc5cc7f6cf86671b49f00607d30188e2d", size = 397229 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/30/624365383fa4a40329c0f0bbbc151abc4a64e30dfc110fc8f6e2afcd02bb/astroid-3.3.5-py3-none-any.whl", hash = "sha256:a9d1c946ada25098d790e079ba2a1b112157278f3fb7e718ae6a9252f5835dc8", size = 274586 }, +] + +[[package]] +name = "asttokens" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/1d/f03bcb60c4a3212e15f99a56085d93093a497718adf828d050b9d675da81/asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0", size = 62284 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/86/4736ac618d82a20d87d2f92ae19441ebc7ac9e7a581d7e58bbe79233b24a/asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24", size = 27764 }, +] + +[[package]] +name = "async-lru" +version = "2.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/e2/2b4651eff771f6fd900d233e175ddc5e2be502c7eb62c0c42f975c6d36cd/async-lru-2.0.4.tar.gz", hash = "sha256:b8a59a5df60805ff63220b2a0c5b5393da5521b113cd5465a44eb037d81a5627", size = 10019 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/9f/3c3503693386c4b0f245eaf5ca6198e3b28879ca0a40bde6b0e319793453/async_lru-2.0.4-py3-none-any.whl", hash = "sha256:ff02944ce3c288c5be660c42dbcca0742b32c3b279d6dceda655190240b99224", size = 6111 }, +] + +[[package]] +name = "async-timeout" +version = "4.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/d6/21b30a550dafea84b1b8eee21b5e23fa16d010ae006011221f33dcd8d7f8/async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f", size = 8345 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/fa/e01228c2938de91d47b307831c62ab9e4001e747789d0b05baf779a6488c/async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028", size = 5721 }, +] + +[[package]] +name = "atomicwrites" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/c6/53da25344e3e3a9c01095a89f16dbcda021c609ddb42dd6d7c0528236fb2/atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11", size = 14227 } + +[[package]] +name = "attrs" +version = "24.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/0f/aafca9af9315aee06a89ffde799a10a582fe8de76c563ee80bbcdc08b3fb/attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", size = 792678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001 }, +] + +[[package]] +name = "babel" +version = "2.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/74/f1bc80f23eeba13393b7222b11d95ca3af2c1e28edca18af487137eefed9/babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316", size = 9348104 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/20/bc79bc575ba2e2a7f70e8a1155618bb1301eaa5132a8271373a6903f73f8/babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", size = 9587599 }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/ca/824b1195773ce6166d388573fc106ce56d4a805bd7427b624e063596ec58/beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", size = 581181 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925 }, +] + +[[package]] +name = "black" +version = "24.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/0d/cc2fb42b8c50d80143221515dd7e4766995bd07c56c9a3ed30baf080b6dc/black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875", size = 645813 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/f3/465c0eb5cddf7dbbfe1fecd9b875d1dcf51b88923cd2c1d7e9ab95c6336b/black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812", size = 1623211 }, + { url = "https://files.pythonhosted.org/packages/df/57/b6d2da7d200773fdfcc224ffb87052cf283cec4d7102fab450b4a05996d8/black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea", size = 1457139 }, + { url = "https://files.pythonhosted.org/packages/6e/c5/9023b7673904a5188f9be81f5e129fff69f51f5515655fbd1d5a4e80a47b/black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f", size = 1753774 }, + { url = "https://files.pythonhosted.org/packages/e1/32/df7f18bd0e724e0d9748829765455d6643ec847b3f87e77456fc99d0edab/black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e", size = 1414209 }, + { url = "https://files.pythonhosted.org/packages/c2/cc/7496bb63a9b06a954d3d0ac9fe7a73f3bf1cd92d7a58877c27f4ad1e9d41/black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad", size = 1607468 }, + { url = "https://files.pythonhosted.org/packages/2b/e3/69a738fb5ba18b5422f50b4f143544c664d7da40f09c13969b2fd52900e0/black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50", size = 1437270 }, + { url = "https://files.pythonhosted.org/packages/c9/9b/2db8045b45844665c720dcfe292fdaf2e49825810c0103e1191515fc101a/black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392", size = 1737061 }, + { url = "https://files.pythonhosted.org/packages/a3/95/17d4a09a5be5f8c65aa4a361444d95edc45def0de887810f508d3f65db7a/black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175", size = 1423293 }, + { url = "https://files.pythonhosted.org/packages/90/04/bf74c71f592bcd761610bbf67e23e6a3cff824780761f536512437f1e655/black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3", size = 1644256 }, + { url = "https://files.pythonhosted.org/packages/4c/ea/a77bab4cf1887f4b2e0bce5516ea0b3ff7d04ba96af21d65024629afedb6/black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65", size = 1448534 }, + { url = "https://files.pythonhosted.org/packages/4e/3e/443ef8bc1fbda78e61f79157f303893f3fddf19ca3c8989b163eb3469a12/black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f", size = 1761892 }, + { url = "https://files.pythonhosted.org/packages/52/93/eac95ff229049a6901bc84fec6908a5124b8a0b7c26ea766b3b8a5debd22/black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8", size = 1434796 }, + { url = "https://files.pythonhosted.org/packages/d0/a0/a993f58d4ecfba035e61fca4e9f64a2ecae838fc9f33ab798c62173ed75c/black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981", size = 1643986 }, + { url = "https://files.pythonhosted.org/packages/37/d5/602d0ef5dfcace3fb4f79c436762f130abd9ee8d950fa2abdbf8bbc555e0/black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b", size = 1448085 }, + { url = "https://files.pythonhosted.org/packages/47/6d/a3a239e938960df1a662b93d6230d4f3e9b4a22982d060fc38c42f45a56b/black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2", size = 1760928 }, + { url = "https://files.pythonhosted.org/packages/dd/cf/af018e13b0eddfb434df4d9cd1b2b7892bab119f7a20123e93f6910982e8/black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b", size = 1436875 }, + { url = "https://files.pythonhosted.org/packages/8d/a7/4b27c50537ebca8bec139b872861f9d2bf501c5ec51fcf897cb924d9e264/black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d", size = 206898 }, +] + +[[package]] +name = "bleach" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/10/77f32b088738f40d4f5be801daa5f327879eadd4562f36a2b5ab975ae571/bleach-6.1.0.tar.gz", hash = "sha256:0a31f1837963c41d46bbf1331b8778e1308ea0791db03cc4e7357b97cf42a8fe", size = 202119 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/63/da7237f805089ecc28a3f36bca6a21c31fcbc2eb380f3b8f1be3312abd14/bleach-6.1.0-py3-none-any.whl", hash = "sha256:3225f354cfc436b9789c66c4ee030194bee0568fbf9cbdad3bc8b5c26c5f12b6", size = 162750 }, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191 }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592 }, + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 }, + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/8b/825cc84cf13a28bfbcba7c416ec22bf85a9584971be15b21dd8300c65b7f/charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6", size = 196363 }, + { url = "https://files.pythonhosted.org/packages/23/81/d7eef6a99e42c77f444fdd7bc894b0ceca6c3a95c51239e74a722039521c/charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b", size = 125639 }, + { url = "https://files.pythonhosted.org/packages/21/67/b4564d81f48042f520c948abac7079356e94b30cb8ffb22e747532cf469d/charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99", size = 120451 }, + { url = "https://files.pythonhosted.org/packages/c2/72/12a7f0943dd71fb5b4e7b55c41327ac0a1663046a868ee4d0d8e9c369b85/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca", size = 140041 }, + { url = "https://files.pythonhosted.org/packages/67/56/fa28c2c3e31217c4c52158537a2cf5d98a6c1e89d31faf476c89391cd16b/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d", size = 150333 }, + { url = "https://files.pythonhosted.org/packages/f9/d2/466a9be1f32d89eb1554cf84073a5ed9262047acee1ab39cbaefc19635d2/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7", size = 142921 }, + { url = "https://files.pythonhosted.org/packages/f8/01/344ec40cf5d85c1da3c1f57566c59e0c9b56bcc5566c08804a95a6cc8257/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3", size = 144785 }, + { url = "https://files.pythonhosted.org/packages/73/8b/2102692cb6d7e9f03b9a33a710e0164cadfce312872e3efc7cfe22ed26b4/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907", size = 146631 }, + { url = "https://files.pythonhosted.org/packages/d8/96/cc2c1b5d994119ce9f088a9a0c3ebd489d360a2eb058e2c8049f27092847/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b", size = 140867 }, + { url = "https://files.pythonhosted.org/packages/c9/27/cde291783715b8ec30a61c810d0120411844bc4c23b50189b81188b273db/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912", size = 149273 }, + { url = "https://files.pythonhosted.org/packages/3a/a4/8633b0fc1a2d1834d5393dafecce4a1cc56727bfd82b4dc18fc92f0d3cc3/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95", size = 152437 }, + { url = "https://files.pythonhosted.org/packages/64/ea/69af161062166b5975ccbb0961fd2384853190c70786f288684490913bf5/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e", size = 150087 }, + { url = "https://files.pythonhosted.org/packages/3b/fd/e60a9d9fd967f4ad5a92810138192f825d77b4fa2a557990fd575a47695b/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe", size = 145142 }, + { url = "https://files.pythonhosted.org/packages/6d/02/8cb0988a1e49ac9ce2eed1e07b77ff118f2923e9ebd0ede41ba85f2dcb04/charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc", size = 94701 }, + { url = "https://files.pythonhosted.org/packages/d6/20/f1d4670a8a723c46be695dff449d86d6092916f9e99c53051954ee33a1bc/charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749", size = 102191 }, + { url = "https://files.pythonhosted.org/packages/9c/61/73589dcc7a719582bf56aae309b6103d2762b526bffe189d635a7fcfd998/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", size = 193339 }, + { url = "https://files.pythonhosted.org/packages/77/d5/8c982d58144de49f59571f940e329ad6e8615e1e82ef84584c5eeb5e1d72/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", size = 124366 }, + { url = "https://files.pythonhosted.org/packages/bf/19/411a64f01ee971bed3231111b69eb56f9331a769072de479eae7de52296d/charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", size = 118874 }, + { url = "https://files.pythonhosted.org/packages/4c/92/97509850f0d00e9f14a46bc751daabd0ad7765cff29cdfb66c68b6dad57f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", size = 138243 }, + { url = "https://files.pythonhosted.org/packages/e2/29/d227805bff72ed6d6cb1ce08eec707f7cfbd9868044893617eb331f16295/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", size = 148676 }, + { url = "https://files.pythonhosted.org/packages/13/bc/87c2c9f2c144bedfa62f894c3007cd4530ba4b5351acb10dc786428a50f0/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", size = 141289 }, + { url = "https://files.pythonhosted.org/packages/eb/5b/6f10bad0f6461fa272bfbbdf5d0023b5fb9bc6217c92bf068fa5a99820f5/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", size = 142585 }, + { url = "https://files.pythonhosted.org/packages/3b/a0/a68980ab8a1f45a36d9745d35049c1af57d27255eff8c907e3add84cf68f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5", size = 144408 }, + { url = "https://files.pythonhosted.org/packages/d7/a1/493919799446464ed0299c8eef3c3fad0daf1c3cd48bff9263c731b0d9e2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", size = 139076 }, + { url = "https://files.pythonhosted.org/packages/fb/9d/9c13753a5a6e0db4a0a6edb1cef7aee39859177b64e1a1e748a6e3ba62c2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c", size = 146874 }, + { url = "https://files.pythonhosted.org/packages/75/d2/0ab54463d3410709c09266dfb416d032a08f97fd7d60e94b8c6ef54ae14b/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", size = 150871 }, + { url = "https://files.pythonhosted.org/packages/8d/c9/27e41d481557be53d51e60750b85aa40eaf52b841946b3cdeff363105737/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", size = 148546 }, + { url = "https://files.pythonhosted.org/packages/ee/44/4f62042ca8cdc0cabf87c0fc00ae27cd8b53ab68be3605ba6d071f742ad3/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", size = 143048 }, + { url = "https://files.pythonhosted.org/packages/01/f8/38842422988b795220eb8038745d27a675ce066e2ada79516c118f291f07/charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99", size = 94389 }, + { url = "https://files.pythonhosted.org/packages/0b/6e/b13bd47fa9023b3699e94abf565b5a2f0b0be6e9ddac9812182596ee62e4/charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", size = 101752 }, + { url = "https://files.pythonhosted.org/packages/d3/0b/4b7a70987abf9b8196845806198975b6aab4ce016632f817ad758a5aa056/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", size = 194445 }, + { url = "https://files.pythonhosted.org/packages/50/89/354cc56cf4dd2449715bc9a0f54f3aef3dc700d2d62d1fa5bbea53b13426/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", size = 125275 }, + { url = "https://files.pythonhosted.org/packages/fa/44/b730e2a2580110ced837ac083d8ad222343c96bb6b66e9e4e706e4d0b6df/charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", size = 119020 }, + { url = "https://files.pythonhosted.org/packages/9d/e4/9263b8240ed9472a2ae7ddc3e516e71ef46617fe40eaa51221ccd4ad9a27/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", size = 139128 }, + { url = "https://files.pythonhosted.org/packages/6b/e3/9f73e779315a54334240353eaea75854a9a690f3f580e4bd85d977cb2204/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", size = 149277 }, + { url = "https://files.pythonhosted.org/packages/1a/cf/f1f50c2f295312edb8a548d3fa56a5c923b146cd3f24114d5adb7e7be558/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", size = 142174 }, + { url = "https://files.pythonhosted.org/packages/16/92/92a76dc2ff3a12e69ba94e7e05168d37d0345fa08c87e1fe24d0c2a42223/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", size = 143838 }, + { url = "https://files.pythonhosted.org/packages/a4/01/2117ff2b1dfc61695daf2babe4a874bca328489afa85952440b59819e9d7/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", size = 146149 }, + { url = "https://files.pythonhosted.org/packages/f6/9b/93a332b8d25b347f6839ca0a61b7f0287b0930216994e8bf67a75d050255/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", size = 140043 }, + { url = "https://files.pythonhosted.org/packages/ab/f6/7ac4a01adcdecbc7a7587767c776d53d369b8b971382b91211489535acf0/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", size = 148229 }, + { url = "https://files.pythonhosted.org/packages/9d/be/5708ad18161dee7dc6a0f7e6cf3a88ea6279c3e8484844c0590e50e803ef/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", size = 151556 }, + { url = "https://files.pythonhosted.org/packages/5a/bb/3d8bc22bacb9eb89785e83e6723f9888265f3a0de3b9ce724d66bd49884e/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", size = 149772 }, + { url = "https://files.pythonhosted.org/packages/f7/fa/d3fc622de05a86f30beea5fc4e9ac46aead4731e73fd9055496732bcc0a4/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", size = 144800 }, + { url = "https://files.pythonhosted.org/packages/9a/65/bdb9bc496d7d190d725e96816e20e2ae3a6fa42a5cac99c3c3d6ff884118/charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", size = 94836 }, + { url = "https://files.pythonhosted.org/packages/3e/67/7b72b69d25b89c0b3cea583ee372c43aa24df15f0e0f8d3982c57804984b/charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", size = 102187 }, + { url = "https://files.pythonhosted.org/packages/f3/89/68a4c86f1a0002810a27f12e9a7b22feb198c59b2f05231349fbce5c06f4/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", size = 194617 }, + { url = "https://files.pythonhosted.org/packages/4f/cd/8947fe425e2ab0aa57aceb7807af13a0e4162cd21eee42ef5b053447edf5/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", size = 125310 }, + { url = "https://files.pythonhosted.org/packages/5b/f0/b5263e8668a4ee9becc2b451ed909e9c27058337fda5b8c49588183c267a/charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", size = 119126 }, + { url = "https://files.pythonhosted.org/packages/ff/6e/e445afe4f7fda27a533f3234b627b3e515a1b9429bc981c9a5e2aa5d97b6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", size = 139342 }, + { url = "https://files.pythonhosted.org/packages/a1/b2/4af9993b532d93270538ad4926c8e37dc29f2111c36f9c629840c57cd9b3/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", size = 149383 }, + { url = "https://files.pythonhosted.org/packages/fb/6f/4e78c3b97686b871db9be6f31d64e9264e889f8c9d7ab33c771f847f79b7/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", size = 142214 }, + { url = "https://files.pythonhosted.org/packages/2b/c9/1c8fe3ce05d30c87eff498592c89015b19fade13df42850aafae09e94f35/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", size = 144104 }, + { url = "https://files.pythonhosted.org/packages/ee/68/efad5dcb306bf37db7db338338e7bb8ebd8cf38ee5bbd5ceaaaa46f257e6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", size = 146255 }, + { url = "https://files.pythonhosted.org/packages/0c/75/1ed813c3ffd200b1f3e71121c95da3f79e6d2a96120163443b3ad1057505/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", size = 140251 }, + { url = "https://files.pythonhosted.org/packages/7d/0d/6f32255c1979653b448d3c709583557a4d24ff97ac4f3a5be156b2e6a210/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", size = 148474 }, + { url = "https://files.pythonhosted.org/packages/ac/a0/c1b5298de4670d997101fef95b97ac440e8c8d8b4efa5a4d1ef44af82f0d/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", size = 151849 }, + { url = "https://files.pythonhosted.org/packages/04/4f/b3961ba0c664989ba63e30595a3ed0875d6790ff26671e2aae2fdc28a399/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", size = 149781 }, + { url = "https://files.pythonhosted.org/packages/d8/90/6af4cd042066a4adad58ae25648a12c09c879efa4849c705719ba1b23d8c/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482", size = 144970 }, + { url = "https://files.pythonhosted.org/packages/cc/67/e5e7e0cbfefc4ca79025238b43cdf8a2037854195b37d6417f3d0895c4c2/charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", size = 94973 }, + { url = "https://files.pythonhosted.org/packages/65/97/fc9bbc54ee13d33dc54a7fcf17b26368b18505500fc01e228c27b5222d80/charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", size = 102308 }, + { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 }, +] + +[[package]] +name = "click" +version = "8.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, +] + +[[package]] +name = "cloudpickle" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/97/c7/f746cadd08c4c08129215cf1b984b632f9e579fc781301e63da9e85c76c1/cloudpickle-3.1.0.tar.gz", hash = "sha256:81a929b6e3c7335c863c771d673d105f02efdb89dfaba0c90495d1c64796601b", size = 66155 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/41/e1d85ca3cab0b674e277c8c4f678cf66a91cd2cecf93df94353a606fe0db/cloudpickle-3.1.0-py3-none-any.whl", hash = "sha256:fe11acda67f61aaaec473e3afe030feb131d78a43461b718185363384f1ba12e", size = 22021 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "comet-ml" +version = "3.47.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dulwich" }, + { name = "everett", extra = ["ini"] }, + { name = "jsonschema" }, + { name = "psutil" }, + { name = "python-box" }, + { name = "requests" }, + { name = "requests-toolbelt" }, + { name = "rich" }, + { name = "semantic-version" }, + { name = "sentry-sdk" }, + { name = "setuptools", marker = "python_full_version >= '3.12'" }, + { name = "simplejson" }, + { name = "urllib3" }, + { name = "wrapt" }, + { name = "wurlitzer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/4f/bcf700e2d9818814aebfe4e8cf6277e642748346c2f9967813ce5b23beb0/comet_ml-3.47.0.tar.gz", hash = "sha256:27e4c3bf1d476a228c24acedc6a76d9bdf15ec738a41dd13b523a83d46961156", size = 514496 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/65/2f494fc126d31429d0ce13a8a1c229b3657799c342d2bc92da2a33977187/comet_ml-3.47.0-py3-none-any.whl", hash = "sha256:81062bbef2d0758c8e77d8a469824d2c20ec13b09855c78b51b078203628b8c2", size = 694865 }, +] + +[[package]] +name = "comm" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/a8/fb783cb0abe2b5fded9f55e5703015cdf1c9c85b3669087c538dd15a6a86/comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e", size = 6210 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/75/49e5bfe642f71f272236b5b2d2691cf915a7283cc0ceda56357b61daa538/comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3", size = 7180 }, +] + +[[package]] +name = "configobj" +version = "5.0.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/c4/c7f9e41bc2e5f8eeae4a08a01c91b2aea3dfab40a3e14b25e87e7db8d501/configobj-5.0.9.tar.gz", hash = "sha256:03c881bbf23aa07bccf1b837005975993c4ab4427ba57f959afdd9d1a2386848", size = 101518 } + +[[package]] +name = "contourpy" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/f6/31a8f28b4a2a4fa0e01085e542f3081ab0588eff8e589d39d775172c9792/contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4", size = 13464370 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/e0/be8dcc796cfdd96708933e0e2da99ba4bb8f9b2caa9d560a50f3f09a65f3/contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7", size = 265366 }, + { url = "https://files.pythonhosted.org/packages/50/d6/c953b400219443535d412fcbbc42e7a5e823291236bc0bb88936e3cc9317/contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42", size = 249226 }, + { url = "https://files.pythonhosted.org/packages/6f/b4/6fffdf213ffccc28483c524b9dad46bb78332851133b36ad354b856ddc7c/contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7", size = 308460 }, + { url = "https://files.pythonhosted.org/packages/cf/6c/118fc917b4050f0afe07179a6dcbe4f3f4ec69b94f36c9e128c4af480fb8/contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab", size = 347623 }, + { url = "https://files.pythonhosted.org/packages/f9/a4/30ff110a81bfe3abf7b9673284d21ddce8cc1278f6f77393c91199da4c90/contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589", size = 317761 }, + { url = "https://files.pythonhosted.org/packages/99/e6/d11966962b1aa515f5586d3907ad019f4b812c04e4546cc19ebf62b5178e/contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41", size = 322015 }, + { url = "https://files.pythonhosted.org/packages/4d/e3/182383743751d22b7b59c3c753277b6aee3637049197624f333dac5b4c80/contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d", size = 1262672 }, + { url = "https://files.pythonhosted.org/packages/78/53/974400c815b2e605f252c8fb9297e2204347d1755a5374354ee77b1ea259/contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223", size = 1321688 }, + { url = "https://files.pythonhosted.org/packages/52/29/99f849faed5593b2926a68a31882af98afbeac39c7fdf7de491d9c85ec6a/contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f", size = 171145 }, + { url = "https://files.pythonhosted.org/packages/a9/97/3f89bba79ff6ff2b07a3cbc40aa693c360d5efa90d66e914f0ff03b95ec7/contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b", size = 216019 }, + { url = "https://files.pythonhosted.org/packages/b3/1f/9375917786cb39270b0ee6634536c0e22abf225825602688990d8f5c6c19/contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad", size = 266356 }, + { url = "https://files.pythonhosted.org/packages/05/46/9256dd162ea52790c127cb58cfc3b9e3413a6e3478917d1f811d420772ec/contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49", size = 250915 }, + { url = "https://files.pythonhosted.org/packages/e1/5d/3056c167fa4486900dfbd7e26a2fdc2338dc58eee36d490a0ed3ddda5ded/contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66", size = 310443 }, + { url = "https://files.pythonhosted.org/packages/ca/c2/1a612e475492e07f11c8e267ea5ec1ce0d89971be496c195e27afa97e14a/contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081", size = 348548 }, + { url = "https://files.pythonhosted.org/packages/45/cf/2c2fc6bb5874158277b4faf136847f0689e1b1a1f640a36d76d52e78907c/contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1", size = 319118 }, + { url = "https://files.pythonhosted.org/packages/03/33/003065374f38894cdf1040cef474ad0546368eea7e3a51d48b8a423961f8/contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d", size = 323162 }, + { url = "https://files.pythonhosted.org/packages/42/80/e637326e85e4105a802e42959f56cff2cd39a6b5ef68d5d9aee3ea5f0e4c/contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c", size = 1265396 }, + { url = "https://files.pythonhosted.org/packages/7c/3b/8cbd6416ca1bbc0202b50f9c13b2e0b922b64be888f9d9ee88e6cfabfb51/contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb", size = 1324297 }, + { url = "https://files.pythonhosted.org/packages/4d/2c/021a7afaa52fe891f25535506cc861c30c3c4e5a1c1ce94215e04b293e72/contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c", size = 171808 }, + { url = "https://files.pythonhosted.org/packages/8d/2f/804f02ff30a7fae21f98198828d0857439ec4c91a96e20cf2d6c49372966/contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67", size = 217181 }, + { url = "https://files.pythonhosted.org/packages/c9/92/8e0bbfe6b70c0e2d3d81272b58c98ac69ff1a4329f18c73bd64824d8b12e/contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f", size = 267838 }, + { url = "https://files.pythonhosted.org/packages/e3/04/33351c5d5108460a8ce6d512307690b023f0cfcad5899499f5c83b9d63b1/contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6", size = 251549 }, + { url = "https://files.pythonhosted.org/packages/51/3d/aa0fe6ae67e3ef9f178389e4caaaa68daf2f9024092aa3c6032e3d174670/contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639", size = 303177 }, + { url = "https://files.pythonhosted.org/packages/56/c3/c85a7e3e0cab635575d3b657f9535443a6f5d20fac1a1911eaa4bbe1aceb/contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c", size = 341735 }, + { url = "https://files.pythonhosted.org/packages/dd/8d/20f7a211a7be966a53f474bc90b1a8202e9844b3f1ef85f3ae45a77151ee/contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06", size = 314679 }, + { url = "https://files.pythonhosted.org/packages/6e/be/524e377567defac0e21a46e2a529652d165fed130a0d8a863219303cee18/contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09", size = 320549 }, + { url = "https://files.pythonhosted.org/packages/0f/96/fdb2552a172942d888915f3a6663812e9bc3d359d53dafd4289a0fb462f0/contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd", size = 1263068 }, + { url = "https://files.pythonhosted.org/packages/2a/25/632eab595e3140adfa92f1322bf8915f68c932bac468e89eae9974cf1c00/contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35", size = 1322833 }, + { url = "https://files.pythonhosted.org/packages/73/e3/69738782e315a1d26d29d71a550dbbe3eb6c653b028b150f70c1a5f4f229/contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb", size = 172681 }, + { url = "https://files.pythonhosted.org/packages/0c/89/9830ba00d88e43d15e53d64931e66b8792b46eb25e2050a88fec4a0df3d5/contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b", size = 218283 }, + { url = "https://files.pythonhosted.org/packages/53/a1/d20415febfb2267af2d7f06338e82171824d08614084714fb2c1dac9901f/contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3", size = 267879 }, + { url = "https://files.pythonhosted.org/packages/aa/45/5a28a3570ff6218d8bdfc291a272a20d2648104815f01f0177d103d985e1/contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7", size = 251573 }, + { url = "https://files.pythonhosted.org/packages/39/1c/d3f51540108e3affa84f095c8b04f0aa833bb797bc8baa218a952a98117d/contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84", size = 303184 }, + { url = "https://files.pythonhosted.org/packages/00/56/1348a44fb6c3a558c1a3a0cd23d329d604c99d81bf5a4b58c6b71aab328f/contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0", size = 340262 }, + { url = "https://files.pythonhosted.org/packages/2b/23/00d665ba67e1bb666152131da07e0f24c95c3632d7722caa97fb61470eca/contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b", size = 313806 }, + { url = "https://files.pythonhosted.org/packages/5a/42/3cf40f7040bb8362aea19af9a5fb7b32ce420f645dd1590edcee2c657cd5/contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da", size = 319710 }, + { url = "https://files.pythonhosted.org/packages/05/32/f3bfa3fc083b25e1a7ae09197f897476ee68e7386e10404bdf9aac7391f0/contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14", size = 1264107 }, + { url = "https://files.pythonhosted.org/packages/1c/1e/1019d34473a736664f2439542b890b2dc4c6245f5c0d8cdfc0ccc2cab80c/contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8", size = 1322458 }, + { url = "https://files.pythonhosted.org/packages/22/85/4f8bfd83972cf8909a4d36d16b177f7b8bdd942178ea4bf877d4a380a91c/contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294", size = 172643 }, + { url = "https://files.pythonhosted.org/packages/cc/4a/fb3c83c1baba64ba90443626c228ca14f19a87c51975d3b1de308dd2cf08/contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087", size = 218301 }, + { url = "https://files.pythonhosted.org/packages/76/65/702f4064f397821fea0cb493f7d3bc95a5d703e20954dce7d6d39bacf378/contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8", size = 278972 }, + { url = "https://files.pythonhosted.org/packages/80/85/21f5bba56dba75c10a45ec00ad3b8190dbac7fd9a8a8c46c6116c933e9cf/contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b", size = 263375 }, + { url = "https://files.pythonhosted.org/packages/0a/64/084c86ab71d43149f91ab3a4054ccf18565f0a8af36abfa92b1467813ed6/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973", size = 307188 }, + { url = "https://files.pythonhosted.org/packages/3d/ff/d61a4c288dc42da0084b8d9dc2aa219a850767165d7d9a9c364ff530b509/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18", size = 345644 }, + { url = "https://files.pythonhosted.org/packages/ca/aa/00d2313d35ec03f188e8f0786c2fc61f589306e02fdc158233697546fd58/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8", size = 317141 }, + { url = "https://files.pythonhosted.org/packages/8d/6a/b5242c8cb32d87f6abf4f5e3044ca397cb1a76712e3fa2424772e3ff495f/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6", size = 323469 }, + { url = "https://files.pythonhosted.org/packages/6f/a6/73e929d43028a9079aca4bde107494864d54f0d72d9db508a51ff0878593/contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2", size = 1260894 }, + { url = "https://files.pythonhosted.org/packages/2b/1e/1e726ba66eddf21c940821df8cf1a7d15cb165f0682d62161eaa5e93dae1/contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927", size = 1314829 }, + { url = "https://files.pythonhosted.org/packages/d1/09/60e486dc2b64c94ed33e58dcfb6f808192c03dfc5574c016218b9b7680dc/contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c", size = 261886 }, + { url = "https://files.pythonhosted.org/packages/19/20/b57f9f7174fcd439a7789fb47d764974ab646fa34d1790551de386457a8e/contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779", size = 311008 }, + { url = "https://files.pythonhosted.org/packages/74/fc/5040d42623a1845d4f17a418e590fd7a79ae8cb2bad2b2f83de63c3bdca4/contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4", size = 215690 }, +] + +[[package]] +name = "coverage" +version = "7.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/12/3669b6382792783e92046730ad3327f53b2726f0603f4c311c4da4824222/coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73", size = 798716 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/93/4ad92f71e28ece5c0326e5f4a6630aa4928a8846654a65cfff69b49b95b9/coverage-7.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07", size = 206713 }, + { url = "https://files.pythonhosted.org/packages/01/ae/747a580b1eda3f2e431d87de48f0604bd7bc92e52a1a95185a4aa585bc47/coverage-7.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0", size = 207149 }, + { url = "https://files.pythonhosted.org/packages/07/1a/1f573f8a6145f6d4c9130bbc120e0024daf1b24cf2a78d7393fa6eb6aba7/coverage-7.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72", size = 235584 }, + { url = "https://files.pythonhosted.org/packages/40/42/c8523f2e4db34aa9389caee0d3688b6ada7a84fcc782e943a868a7f302bd/coverage-7.6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51", size = 233486 }, + { url = "https://files.pythonhosted.org/packages/8d/95/565c310fffa16ede1a042e9ea1ca3962af0d8eb5543bc72df6b91dc0c3d5/coverage-7.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491", size = 234649 }, + { url = "https://files.pythonhosted.org/packages/d5/81/3b550674d98968ec29c92e3e8650682be6c8b1fa7581a059e7e12e74c431/coverage-7.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b", size = 233744 }, + { url = "https://files.pythonhosted.org/packages/0d/70/d66c7f51b3e33aabc5ea9f9624c1c9d9655472962270eb5e7b0d32707224/coverage-7.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea", size = 232204 }, + { url = "https://files.pythonhosted.org/packages/23/2d/2b3a2dbed7a5f40693404c8a09e779d7c1a5fbed089d3e7224c002129ec8/coverage-7.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a", size = 233335 }, + { url = "https://files.pythonhosted.org/packages/5a/4f/92d1d2ad720d698a4e71c176eacf531bfb8e0721d5ad560556f2c484a513/coverage-7.6.4-cp310-cp310-win32.whl", hash = "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa", size = 209435 }, + { url = "https://files.pythonhosted.org/packages/c7/b9/cdf158e7991e2287bcf9082670928badb73d310047facac203ff8dcd5ff3/coverage-7.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172", size = 210243 }, + { url = "https://files.pythonhosted.org/packages/87/31/9c0cf84f0dfcbe4215b7eb95c31777cdc0483c13390e69584c8150c85175/coverage-7.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b", size = 206819 }, + { url = "https://files.pythonhosted.org/packages/53/ed/a38401079ad320ad6e054a01ec2b61d270511aeb3c201c80e99c841229d5/coverage-7.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25", size = 207263 }, + { url = "https://files.pythonhosted.org/packages/20/e7/c3ad33b179ab4213f0d70da25a9c214d52464efa11caeab438592eb1d837/coverage-7.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546", size = 239205 }, + { url = "https://files.pythonhosted.org/packages/36/91/fc02e8d8e694f557752120487fd982f654ba1421bbaa5560debf96ddceda/coverage-7.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b", size = 236612 }, + { url = "https://files.pythonhosted.org/packages/cc/57/cb08f0eda0389a9a8aaa4fc1f9fec7ac361c3e2d68efd5890d7042c18aa3/coverage-7.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e", size = 238479 }, + { url = "https://files.pythonhosted.org/packages/d5/c9/2c7681a9b3ca6e6f43d489c2e6653a53278ed857fd6e7010490c307b0a47/coverage-7.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718", size = 237405 }, + { url = "https://files.pythonhosted.org/packages/b5/4e/ebfc6944b96317df8b537ae875d2e57c27b84eb98820bc0a1055f358f056/coverage-7.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db", size = 236038 }, + { url = "https://files.pythonhosted.org/packages/13/f2/3a0bf1841a97c0654905e2ef531170f02c89fad2555879db8fe41a097871/coverage-7.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522", size = 236812 }, + { url = "https://files.pythonhosted.org/packages/b9/9c/66bf59226b52ce6ed9541b02d33e80a6e816a832558fbdc1111a7bd3abd4/coverage-7.6.4-cp311-cp311-win32.whl", hash = "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf", size = 209400 }, + { url = "https://files.pythonhosted.org/packages/2a/a0/b0790934c04dfc8d658d4a62acb8f7ca0efdf3818456fcad757b11c6479d/coverage-7.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19", size = 210243 }, + { url = "https://files.pythonhosted.org/packages/7d/e7/9291de916d084f41adddfd4b82246e68d61d6a75747f075f7e64628998d2/coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2", size = 207013 }, + { url = "https://files.pythonhosted.org/packages/27/03/932c2c5717a7fa80cd43c6a07d3177076d97b79f12f40f882f9916db0063/coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117", size = 207251 }, + { url = "https://files.pythonhosted.org/packages/d5/3f/0af47dcb9327f65a45455fbca846fe96eb57c153af46c4754a3ba678938a/coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613", size = 240268 }, + { url = "https://files.pythonhosted.org/packages/8a/3c/37a9d81bbd4b23bc7d46ca820e16174c613579c66342faa390a271d2e18b/coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27", size = 237298 }, + { url = "https://files.pythonhosted.org/packages/c0/70/6b0627e5bd68204ee580126ed3513140b2298995c1233bd67404b4e44d0e/coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52", size = 239367 }, + { url = "https://files.pythonhosted.org/packages/3c/eb/634d7dfab24ac3b790bebaf9da0f4a5352cbc125ce6a9d5c6cf4c6cae3c7/coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2", size = 238853 }, + { url = "https://files.pythonhosted.org/packages/d9/0d/8e3ed00f1266ef7472a4e33458f42e39492e01a64281084fb3043553d3f1/coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1", size = 237160 }, + { url = "https://files.pythonhosted.org/packages/ce/9c/4337f468ef0ab7a2e0887a9c9da0e58e2eada6fc6cbee637a4acd5dfd8a9/coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5", size = 238824 }, + { url = "https://files.pythonhosted.org/packages/5e/09/3e94912b8dd37251377bb02727a33a67ee96b84bbbe092f132b401ca5dd9/coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17", size = 209639 }, + { url = "https://files.pythonhosted.org/packages/01/69/d4f3a4101171f32bc5b3caec8ff94c2c60f700107a6aaef7244b2c166793/coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08", size = 210428 }, + { url = "https://files.pythonhosted.org/packages/c2/4d/2dede4f7cb5a70fb0bb40a57627fddf1dbdc6b9c1db81f7c4dcdcb19e2f4/coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9", size = 207039 }, + { url = "https://files.pythonhosted.org/packages/3f/f9/d86368ae8c79e28f1fb458ebc76ae9ff3e8bd8069adc24e8f2fed03c58b7/coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba", size = 207298 }, + { url = "https://files.pythonhosted.org/packages/64/c5/b4cc3c3f64622c58fbfd4d8b9a7a8ce9d355f172f91fcabbba1f026852f6/coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c", size = 239813 }, + { url = "https://files.pythonhosted.org/packages/8a/86/14c42e60b70a79b26099e4d289ccdfefbc68624d096f4481163085aa614c/coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06", size = 236959 }, + { url = "https://files.pythonhosted.org/packages/7f/f8/4436a643631a2fbab4b44d54f515028f6099bfb1cd95b13cfbf701e7f2f2/coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f", size = 238950 }, + { url = "https://files.pythonhosted.org/packages/49/50/1571810ddd01f99a0a8be464a4ac8b147f322cd1e8e296a1528984fc560b/coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b", size = 238610 }, + { url = "https://files.pythonhosted.org/packages/f3/8c/6312d241fe7cbd1f0cade34a62fea6f333d1a261255d76b9a87074d8703c/coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21", size = 236697 }, + { url = "https://files.pythonhosted.org/packages/ce/5f/fef33dfd05d87ee9030f614c857deb6df6556b8f6a1c51bbbb41e24ee5ac/coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a", size = 238541 }, + { url = "https://files.pythonhosted.org/packages/a9/64/6a984b6e92e1ea1353b7ffa08e27f707a5e29b044622445859200f541e8c/coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e", size = 209707 }, + { url = "https://files.pythonhosted.org/packages/5c/60/ce5a9e942e9543783b3db5d942e0578b391c25cdd5e7f342d854ea83d6b7/coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963", size = 210439 }, + { url = "https://files.pythonhosted.org/packages/78/53/6719677e92c308207e7f10561a1b16ab8b5c00e9328efc9af7cfd6fb703e/coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f", size = 207784 }, + { url = "https://files.pythonhosted.org/packages/fa/dd/7054928930671fcb39ae6a83bb71d9ab5f0afb733172543ced4b09a115ca/coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806", size = 208058 }, + { url = "https://files.pythonhosted.org/packages/b5/7d/fd656ddc2b38301927b9eb3aae3fe827e7aa82e691923ed43721fd9423c9/coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11", size = 250772 }, + { url = "https://files.pythonhosted.org/packages/90/d0/eb9a3cc2100b83064bb086f18aedde3afffd7de6ead28f69736c00b7f302/coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3", size = 246490 }, + { url = "https://files.pythonhosted.org/packages/45/44/3f64f38f6faab8a0cfd2c6bc6eb4c6daead246b97cf5f8fc23bf3788f841/coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a", size = 248848 }, + { url = "https://files.pythonhosted.org/packages/5d/11/4c465a5f98656821e499f4b4619929bd5a34639c466021740ecdca42aa30/coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc", size = 248340 }, + { url = "https://files.pythonhosted.org/packages/f1/96/ebecda2d016cce9da812f404f720ca5df83c6b29f65dc80d2000d0078741/coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70", size = 246229 }, + { url = "https://files.pythonhosted.org/packages/16/d9/3d820c00066ae55d69e6d0eae11d6149a5ca7546de469ba9d597f01bf2d7/coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef", size = 247510 }, + { url = "https://files.pythonhosted.org/packages/8f/c3/4fa1eb412bb288ff6bfcc163c11700ff06e02c5fad8513817186e460ed43/coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e", size = 210353 }, + { url = "https://files.pythonhosted.org/packages/7e/77/03fc2979d1538884d921c2013075917fc927f41cd8526909852fe4494112/coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1", size = 211502 }, + { url = "https://files.pythonhosted.org/packages/cc/56/e1d75e8981a2a92c2a777e67c26efa96c66da59d645423146eb9ff3a851b/coverage-7.6.4-pp39.pp310-none-any.whl", hash = "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e", size = 198954 }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, +] + +[[package]] +name = "datasets" +version = "2.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "dill" }, + { name = "filelock" }, + { name = "fsspec", extra = ["http"] }, + { name = "huggingface-hub" }, + { name = "multiprocess" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "pyarrow-hotfix" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4f/ff/7dc4556366cdd62da3703972ad569d35b075a32c7690f9931ca12fa66a69/datasets-2.17.1.tar.gz", hash = "sha256:66ec24077807f374f379b62ab0256c4dcb7c38a57ff1529a22993e8d95f2f9f1", size = 2208209 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/75/ead8e4489217835fd8004c9c89dad8217deacd7e2591340dbe249b58c29f/datasets-2.17.1-py3-none-any.whl", hash = "sha256:346974daf2fe9c14ddb35646896b2308b95e7dc27709d1a6e25273573b140cf8", size = 536655 }, +] + +[[package]] +name = "debugpy" +version = "1.8.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/00/5a8b5dc8f52617c5e41845e26290ebea1ba06377cc08155b6d245c27b386/debugpy-1.8.7.zip", hash = "sha256:18b8f731ed3e2e1df8e9cdaa23fb1fc9c24e570cd0081625308ec51c82efe42e", size = 4957835 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/50/1850a5a0cab6f65a21e452166ec60bac5f8a995184d17e18bb9dc3789c72/debugpy-1.8.7-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:95fe04a573b8b22896c404365e03f4eda0ce0ba135b7667a1e57bd079793b96b", size = 2090182 }, + { url = "https://files.pythonhosted.org/packages/87/51/ef4d5c55c06689b377678bdee870e3df8eb2a3d9cf0e618b4d7255413c8a/debugpy-1.8.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:628a11f4b295ffb4141d8242a9bb52b77ad4a63a2ad19217a93be0f77f2c28c9", size = 3547569 }, + { url = "https://files.pythonhosted.org/packages/eb/df/a4ea1f95022f93522b59b71ec42d6703abe3e0bee753070118816555fee9/debugpy-1.8.7-cp310-cp310-win32.whl", hash = "sha256:85ce9c1d0eebf622f86cc68618ad64bf66c4fc3197d88f74bb695a416837dd55", size = 5153144 }, + { url = "https://files.pythonhosted.org/packages/47/f7/912408b69e83659bd62fa29ebb7984efe81aed4f5e08bfe10e31a1dc3c3a/debugpy-1.8.7-cp310-cp310-win_amd64.whl", hash = "sha256:29e1571c276d643757ea126d014abda081eb5ea4c851628b33de0c2b6245b037", size = 5185605 }, + { url = "https://files.pythonhosted.org/packages/f6/0a/4a4516ef4c07891542cb25620085507cab3c6b23a42b5630c17788fff83e/debugpy-1.8.7-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:caf528ff9e7308b74a1749c183d6808ffbedbb9fb6af78b033c28974d9b8831f", size = 2204794 }, + { url = "https://files.pythonhosted.org/packages/46/6f/2bb0bba20b8b74b7c341379dd99275cf6aa7722c1948fa99728716aad1b9/debugpy-1.8.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cba1d078cf2e1e0b8402e6bda528bf8fda7ccd158c3dba6c012b7897747c41a0", size = 3122160 }, + { url = "https://files.pythonhosted.org/packages/c0/ce/833351375cef971f0caa63fa82adf3f6949ad85410813026a4a436083a71/debugpy-1.8.7-cp311-cp311-win32.whl", hash = "sha256:171899588bcd412151e593bd40d9907133a7622cd6ecdbdb75f89d1551df13c2", size = 5078675 }, + { url = "https://files.pythonhosted.org/packages/7d/e1/e9ac2d546143a4defbaa2e609e173c912fb989cdfb5385c9771770a6bf5c/debugpy-1.8.7-cp311-cp311-win_amd64.whl", hash = "sha256:6e1c4ffb0c79f66e89dfd97944f335880f0d50ad29525dc792785384923e2211", size = 5102927 }, + { url = "https://files.pythonhosted.org/packages/59/4b/9f52ca1a799601a10cd2673503658bd8c8ecc4a7a43302ee29cf062474ec/debugpy-1.8.7-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:4d27d842311353ede0ad572600c62e4bcd74f458ee01ab0dd3a1a4457e7e3706", size = 2529803 }, + { url = "https://files.pythonhosted.org/packages/80/79/8bba39190d2ea17840925d287f1c6c3a7c60b58f5090444e9ecf176c540f/debugpy-1.8.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c1fd62ae0356e194f3e7b7a92acd931f71fe81c4b3be2c17a7b8a4b546ec2", size = 4170911 }, + { url = "https://files.pythonhosted.org/packages/3b/19/5b3d312936db8eb281310fa27903459328ed722d845d594ba5feaeb2f0b3/debugpy-1.8.7-cp312-cp312-win32.whl", hash = "sha256:2f729228430ef191c1e4df72a75ac94e9bf77413ce5f3f900018712c9da0aaca", size = 5195476 }, + { url = "https://files.pythonhosted.org/packages/9f/49/ad20b29f8c921fd5124530d3d39b8f2077efd51b71339a2eff02bba693e9/debugpy-1.8.7-cp312-cp312-win_amd64.whl", hash = "sha256:45c30aaefb3e1975e8a0258f5bbd26cd40cde9bfe71e9e5a7ac82e79bad64e39", size = 5235031 }, + { url = "https://files.pythonhosted.org/packages/41/95/29b247518d0a6afdb5249f5d05743c9c5bfaf4bd13a85b81cb5e1dc65837/debugpy-1.8.7-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:d050a1ec7e925f514f0f6594a1e522580317da31fbda1af71d1530d6ea1f2b40", size = 2517557 }, + { url = "https://files.pythonhosted.org/packages/4d/93/026e2000a0740e2f54b198f8dc317accf3a70b6524b2b15fa8e6eca74414/debugpy-1.8.7-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f4349a28e3228a42958f8ddaa6333d6f8282d5edaea456070e48609c5983b7", size = 4162703 }, + { url = "https://files.pythonhosted.org/packages/c3/92/a48e653b19a171434290ecdc5935b7a292a65488139c5271d6d0eceeb0f1/debugpy-1.8.7-cp313-cp313-win32.whl", hash = "sha256:11ad72eb9ddb436afb8337891a986302e14944f0f755fd94e90d0d71e9100bba", size = 5195220 }, + { url = "https://files.pythonhosted.org/packages/4e/b3/dc3c5527edafcd1a6d0f8c4ecc6c5c9bc431f77340cf4193328e98f0ac38/debugpy-1.8.7-cp313-cp313-win_amd64.whl", hash = "sha256:2efb84d6789352d7950b03d7f866e6d180284bc02c7e12cb37b489b7083d81aa", size = 5235333 }, + { url = "https://files.pythonhosted.org/packages/51/b1/a0866521c71a6ae3d3ca320e74835163a4671b1367ba360a55a0a51e5a91/debugpy-1.8.7-py2.py3-none-any.whl", hash = "sha256:57b00de1c8d2c84a61b90880f7e5b6deaf4c312ecbde3a0e8912f2a56c4ac9ae", size = 5210683 }, +] + +[[package]] +name = "decorator" +version = "5.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/0c/8d907af351aa16b42caae42f9d6aa37b900c67308052d10fdce809f8d952/decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", size = 35016 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/50/83c593b07763e1161326b3b8c6686f0f4b0f24d5526546bee538c89837d6/decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186", size = 9073 }, +] + +[[package]] +name = "deepdiff" +version = "7.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ordered-set" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/10/6f4b0bd0627d542f63a24f38e29d77095dc63d5f45bc1a7b4a6ca8750fa9/deepdiff-7.0.1.tar.gz", hash = "sha256:260c16f052d4badbf60351b4f77e8390bee03a0b516246f6839bc813fb429ddf", size = 421718 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/e6/d27d37dc55dbf40cdbd665aa52844b065ac760c9a02a02265f97ea7a4256/deepdiff-7.0.1-py3-none-any.whl", hash = "sha256:447760081918216aa4fd4ca78a4b6a848b81307b2ea94c810255334b759e1dc3", size = 80825 }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, +] + +[[package]] +name = "diffusion-for-multi-scale-molecular-dynamics" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "black" }, + { name = "comet-ml" }, + { name = "datasets" }, + { name = "deepdiff" }, + { name = "einops" }, + { name = "flake8" }, + { name = "flake8-docstrings" }, + { name = "gitpython" }, + { name = "hydra-core" }, + { name = "isort" }, + { name = "jinja2" }, + { name = "jupyter" }, + { name = "kaleido" }, + { name = "lammps" }, + { name = "mace-torch" }, + { name = "maml" }, + { name = "matplotlib" }, + { name = "monty" }, + { name = "myst-parser" }, + { name = "orion" }, + { name = "ovito" }, + { name = "pip" }, + { name = "pyarrow" }, + { name = "pykeops" }, + { name = "pymatgen" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-mock" }, + { name = "pytorch-lightning" }, + { name = "pytype" }, + { name = "pyyaml" }, + { name = "rich" }, + { name = "sphinx" }, + { name = "sphinx-autoapi" }, + { name = "sphinx-rtd-theme" }, + { name = "sphinxcontrib-katex" }, + { name = "sphinxcontrib-napoleon" }, + { name = "tensorboard" }, + { name = "torch" }, + { name = "torch-geometric" }, + { name = "torchode" }, + { name = "torchsde" }, + { name = "tqdm" }, +] + +[package.metadata] +requires-dist = [ + { name = "black", specifier = ">=24.10.0" }, + { name = "comet-ml", specifier = ">=3.47.0" }, + { name = "datasets", specifier = "==2.17.1" }, + { name = "deepdiff", specifier = "==7.0.1" }, + { name = "einops", specifier = "==0.8.0" }, + { name = "flake8", specifier = "==4.0.1" }, + { name = "flake8-docstrings", specifier = "==1.6.0" }, + { name = "gitpython", specifier = "==3.1.27" }, + { name = "hydra-core", specifier = "==1.3.2" }, + { name = "isort", specifier = "==5.13.2" }, + { name = "jinja2", specifier = "==3.1.2" }, + { name = "jupyter", specifier = "==1.0.0" }, + { name = "kaleido", specifier = "==0.2.1" }, + { name = "lammps", specifier = ">=2024.8.29.1.0" }, + { name = "mace-torch", specifier = "==0.3.4" }, + { name = "maml", specifier = "==2023.9.9" }, + { name = "matplotlib", specifier = "==3.8.3" }, + { name = "monty", specifier = "==2024.2.2" }, + { name = "myst-parser", specifier = "==2.0.0" }, + { name = "orion", specifier = ">=0.2.7" }, + { name = "ovito", specifier = "==3.10.6.post2" }, + { name = "pip", specifier = ">=24.2" }, + { name = "pyarrow", specifier = "==15.0.1" }, + { name = "pykeops", specifier = "==2.2.3" }, + { name = "pymatgen", specifier = "==2024.2.23" }, + { name = "pytest", specifier = "==7.1.2" }, + { name = "pytest-cov", specifier = "==3.0.0" }, + { name = "pytest-mock", specifier = "==3.12.0" }, + { name = "pytorch-lightning", specifier = ">=2.4.0" }, + { name = "pytype", specifier = "==2024.2.13" }, + { name = "pyyaml", specifier = "==6.0.1" }, + { name = "rich", specifier = "==13.7.1" }, + { name = "sphinx", specifier = "==7.2.6" }, + { name = "sphinx-autoapi", specifier = "==3.0.0" }, + { name = "sphinx-rtd-theme", specifier = "==2.0.0" }, + { name = "sphinxcontrib-katex", specifier = "==0.8.6" }, + { name = "sphinxcontrib-napoleon", specifier = "==0.7" }, + { name = "tensorboard", specifier = "==2.16.2" }, + { name = "torch", specifier = "==2.2.0" }, + { name = "torch-geometric", specifier = "==2.5.3" }, + { name = "torchode", specifier = "==0.2.0" }, + { name = "torchsde", specifier = "==0.2.6" }, + { name = "tqdm", specifier = "==4.64.0" }, +] + +[[package]] +name = "dill" +version = "0.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/17/4d/ac7ffa80c69ea1df30a8aa11b3578692a5118e7cd1aa157e3ef73b092d15/dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca", size = 184847 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/7a/cef76fd8438a42f96db64ddaa85280485a9c395e7df3db8158cfec1eee34/dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7", size = 116252 }, +] + +[[package]] +name = "dnspython" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632 }, +] + +[[package]] +name = "docutils" +version = "0.20.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/53/a5da4f2c5739cf66290fac1431ee52aff6851c7c8ffd8264f13affd7bcdd/docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b", size = 2058365 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/87/f238c0670b94533ac0353a4e2a1a771a0cc73277b88bff23d3ae35a256c1/docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6", size = 572666 }, +] + +[[package]] +name = "dulwich" +version = "0.22.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/47/c8bf38f8874829730775fbe5510b54087ff8529dbb9612bd144b76376ea7/dulwich-0.22.3.tar.gz", hash = "sha256:7968c7b8a877b614c46b5ee7c1b28411772123004d7cf6357e763ad2cbeb8254", size = 447748 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/42/5a0132fd8a5d59358d8c9d1d996ba0d7e063b635d2ee489812e47a7342a5/dulwich-0.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1460237edf8f64897128109e624a002c5d027fd2894ac24f802ba073a1c75b4e", size = 874174 }, + { url = "https://files.pythonhosted.org/packages/a0/a5/b533c13b6d18943ccaa516df19efd7ad58834a7fdb1e13cb1cfa009eda4f/dulwich-0.22.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66728526affdcc5e7cc2fd4a3ddbcb5f244aef03d88d1543098f9e85fb9070c6", size = 990159 }, + { url = "https://files.pythonhosted.org/packages/eb/50/4df5fe7fc50d16c0a0daa7f68ed8060cffdb320644da1abf2d0ab1a559d1/dulwich-0.22.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4d45dd321d48dcf13d7d677b60d58c725dd6e743fca0bc3aa2cd3e5e6a1c142", size = 968086 }, + { url = "https://files.pythonhosted.org/packages/e6/9a/15690043787ff28571632d71b547d1bdfdf3f50e9431083a1298bc158f96/dulwich-0.22.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6720b76a068af69556d12cd2464def882cf212eceedf7b9e450bed62f74c0e0", size = 1009344 }, + { url = "https://files.pythonhosted.org/packages/50/37/a231037b690f7a2d5df70ac5dae338ef76f0033572efeb0442bb8cb604be/dulwich-0.22.3-cp310-cp310-win32.whl", hash = "sha256:4a3517be3fa48ef3ce84e0e5e35a88105b937e41575b2ba07e08b9c8a900c4db", size = 568891 }, + { url = "https://files.pythonhosted.org/packages/b4/63/834837d12552d8fbeb22b453db3b51b58fbd580e1157c178b06d501b04e8/dulwich-0.22.3-cp310-cp310-win_amd64.whl", hash = "sha256:20d56c4d437e6fee3ebfc4240fc01ce4e17fc611159743520d0a687930ea9a94", size = 589736 }, + { url = "https://files.pythonhosted.org/packages/43/4b/88697289e299471cc4f25502a6f6cfad300c7a05b59fd2dc1794c696b10c/dulwich-0.22.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dd7601e6babe745c65f92f067eac0cb77a1ce770cc8f81662c70472e17aca866", size = 873864 }, + { url = "https://files.pythonhosted.org/packages/8f/e0/e70e04901a8886ecb44b17f1d1b79d2d02ad1221b94e4b323e0bd1142bf5/dulwich-0.22.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e10deab8067af927eb2523e912a0f7532a7aa5df0c03e9811681abc13f9d8c05", size = 989716 }, + { url = "https://files.pythonhosted.org/packages/69/4f/c30f8c50dbae7ba8d261eed60edf49917d190aac779e091d9023f716a654/dulwich-0.22.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:382a8cef3bfebad66b0e5be836ac0788d48f49d8158406523a42bc52f7e88916", size = 967738 }, + { url = "https://files.pythonhosted.org/packages/5d/c6/46b8bbcb36be2f0d6635149b5de067c53f97649cb97243b27a8e02be0d07/dulwich-0.22.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0ea6e8bb0e8b536aa1c725ff81a2ffacc358ade4a82a803c82e7d6c6e74254f", size = 1009394 }, + { url = "https://files.pythonhosted.org/packages/d3/50/cb1d7c79424f69a1fa3124542695a5dd18ac65b9cab96b5eb690c97882d6/dulwich-0.22.3-cp311-cp311-win32.whl", hash = "sha256:abe0859ab1226f5ed09733487b19eefcf6ad0258620fd98d820fc552a67d6538", size = 569038 }, + { url = "https://files.pythonhosted.org/packages/69/af/54a511e713d13f53ecd9d0d00f104102fabdaae58ca3229b0947f6870666/dulwich-0.22.3-cp311-cp311-win_amd64.whl", hash = "sha256:b0cbfe6d191e354f1af362fd4817243b2361f7a27f1619c3d67cdc48987aef44", size = 589883 }, + { url = "https://files.pythonhosted.org/packages/43/68/201aa4a744bfd1185451bb153e67e0ddb95c53b6aad4eb9758e653be1855/dulwich-0.22.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:607446fcc9d8e4051ce0f8714c5f757b07b20e652c59cb15ae155440a930a720", size = 872124 }, + { url = "https://files.pythonhosted.org/packages/db/88/69370514a237249b182054cc3d979a2aea4d061137ea6f3fc0201bdee863/dulwich-0.22.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07a3fb3a6f85caf33863b896a9fb6fb3b1df4ae91f67ca3682fe33cf3251f6f8", size = 987459 }, + { url = "https://files.pythonhosted.org/packages/cd/0e/84ae26b13485a4eab2104b18b975d5c0c559f8a1072deaf14507e7720064/dulwich-0.22.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb21a4ec9c5f965dee83fca4a4003eae7c23ec58573a646ea4826aa081cdaa02", size = 966155 }, + { url = "https://files.pythonhosted.org/packages/df/6e/f0aa674dcf55be20eba0e1e80673eb3daf4ca3e9798a2e7264400e37275b/dulwich-0.22.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb96c046cbf8011cf5fded9bcd96b3e4d0ebd2e277a87927efa91a5e639173c9", size = 1005927 }, + { url = "https://files.pythonhosted.org/packages/6b/30/875803f219dc691006ca868d678f8c171adb5060cc0b78bcaac1074f398f/dulwich-0.22.3-cp312-cp312-win32.whl", hash = "sha256:bed17abec074e3a0be07253e020425935eb5dbc639e5701854925e5d4ee91d20", size = 567718 }, + { url = "https://files.pythonhosted.org/packages/9f/57/2d8968c13a1b9ea10d5bfb6a42bb46a5bb503072ac09628b39ef27996b3c/dulwich-0.22.3-cp312-cp312-win_amd64.whl", hash = "sha256:e6a005e2a694f528ae839834bdaf674cb36a366e6a16ed8cc2646f99a2018385", size = 588097 }, + { url = "https://files.pythonhosted.org/packages/f2/8c/45a4a0e1fcdf9cafc7cc2e0d1cb3e22c7e1e7eb871e253f8e63b9920bad7/dulwich-0.22.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:315b33bb0658ae2892bdcbdfff2d8503adc54eb65880aca86ce158aae0962381", size = 871902 }, + { url = "https://files.pythonhosted.org/packages/9e/30/62fce572e0c2dd5178665f0ffbf7dda6f5887eba14ac971e8c3138b8c3d6/dulwich-0.22.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15fb3c2f273fbf08459ccd096f2930fc744dd8d78c4601b69be89de23e384698", size = 987344 }, + { url = "https://files.pythonhosted.org/packages/d2/47/e813770a624c400fd14a2a9d56e3f275190f8b08620fe0fe715f0d45b1ab/dulwich-0.22.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1ec82d8d4cc167f760f0f72febd1d4a99e23cbde758a2049b476d30e0b36467", size = 965847 }, + { url = "https://files.pythonhosted.org/packages/5a/db/6c6ef54133a24cbf6f5245d826f50800c3f4cfae6bde793ac8c2d224abf0/dulwich-0.22.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aab48954d4fefec6e84ba0c0a44c32d1d99b6162d43efa1ecd3a8b8d2ae5a707", size = 1005967 }, + { url = "https://files.pythonhosted.org/packages/6e/54/ed11f1fd6be0483f6cd153ef10a995f84463f6c2581779178a021ef7bdcd/dulwich-0.22.3-cp313-cp313-win32.whl", hash = "sha256:e24224fa4cdda64c0854c88ee3d1b47c7c74a47ef259b38dcd5a78b8dcbe5020", size = 567366 }, + { url = "https://files.pythonhosted.org/packages/10/50/32e4975370a9382a1a2e26d0572fe7fd29ef60713d8c2fe215198e66e326/dulwich-0.22.3-cp313-cp313-win_amd64.whl", hash = "sha256:02be7c0d183f08dfd34f27522bb5f5be9431d88eb76fd2a01012aa14fc4b26df", size = 587552 }, + { url = "https://files.pythonhosted.org/packages/ea/66/54853e41f5101ed9d0be1ab2277ac2a1dd13ad918c7cdac1a69e807374c4/dulwich-0.22.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c2f065727f319fb3c9e6f3a3d91a1bfdedb7b902bfc671cade9a7c8b27dd5c8", size = 893323 }, + { url = "https://files.pythonhosted.org/packages/8a/5b/745f2477eafb63e2a6f7baae8fdbd7b12ffa277eb4fd4da3baf6cd99fd55/dulwich-0.22.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcc93ff43e74415af094c99ca7f70be86d68f80fbace4282e17db440f38c1669", size = 992838 }, + { url = "https://files.pythonhosted.org/packages/c4/21/63bcb2e714b134c4e3d330bceb42d12e9d152b9776863d84e9452ff1852d/dulwich-0.22.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73f960ae68264ce09bade48151beea887761f046934cdb91a8ce2a8c9ecb7673", size = 972675 }, + { url = "https://files.pythonhosted.org/packages/66/5d/53c5893eae80c82256d6f442fef5fc7d8d8634452a161c58e30bdd617056/dulwich-0.22.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3e5bb4dedbd1a379871b1db7ef84de051446c8e4983e9c884ca5ea0f7ef94887", size = 1011265 }, + { url = "https://files.pythonhosted.org/packages/9b/40/9a8a7362dc550c5a50a81461bf5a11fc29e6ebc1ba24320bed2da6b26f53/dulwich-0.22.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:da3f10729f8cb6e1a936fc56439391feb3ad56642d1172d32a5e95f51b1bc73b", size = 592556 }, +] + +[[package]] +name = "e3nn" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opt-einsum-fx" }, + { name = "scipy" }, + { name = "sympy" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/98/8e7102dea93106603383fda23bf96649c397a37b910e7c76086e584cd92d/e3nn-0.4.4.tar.gz", hash = "sha256:51c91a84c1fb72e7e3600000958fa8caad48f8270937090fb8d0f8bfffbb3525", size = 361661 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/b6/c8327065d1dd8bca28521fe65ff72b649ed17ca8a1f0c1f498006d7567b7/e3nn-0.4.4-py3-none-any.whl", hash = "sha256:87d99876abb362a6e07d555d10752ff2e20c2b7731d8928ebe5d9121fb019f84", size = 387707 }, +] + +[[package]] +name = "einops" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/ca/9f5dcb8bead39959454c3912266bedc4c315839cee0e0ca9f4328f4588c1/einops-0.8.0.tar.gz", hash = "sha256:63486517fed345712a8385c100cb279108d9d47e6ae59099b07657e983deae85", size = 58861 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/5a/f0b9ad6c0a9017e62d4735daaeb11ba3b6c009d69a26141b258cd37b5588/einops-0.8.0-py3-none-any.whl", hash = "sha256:9572fb63046264a862693b0a87088af3bdc8c068fde03de63453cbbde245465f", size = 43223 }, +] + +[[package]] +name = "everett" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/b4/c7c61c0b243c4277d19299cd1bccee8b2b57d04073c0d8625799fe47f5c9/everett-3.1.0.tar.gz", hash = "sha256:46175da5bcb06c193aa129e59714bca981344ff067c3a8bc2e625bc0b3dc01f6", size = 73796 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/9a/d882fd7562208456236fb2e62b762bf16fbc9ecde842bb871f676ca0f7e1/everett-3.1.0-py2.py3-none-any.whl", hash = "sha256:db13891b849e45e54faea93ee79881d12458c5378f5b9b7f806eeff03ce1de3c", size = 35702 }, +] + +[package.optional-dependencies] +ini = [ + { name = "configobj" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, +] + +[[package]] +name = "executing" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/e3/7d45f492c2c4a0e8e0fad57d081a7c8a0286cdd86372b070cca1ec0caa1e/executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab", size = 977485 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/fd/afcd0496feca3276f509df3dbd5dae726fcc756f1a08d9e25abe1733f962/executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf", size = 25805 }, +] + +[[package]] +name = "falcon" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/10/c14b45fcf3c78c8851441678b59d7d50cf6c5f8d0d816a4485d62b076ce1/falcon-4.0.0.tar.gz", hash = "sha256:2a63c7503aef1139cb4c9d3d0ab1a901870215c0ea9bc2740307df2b4a288854", size = 627291 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/70/5ddb7aa4501cda5860df381d3b9af6ee83bf051d16c0cd8310ba6e62bb43/falcon-4.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f93f4bc55480f019446bfd962ae0422a9907d237b1ea52549c20437e7448c71", size = 2587126 }, + { url = "https://files.pythonhosted.org/packages/c7/69/990976720660c9ad71ffadc408c931c6e725fba2af60bb9a4cd6e435b469/falcon-4.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37aea638333a9d07b540c0e36e4d4b486643bf99061b3ed0e46fc626250d4840", size = 2466709 }, + { url = "https://files.pythonhosted.org/packages/a3/e8/21da48b3cc1ff0acef7712665162ed299c0f031c4116e77015e0c19ff569/falcon-4.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49cda80b389fbd6198873114cbd259029e67f43c11fe2cba2577a67f5b1b9421", size = 10662629 }, + { url = "https://files.pythonhosted.org/packages/21/8b/ff6560842541b0cd585d00792f9a463025e3996860949070657392454a62/falcon-4.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a40d256a931da930cb05c7d30312487ceaac535bce6cb5ee31dc756010113c6", size = 11258168 }, + { url = "https://files.pythonhosted.org/packages/94/68/0397e1df87923791bae1319f9fab8bc62a9476c19bb0e55e4a6e7e9023a6/falcon-4.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d611254ca1fca13cfdf8eeb72d44ad062b7c70501cd649065dda688a4268b9c", size = 10783053 }, + { url = "https://files.pythonhosted.org/packages/4e/ba/24ee93bb83446ffdf4d6f0e92c40391bfa09b002e3a643d04b4dbd1c9030/falcon-4.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5f4b5c23ba4da6964174b091c692f4d45d6c5884d4f0a38fdc2bfbddde0957ff", size = 10473901 }, + { url = "https://files.pythonhosted.org/packages/90/f0/6f52fa1a4e4cab454096fd62d386bccd7a3e1a3a3fe8ea0ae40249c21db4/falcon-4.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d622658e49cfc7758e72437bf76e0c75ba4908e4c1ba6036f222a130fb18a9bb", size = 10905761 }, + { url = "https://files.pythonhosted.org/packages/fd/6d/160990cb3937459498766e5a81883d8aeb7fddc87654bcb19d2b03263b15/falcon-4.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:9e5bb70afdcf47c91a7b974e78dccd3315311873eb738cf99ccfb364439e8898", size = 2387474 }, + { url = "https://files.pythonhosted.org/packages/33/22/49101ea22f5847340d7cb5384159423cabea25e57f83ffa701917092e90c/falcon-4.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4239c89232aedbff6be0ceb10ead5093fa0327234d99af1387330554bd88355", size = 2593263 }, + { url = "https://files.pythonhosted.org/packages/d8/00/63ce8cfd67c411a59b77189b1bffef3ee7262abcd74677e2f44abc68ac1f/falcon-4.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1ea7d9c87dc4d948d42261db446e4456894925d552666ebd955b0bc9ccf98f7", size = 2468181 }, + { url = "https://files.pythonhosted.org/packages/f6/68/ff24f6a87ea191758d69a321a1581fa900b59b10cd9a661c28075ee88b29/falcon-4.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff149e5cf1a1ab1c74dc592fdf9a0caf17417efbebeb275f4102edeb9f74e0d8", size = 11875787 }, + { url = "https://files.pythonhosted.org/packages/d9/bc/44f89954ac14d584cbdcce85bfe1c21da9cc357a211bcb1736e6e4295a9e/falcon-4.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c30fa42783e8ae8b688fb3a66fdf1fc5f52802be7bc69dd9b21f2929efcdcc7", size = 12418671 }, + { url = "https://files.pythonhosted.org/packages/bb/88/062b8c46600a0f6ab04589530ef9310ce182551262e642d9559e720f3d25/falcon-4.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f112076ec76a48d226a81d0b05787eabfbf6f307c97f9a0b4141c55c0a384fd4", size = 11965197 }, + { url = "https://files.pythonhosted.org/packages/ec/7b/ff5de4c3a17c85372ebfa32e7829ebea5162372357ad1340ca69a5ece481/falcon-4.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b1de53b7737ab015711046f234d41c77894cfb03bbbcf370c21be858db8e441d", size = 11651756 }, + { url = "https://files.pythonhosted.org/packages/35/bd/13396d5256af16883fc8b6bd16d0f316b6cadeb22d351056edd79f2dabcb/falcon-4.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18a7daee294f77d9991be250018f9e22826e9f246b2618357597faf19e62a20d", size = 12042326 }, + { url = "https://files.pythonhosted.org/packages/30/b1/3e650a3ea86e4314e3edd9709140ae733a217d14cb1ffdd57ed255daf342/falcon-4.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:6f7a0b8602a9796452c08240063e4e3e4884b86bbc126b04431c003be37e5373", size = 2396312 }, + { url = "https://files.pythonhosted.org/packages/e8/e3/b1fd17a9e742f23bb256c7bcf3053eb12de6c3f96d81fa51ff2016907f85/falcon-4.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f279121d797d48fff99b31fe561653a60e4439e285b817894838f44e403b6e06", size = 2566009 }, + { url = "https://files.pythonhosted.org/packages/a9/81/9f79b32c40581f2cc26236b06398d92972d45166256c2a0f83fde5dd483c/falcon-4.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a58d0bd9190c5adc75950ab745a9e39e79f83597ab019c47d5b2e714f4780f44", size = 2457940 }, + { url = "https://files.pythonhosted.org/packages/75/cf/17f85a7b5e44a962857fd9060f1e2d2adf37555b2adfb03e80f3331be7b1/falcon-4.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:535f5abd605e5c263dfebaaec3e2e04f87743ea9d5c23f1ebc19431819c10ee7", size = 11916272 }, + { url = "https://files.pythonhosted.org/packages/ae/50/64cf5a3717b72bc919771cba49d760092dfaf5eefc008ae8ec4da0876bca/falcon-4.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:490b841e7cb264c7a2881d697033909005b08b48ccf7890f13de50d82eb6c935", size = 12490718 }, + { url = "https://files.pythonhosted.org/packages/1d/9b/b4afcc91b92e33fe9f46da33d85faf456671f24cd8ddcf9b491f66b8d35d/falcon-4.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a94ca3cab737799ee61457295e5a1074395c11c5cb2d8cffc0969997bf979009", size = 12114105 }, + { url = "https://files.pythonhosted.org/packages/3b/24/11daeeb98d5d9642eaeaa3486a06134f745ac338039a2f03cc0aae714bf8/falcon-4.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:05ba04f45c0ec8cc56138cb2b23ab364bd53e2be639ec0ef3041c282af8950e3", size = 11618054 }, + { url = "https://files.pythonhosted.org/packages/10/76/d4caf82abea3465f861dfe3752b3f991ff2990df4735feebfea3b9eb8067/falcon-4.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b4ace5559c7792378a3a786989234004edc39d165080e38a300e315845f98dfe", size = 12264343 }, + { url = "https://files.pythonhosted.org/packages/54/f7/36e254af1be3e6b5ae56e7b4524ffe85f54b738e8408bf572fc00d92c24e/falcon-4.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:13f83d8b5313e96ad047364430f9ff018903105409c0befdc64f6fa3c27f1dbf", size = 2348853 }, + { url = "https://files.pythonhosted.org/packages/25/15/287abab1a3eec7e22812393f8e491016c1e797431cc1cd4356b7fa71dedc/falcon-4.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e99c7d272625c348f74e511e19c6d9103809650dff7866dcaa37d0040f862005", size = 2529228 }, + { url = "https://files.pythonhosted.org/packages/de/b4/bfa31d482c2eb225a5d7b72da6a81b7b18529194f74c03425408defa61d9/falcon-4.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:860f1e0923d62cd3dd6fe14a7d19edd95106ddd1aa48304511da74b694930fa4", size = 2423079 }, + { url = "https://files.pythonhosted.org/packages/5a/e8/65c428363d95cb001822767ee010bccbc01e6009df460e29c7bcd26ca3f3/falcon-4.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:459511541b0b8638bb773a31ceb8a9b1beee341bf0902a45b7c24548b9fc58e6", size = 11710927 }, + { url = "https://files.pythonhosted.org/packages/9d/3c/8b2447a09d8bcdba1311404a95c4b142717182372f976f1bc80c1d0f3cad/falcon-4.0.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87e8365ae3018db009ed907f5999243299fadcc2ee895198acf0628ffb0d769f", size = 12287161 }, + { url = "https://files.pythonhosted.org/packages/72/ef/e8d0727cb16a17fb5a94c90dda677b4c2317d63acd2ed58993251118e543/falcon-4.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93a9bd863b72431bb9a70265b20dea0e6d1139b8bac943d8f64884af2834f17c", size = 11925638 }, + { url = "https://files.pythonhosted.org/packages/67/3a/71dc9a5d7c584f72448c9ba2dff7afc846011e0949d3af6e99ac0897f082/falcon-4.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:804add1eca47fda3aed77f9bb9fa6a013f2b41e374071faeddceff9428dc5a7b", size = 11437274 }, + { url = "https://files.pythonhosted.org/packages/ff/1d/de894ca16a004703ac08d218f1d5c745f97c6f87d9001f5d2ab34f8d7e0c/falcon-4.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:31aea205e03fa7cff583c0d7515e39cbf071831e0db234cf1158c15966e607fa", size = 12102999 }, + { url = "https://files.pythonhosted.org/packages/15/5d/fd3eed1e35126495b58b2fef9213d80eaf24c012b1b0ad16c624726b466d/falcon-4.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:a37d03fa6644cf234b4c23f8950557c8e89aa4cfb4c733a935e504bade9ccad7", size = 2324464 }, + { url = "https://files.pythonhosted.org/packages/37/47/433d614ed99eafdab13670988377f0a2e3929b573c62e43945f079430468/falcon-4.0.0-py3-none-any.whl", hash = "sha256:87d294700b0b62a3c5ed1f70a61e776d7ed4c9844324e61bac4a0b9c8af4927d", size = 586973 }, +] + +[[package]] +name = "falcon-cors" +version = "1.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "falcon" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/92/494602f8221b8875b8b88d89c1571d3361f99ce4facc1883b30bba3f765e/falcon-cors-1.1.7.tar.gz", hash = "sha256:1788ec5c31b5a39240f7979a7c7170a33ea4952dc97e151858a82dcde1a9fe8a", size = 6267 } + +[[package]] +name = "fastjsonschema" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/3f/3ad5e7be13b4b8b55f4477141885ab2364f65d5f6ad5f7a9daffd634d066/fastjsonschema-2.20.0.tar.gz", hash = "sha256:3d48fc5300ee96f5d116f10fe6f28d938e6008f59a6a025c2649475b87f76a23", size = 373056 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/ca/086311cdfc017ec964b2436fe0c98c1f4efcb7e4c328956a22456e497655/fastjsonschema-2.20.0-py3-none-any.whl", hash = "sha256:5875f0b0fa7a0043a91e93a9b8f793bcbbba9691e7fd83dca95c28ba26d21f0a", size = 23543 }, +] + +[[package]] +name = "filelock" +version = "3.16.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, +] + +[[package]] +name = "flake8" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mccabe" }, + { name = "pycodestyle" }, + { name = "pyflakes" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e6/84/d8db922289195c435779b4ca3a3f583f263f87e67954f7b2e83c8da21f48/flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d", size = 154905 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/39/cde2c8a227abb4f9ce62fe55586b920f438f1d2903a1a22514d0b982c333/flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d", size = 64091 }, +] + +[[package]] +name = "flake8-docstrings" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flake8" }, + { name = "pydocstyle" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/a6/b8a953fb256ee383fed9094f7270ab75cd637c23749c211f0e6b3552a31e/flake8-docstrings-1.6.0.tar.gz", hash = "sha256:9fe7c6a306064af8e62a055c2f61e9eb1da55f84bb39caef2b84ce53708ac34b", size = 6170 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/ad/35dc9b3585ddbab617679b97487450f873e582408843c07fe97f524463c2/flake8_docstrings-1.6.0-py2.py3-none-any.whl", hash = "sha256:99cac583d6c7e32dd28bbfbef120a7c0d1b6dde4adb5a9fd441c4227a6534bde", size = 5709 }, +] + +[[package]] +name = "fonttools" +version = "4.54.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/1d/70b58e342e129f9c0ce030029fb4b2b0670084bbbfe1121d008f6a1e361c/fonttools-4.54.1.tar.gz", hash = "sha256:957f669d4922f92c171ba01bef7f29410668db09f6c02111e22b2bce446f3285", size = 3463867 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/f9/285c9a2d0e86b9bf2babfe19bec00502361fda56cea144d6a269ab9a32e6/fonttools-4.54.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ed7ee041ff7b34cc62f07545e55e1468808691dddfd315d51dd82a6b37ddef2", size = 2766970 }, + { url = "https://files.pythonhosted.org/packages/2f/9a/9d899e7ae55b0dd30632e6ca36c0f5fa1205b1b096ec171c9be903673058/fonttools-4.54.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41bb0b250c8132b2fcac148e2e9198e62ff06f3cc472065dff839327945c5882", size = 2254639 }, + { url = "https://files.pythonhosted.org/packages/16/6f/b99e0c347732fb003077a2cff38c26f381969b74329aa5597e344d540fe1/fonttools-4.54.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7965af9b67dd546e52afcf2e38641b5be956d68c425bef2158e95af11d229f10", size = 4574346 }, + { url = "https://files.pythonhosted.org/packages/e5/12/9a45294a7c4520cc32936edd15df1d5c24af701d2f5f51070a9a43d7664b/fonttools-4.54.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278913a168f90d53378c20c23b80f4e599dca62fbffae4cc620c8eed476b723e", size = 4630045 }, + { url = "https://files.pythonhosted.org/packages/64/52/ba4f00eb6003e4089264cd9ce126cddec2b39c78f1ab01be9dc389a197ca/fonttools-4.54.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0e88e3018ac809b9662615072dcd6b84dca4c2d991c6d66e1970a112503bba7e", size = 4569527 }, + { url = "https://files.pythonhosted.org/packages/41/ff/85f93a14c8acf978f332508f980dcaff5ed5f0cf284371eb101a78f0b1f4/fonttools-4.54.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4aa4817f0031206e637d1e685251ac61be64d1adef111060df84fdcbc6ab6c44", size = 4741677 }, + { url = "https://files.pythonhosted.org/packages/6f/f0/06ea7d9f8b7b6d4758a50271517db04039c4c6da8fa0475d417e005624d0/fonttools-4.54.1-cp310-cp310-win32.whl", hash = "sha256:7e3b7d44e18c085fd8c16dcc6f1ad6c61b71ff463636fcb13df7b1b818bd0c02", size = 2166797 }, + { url = "https://files.pythonhosted.org/packages/71/73/545c817e34b8c34585291951722e1a5ae579380deb009576d9d244b13ab0/fonttools-4.54.1-cp310-cp310-win_amd64.whl", hash = "sha256:dd9cc95b8d6e27d01e1e1f1fae8559ef3c02c76317da650a19047f249acd519d", size = 2210552 }, + { url = "https://files.pythonhosted.org/packages/aa/2c/8b5d82fe2d9c7f260fb73121418f5e07d4e38c329ea3886a5b0e55586113/fonttools-4.54.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5419771b64248484299fa77689d4f3aeed643ea6630b2ea750eeab219588ba20", size = 2768112 }, + { url = "https://files.pythonhosted.org/packages/37/2e/f94118b92f7b6a9ec93840101b64bfdd09f295b266133857e8e852a5c35c/fonttools-4.54.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:301540e89cf4ce89d462eb23a89464fef50915255ece765d10eee8b2bf9d75b2", size = 2254739 }, + { url = "https://files.pythonhosted.org/packages/45/4b/8a32f56a13e78256192f77d6b65583c43538c7955f5420887bb574b91ddf/fonttools-4.54.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ae5091547e74e7efecc3cbf8e75200bc92daaeb88e5433c5e3e95ea8ce5aa7", size = 4879772 }, + { url = "https://files.pythonhosted.org/packages/96/13/748b7f7239893ff0796de11074b0ad8aa4c3da2d9f4d79a128b0b16147f3/fonttools-4.54.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82834962b3d7c5ca98cb56001c33cf20eb110ecf442725dc5fdf36d16ed1ab07", size = 4927686 }, + { url = "https://files.pythonhosted.org/packages/7c/82/91bc5a378b4a0593fa90ea706f68ce7e9e871c6873e0d91e134d107758db/fonttools-4.54.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d26732ae002cc3d2ecab04897bb02ae3f11f06dd7575d1df46acd2f7c012a8d8", size = 4890789 }, + { url = "https://files.pythonhosted.org/packages/ea/ca/82be5d4f8b78405cdb3f7f3f1316af5e8db93216121f19da9f684a35beee/fonttools-4.54.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58974b4987b2a71ee08ade1e7f47f410c367cdfc5a94fabd599c88165f56213a", size = 5061351 }, + { url = "https://files.pythonhosted.org/packages/da/2f/fd6e1b01c80c473c3ac52492dcf8d26cdf5f4a89b4f30875ecfbda55e7ff/fonttools-4.54.1-cp311-cp311-win32.whl", hash = "sha256:ab774fa225238986218a463f3fe151e04d8c25d7de09df7f0f5fce27b1243dbc", size = 2166210 }, + { url = "https://files.pythonhosted.org/packages/63/f1/3a081cd047d83b5966cb0d7ef3fea929ee6eddeb94d8fbfdb2a19bd60cc7/fonttools-4.54.1-cp311-cp311-win_amd64.whl", hash = "sha256:07e005dc454eee1cc60105d6a29593459a06321c21897f769a281ff2d08939f6", size = 2211946 }, + { url = "https://files.pythonhosted.org/packages/27/b6/f9d365932dcefefdcc794985f8846471e60932070c557e0f66ed195fccec/fonttools-4.54.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:54471032f7cb5fca694b5f1a0aaeba4af6e10ae989df408e0216f7fd6cdc405d", size = 2761873 }, + { url = "https://files.pythonhosted.org/packages/67/9d/cfbfe36e5061a8f68b154454ba2304eb01f40d4ba9b63e41d9058909baed/fonttools-4.54.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fa92cb248e573daab8d032919623cc309c005086d743afb014c836636166f08", size = 2251828 }, + { url = "https://files.pythonhosted.org/packages/90/41/5573e074739efd9227dd23647724f01f6f07ad062fe09d02e91c5549dcf7/fonttools-4.54.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a911591200114969befa7f2cb74ac148bce5a91df5645443371aba6d222e263", size = 4792544 }, + { url = "https://files.pythonhosted.org/packages/08/07/aa85cc62abcc940b25d14b542cf585eebf4830032a7f6a1395d696bb3231/fonttools-4.54.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93d458c8a6a354dc8b48fc78d66d2a8a90b941f7fec30e94c7ad9982b1fa6bab", size = 4875892 }, + { url = "https://files.pythonhosted.org/packages/47/23/c5726c2615446c498a976bed21c35a242a97eee39930a2655d616ca885cc/fonttools-4.54.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5eb2474a7c5be8a5331146758debb2669bf5635c021aee00fd7c353558fc659d", size = 4769822 }, + { url = "https://files.pythonhosted.org/packages/8f/7b/87f7f7d35e0732ac67422dfa6f05e2b568fb6ca2dcd7f3e4f500293cfd75/fonttools-4.54.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9c563351ddc230725c4bdf7d9e1e92cbe6ae8553942bd1fb2b2ff0884e8b714", size = 5029455 }, + { url = "https://files.pythonhosted.org/packages/e0/09/241aa498587889576838aa73c78d22b70ce06970807a5475d372baa7ccb7/fonttools-4.54.1-cp312-cp312-win32.whl", hash = "sha256:fdb062893fd6d47b527d39346e0c5578b7957dcea6d6a3b6794569370013d9ac", size = 2154411 }, + { url = "https://files.pythonhosted.org/packages/b9/0a/a57caaff3bc880779317cb157e5b49dc47fad54effe027016abd355b0651/fonttools-4.54.1-cp312-cp312-win_amd64.whl", hash = "sha256:e4564cf40cebcb53f3dc825e85910bf54835e8a8b6880d59e5159f0f325e637e", size = 2200412 }, + { url = "https://files.pythonhosted.org/packages/05/3d/cc515cae84a11d696f2cb7c139a90997b15f02e2e97ec09a5d79302cbcd7/fonttools-4.54.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6e37561751b017cf5c40fce0d90fd9e8274716de327ec4ffb0df957160be3bff", size = 2749174 }, + { url = "https://files.pythonhosted.org/packages/03/03/05d4b22d1a674d066380657f60bbc0eda2d206446912e676d1a33a206878/fonttools-4.54.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:357cacb988a18aace66e5e55fe1247f2ee706e01debc4b1a20d77400354cddeb", size = 2246267 }, + { url = "https://files.pythonhosted.org/packages/52/c3/bb6086adb675e8b0963a7dbb7769e7118c95b687dd318cd660aefd4b4c8c/fonttools-4.54.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e953cc0bddc2beaf3a3c3b5dd9ab7554677da72dfaf46951e193c9653e515a", size = 4855090 }, + { url = "https://files.pythonhosted.org/packages/80/a1/d7192b6a104e3f9ea8e5b1c3463a6240399f0fa826a782eff636cbe0495a/fonttools-4.54.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58d29b9a294573d8319f16f2f79e42428ba9b6480442fa1836e4eb89c4d9d61c", size = 5005449 }, + { url = "https://files.pythonhosted.org/packages/5a/6c/ecfd5c6cd8c9006e85b128d073af26bb263e8aa47506374cb14b25bcf65f/fonttools-4.54.1-cp313-cp313-win32.whl", hash = "sha256:9ef1b167e22709b46bf8168368b7b5d3efeaaa746c6d39661c1b4405b6352e58", size = 2152496 }, + { url = "https://files.pythonhosted.org/packages/63/da/f7a1d837de419e3d4cccbd0dbf53c7399f610f65ceb9bcbf2480f3ae7950/fonttools-4.54.1-cp313-cp313-win_amd64.whl", hash = "sha256:262705b1663f18c04250bd1242b0515d3bbae177bee7752be67c979b7d47f43d", size = 2197257 }, + { url = "https://files.pythonhosted.org/packages/57/5e/de2e6e51cb6894f2f2bc2641f6c845561361b622e96df3cca04df77222c9/fonttools-4.54.1-py3-none-any.whl", hash = "sha256:37cddd62d83dc4f72f7c3f3c2bcf2697e89a30efb152079896544a93907733bd", size = 1096920 }, +] + +[[package]] +name = "fqdn" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121 }, +] + +[[package]] +name = "frozenlist" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/3d/2102257e7acad73efc4a0c306ad3953f68c504c16982bbdfee3ad75d8085/frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b", size = 37820 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/35/1328c7b0f780d34f8afc1d87ebdc2bb065a123b24766a0b475f0d67da637/frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac", size = 94315 }, + { url = "https://files.pythonhosted.org/packages/f4/d6/ca016b0adcf8327714ccef969740688808c86e0287bf3a639ff582f24e82/frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868", size = 53805 }, + { url = "https://files.pythonhosted.org/packages/ae/83/bcdaa437a9bd693ba658a0310f8cdccff26bd78e45fccf8e49897904a5cd/frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776", size = 52163 }, + { url = "https://files.pythonhosted.org/packages/d4/e9/759043ab7d169b74fe05ebfbfa9ee5c881c303ebc838e308346204309cd0/frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a", size = 238595 }, + { url = "https://files.pythonhosted.org/packages/f8/ce/b9de7dc61e753dc318cf0de862181b484178210c5361eae6eaf06792264d/frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad", size = 262428 }, + { url = "https://files.pythonhosted.org/packages/36/ce/dc6f29e0352fa34ebe45421960c8e7352ca63b31630a576e8ffb381e9c08/frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c", size = 258867 }, + { url = "https://files.pythonhosted.org/packages/51/47/159ac53faf8a11ae5ee8bb9db10327575557504e549cfd76f447b969aa91/frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe", size = 229412 }, + { url = "https://files.pythonhosted.org/packages/ec/25/0c87df2e53c0c5d90f7517ca0ff7aca78d050a8ec4d32c4278e8c0e52e51/frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a", size = 239539 }, + { url = "https://files.pythonhosted.org/packages/97/94/a1305fa4716726ae0abf3b1069c2d922fcfd442538cb850f1be543f58766/frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98", size = 253379 }, + { url = "https://files.pythonhosted.org/packages/53/82/274e19f122e124aee6d113188615f63b0736b4242a875f482a81f91e07e2/frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75", size = 245901 }, + { url = "https://files.pythonhosted.org/packages/b8/28/899931015b8cffbe155392fe9ca663f981a17e1adc69589ee0e1e7cdc9a2/frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5", size = 263797 }, + { url = "https://files.pythonhosted.org/packages/6e/4f/b8a5a2f10c4a58c52a52a40cf6cf1ffcdbf3a3b64f276f41dab989bf3ab5/frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950", size = 264415 }, + { url = "https://files.pythonhosted.org/packages/b0/2c/7be3bdc59dbae444864dbd9cde82790314390ec54636baf6b9ce212627ad/frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc", size = 253964 }, + { url = "https://files.pythonhosted.org/packages/2e/ec/4fb5a88f6b9a352aed45ab824dd7ce4801b7bcd379adcb927c17a8f0a1a8/frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1", size = 44559 }, + { url = "https://files.pythonhosted.org/packages/61/15/2b5d644d81282f00b61e54f7b00a96f9c40224107282efe4cd9d2bf1433a/frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439", size = 50434 }, + { url = "https://files.pythonhosted.org/packages/01/bc/8d33f2d84b9368da83e69e42720cff01c5e199b5a868ba4486189a4d8fa9/frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0", size = 97060 }, + { url = "https://files.pythonhosted.org/packages/af/b2/904500d6a162b98a70e510e743e7ea992241b4f9add2c8063bf666ca21df/frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49", size = 55347 }, + { url = "https://files.pythonhosted.org/packages/5b/9c/f12b69997d3891ddc0d7895999a00b0c6a67f66f79498c0e30f27876435d/frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced", size = 53374 }, + { url = "https://files.pythonhosted.org/packages/ac/6e/e0322317b7c600ba21dec224498c0c5959b2bce3865277a7c0badae340a9/frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0", size = 273288 }, + { url = "https://files.pythonhosted.org/packages/a7/76/180ee1b021568dad5b35b7678616c24519af130ed3fa1e0f1ed4014e0f93/frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106", size = 284737 }, + { url = "https://files.pythonhosted.org/packages/05/08/40159d706a6ed983c8aca51922a93fc69f3c27909e82c537dd4054032674/frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068", size = 280267 }, + { url = "https://files.pythonhosted.org/packages/e0/18/9f09f84934c2b2aa37d539a322267939770362d5495f37783440ca9c1b74/frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2", size = 258778 }, + { url = "https://files.pythonhosted.org/packages/b3/c9/0bc5ee7e1f5cc7358ab67da0b7dfe60fbd05c254cea5c6108e7d1ae28c63/frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19", size = 272276 }, + { url = "https://files.pythonhosted.org/packages/12/5d/147556b73a53ad4df6da8bbb50715a66ac75c491fdedac3eca8b0b915345/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82", size = 272424 }, + { url = "https://files.pythonhosted.org/packages/83/61/2087bbf24070b66090c0af922685f1d0596c24bb3f3b5223625bdeaf03ca/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec", size = 260881 }, + { url = "https://files.pythonhosted.org/packages/a8/be/a235bc937dd803258a370fe21b5aa2dd3e7bfe0287a186a4bec30c6cccd6/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a", size = 282327 }, + { url = "https://files.pythonhosted.org/packages/5d/e7/b2469e71f082948066b9382c7b908c22552cc705b960363c390d2e23f587/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74", size = 281502 }, + { url = "https://files.pythonhosted.org/packages/db/1b/6a5b970e55dffc1a7d0bb54f57b184b2a2a2ad0b7bca16a97ca26d73c5b5/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2", size = 272292 }, + { url = "https://files.pythonhosted.org/packages/1a/05/ebad68130e6b6eb9b287dacad08ea357c33849c74550c015b355b75cc714/frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17", size = 44446 }, + { url = "https://files.pythonhosted.org/packages/b3/21/c5aaffac47fd305d69df46cfbf118768cdf049a92ee6b0b5cb029d449dcf/frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825", size = 50459 }, + { url = "https://files.pythonhosted.org/packages/b4/db/4cf37556a735bcdb2582f2c3fa286aefde2322f92d3141e087b8aeb27177/frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae", size = 93937 }, + { url = "https://files.pythonhosted.org/packages/46/03/69eb64642ca8c05f30aa5931d6c55e50b43d0cd13256fdd01510a1f85221/frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb", size = 53656 }, + { url = "https://files.pythonhosted.org/packages/3f/ab/c543c13824a615955f57e082c8a5ee122d2d5368e80084f2834e6f4feced/frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b", size = 51868 }, + { url = "https://files.pythonhosted.org/packages/a9/b8/438cfd92be2a124da8259b13409224d9b19ef8f5a5b2507174fc7e7ea18f/frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86", size = 280652 }, + { url = "https://files.pythonhosted.org/packages/54/72/716a955521b97a25d48315c6c3653f981041ce7a17ff79f701298195bca3/frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480", size = 286739 }, + { url = "https://files.pythonhosted.org/packages/65/d8/934c08103637567084568e4d5b4219c1016c60b4d29353b1a5b3587827d6/frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09", size = 289447 }, + { url = "https://files.pythonhosted.org/packages/70/bb/d3b98d83ec6ef88f9bd63d77104a305d68a146fd63a683569ea44c3085f6/frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a", size = 265466 }, + { url = "https://files.pythonhosted.org/packages/0b/f2/b8158a0f06faefec33f4dff6345a575c18095a44e52d4f10c678c137d0e0/frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd", size = 281530 }, + { url = "https://files.pythonhosted.org/packages/ea/a2/20882c251e61be653764038ece62029bfb34bd5b842724fff32a5b7a2894/frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6", size = 281295 }, + { url = "https://files.pythonhosted.org/packages/4c/f9/8894c05dc927af2a09663bdf31914d4fb5501653f240a5bbaf1e88cab1d3/frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1", size = 268054 }, + { url = "https://files.pythonhosted.org/packages/37/ff/a613e58452b60166507d731812f3be253eb1229808e59980f0405d1eafbf/frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b", size = 286904 }, + { url = "https://files.pythonhosted.org/packages/cc/6e/0091d785187f4c2020d5245796d04213f2261ad097e0c1cf35c44317d517/frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e", size = 290754 }, + { url = "https://files.pythonhosted.org/packages/a5/c2/e42ad54bae8bcffee22d1e12a8ee6c7717f7d5b5019261a8c861854f4776/frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8", size = 282602 }, + { url = "https://files.pythonhosted.org/packages/b6/61/56bad8cb94f0357c4bc134acc30822e90e203b5cb8ff82179947de90c17f/frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89", size = 44063 }, + { url = "https://files.pythonhosted.org/packages/3e/dc/96647994a013bc72f3d453abab18340b7f5e222b7b7291e3697ca1fcfbd5/frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5", size = 50452 }, + { url = "https://files.pythonhosted.org/packages/83/10/466fe96dae1bff622021ee687f68e5524d6392b0a2f80d05001cd3a451ba/frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7", size = 11552 }, +] + +[[package]] +name = "fsspec" +version = "2023.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/f7/16ec1f92523165d10301cfa8cb83df0356dbe615d4ca5ed611a16f53e09a/fsspec-2023.10.0.tar.gz", hash = "sha256:330c66757591df346ad3091a53bd907e15348c2ba17d63fd54f5c39c4457d2a5", size = 165452 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/f6/3eccfb530aac90ad1301c582da228e4763f19e719ac8200752a4841b0b2d/fsspec-2023.10.0-py3-none-any.whl", hash = "sha256:346a8f024efeb749d2a5fca7ba8854474b1ff9af7c3faaf636a4548781136529", size = 166384 }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp" }, + { name = "requests" }, +] + +[[package]] +name = "gitdb" +version = "4.0.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/0d/bbb5b5ee188dec84647a4664f3e11b06ade2bde568dbd489d9d64adef8ed/gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b", size = 394469 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/5b/8f0c4a5bb9fd491c277c21eff7ccae71b47d43c4446c9d0c6cff2fe8c2c4/gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4", size = 62721 }, +] + +[[package]] +name = "gitpython" +version = "3.1.27" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/39/5b91b6c40570dc1c753359de7492404ba8fe7d71af40b618a780c7ad1fc7/GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704", size = 192188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/32/ce68915670da6fd6b1e3fb4b3554b4462512f6441dddd194fc0f4f6ec653/GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d", size = 181152 }, +] + +[[package]] +name = "grpcio" +version = "1.67.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/ae/3c47d71ab4abd4bd60a7e2806071fe0a4b6937b9eabe522291787087ea1f/grpcio-1.67.0.tar.gz", hash = "sha256:e090b2553e0da1c875449c8e75073dd4415dd71c9bde6a406240fdf4c0ee467c", size = 12569330 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/da/c4a24a5245aba95c411a21c7525a41113b669b646a79ab8523551c4185cf/grpcio-1.67.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:bd79929b3bb96b54df1296cd3bf4d2b770bd1df6c2bdf549b49bab286b925cdc", size = 5108503 }, + { url = "https://files.pythonhosted.org/packages/08/29/1f46e9d2d9d34f4117f7dccfd7e222f1b0ea1fa1c5bd319e7b7017f4bc32/grpcio-1.67.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:16724ffc956ea42967f5758c2f043faef43cb7e48a51948ab593570570d1e68b", size = 10930122 }, + { url = "https://files.pythonhosted.org/packages/f0/ff/20774848a070b544c52a6e198d4bb439528bd440678f3bd3f65a41a9d804/grpcio-1.67.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:2b7183c80b602b0ad816315d66f2fb7887614ead950416d60913a9a71c12560d", size = 5630547 }, + { url = "https://files.pythonhosted.org/packages/60/05/4986994d96011c6b853f2f40ea2bf0c7ed97fc3a2391d004064697de01b7/grpcio-1.67.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:efe32b45dd6d118f5ea2e5deaed417d8a14976325c93812dd831908522b402c9", size = 6237824 }, + { url = "https://files.pythonhosted.org/packages/fa/1c/772a501cd18baffba5f9eeb54ce353c8749e9217c262bb7953427417db40/grpcio-1.67.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe89295219b9c9e47780a0f1c75ca44211e706d1c598242249fe717af3385ec8", size = 5881526 }, + { url = "https://files.pythonhosted.org/packages/6c/38/6f0243ce5b5f2b5f4cc34c8e0ba6b466db4b333bfb643f61e459bbe0b92c/grpcio-1.67.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa8d025fae1595a207b4e47c2e087cb88d47008494db258ac561c00877d4c8f8", size = 6582793 }, + { url = "https://files.pythonhosted.org/packages/ed/9f/c489cd122618ea808593d20a47ff68722b3c99c030c175550b85bb256fb0/grpcio-1.67.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f95e15db43e75a534420e04822df91f645664bf4ad21dfaad7d51773c80e6bb4", size = 6162111 }, + { url = "https://files.pythonhosted.org/packages/b7/a6/6384d59d26a5dbc7adffc0abf3d88107494ba3eb92bc9bd3f7fc7c18679d/grpcio-1.67.0-cp310-cp310-win32.whl", hash = "sha256:a6b9a5c18863fd4b6624a42e2712103fb0f57799a3b29651c0e5b8119a519d65", size = 3614488 }, + { url = "https://files.pythonhosted.org/packages/6b/20/5da50579c2b6341490459a44a97fd53d23a5c0e928bea78cf80ce67f8b1a/grpcio-1.67.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6eb68493a05d38b426604e1dc93bfc0137c4157f7ab4fac5771fd9a104bbaa6", size = 4350825 }, + { url = "https://files.pythonhosted.org/packages/86/a2/5d3b07fe984e3eab147ebe141f0111ab19eb0c27dfdf19360c3de60a0341/grpcio-1.67.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:e91d154689639932305b6ea6f45c6e46bb51ecc8ea77c10ef25aa77f75443ad4", size = 5116425 }, + { url = "https://files.pythonhosted.org/packages/79/23/18730cca0d18ffde1de132a9230745a5c113cbc6dd8cde71c2288a21f5a3/grpcio-1.67.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cb204a742997277da678611a809a8409657b1398aaeebf73b3d9563b7d154c13", size = 11005387 }, + { url = "https://files.pythonhosted.org/packages/33/30/f8fa49eb3f30e4c730f3f37aa33f49cbad592906b93a9445e8ceedeaa96c/grpcio-1.67.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:ae6de510f670137e755eb2a74b04d1041e7210af2444103c8c95f193340d17ee", size = 5627195 }, + { url = "https://files.pythonhosted.org/packages/80/39/e1f7ac3938ac7763732d545fcfdcff23ed8e993513321b3d21cae146beb4/grpcio-1.67.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74b900566bdf68241118f2918d312d3bf554b2ce0b12b90178091ea7d0a17b3d", size = 6237935 }, + { url = "https://files.pythonhosted.org/packages/8e/a5/b99333f0a9f4599468bb4b7cb59aa1a7e2a2f67a59b5b13fdc7ea0acf0ad/grpcio-1.67.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4e95e43447a02aa603abcc6b5e727d093d161a869c83b073f50b9390ecf0fa8", size = 5879332 }, + { url = "https://files.pythonhosted.org/packages/6a/22/b9800736805c5bddd0c9a9d3b1556c682a0dee8ae63051c565d888a2bc87/grpcio-1.67.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0bb94e66cd8f0baf29bd3184b6aa09aeb1a660f9ec3d85da615c5003154bc2bf", size = 6578617 }, + { url = "https://files.pythonhosted.org/packages/20/a5/dd2e69777767c321ddaa886047dccc555f09f4fcdfc5164e440f1f4b589d/grpcio-1.67.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:82e5bd4b67b17c8c597273663794a6a46a45e44165b960517fe6d8a2f7f16d23", size = 6160509 }, + { url = "https://files.pythonhosted.org/packages/b7/5a/b12f69f687d9eb593405fa450a24ba4ee8f6058c6c43d1995bed023c6a61/grpcio-1.67.0-cp311-cp311-win32.whl", hash = "sha256:7fc1d2b9fd549264ae585026b266ac2db53735510a207381be509c315b4af4e8", size = 3614902 }, + { url = "https://files.pythonhosted.org/packages/aa/81/5a3503b9757a89c7d1fa7672b788fcbcafce91cdc94a3e0c53513a3201d7/grpcio-1.67.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac11ecb34a86b831239cc38245403a8de25037b448464f95c3315819e7519772", size = 4352547 }, + { url = "https://files.pythonhosted.org/packages/b0/2d/b2a783f1d93735a259676de5558ef019ac3511e894b8e9d224edc0d7d034/grpcio-1.67.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:227316b5631260e0bef8a3ce04fa7db4cc81756fea1258b007950b6efc90c05d", size = 5086495 }, + { url = "https://files.pythonhosted.org/packages/7b/13/c1f537a88dad543ca0a7be4dfee80a21b3b02b7df27750997777355e5840/grpcio-1.67.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d90cfdafcf4b45a7a076e3e2a58e7bc3d59c698c4f6470b0bb13a4d869cf2273", size = 10979109 }, + { url = "https://files.pythonhosted.org/packages/b7/83/d7cb72f2202fe8d608d25c7e9d6d75184bf6ef658688c818821add102211/grpcio-1.67.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:77196216d5dd6f99af1c51e235af2dd339159f657280e65ce7e12c1a8feffd1d", size = 5586952 }, + { url = "https://files.pythonhosted.org/packages/e5/18/8df585d0158af9e2b46ee2388bdb21de0e7f5bf4a47a86a861ebdbf947b5/grpcio-1.67.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15c05a26a0f7047f720da41dc49406b395c1470eef44ff7e2c506a47ac2c0591", size = 6212460 }, + { url = "https://files.pythonhosted.org/packages/47/46/027f8943113961784ce1eb69a28544d9a62ffb286332820ba634d979c91c/grpcio-1.67.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3840994689cc8cbb73d60485c594424ad8adb56c71a30d8948d6453083624b52", size = 5849002 }, + { url = "https://files.pythonhosted.org/packages/eb/26/fb19d5bc277e665382c835d7af1f8c1e3197576eed76327824d79e2a4bef/grpcio-1.67.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5a1e03c3102b6451028d5dc9f8591131d6ab3c8a0e023d94c28cb930ed4b5f81", size = 6568222 }, + { url = "https://files.pythonhosted.org/packages/e0/cc/387efa986f166c068d48331c699e6ee662a057371065f35d3ca1bc09d799/grpcio-1.67.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:682968427a63d898759474e3b3178d42546e878fdce034fd7474ef75143b64e3", size = 6148002 }, + { url = "https://files.pythonhosted.org/packages/24/57/529504e3e3e910f0537a0a36184cb7241d0d111109d6588096a9f8c139bf/grpcio-1.67.0-cp312-cp312-win32.whl", hash = "sha256:d01793653248f49cf47e5695e0a79805b1d9d4eacef85b310118ba1dfcd1b955", size = 3596220 }, + { url = "https://files.pythonhosted.org/packages/1d/1f/acf03ee901313446d52c3916d527d4981de9f6f3edc69267d05509dcfa7b/grpcio-1.67.0-cp312-cp312-win_amd64.whl", hash = "sha256:985b2686f786f3e20326c4367eebdaed3e7aa65848260ff0c6644f817042cb15", size = 4343545 }, + { url = "https://files.pythonhosted.org/packages/7a/e7/cc7feccb18ef0b5aa67ccb7859a091fa836c5d361a0109b9fca578e59e64/grpcio-1.67.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:8c9a35b8bc50db35ab8e3e02a4f2a35cfba46c8705c3911c34ce343bd777813a", size = 5087009 }, + { url = "https://files.pythonhosted.org/packages/bd/56/10175f4b1600b16e601680df053361924a9fcd9e1c0ad9b8bd1ba2b4c864/grpcio-1.67.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:42199e704095b62688998c2d84c89e59a26a7d5d32eed86d43dc90e7a3bd04aa", size = 10937553 }, + { url = "https://files.pythonhosted.org/packages/aa/85/115538b1aeb09d66c6e637608a56eddacd59eb71ab0161ad59172c01d436/grpcio-1.67.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:c4c425f440fb81f8d0237c07b9322fc0fb6ee2b29fbef5f62a322ff8fcce240d", size = 5586507 }, + { url = "https://files.pythonhosted.org/packages/0f/db/f402a455e287154683235183c2843c27fffe2fc03fa4c45b57dd90011401/grpcio-1.67.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:323741b6699cd2b04a71cb38f502db98f90532e8a40cb675393d248126a268af", size = 6211948 }, + { url = "https://files.pythonhosted.org/packages/92/e4/5957806105aad556f7df6a420b6c69044b6f707926392118772a8ba96de4/grpcio-1.67.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:662c8e105c5e5cee0317d500eb186ed7a93229586e431c1bf0c9236c2407352c", size = 5849392 }, + { url = "https://files.pythonhosted.org/packages/88/ab/c496a406f4682c56e933bef6b0ed22b9eaec84c6915f83d5cddd94126e16/grpcio-1.67.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f6bd2ab135c64a4d1e9e44679a616c9bc944547357c830fafea5c3caa3de5153", size = 6571359 }, + { url = "https://files.pythonhosted.org/packages/9e/a8/96b3ef565791d7282c300c07c2a7080471311e7d5a239db15678aaac47eb/grpcio-1.67.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:2f55c1e0e2ae9bdd23b3c63459ee4c06d223b68aeb1961d83c48fb63dc29bc03", size = 6147905 }, + { url = "https://files.pythonhosted.org/packages/cd/b7/846cc563209ff5af88bc7dcb269948210674c2f743e7fd8e1a2ad9708e89/grpcio-1.67.0-cp313-cp313-win32.whl", hash = "sha256:fd6bc27861e460fe28e94226e3673d46e294ca4673d46b224428d197c5935e69", size = 3594603 }, + { url = "https://files.pythonhosted.org/packages/bd/74/49d27908b369b72fd3373ec0f16d7f58614fb7101cb38b266afeab846cca/grpcio-1.67.0-cp313-cp313-win_amd64.whl", hash = "sha256:cf51d28063338608cd8d3cd64677e922134837902b70ce00dad7f116e3998210", size = 4345468 }, +] + +[[package]] +name = "gunicorn" +version = "23.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029 }, +] + +[[package]] +name = "h11" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, +] + +[[package]] +name = "httpcore" +version = "1.0.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/44/ed0fa6a17845fb033bd885c03e842f08c1b9406c86a2e60ac1ae1b9206a6/httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f", size = 85180 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/89/b161908e2f51be56568184aeb4a880fd287178d176fd1c860d2217f41106/httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f", size = 78011 }, +] + +[[package]] +name = "httpx" +version = "0.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395 }, +] + +[[package]] +name = "huggingface-hub" +version = "0.26.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/99/c8fdef6fe09a1719e5e5de24b012de5824889168c96143f5531cab5af42b/huggingface_hub-0.26.1.tar.gz", hash = "sha256:414c0d9b769eecc86c70f9d939d0f48bb28e8461dd1130021542eff0212db890", size = 375458 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/4d/017d8d7cff5100092da8ea19139bcb1965bbadcbb5ddd0480e2badc299e8/huggingface_hub-0.26.1-py3-none-any.whl", hash = "sha256:5927a8fc64ae68859cd954b7cc29d1c8390a5e15caba6d3d349c973be8fdacf3", size = 447439 }, +] + +[[package]] +name = "hydra-core" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "antlr4-python3-runtime" }, + { name = "omegaconf" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/8e/07e42bc434a847154083b315779b0a81d567154504624e181caf2c71cd98/hydra-core-1.3.2.tar.gz", hash = "sha256:8a878ed67216997c3e9d88a8e72e7b4767e81af37afb4ea3334b269a4390a824", size = 3263494 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/50/e0edd38dcd63fb26a8547f13d28f7a008bc4a3fd4eb4ff030673f22ad41a/hydra_core-1.3.2-py3-none-any.whl", hash = "sha256:fa0238a9e31df3373b35b0bfb672c34cc92718d21f81311d8996a16de1141d8b", size = 154547 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769 }, +] + +[[package]] +name = "importlab" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "networkx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/22/ab9494dccf1e237276f98364d53673bc0ab97ebe5cb671e960f18710457d/importlab-0.8.1.tar.gz", hash = "sha256:b3893853b1f6eb027da509c3b40e6787e95dd66b4b66f1b3613aad77556e1465", size = 28856 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/1e/cc7360b4259f283b1a2de153335ce15ac9e710d66145aa471cffefe4b394/importlab-0.8.1-py2.py3-none-any.whl", hash = "sha256:124cfa00e8a34fefe8aac1a5e94f56c781b178c9eb61a1d3f60f7e03b77338d3", size = 21671 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "ipykernel" +version = "6.29.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "platform_system == 'Darwin'" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "matplotlib-inline" }, + { name = "nest-asyncio" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/5c/67594cb0c7055dc50814b21731c22a601101ea3b1b50a9a1b090e11f5d0f/ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215", size = 163367 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/5c/368ae6c01c7628438358e6d337c19b05425727fbb221d2a3c4303c372f42/ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5", size = 117173 }, +] + +[[package]] +name = "ipython" +version = "8.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/21/48db7d9dd622b9692575004c7c98f85f5629428f58596c59606d36c51b58/ipython-8.28.0.tar.gz", hash = "sha256:0d0d15ca1e01faeb868ef56bc7ee5a0de5bd66885735682e8a322ae289a13d1a", size = 5495762 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/3a/5d8680279ada9571de8469220069d27024ee47624af534e537c9ff49a450/ipython-8.28.0-py3-none-any.whl", hash = "sha256:530ef1e7bb693724d3cdc37287c80b07ad9b25986c007a53aa1857272dac3f35", size = 819456 }, +] + +[[package]] +name = "ipywidgets" +version = "8.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "comm" }, + { name = "ipython" }, + { name = "jupyterlab-widgets" }, + { name = "traitlets" }, + { name = "widgetsnbextension" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/4c/dab2a281b07596a5fc220d49827fe6c794c66f1493d7a74f1df0640f2cc5/ipywidgets-8.1.5.tar.gz", hash = "sha256:870e43b1a35656a80c18c9503bbf2d16802db1cb487eec6fab27d683381dde17", size = 116723 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/2d/9c0b76f2f9cc0ebede1b9371b6f317243028ed60b90705863d493bae622e/ipywidgets-8.1.5-py3-none-any.whl", hash = "sha256:3290f526f87ae6e77655555baba4f36681c555b8bdbbff430b70e52c34c86245", size = 139767 }, +] + +[[package]] +name = "isoduration" +version = "20.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "arrow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321 }, +] + +[[package]] +name = "isort" +version = "5.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310 }, +] + +[[package]] +name = "jedi" +version = "0.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/99/99b493cec4bf43176b678de30f81ed003fd6a647a301b9c927280c600f0a/jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd", size = 1227821 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/9f/bc63f0f0737ad7a60800bfd472a4836661adae21f9c2535f3957b1e54ceb/jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0", size = 1569361 }, +] + +[[package]] +name = "jinja2" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/ff/75c28576a1d900e87eb6335b063fab47a8ef3c8b4d88524c4bf78f670cce/Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", size = 268239 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", size = 133101 }, +] + +[[package]] +name = "joblib" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/64/33/60135848598c076ce4b231e1b1895170f45fbcaeaa2c9d5e38b04db70c35/joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e", size = 2116621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/29/df4b9b42f2be0b623cbd5e2140cafcaa2bef0759a00b7b70104dcfe2fb51/joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", size = 301817 }, +] + +[[package]] +name = "json5" +version = "0.9.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/59/51b032d53212a51f17ebbcc01bd4217faab6d6c09ed0d856a987a5f42bbc/json5-0.9.25.tar.gz", hash = "sha256:548e41b9be043f9426776f05df8635a00fe06104ea51ed24b67f908856e151ae", size = 40332 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/3c/4f8791ee53ab9eeb0b022205aa79387119a74cc9429582ce04098e6fc540/json5-0.9.25-py3-none-any.whl", hash = "sha256:34ed7d834b1341a86987ed52f3f76cd8ee184394906b6e22a1e0deb9ab294e8f", size = 30109 }, +] + +[[package]] +name = "jsonpointer" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595 }, +] + +[[package]] +name = "jsonschema" +version = "4.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 }, +] + +[package.optional-dependencies] +format-nongpl = [ + { name = "fqdn" }, + { name = "idna" }, + { name = "isoduration" }, + { name = "jsonpointer" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "uri-template" }, + { name = "webcolors" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2024.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/db/58f950c996c793472e336ff3655b13fbcf1e3b359dcf52dcf3ed3b52c352/jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", size = 15561 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/0f/8910b19ac0670a0f80ce1008e5e751c4a57e14d2c4c13a482aa6079fa9d6/jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf", size = 18459 }, +] + +[[package]] +name = "jupyter" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipykernel" }, + { name = "ipywidgets" }, + { name = "jupyter-console" }, + { name = "nbconvert" }, + { name = "notebook" }, + { name = "qtconsole" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/a9/371d0b8fe37dd231cf4b2cff0a9f0f25e98f3a73c3771742444be27f2944/jupyter-1.0.0.tar.gz", hash = "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f", size = 12916 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/df/0f5dd132200728a86190397e1ea87cd76244e42d39ec5e88efd25b2abd7e/jupyter-1.0.0-py2.py3-none-any.whl", hash = "sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78", size = 2736 }, +] + +[[package]] +name = "jupyter-client" +version = "8.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105 }, +] + +[[package]] +name = "jupyter-console" +version = "6.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipykernel" }, + { name = "ipython" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "pyzmq" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/2d/e2fd31e2fc41c14e2bcb6c976ab732597e907523f6b2420305f9fc7fdbdb/jupyter_console-6.6.3.tar.gz", hash = "sha256:566a4bf31c87adbfadf22cdf846e3069b59a71ed5da71d6ba4d8aaad14a53539", size = 34363 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/77/71d78d58f15c22db16328a476426f7ac4a60d3a5a7ba3b9627ee2f7903d4/jupyter_console-6.6.3-py3-none-any.whl", hash = "sha256:309d33409fcc92ffdad25f0bcdf9a4a9daa61b6f341177570fdac03de5352485", size = 24510 }, +] + +[[package]] +name = "jupyter-core" +version = "5.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "pywin32", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/11/b56381fa6c3f4cc5d2cf54a7dbf98ad9aa0b339ef7a601d6053538b079a7/jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9", size = 87629 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/fb/108ecd1fe961941959ad0ee4e12ee7b8b1477247f30b1fdfd83ceaf017f0/jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409", size = 28965 }, +] + +[[package]] +name = "jupyter-events" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema", extra = ["format-nongpl"] }, + { name = "python-json-logger" }, + { name = "pyyaml" }, + { name = "referencing" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/53/7537a1aa558229bb0b1b178d814c9d68a9c697d3aecb808a1cb2646acf1f/jupyter_events-0.10.0.tar.gz", hash = "sha256:670b8229d3cc882ec782144ed22e0d29e1c2d639263f92ca8383e66682845e22", size = 61516 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/94/059180ea70a9a326e1815176b2370da56376da347a796f8c4f0b830208ef/jupyter_events-0.10.0-py3-none-any.whl", hash = "sha256:4b72130875e59d57716d327ea70d3ebc3af1944d3717e5a498b8a06c6c159960", size = 18777 }, +] + +[[package]] +name = "jupyter-lsp" +version = "2.2.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/b4/3200b0b09c12bc3b72d943d923323c398eff382d1dcc7c0dbc8b74630e40/jupyter-lsp-2.2.5.tar.gz", hash = "sha256:793147a05ad446f809fd53ef1cd19a9f5256fd0a2d6b7ce943a982cb4f545001", size = 48741 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/e0/7bd7cff65594fd9936e2f9385701e44574fc7d721331ff676ce440b14100/jupyter_lsp-2.2.5-py3-none-any.whl", hash = "sha256:45fbddbd505f3fbfb0b6cb2f1bc5e15e83ab7c79cd6e89416b248cb3c00c11da", size = 69146 }, +] + +[[package]] +name = "jupyter-server" +version = "2.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "argon2-cffi" }, + { name = "jinja2" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "jupyter-events" }, + { name = "jupyter-server-terminals" }, + { name = "nbconvert" }, + { name = "nbformat" }, + { name = "overrides" }, + { name = "packaging" }, + { name = "prometheus-client" }, + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "pyzmq" }, + { name = "send2trash" }, + { name = "terminado" }, + { name = "tornado" }, + { name = "traitlets" }, + { name = "websocket-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/34/88b47749c7fa9358e10eac356c4b97d94a91a67d5c935a73f69bc4a31118/jupyter_server-2.14.2.tar.gz", hash = "sha256:66095021aa9638ced276c248b1d81862e4c50f292d575920bbe960de1c56b12b", size = 719933 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/e1/085edea6187a127ca8ea053eb01f4e1792d778b4d192c74d32eb6730fed6/jupyter_server-2.14.2-py3-none-any.whl", hash = "sha256:47ff506127c2f7851a17bf4713434208fc490955d0e8632e95014a9a9afbeefd", size = 383556 }, +] + +[[package]] +name = "jupyter-server-terminals" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "terminado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/d5/562469734f476159e99a55426d697cbf8e7eb5efe89fb0e0b4f83a3d3459/jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269", size = 31430 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa", size = 13656 }, +] + +[[package]] +name = "jupyterlab" +version = "4.2.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-lru" }, + { name = "httpx" }, + { name = "ipykernel" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyter-lsp" }, + { name = "jupyter-server" }, + { name = "jupyterlab-server" }, + { name = "notebook-shim" }, + { name = "packaging" }, + { name = "setuptools" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4a/78/ba006df6edaa561fe40be26c35e9da3f9316f071167cd7cc1a1a25bd2664/jupyterlab-4.2.5.tar.gz", hash = "sha256:ae7f3a1b8cb88b4f55009ce79fa7c06f99d70cd63601ee4aa91815d054f46f75", size = 21508698 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/3f/24a0f0ce60959cfd9756a3291cd3a5581e51cbd6f7b4aa121f5bba5320e3/jupyterlab-4.2.5-py3-none-any.whl", hash = "sha256:73b6e0775d41a9fee7ee756c80f58a6bed4040869ccc21411dc559818874d321", size = 11641981 }, +] + +[[package]] +name = "jupyterlab-pygments" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884 }, +] + +[[package]] +name = "jupyterlab-server" +version = "2.27.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "jinja2" }, + { name = "json5" }, + { name = "jsonschema" }, + { name = "jupyter-server" }, + { name = "packaging" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/c9/a883ce65eb27905ce77ace410d83587c82ea64dc85a48d1f7ed52bcfa68d/jupyterlab_server-2.27.3.tar.gz", hash = "sha256:eb36caca59e74471988f0ae25c77945610b887f777255aa21f8065def9e51ed4", size = 76173 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl", hash = "sha256:e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4", size = 59700 }, +] + +[[package]] +name = "jupyterlab-widgets" +version = "3.0.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/73/fa26bbb747a9ea4fca6b01453aa22990d52ab62dd61384f1ac0dc9d4e7ba/jupyterlab_widgets-3.0.13.tar.gz", hash = "sha256:a2966d385328c1942b683a8cd96b89b8dd82c8b8f81dda902bb2bc06d46f5bed", size = 203556 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/93/858e87edc634d628e5d752ba944c2833133a28fa87bb093e6832ced36a3e/jupyterlab_widgets-3.0.13-py3-none-any.whl", hash = "sha256:e3cda2c233ce144192f1e29914ad522b2f4c40e77214b0cc97377ca3d323db54", size = 214392 }, +] + +[[package]] +name = "kaleido" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f7/0ccaa596ec341963adbb4f839774c36d5659e75a0812d946732b927d480e/kaleido-0.2.1-py2.py3-none-macosx_10_11_x86_64.whl", hash = "sha256:ca6f73e7ff00aaebf2843f73f1d3bacde1930ef5041093fe76b83a15785049a7", size = 85153681 }, + { url = "https://files.pythonhosted.org/packages/45/8e/4297556be5a07b713bb42dde0f748354de9a6918dee251c0e6bdcda341e7/kaleido-0.2.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:bb9a5d1f710357d5d432ee240ef6658a6d124c3e610935817b4b42da9c787c05", size = 85808197 }, + { url = "https://files.pythonhosted.org/packages/ae/b3/a0f0f4faac229b0011d8c4a7ee6da7c2dca0b6fd08039c95920846f23ca4/kaleido-0.2.1-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:aa21cf1bf1c78f8fa50a9f7d45e1003c387bd3d6fe0a767cfbbf344b95bdc3a8", size = 79902476 }, + { url = "https://files.pythonhosted.org/packages/a1/2b/680662678a57afab1685f0c431c2aba7783ce4344f06ec162074d485d469/kaleido-0.2.1-py2.py3-none-manylinux2014_aarch64.whl", hash = "sha256:845819844c8082c9469d9c17e42621fbf85c2b237ef8a86ec8a8527f98b6512a", size = 83711746 }, + { url = "https://files.pythonhosted.org/packages/88/89/4b6f8bb3f9ab036fd4ad1cb2d628ab5c81db32ac9aa0641d7b180073ba43/kaleido-0.2.1-py2.py3-none-win32.whl", hash = "sha256:ecc72635860be616c6b7161807a65c0dbd9b90c6437ac96965831e2e24066552", size = 62312480 }, + { url = "https://files.pythonhosted.org/packages/f7/9a/0408b02a4bcb3cf8b338a2b074ac7d1b2099e2b092b42473def22f7b625f/kaleido-0.2.1-py2.py3-none-win_amd64.whl", hash = "sha256:4670985f28913c2d063c5734d125ecc28e40810141bdb0a46f15b76c1d45f23c", size = 65945521 }, +] + +[[package]] +name = "keopscore" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/31/1da3877139d75e392f5fd0cbdc3b718ae726f5308ed792d385250c43167c/keopscore-2.2.3.tar.gz", hash = "sha256:64d5dad1e8c806d7070cdc60e48fd5bbf006f2bf1afd39ad3fa5e9fb213517d2", size = 100286 } + +[[package]] +name = "kiwisolver" +version = "1.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/4d/2255e1c76304cbd60b48cee302b66d1dde4468dc5b1160e4b7cb43778f2a/kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60", size = 97286 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/14/fc943dd65268a96347472b4fbe5dcc2f6f55034516f80576cd0dd3a8930f/kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6", size = 122440 }, + { url = "https://files.pythonhosted.org/packages/1e/46/e68fed66236b69dd02fcdb506218c05ac0e39745d696d22709498896875d/kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17", size = 65758 }, + { url = "https://files.pythonhosted.org/packages/ef/fa/65de49c85838681fc9cb05de2a68067a683717321e01ddafb5b8024286f0/kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9", size = 64311 }, + { url = "https://files.pythonhosted.org/packages/42/9c/cc8d90f6ef550f65443bad5872ffa68f3dee36de4974768628bea7c14979/kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9", size = 1637109 }, + { url = "https://files.pythonhosted.org/packages/55/91/0a57ce324caf2ff5403edab71c508dd8f648094b18cfbb4c8cc0fde4a6ac/kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c", size = 1617814 }, + { url = "https://files.pythonhosted.org/packages/12/5d/c36140313f2510e20207708adf36ae4919416d697ee0236b0ddfb6fd1050/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599", size = 1400881 }, + { url = "https://files.pythonhosted.org/packages/56/d0/786e524f9ed648324a466ca8df86298780ef2b29c25313d9a4f16992d3cf/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05", size = 1512972 }, + { url = "https://files.pythonhosted.org/packages/67/5a/77851f2f201e6141d63c10a0708e996a1363efaf9e1609ad0441b343763b/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407", size = 1444787 }, + { url = "https://files.pythonhosted.org/packages/06/5f/1f5eaab84355885e224a6fc8d73089e8713dc7e91c121f00b9a1c58a2195/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278", size = 2199212 }, + { url = "https://files.pythonhosted.org/packages/b5/28/9152a3bfe976a0ae21d445415defc9d1cd8614b2910b7614b30b27a47270/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5", size = 2346399 }, + { url = "https://files.pythonhosted.org/packages/26/f6/453d1904c52ac3b400f4d5e240ac5fec25263716723e44be65f4d7149d13/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad", size = 2308688 }, + { url = "https://files.pythonhosted.org/packages/5a/9a/d4968499441b9ae187e81745e3277a8b4d7c60840a52dc9d535a7909fac3/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895", size = 2445493 }, + { url = "https://files.pythonhosted.org/packages/07/c9/032267192e7828520dacb64dfdb1d74f292765f179e467c1cba97687f17d/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3", size = 2262191 }, + { url = "https://files.pythonhosted.org/packages/6c/ad/db0aedb638a58b2951da46ddaeecf204be8b4f5454df020d850c7fa8dca8/kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc", size = 46644 }, + { url = "https://files.pythonhosted.org/packages/12/ca/d0f7b7ffbb0be1e7c2258b53554efec1fd652921f10d7d85045aff93ab61/kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c", size = 55877 }, + { url = "https://files.pythonhosted.org/packages/97/6c/cfcc128672f47a3e3c0d918ecb67830600078b025bfc32d858f2e2d5c6a4/kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a", size = 48347 }, + { url = "https://files.pythonhosted.org/packages/e9/44/77429fa0a58f941d6e1c58da9efe08597d2e86bf2b2cce6626834f49d07b/kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54", size = 122442 }, + { url = "https://files.pythonhosted.org/packages/e5/20/8c75caed8f2462d63c7fd65e16c832b8f76cda331ac9e615e914ee80bac9/kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95", size = 65762 }, + { url = "https://files.pythonhosted.org/packages/f4/98/fe010f15dc7230f45bc4cf367b012d651367fd203caaa992fd1f5963560e/kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935", size = 64319 }, + { url = "https://files.pythonhosted.org/packages/8b/1b/b5d618f4e58c0675654c1e5051bcf42c776703edb21c02b8c74135541f60/kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb", size = 1334260 }, + { url = "https://files.pythonhosted.org/packages/b8/01/946852b13057a162a8c32c4c8d2e9ed79f0bb5d86569a40c0b5fb103e373/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02", size = 1426589 }, + { url = "https://files.pythonhosted.org/packages/70/d1/c9f96df26b459e15cf8a965304e6e6f4eb291e0f7a9460b4ad97b047561e/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51", size = 1541080 }, + { url = "https://files.pythonhosted.org/packages/d3/73/2686990eb8b02d05f3de759d6a23a4ee7d491e659007dd4c075fede4b5d0/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052", size = 1470049 }, + { url = "https://files.pythonhosted.org/packages/a7/4b/2db7af3ed3af7c35f388d5f53c28e155cd402a55432d800c543dc6deb731/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18", size = 1426376 }, + { url = "https://files.pythonhosted.org/packages/05/83/2857317d04ea46dc5d115f0df7e676997bbd968ced8e2bd6f7f19cfc8d7f/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545", size = 2222231 }, + { url = "https://files.pythonhosted.org/packages/0d/b5/866f86f5897cd4ab6d25d22e403404766a123f138bd6a02ecb2cdde52c18/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b", size = 2368634 }, + { url = "https://files.pythonhosted.org/packages/c1/ee/73de8385403faba55f782a41260210528fe3273d0cddcf6d51648202d6d0/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36", size = 2329024 }, + { url = "https://files.pythonhosted.org/packages/a1/e7/cd101d8cd2cdfaa42dc06c433df17c8303d31129c9fdd16c0ea37672af91/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3", size = 2468484 }, + { url = "https://files.pythonhosted.org/packages/e1/72/84f09d45a10bc57a40bb58b81b99d8f22b58b2040c912b7eb97ebf625bf2/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523", size = 2284078 }, + { url = "https://files.pythonhosted.org/packages/d2/d4/71828f32b956612dc36efd7be1788980cb1e66bfb3706e6dec9acad9b4f9/kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d", size = 46645 }, + { url = "https://files.pythonhosted.org/packages/a1/65/d43e9a20aabcf2e798ad1aff6c143ae3a42cf506754bcb6a7ed8259c8425/kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b", size = 56022 }, + { url = "https://files.pythonhosted.org/packages/35/b3/9f75a2e06f1b4ca00b2b192bc2b739334127d27f1d0625627ff8479302ba/kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376", size = 48536 }, + { url = "https://files.pythonhosted.org/packages/97/9c/0a11c714cf8b6ef91001c8212c4ef207f772dd84540104952c45c1f0a249/kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2", size = 121808 }, + { url = "https://files.pythonhosted.org/packages/f2/d8/0fe8c5f5d35878ddd135f44f2af0e4e1d379e1c7b0716f97cdcb88d4fd27/kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a", size = 65531 }, + { url = "https://files.pythonhosted.org/packages/80/c5/57fa58276dfdfa612241d640a64ca2f76adc6ffcebdbd135b4ef60095098/kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee", size = 63894 }, + { url = "https://files.pythonhosted.org/packages/8b/e9/26d3edd4c4ad1c5b891d8747a4f81b1b0aba9fb9721de6600a4adc09773b/kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640", size = 1369296 }, + { url = "https://files.pythonhosted.org/packages/b6/67/3f4850b5e6cffb75ec40577ddf54f7b82b15269cc5097ff2e968ee32ea7d/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f", size = 1461450 }, + { url = "https://files.pythonhosted.org/packages/52/be/86cbb9c9a315e98a8dc6b1d23c43cffd91d97d49318854f9c37b0e41cd68/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483", size = 1579168 }, + { url = "https://files.pythonhosted.org/packages/0f/00/65061acf64bd5fd34c1f4ae53f20b43b0a017a541f242a60b135b9d1e301/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258", size = 1507308 }, + { url = "https://files.pythonhosted.org/packages/21/e4/c0b6746fd2eb62fe702118b3ca0cb384ce95e1261cfada58ff693aeec08a/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e", size = 1464186 }, + { url = "https://files.pythonhosted.org/packages/0a/0f/529d0a9fffb4d514f2782c829b0b4b371f7f441d61aa55f1de1c614c4ef3/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107", size = 2247877 }, + { url = "https://files.pythonhosted.org/packages/d1/e1/66603ad779258843036d45adcbe1af0d1a889a07af4635f8b4ec7dccda35/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948", size = 2404204 }, + { url = "https://files.pythonhosted.org/packages/8d/61/de5fb1ca7ad1f9ab7970e340a5b833d735df24689047de6ae71ab9d8d0e7/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038", size = 2352461 }, + { url = "https://files.pythonhosted.org/packages/ba/d2/0edc00a852e369827f7e05fd008275f550353f1f9bcd55db9363d779fc63/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383", size = 2501358 }, + { url = "https://files.pythonhosted.org/packages/84/15/adc15a483506aec6986c01fb7f237c3aec4d9ed4ac10b756e98a76835933/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520", size = 2314119 }, + { url = "https://files.pythonhosted.org/packages/36/08/3a5bb2c53c89660863a5aa1ee236912269f2af8762af04a2e11df851d7b2/kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b", size = 46367 }, + { url = "https://files.pythonhosted.org/packages/19/93/c05f0a6d825c643779fc3c70876bff1ac221f0e31e6f701f0e9578690d70/kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb", size = 55884 }, + { url = "https://files.pythonhosted.org/packages/d2/f9/3828d8f21b6de4279f0667fb50a9f5215e6fe57d5ec0d61905914f5b6099/kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a", size = 48528 }, + { url = "https://files.pythonhosted.org/packages/c4/06/7da99b04259b0f18b557a4effd1b9c901a747f7fdd84cf834ccf520cb0b2/kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e", size = 121913 }, + { url = "https://files.pythonhosted.org/packages/97/f5/b8a370d1aa593c17882af0a6f6755aaecd643640c0ed72dcfd2eafc388b9/kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6", size = 65627 }, + { url = "https://files.pythonhosted.org/packages/2a/fc/6c0374f7503522539e2d4d1b497f5ebad3f8ed07ab51aed2af988dd0fb65/kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750", size = 63888 }, + { url = "https://files.pythonhosted.org/packages/bf/3e/0b7172793d0f41cae5c923492da89a2ffcd1adf764c16159ca047463ebd3/kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d", size = 1369145 }, + { url = "https://files.pythonhosted.org/packages/77/92/47d050d6f6aced2d634258123f2688fbfef8ded3c5baf2c79d94d91f1f58/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379", size = 1461448 }, + { url = "https://files.pythonhosted.org/packages/9c/1b/8f80b18e20b3b294546a1adb41701e79ae21915f4175f311a90d042301cf/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c", size = 1578750 }, + { url = "https://files.pythonhosted.org/packages/a4/fe/fe8e72f3be0a844f257cadd72689c0848c6d5c51bc1d60429e2d14ad776e/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34", size = 1507175 }, + { url = "https://files.pythonhosted.org/packages/39/fa/cdc0b6105d90eadc3bee525fecc9179e2b41e1ce0293caaf49cb631a6aaf/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1", size = 1463963 }, + { url = "https://files.pythonhosted.org/packages/6e/5c/0c03c4e542720c6177d4f408e56d1c8315899db72d46261a4e15b8b33a41/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f", size = 2248220 }, + { url = "https://files.pythonhosted.org/packages/3d/ee/55ef86d5a574f4e767df7da3a3a7ff4954c996e12d4fbe9c408170cd7dcc/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b", size = 2404463 }, + { url = "https://files.pythonhosted.org/packages/0f/6d/73ad36170b4bff4825dc588acf4f3e6319cb97cd1fb3eb04d9faa6b6f212/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27", size = 2352842 }, + { url = "https://files.pythonhosted.org/packages/0b/16/fa531ff9199d3b6473bb4d0f47416cdb08d556c03b8bc1cccf04e756b56d/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a", size = 2501635 }, + { url = "https://files.pythonhosted.org/packages/78/7e/aa9422e78419db0cbe75fb86d8e72b433818f2e62e2e394992d23d23a583/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee", size = 2314556 }, + { url = "https://files.pythonhosted.org/packages/a8/b2/15f7f556df0a6e5b3772a1e076a9d9f6c538ce5f05bd590eca8106508e06/kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07", size = 46364 }, + { url = "https://files.pythonhosted.org/packages/0b/db/32e897e43a330eee8e4770bfd2737a9584b23e33587a0812b8e20aac38f7/kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76", size = 55887 }, + { url = "https://files.pythonhosted.org/packages/c8/a4/df2bdca5270ca85fd25253049eb6708d4127be2ed0e5c2650217450b59e9/kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650", size = 48530 }, + { url = "https://files.pythonhosted.org/packages/ac/59/741b79775d67ab67ced9bb38552da688c0305c16e7ee24bba7a2be253fb7/kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643", size = 59491 }, + { url = "https://files.pythonhosted.org/packages/58/cc/fb239294c29a5656e99e3527f7369b174dd9cc7c3ef2dea7cb3c54a8737b/kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706", size = 57648 }, + { url = "https://files.pythonhosted.org/packages/3b/ef/2f009ac1f7aab9f81efb2d837301d255279d618d27b6015780115ac64bdd/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6", size = 84257 }, + { url = "https://files.pythonhosted.org/packages/81/e1/c64f50987f85b68b1c52b464bb5bf73e71570c0f7782d626d1eb283ad620/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2", size = 80906 }, + { url = "https://files.pythonhosted.org/packages/fd/71/1687c5c0a0be2cee39a5c9c389e546f9c6e215e46b691d00d9f646892083/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4", size = 79951 }, + { url = "https://files.pythonhosted.org/packages/ea/8b/d7497df4a1cae9367adf21665dd1f896c2a7aeb8769ad77b662c5e2bcce7/kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a", size = 55715 }, +] + +[[package]] +name = "lammps" +version = "2024.8.29.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/73/2e5f7dbd2cd9256b14b15ad2ec247e39b16130d24438682118cac5dd833b/lammps-2024.8.29.1.0.tar.gz", hash = "sha256:27b83280e6e54f720fef1eb100682c32c53da29cc087e48c823e373e78528235", size = 13223 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/e7/e95d117e0670397246d8fdf5d70dd9f0e6589ee2f3e30d908f600f83ecc7/lammps-2024.8.29.1.0-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:dad4cbad2f210993357a1228ef6809ba4f5863c868b1c079e7293792d21bd907", size = 91786906 }, + { url = "https://files.pythonhosted.org/packages/65/ce/93241ccecf70e8ba738ed62ffccd9714ef0216dee5506b6fd862e6a3c640/lammps-2024.8.29.1.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:395114988272e8d9263525fb03eff242afffbae5d03eda0ff69e3b3f38c04a6a", size = 80742110 }, + { url = "https://files.pythonhosted.org/packages/77/4f/e771cb406825702e1d021d243554d1c29a9ed1a990d1d61763d1a3c95720/lammps-2024.8.29.1.0-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b34ed188c9329a785e9785ac3ad72774d16b7a58adb07f3aa1f015f341b467e", size = 89279364 }, + { url = "https://files.pythonhosted.org/packages/48/55/399826a175dd8d368007ddea2d030628cfb01e79d924a01154ffbb5468eb/lammps-2024.8.29.1.0-py2.py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de3575226a91681c7ff735933dcd77d47e9d4d84f1d065c19a4164606fd074a6", size = 97736740 }, + { url = "https://files.pythonhosted.org/packages/8f/a9/5cde78aa9130ec4d369a340f3d41cddc70eb9a0cceff0fb5dd07f77cd84d/lammps-2024.8.29.1.0-py2.py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:deeca3fc4a90e626e1fe1f3908097dea64315d043aa978a322bfce326b6a412f", size = 94256915 }, + { url = "https://files.pythonhosted.org/packages/f1/83/66fb2e47f6ae9b1708040d8040931f12c2fcc1a382fb611759163af60ae7/lammps-2024.8.29.1.0-py2.py3-none-win_amd64.whl", hash = "sha256:11bebd47818af348db6031771e2bd5ff7725902681c39a3ccd90b156cface403", size = 56742856 }, +] + +[[package]] +name = "latexcodec" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/e7/ed339caf3662976949e4fdbfdf4a6db818b8d2aa1cf2b5f73af89e936bba/latexcodec-3.0.0.tar.gz", hash = "sha256:917dc5fe242762cc19d963e6548b42d63a118028cdd3361d62397e3b638b6bc5", size = 31023 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/bf/ea8887e9f31a8f93ca306699d11909c6140151393a4216f0d9f85a004077/latexcodec-3.0.0-py3-none-any.whl", hash = "sha256:6f3477ad5e61a0a99bd31a6a370c34e88733a6bad9c921a3ffcfacada12f41a7", size = 18150 }, +] + +[[package]] +name = "libcst" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/c4/5577b92173199299e0d32404aa92a156d353d6ec0f74148f6e418e0defef/libcst-1.5.0.tar.gz", hash = "sha256:8478abf21ae3861a073e898d80b822bd56e578886331b33129ba77fec05b8c24", size = 772970 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/44/c8f1e0d83bbdabc240c05d5bedddfd4e095a0031b8df473d8eb004f12554/libcst-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:23d0e07fd3ed11480f8993a1e99d58a45f914a711b14f858b8db08ae861a8a34", size = 2112640 }, + { url = "https://files.pythonhosted.org/packages/20/d5/3d5819da92a8f997ecf0b5a77d65865d4d2aa4209b34e32835b555218689/libcst-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d92c5ae2e2dc9356ad7e3d05077d9b7e5065423e45788fd86729c88729e45c6e", size = 2026866 }, + { url = "https://files.pythonhosted.org/packages/74/19/d2ebded5990f2f5ab4c86412df75338b9d8b386fbb5e430669f287bc8d9c/libcst-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96adc45e96476350df6b8a5ddbb1e1d6a83a7eb3f13087e52eb7cd2f9b65bcc7", size = 2203742 }, + { url = "https://files.pythonhosted.org/packages/87/98/d47a9a88df48cc33db7e1219cd7c29bfdfd8d695634f3f2e86ff04bbd58d/libcst-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5978fd60c66794bb60d037b2e6427ea52d032636e84afce32b0f04e1cf500a", size = 2253801 }, + { url = "https://files.pythonhosted.org/packages/b8/ca/7fdcbab8f8e8c46336099af7929d0f0e5873222830010aae0160d16544c1/libcst-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6502aeb11412afc759036160c686be1107eb5a4466db56b207c786b9b4da7c4", size = 2324610 }, + { url = "https://files.pythonhosted.org/packages/24/fb/db7c696b7bf8e295aa9bf37091fbd1bad35e491be44926da2b20907c3452/libcst-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9cccfc0a78e110c0d0a9d2c6fdeb29feb5274c9157508a8baef7edf352420f6d", size = 2030364 }, + { url = "https://files.pythonhosted.org/packages/b5/82/5b9d1f89bdba4106de6080ab3384157581af4f0b94e04a7150b917b5b945/libcst-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:585b3aa705b3767d717d2100935d8ef557275ecdd3fac81c3e28db0959efb0ea", size = 2112655 }, + { url = "https://files.pythonhosted.org/packages/17/4d/c6ed4323e77717edf3f47af8cabbdd4a7de7983fc5a1cc20130947f65f9d/libcst-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8935dd3393e30c2f97344866a4cb14efe560200e232166a8db1de7865c2ef8b2", size = 2026906 }, + { url = "https://files.pythonhosted.org/packages/eb/ad/10cffc6a69da4320cc75f7f031a48292b61ad5ba0ba94fa9f963cb0b5f67/libcst-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc80ea16c7d44e38f193e4d4ef7ff1e0ba72d8e60e8b61ac6f4c87f070a118bd", size = 2203824 }, + { url = "https://files.pythonhosted.org/packages/e8/88/016b3feb75a3b16896e27691439c3bd493ae7d896bb4e31d6bd4c2e5c20b/libcst-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02be4aab728261bb76d16e77c9a457884cebb60d09c8edee844de43b0e08aff7", size = 2253854 }, + { url = "https://files.pythonhosted.org/packages/69/8e/5a60d53493e259743fd574abe442dd7f3b497ebb58dee168473a03f90d3e/libcst-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8fcd78be4d9ce3c36d0c5d0bdd384e0c7d5f72970a9e4ebd56070141972b4ad", size = 2324725 }, + { url = "https://files.pythonhosted.org/packages/65/86/ddf0d593f4ef5994f456e00e99a1eb28b661aab5df960034199f4d8bbeb4/libcst-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:52b6aadfe54e3ae52c3b815eaaa17ba4da9ff010d5e8adf6a70697872886dd10", size = 2030364 }, + { url = "https://files.pythonhosted.org/packages/a7/23/9cdb3362ad75490108a03abeaae8d7f7fb0d86586d806102ae9d9690d6b8/libcst-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:83bc5fbe34d33597af1d5ea113dcb9b5dd5afe5a5f4316bac4293464d5e3971a", size = 2108563 }, + { url = "https://files.pythonhosted.org/packages/48/ec/4a1a34c3dbe6d51815700a0c14991f4124f10e82f9959d4fb5a9b0b06c74/libcst-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f10124bf99a0b075eae136ef0ce06204e5f6b8da4596a9c4853a0663e80ddf3", size = 2024056 }, + { url = "https://files.pythonhosted.org/packages/da/b7/1976377c19f9477267daac2ea8e2d5a72ce12d5b523ff147d404fb7ae74e/libcst-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48e581af6127c5af4c9f483e5986d94f0c6b2366967ee134f0a8eba0aa4c8c12", size = 2199473 }, + { url = "https://files.pythonhosted.org/packages/63/c4/e056f3f34642f294421bd4a4d4b40aeccaf153a456bcb4d7e54f4337143f/libcst-1.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dba93cca0a5c6d771ed444c44d21ce8ea9b277af7036cea3743677aba9fbbb8", size = 2251411 }, + { url = "https://files.pythonhosted.org/packages/e8/d6/574fc6c8b0ca81586ee05f284ef6987730b841b31ce246ef9d3c45f17ec4/libcst-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80b5c4d87721a7bab265c202575809b810815ab81d5e2e7a5d4417a087975840", size = 2323144 }, + { url = "https://files.pythonhosted.org/packages/b1/92/5cb62834eec397f4b3218c03acc28b6b8470f87c8dad9e9b0fd738c3948c/libcst-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:b48bf71d52c1e891a0948465a94d9817b5fc1ec1a09603566af90585f3b11948", size = 2029603 }, + { url = "https://files.pythonhosted.org/packages/60/5e/dd156f628fed03a273d995008f1669e1964727df6a8818bbedaac51f9ae5/libcst-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:88520b6dea59eaea0cae80f77c0a632604a82c5b2d23dedb4b5b34035cbf1615", size = 2108562 }, + { url = "https://files.pythonhosted.org/packages/2c/54/f63bf0bd2d70179e0557c9474a0511e33e646d398945b5a01de36237ce60/libcst-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:208ea92d80b2eeed8cbc879d5f39f241582a5d56b916b1b65ed2be2f878a2425", size = 2024057 }, + { url = "https://files.pythonhosted.org/packages/dc/37/ce62947fd7305fb501589e4b8f6e82e3cf61fca2d62392e281c17a2112f5/libcst-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4592872aaf5b7fa5c2727a7d73c0985261f1b3fe7eff51f4fd5b8174f30b4e2", size = 2199474 }, + { url = "https://files.pythonhosted.org/packages/c9/95/b878c95af17f3e341ac5dc18e3160d45d86b2c05a0cafd866ceb0b766bbd/libcst-1.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2788b2b5838b78fe15df8e9fa6b6903195ea49b2d2ba43e8f423f6c90e4b69f", size = 2251410 }, + { url = "https://files.pythonhosted.org/packages/e1/26/697b54aa839c4dc6ea2787d5e977ed4be0636149f85df1a0cba7a29bd188/libcst-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b5b5bcd3a9ba92840f27ad34eaa038acbee195ec337da39536c0a2efbbf28efd", size = 2323144 }, + { url = "https://files.pythonhosted.org/packages/a0/9f/5b5481d716670ed5fbd8d06dfa94b7108272b645da2f2406eb909cb6a450/libcst-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:4d6acb0bdee1e55b44c6215c59755ec4693ac01e74bb1fde04c37358b378835d", size = 2029600 }, +] + +[[package]] +name = "lightning-utilities" +version = "0.11.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "setuptools" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/ab/0c421d19e316420a2d54a2da5ae4a4dcd557928ccf7953dbbe4b4eee34d6/lightning_utilities-0.11.8.tar.gz", hash = "sha256:8dfbdc6c52f9847efc948dc462ab8bebb4f4e9a43bd69c82c1b1da484dac20e6", size = 28614 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/5d/4c4672025c23a305231a81bf492f65aa3ea0965a89f9ca369a9ee7d47fd9/lightning_utilities-0.11.8-py3-none-any.whl", hash = "sha256:a57edb34a44258f0c61eed8b8b88926766e9052f5e60bbe69e4871a2b2bfd970", size = 26816 }, +] + +[[package]] +name = "mace-torch" +version = "0.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ase" }, + { name = "e3nn" }, + { name = "matplotlib" }, + { name = "matscipy" }, + { name = "numpy" }, + { name = "opt-einsum" }, + { name = "pandas" }, + { name = "prettytable" }, + { name = "torch" }, + { name = "torch-ema" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/74/e6c9b4e8dd36e83e98984c2800f38b3f88243041c29c1173b329d08d09ca/mace-torch-0.3.4.tar.gz", hash = "sha256:937562212d59dd3c34a973a3fb925d3b4ffb48fd54ca1c279cb2408e696c4a53", size = 75739 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/5f/7a95615c15b42e49d5f6d71d69b3a6399effe543264236dd7997673404ce/mace_torch-0.3.4-py3-none-any.whl", hash = "sha256:ad88aede3d30d428126835f61fe6743b99c62f31e636eabd7b88319b736a0869", size = 87861 }, +] + +[[package]] +name = "maml" +version = "2023.9.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "monty" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "pymatgen" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/53/adcb3410e3957b0ee9938bc9e4ed63b12455cc89b4044db3f3edf281ce55/maml-2023.9.9.tar.gz", hash = "sha256:163e19da32f32ea65377b434a11dcf7529e1e676c5585518d0af447dae2a2a4d", size = 4204750 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/f8/8310ac401ee126ce0d000f45135caa6c476d6b3a0174d151fa9def69641b/maml-2023.9.9-py3-none-any.whl", hash = "sha256:c3d139b2487df6ac65aeb00e48b2366bd076f1ea71be107b739d3f6bd55631be", size = 4301177 }, +] + +[[package]] +name = "markdown" +version = "3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349 }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 }, + { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393 }, + { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732 }, + { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866 }, + { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964 }, + { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977 }, + { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366 }, + { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091 }, + { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065 }, + { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514 }, + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "matplotlib" +version = "3.8.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/aa/607a121331d5323b164f1c0696016ccc9d956a256771c4d91e311a302f13/matplotlib-3.8.3.tar.gz", hash = "sha256:7b416239e9ae38be54b028abbf9048aff5054a9aba5416bef0bd17f9162ce161", size = 35879872 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/fc/e1b3056b0fd2d130c99d6425e367e1e1df37c925408b152db7e207e42589/matplotlib-3.8.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cf60138ccc8004f117ab2a2bad513cc4d122e55864b4fe7adf4db20ca68a078f", size = 7598623 }, + { url = "https://files.pythonhosted.org/packages/a9/84/d2c2e099a16279b21cfcdd2ef4375878a1458fa566e5f1a725d0b7d84f5e/matplotlib-3.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f557156f7116be3340cdeef7f128fa99b0d5d287d5f41a16e169819dcf22357", size = 7492499 }, + { url = "https://files.pythonhosted.org/packages/02/ff/7fa9f35cbc1e8b6594cdc484f7fd06bb09243c4b91f77074432c9d853b38/matplotlib-3.8.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f386cf162b059809ecfac3bcc491a9ea17da69fa35c8ded8ad154cd4b933d5ec", size = 11384622 }, + { url = "https://files.pythonhosted.org/packages/c1/f2/325897d6c498278b0f8b460d44b516f5db865ddb4ba9018e9fe58a3e4633/matplotlib-3.8.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3c5f96f57b0369c288bf6f9b5274ba45787f7e0589a34d24bdbaf6d3344632f", size = 11614113 }, + { url = "https://files.pythonhosted.org/packages/4b/01/7cfd84e4bf42293d4bee1b0c01ec7081bb36c22b787444619779985229ba/matplotlib-3.8.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:83e0f72e2c116ca7e571c57aa29b0fe697d4c6425c4e87c6e994159e0c008635", size = 9539565 }, + { url = "https://files.pythonhosted.org/packages/e4/43/fd3cd5989d6b592af1c2e4f37bf887f74b790f10b568b2497fe874a67fc7/matplotlib-3.8.3-cp310-cp310-win_amd64.whl", hash = "sha256:1c5c8290074ba31a41db1dc332dc2b62def469ff33766cbe325d32a3ee291aea", size = 7646423 }, + { url = "https://files.pythonhosted.org/packages/8d/07/7e245ce1d7daec77cb1ca3b8caf094afb04c4c552a904787a1d684a2b606/matplotlib-3.8.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5184e07c7e1d6d1481862ee361905b7059f7fe065fc837f7c3dc11eeb3f2f900", size = 7601453 }, + { url = "https://files.pythonhosted.org/packages/24/db/6ec78a4f10673a641cdb11694c2de2f64aa00e838551248cb11b8b057440/matplotlib-3.8.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d7e7e0993d0758933b1a241a432b42c2db22dfa37d4108342ab4afb9557cbe3e", size = 7494995 }, + { url = "https://files.pythonhosted.org/packages/c3/9a/9ba49c25d563f5318f28f57e37d1232cb89416a40224395e9b42fa8c1315/matplotlib-3.8.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04b36ad07eac9740fc76c2aa16edf94e50b297d6eb4c081e3add863de4bb19a7", size = 11389595 }, + { url = "https://files.pythonhosted.org/packages/ef/1d/bf1d78126c3d106100232d3a18b7f3732e7dc3b71ee38ab735e4064b19cc/matplotlib-3.8.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c42dae72a62f14982f1474f7e5c9959fc4bc70c9de11cc5244c6e766200ba65", size = 11619137 }, + { url = "https://files.pythonhosted.org/packages/b8/1d/bb533b106bbdeeff05368e4540778b86df576ee9ef886e54bc45b73f6678/matplotlib-3.8.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf5932eee0d428192c40b7eac1399d608f5d995f975cdb9d1e6b48539a5ad8d0", size = 9551948 }, + { url = "https://files.pythonhosted.org/packages/a1/27/8a807464b0cf47fdf3ba8cbb542d4f3a551da0254d7588667857f8a8a88a/matplotlib-3.8.3-cp311-cp311-win_amd64.whl", hash = "sha256:40321634e3a05ed02abf7c7b47a50be50b53ef3eaa3a573847431a545585b407", size = 7648318 }, + { url = "https://files.pythonhosted.org/packages/41/ab/7c8a94d30c2d86d8effdfe5846ac1f2ad75aa9d97f4f3ece49ae004c4c61/matplotlib-3.8.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:09074f8057917d17ab52c242fdf4916f30e99959c1908958b1fc6032e2d0f6d4", size = 7599026 }, + { url = "https://files.pythonhosted.org/packages/7f/23/2333941006444e8e3a8078eb425ce56410ef7d76bc13ab3e9f6db5101705/matplotlib-3.8.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5745f6d0fb5acfabbb2790318db03809a253096e98c91b9a31969df28ee604aa", size = 7491681 }, + { url = "https://files.pythonhosted.org/packages/13/b6/ed740cb55fd9199b6cba22cc16c65f2574ebbf826172ea2dbc3b857f03e3/matplotlib-3.8.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97653d869a71721b639714b42d87cda4cfee0ee74b47c569e4874c7590c55c5", size = 11382991 }, + { url = "https://files.pythonhosted.org/packages/c6/66/2a08ecbafb0970f54e79c6a91139d62c7821b9f67eccb204b94412ce344d/matplotlib-3.8.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:242489efdb75b690c9c2e70bb5c6550727058c8a614e4c7716f363c27e10bba1", size = 11606938 }, + { url = "https://files.pythonhosted.org/packages/03/63/fe7da070f1237d1f2a4dcefd4ae171dd8d7af4ec9fa6a6ee004995d2972b/matplotlib-3.8.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:83c0653c64b73926730bd9ea14aa0f50f202ba187c307a881673bad4985967b7", size = 9547713 }, + { url = "https://files.pythonhosted.org/packages/50/ce/a6bc93f7a44dd1fd23698698e369e141f4f24e7098d0a5937808afee3f5e/matplotlib-3.8.3-cp312-cp312-win_amd64.whl", hash = "sha256:ef6c1025a570354297d6c15f7d0f296d95f88bd3850066b7f1e7b4f2f4c13a39", size = 7647636 }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 }, +] + +[[package]] +name = "matscipy" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ase" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/a4/99d104ebaab040212eb46e9e78b0c264d79fa1396305745ad1d8f974fe59/matscipy-1.1.1.tar.gz", hash = "sha256:2d806d27bfcb99c6e365e0e20cee08e71952ce37b5df3667a1b955dbe26138c2", size = 10362807 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/30/a2b676f877d812c8025fe213184fd13b67faa3602a1220681f90bb962f53/matscipy-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:586897ab3eb8657ad41d93395c1185ed26739a39e33a0c555f188996a530d05d", size = 443591 }, + { url = "https://files.pythonhosted.org/packages/98/68/b4ce7491c0bea05586567ab84320ada9b26ce904db5883d8fdbeefeacc2d/matscipy-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4b101fa81d2206a33a80fad5640a7189d0982c2dbe9f200314739592b2d25d8", size = 443956 }, + { url = "https://files.pythonhosted.org/packages/27/e8/46389744f5dad0ef3797e64dceda4c998e3aeff40aaccb84f9159a220c9a/matscipy-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6eb023e71578678550ce713a9559c2704077100ae63b0bc252e385bd244a6e2b", size = 447878 }, + { url = "https://files.pythonhosted.org/packages/d8/a3/7d8941b1d749a745e1ab9761983f11fd91618ccfaa56662b2787c15e8334/matscipy-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233b8f6a27aeabe27f2288318ad5e24ee7476547eb0ba5e236d1f320e2a90e02", size = 448829 }, + { url = "https://files.pythonhosted.org/packages/36/b7/119092a4cd46c571ba1f717ac49a57b4ca59463c36b8ad53afeb66f677b9/matscipy-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:a676d134e1aa31da17ea12d63eb3648f78df4c6e1e283b29b270a5017e2ba4aa", size = 565893 }, + { url = "https://files.pythonhosted.org/packages/c6/08/e27c620d821ff7653cc93c6211046354c7aa422b2289a2c202aeab6d7ac4/matscipy-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1865a7de629d356289867f1298ff08a280976fd20174a9749699cb1cb44397cb", size = 443590 }, + { url = "https://files.pythonhosted.org/packages/e0/4d/8be5b139fbcb889f24a3edd2bba157575f931e91408309f231906e11ea72/matscipy-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c4824ff40a187d0a84ca0b341aa449109398798d717014f7f59e2660ecd6f05", size = 443958 }, + { url = "https://files.pythonhosted.org/packages/2d/eb/cdf942573eacbc7a267bb7d8f84529096434d12a0d8e8b05b6b51ccd65a9/matscipy-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15ebc86f7ef293fa5161bccf9573f20f7cecced9fea8f1efe833ae67775cf45f", size = 447879 }, + { url = "https://files.pythonhosted.org/packages/77/79/c12cca097156f55a71102a400d02fd352c6adb8b3d16464c4b4782d84147/matscipy-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b32fd06e1b97f2df632ca5289863efef522e11c7e5ca62023bee8b01de0a274", size = 448830 }, + { url = "https://files.pythonhosted.org/packages/84/a3/76dbe44f0e1efe020886e841ad72a9101282a66f92009d9bbf995cbdf4aa/matscipy-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:c39429604ad02865057ebe611ef15f077facc58890c33cc46449889a48539fd9", size = 565894 }, + { url = "https://files.pythonhosted.org/packages/f5/ba/e082a33beeace6493c300ac0ddf6d7346cfb0d7ce4f06e4336ba39bd8863/matscipy-1.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:661bdb24472d192effce98ffb80b9b90ed481238a27bab0856c88ea6cdf6b2fd", size = 443556 }, + { url = "https://files.pythonhosted.org/packages/4e/4b/321270220155ab2985520e27a6bdd5818f1d7a908dd066b15afd5f39d8e5/matscipy-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:845948900663640a6b1d7b2e8d20956dd443dbfa3dfbd1b2ceae45982ac0caf0", size = 443717 }, + { url = "https://files.pythonhosted.org/packages/39/ae/75c17ed5b45fa2fb8e529fe0fa524878b759f4bc9affbd14ea95ce52a485/matscipy-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7180d932332dd8d3e00223954da44e9571167f58c768c2cee48cf7f85f0c4f27", size = 447973 }, + { url = "https://files.pythonhosted.org/packages/a7/e2/51247a22f02979020cb95b6480822478c8491b7fb6f792eff125dd15fb1a/matscipy-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8088bf43f3b383784321ad044bb03031c6c6a94038a0a7ec3c80b9122ded39b", size = 449034 }, + { url = "https://files.pythonhosted.org/packages/77/11/5284ce41986d69f71116c7b7d5dde57da76b5bccf1dba10abc06958aa086/matscipy-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:15eccc94d60f6b7365712a611bb144f37b647a665f745a4dd2c0ed170a534c2d", size = 565994 }, +] + +[[package]] +name = "mccabe" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/18/fa675aa501e11d6d6ca0ae73a101b2f3571a565e0f7d38e062eec18a91ee/mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f", size = 8612 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/89/479dc97e18549e21354893e4ee4ef36db1d237534982482c3681ee6e7b57/mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", size = 8556 }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/03/a2ecab526543b152300717cf232bb4bb8605b6edb946c845016fa9c9c9fd/mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5", size = 43542 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/f7/7782a043553ee469c1ff49cfa1cdace2d6bf99a1f333cf38676b3ddf30da/mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636", size = 55316 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "mistune" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/c8/f0173fe3bf85fd891aee2e7bcd8207dfe26c2c683d727c5a6cc3aec7b628/mistune-3.0.2.tar.gz", hash = "sha256:fc7f93ded930c92394ef2cb6f04a8aabab4117a91449e72dcc8dfa646a508be8", size = 90840 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/74/c95adcdf032956d9ef6c89a9b8a5152bf73915f8c633f3e3d88d06bd699c/mistune-3.0.2-py3-none-any.whl", hash = "sha256:71481854c30fdbc938963d3605b72501f5c10a9320ecd412c121c163a1c7d205", size = 47958 }, +] + +[[package]] +name = "monty" +version = "2024.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/6d/83e538387c561005cdacb339d486e29814dd766e69971983d56eedf675d4/monty-2024.2.2.tar.gz", hash = "sha256:669f56f7b23276fdc056bd1a3abca9c44c054f6e44d3a908b47ea09b55664030", size = 78170 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/39/4498be12d8025905f1c339ff2bcc7781e76900f8f85f522f71c854370103/monty-2024.2.2-py3-none-any.whl", hash = "sha256:d84b739315f02e337cc5d3fed70b196e6beb2b1bb8f2520a6e567a1bc31bf9d0", size = 63006 }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 }, +] + +[[package]] +name = "multidict" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/be/504b89a5e9ca731cd47487e91c469064f8ae5af93b7259758dcfc2b9c848/multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a", size = 64002 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/68/259dee7fd14cf56a17c554125e534f6274c2860159692a414d0b402b9a6d/multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60", size = 48628 }, + { url = "https://files.pythonhosted.org/packages/50/79/53ba256069fe5386a4a9e80d4e12857ced9de295baf3e20c68cdda746e04/multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1", size = 29327 }, + { url = "https://files.pythonhosted.org/packages/ff/10/71f1379b05b196dae749b5ac062e87273e3f11634f447ebac12a571d90ae/multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53", size = 29689 }, + { url = "https://files.pythonhosted.org/packages/71/45/70bac4f87438ded36ad4793793c0095de6572d433d98575a5752629ef549/multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5", size = 126639 }, + { url = "https://files.pythonhosted.org/packages/80/cf/17f35b3b9509b4959303c05379c4bfb0d7dd05c3306039fc79cf035bbac0/multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581", size = 134315 }, + { url = "https://files.pythonhosted.org/packages/ef/1f/652d70ab5effb33c031510a3503d4d6efc5ec93153562f1ee0acdc895a57/multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56", size = 129471 }, + { url = "https://files.pythonhosted.org/packages/a6/64/2dd6c4c681688c0165dea3975a6a4eab4944ea30f35000f8b8af1df3148c/multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429", size = 124585 }, + { url = "https://files.pythonhosted.org/packages/87/56/e6ee5459894c7e554b57ba88f7257dc3c3d2d379cb15baaa1e265b8c6165/multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748", size = 116957 }, + { url = "https://files.pythonhosted.org/packages/36/9e/616ce5e8d375c24b84f14fc263c7ef1d8d5e8ef529dbc0f1df8ce71bb5b8/multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db", size = 128609 }, + { url = "https://files.pythonhosted.org/packages/8c/4f/4783e48a38495d000f2124020dc96bacc806a4340345211b1ab6175a6cb4/multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056", size = 123016 }, + { url = "https://files.pythonhosted.org/packages/3e/b3/4950551ab8fc39862ba5e9907dc821f896aa829b4524b4deefd3e12945ab/multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76", size = 133542 }, + { url = "https://files.pythonhosted.org/packages/96/4d/f0ce6ac9914168a2a71df117935bb1f1781916acdecbb43285e225b484b8/multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160", size = 130163 }, + { url = "https://files.pythonhosted.org/packages/be/72/17c9f67e7542a49dd252c5ae50248607dfb780bcc03035907dafefb067e3/multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7", size = 126832 }, + { url = "https://files.pythonhosted.org/packages/71/9f/72d719e248cbd755c8736c6d14780533a1606ffb3fbb0fbd77da9f0372da/multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0", size = 26402 }, + { url = "https://files.pythonhosted.org/packages/04/5a/d88cd5d00a184e1ddffc82aa2e6e915164a6d2641ed3606e766b5d2f275a/multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d", size = 28800 }, + { url = "https://files.pythonhosted.org/packages/93/13/df3505a46d0cd08428e4c8169a196131d1b0c4b515c3649829258843dde6/multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6", size = 48570 }, + { url = "https://files.pythonhosted.org/packages/f0/e1/a215908bfae1343cdb72f805366592bdd60487b4232d039c437fe8f5013d/multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156", size = 29316 }, + { url = "https://files.pythonhosted.org/packages/70/0f/6dc70ddf5d442702ed74f298d69977f904960b82368532c88e854b79f72b/multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb", size = 29640 }, + { url = "https://files.pythonhosted.org/packages/d8/6d/9c87b73a13d1cdea30b321ef4b3824449866bd7f7127eceed066ccb9b9ff/multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b", size = 131067 }, + { url = "https://files.pythonhosted.org/packages/cc/1e/1b34154fef373371fd6c65125b3d42ff5f56c7ccc6bfff91b9b3c60ae9e0/multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72", size = 138507 }, + { url = "https://files.pythonhosted.org/packages/fb/e0/0bc6b2bac6e461822b5f575eae85da6aae76d0e2a79b6665d6206b8e2e48/multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304", size = 133905 }, + { url = "https://files.pythonhosted.org/packages/ba/af/73d13b918071ff9b2205fcf773d316e0f8fefb4ec65354bbcf0b10908cc6/multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351", size = 129004 }, + { url = "https://files.pythonhosted.org/packages/74/21/23960627b00ed39643302d81bcda44c9444ebcdc04ee5bedd0757513f259/multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb", size = 121308 }, + { url = "https://files.pythonhosted.org/packages/8b/5c/cf282263ffce4a596ed0bb2aa1a1dddfe1996d6a62d08842a8d4b33dca13/multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3", size = 132608 }, + { url = "https://files.pythonhosted.org/packages/d7/3e/97e778c041c72063f42b290888daff008d3ab1427f5b09b714f5a8eff294/multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399", size = 127029 }, + { url = "https://files.pythonhosted.org/packages/47/ac/3efb7bfe2f3aefcf8d103e9a7162572f01936155ab2f7ebcc7c255a23212/multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423", size = 137594 }, + { url = "https://files.pythonhosted.org/packages/42/9b/6c6e9e8dc4f915fc90a9b7798c44a30773dea2995fdcb619870e705afe2b/multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3", size = 134556 }, + { url = "https://files.pythonhosted.org/packages/1d/10/8e881743b26aaf718379a14ac58572a240e8293a1c9d68e1418fb11c0f90/multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753", size = 130993 }, + { url = "https://files.pythonhosted.org/packages/45/84/3eb91b4b557442802d058a7579e864b329968c8d0ea57d907e7023c677f2/multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80", size = 26405 }, + { url = "https://files.pythonhosted.org/packages/9f/0b/ad879847ecbf6d27e90a6eabb7eff6b62c129eefe617ea45eae7c1f0aead/multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926", size = 28795 }, + { url = "https://files.pythonhosted.org/packages/fd/16/92057c74ba3b96d5e211b553895cd6dc7cc4d1e43d9ab8fafc727681ef71/multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa", size = 48713 }, + { url = "https://files.pythonhosted.org/packages/94/3d/37d1b8893ae79716179540b89fc6a0ee56b4a65fcc0d63535c6f5d96f217/multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436", size = 29516 }, + { url = "https://files.pythonhosted.org/packages/a2/12/adb6b3200c363062f805275b4c1e656be2b3681aada66c80129932ff0bae/multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761", size = 29557 }, + { url = "https://files.pythonhosted.org/packages/47/e9/604bb05e6e5bce1e6a5cf80a474e0f072e80d8ac105f1b994a53e0b28c42/multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e", size = 130170 }, + { url = "https://files.pythonhosted.org/packages/7e/13/9efa50801785eccbf7086b3c83b71a4fb501a4d43549c2f2f80b8787d69f/multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef", size = 134836 }, + { url = "https://files.pythonhosted.org/packages/bf/0f/93808b765192780d117814a6dfcc2e75de6dcc610009ad408b8814dca3ba/multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95", size = 133475 }, + { url = "https://files.pythonhosted.org/packages/d3/c8/529101d7176fe7dfe1d99604e48d69c5dfdcadb4f06561f465c8ef12b4df/multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925", size = 131049 }, + { url = "https://files.pythonhosted.org/packages/ca/0c/fc85b439014d5a58063e19c3a158a889deec399d47b5269a0f3b6a2e28bc/multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966", size = 120370 }, + { url = "https://files.pythonhosted.org/packages/db/46/d4416eb20176492d2258fbd47b4abe729ff3b6e9c829ea4236f93c865089/multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305", size = 125178 }, + { url = "https://files.pythonhosted.org/packages/5b/46/73697ad7ec521df7de5531a32780bbfd908ded0643cbe457f981a701457c/multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2", size = 119567 }, + { url = "https://files.pythonhosted.org/packages/cd/ed/51f060e2cb0e7635329fa6ff930aa5cffa17f4c7f5c6c3ddc3500708e2f2/multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2", size = 129822 }, + { url = "https://files.pythonhosted.org/packages/df/9e/ee7d1954b1331da3eddea0c4e08d9142da5f14b1321c7301f5014f49d492/multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6", size = 128656 }, + { url = "https://files.pythonhosted.org/packages/77/00/8538f11e3356b5d95fa4b024aa566cde7a38aa7a5f08f4912b32a037c5dc/multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3", size = 125360 }, + { url = "https://files.pythonhosted.org/packages/be/05/5d334c1f2462d43fec2363cd00b1c44c93a78c3925d952e9a71caf662e96/multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133", size = 26382 }, + { url = "https://files.pythonhosted.org/packages/a3/bf/f332a13486b1ed0496d624bcc7e8357bb8053823e8cd4b9a18edc1d97e73/multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1", size = 28529 }, + { url = "https://files.pythonhosted.org/packages/22/67/1c7c0f39fe069aa4e5d794f323be24bf4d33d62d2a348acdb7991f8f30db/multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008", size = 48771 }, + { url = "https://files.pythonhosted.org/packages/3c/25/c186ee7b212bdf0df2519eacfb1981a017bda34392c67542c274651daf23/multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f", size = 29533 }, + { url = "https://files.pythonhosted.org/packages/67/5e/04575fd837e0958e324ca035b339cea174554f6f641d3fb2b4f2e7ff44a2/multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28", size = 29595 }, + { url = "https://files.pythonhosted.org/packages/d3/b2/e56388f86663810c07cfe4a3c3d87227f3811eeb2d08450b9e5d19d78876/multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b", size = 130094 }, + { url = "https://files.pythonhosted.org/packages/6c/ee/30ae9b4186a644d284543d55d491fbd4239b015d36b23fea43b4c94f7052/multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c", size = 134876 }, + { url = "https://files.pythonhosted.org/packages/84/c7/70461c13ba8ce3c779503c70ec9d0345ae84de04521c1f45a04d5f48943d/multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3", size = 133500 }, + { url = "https://files.pythonhosted.org/packages/4a/9f/002af221253f10f99959561123fae676148dd730e2daa2cd053846a58507/multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44", size = 131099 }, + { url = "https://files.pythonhosted.org/packages/82/42/d1c7a7301d52af79d88548a97e297f9d99c961ad76bbe6f67442bb77f097/multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2", size = 120403 }, + { url = "https://files.pythonhosted.org/packages/68/f3/471985c2c7ac707547553e8f37cff5158030d36bdec4414cb825fbaa5327/multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3", size = 125348 }, + { url = "https://files.pythonhosted.org/packages/67/2c/e6df05c77e0e433c214ec1d21ddd203d9a4770a1f2866a8ca40a545869a0/multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa", size = 119673 }, + { url = "https://files.pythonhosted.org/packages/c5/cd/bc8608fff06239c9fb333f9db7743a1b2eafe98c2666c9a196e867a3a0a4/multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa", size = 129927 }, + { url = "https://files.pythonhosted.org/packages/44/8e/281b69b7bc84fc963a44dc6e0bbcc7150e517b91df368a27834299a526ac/multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4", size = 128711 }, + { url = "https://files.pythonhosted.org/packages/12/a4/63e7cd38ed29dd9f1881d5119f272c898ca92536cdb53ffe0843197f6c85/multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6", size = 125519 }, + { url = "https://files.pythonhosted.org/packages/38/e0/4f5855037a72cd8a7a2f60a3952d9aa45feedb37ae7831642102604e8a37/multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81", size = 26426 }, + { url = "https://files.pythonhosted.org/packages/7e/a5/17ee3a4db1e310b7405f5d25834460073a8ccd86198ce044dfaf69eac073/multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774", size = 28531 }, + { url = "https://files.pythonhosted.org/packages/99/b7/b9e70fde2c0f0c9af4cc5277782a89b66d35948ea3369ec9f598358c3ac5/multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506", size = 10051 }, +] + +[[package]] +name = "multiprocess" +version = "0.70.16" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/ae/04f39c5d0d0def03247c2893d6f2b83c136bf3320a2154d7b8858f2ba72d/multiprocess-0.70.16.tar.gz", hash = "sha256:161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1", size = 1772603 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/76/6e712a2623d146d314f17598df5de7224c85c0060ef63fd95cc15a25b3fa/multiprocess-0.70.16-pp310-pypy310_pp73-macosx_10_13_x86_64.whl", hash = "sha256:476887be10e2f59ff183c006af746cb6f1fd0eadcfd4ef49e605cbe2659920ee", size = 134980 }, + { url = "https://files.pythonhosted.org/packages/0f/ab/1e6e8009e380e22254ff539ebe117861e5bdb3bff1fc977920972237c6c7/multiprocess-0.70.16-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d951bed82c8f73929ac82c61f01a7b5ce8f3e5ef40f5b52553b4f547ce2b08ec", size = 134982 }, + { url = "https://files.pythonhosted.org/packages/bc/f7/7ec7fddc92e50714ea3745631f79bd9c96424cb2702632521028e57d3a36/multiprocess-0.70.16-py310-none-any.whl", hash = "sha256:c4a9944c67bd49f823687463660a2d6daae94c289adff97e0f9d696ba6371d02", size = 134824 }, + { url = "https://files.pythonhosted.org/packages/50/15/b56e50e8debaf439f44befec5b2af11db85f6e0f344c3113ae0be0593a91/multiprocess-0.70.16-py311-none-any.whl", hash = "sha256:af4cabb0dac72abfb1e794fa7855c325fd2b55a10a44628a3c1ad3311c04127a", size = 143519 }, + { url = "https://files.pythonhosted.org/packages/0a/7d/a988f258104dcd2ccf1ed40fdc97e26c4ac351eeaf81d76e266c52d84e2f/multiprocess-0.70.16-py312-none-any.whl", hash = "sha256:fc0544c531920dde3b00c29863377f87e1632601092ea2daca74e4beb40faa2e", size = 146741 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "myst-parser" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "jinja2" }, + { name = "markdown-it-py" }, + { name = "mdit-py-plugins" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e8/c1/48ea47b78ade0bb0281f34c9e343e3ea0c681fbc81464dbfd134e983954f/myst_parser-2.0.0.tar.gz", hash = "sha256:ea929a67a6a0b1683cdbe19b8d2e724cd7643f8aa3e7bb18dd65beac3483bead", size = 85800 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/f6/6d61a023d758f488e36638076e8a4ec4447a2cdf86938cf6c60cf1c860e6/myst_parser-2.0.0-py3-none-any.whl", hash = "sha256:7c36344ae39c8e740dad7fdabf5aa6fc4897a813083c6cc9990044eb93656b14", size = 77158 }, +] + +[[package]] +name = "nbclient" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "nbformat" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/d2/39bc36604f24bccd44d374ac34769bc58c53a1da5acd1e83f0165aa4940e/nbclient-0.10.0.tar.gz", hash = "sha256:4b3f1b7dba531e498449c4db4f53da339c91d449dc11e9af3a43b4eb5c5abb09", size = 62246 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/e8/00517a23d3eeaed0513e718fbc94aab26eaa1758f5690fc8578839791c79/nbclient-0.10.0-py3-none-any.whl", hash = "sha256:f13e3529332a1f1f81d82a53210322476a168bb7090a0289c795fe9cc11c9d3f", size = 25318 }, +] + +[[package]] +name = "nbconvert" +version = "7.16.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "bleach" }, + { name = "defusedxml" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyterlab-pygments" }, + { name = "markupsafe" }, + { name = "mistune" }, + { name = "nbclient" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "pandocfilters" }, + { name = "pygments" }, + { name = "tinycss2" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/e8/ba521a033b21132008e520c28ceb818f9f092da5f0261e94e509401b29f9/nbconvert-7.16.4.tar.gz", hash = "sha256:86ca91ba266b0a448dc96fa6c5b9d98affabde2867b363258703536807f9f7f4", size = 854422 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/bb/bb5b6a515d1584aa2fd89965b11db6632e4bdc69495a52374bcc36e56cfa/nbconvert-7.16.4-py3-none-any.whl", hash = "sha256:05873c620fe520b6322bf8a5ad562692343fe3452abda5765c7a34b7d1aa3eb3", size = 257388 }, +] + +[[package]] +name = "nbformat" +version = "5.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastjsonschema" }, + { name = "jsonschema" }, + { name = "jupyter-core" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454 }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, +] + +[[package]] +name = "networkx" +version = "3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/a1/47b974da1a73f063c158a1f4cc33ed0abf7c04f98a19050e80c533c31f0c/networkx-3.1.tar.gz", hash = "sha256:de346335408f84de0eada6ff9fafafff9bcda11f0a0dfaa931133debb146ab61", size = 2021691 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/05/9d4f9b78ead6b2661d6e8ea772e111fc4a9fbd866ad0c81906c11206b55e/networkx-3.1-py3-none-any.whl", hash = "sha256:4f33f68cb2afcf86f28a45f43efc27a9386b535d567d2127f8f61d51dec58d36", size = 2072251 }, +] + +[[package]] +name = "ninja" +version = "1.11.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/2c/d717d13a413d6f7579cdaa1e28e6e2c98de95461549b08d311c8a5bf4c51/ninja-1.11.1.1.tar.gz", hash = "sha256:9d793b08dd857e38d0b6ffe9e6b7145d7c485a42dcfea04905ca0cdb6017cc3c", size = 132392 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/6e/04ed11bb244039908f6f212cb5f3e97933e238655248e4ce307c1687ba1f/ninja-1.11.1.1-py2.py3-none-macosx_10_9_universal2.macosx_10_9_x86_64.macosx_11_0_arm64.macosx_11_0_universal2.whl", hash = "sha256:376889c76d87b95b5719fdd61dd7db193aa7fd4432e5d52d2e44e4c497bdbbee", size = 270611 }, + { url = "https://files.pythonhosted.org/packages/2c/52/0e5423311eb9939b6f9354059a6d88a6211eb4fa1c7a4ef303ecee1c1fe0/ninja-1.11.1.1-py2.py3-none-manylinux1_i686.manylinux_2_5_i686.whl", hash = "sha256:ecf80cf5afd09f14dcceff28cb3f11dc90fb97c999c89307aea435889cb66877", size = 324256 }, + { url = "https://files.pythonhosted.org/packages/6d/92/8d7aebd4430ab5ff65df2bfee6d5745f95c004284db2d8ca76dcbfd9de47/ninja-1.11.1.1-py2.py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:84502ec98f02a037a169c4b0d5d86075eaf6afc55e1879003d6cab51ced2ea4b", size = 307194 }, + { url = "https://files.pythonhosted.org/packages/01/c8/96424839fd127b4492229acf50763ed9940d864ca35d17d151934aef1f6f/ninja-1.11.1.1-py2.py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:73b93c14046447c7c5cc892433d4fae65d6364bec6685411cb97a8bcf815f93a", size = 155643 }, + { url = "https://files.pythonhosted.org/packages/6b/fa/5ca8e65a98cdb9a71d4f1e38cac7bd757bbb9555a5aef5a4d293aa890e5c/ninja-1.11.1.1-py2.py3-none-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18302d96a5467ea98b68e1cae1ae4b4fb2b2a56a82b955193c637557c7273dbd", size = 179538 }, + { url = "https://files.pythonhosted.org/packages/45/ef/60086f02cbc6882da00a02c81d645cefd8d2d65b01fade41b873d8dd85a2/ninja-1.11.1.1-py2.py3-none-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:aad34a70ef15b12519946c5633344bc775a7656d789d9ed5fdb0d456383716ef", size = 156217 }, + { url = "https://files.pythonhosted.org/packages/1c/00/2fd13ac6aafdb566f00d6b541101fca54e58ae58bf96c00f9780df019607/ninja-1.11.1.1-py2.py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:d491fc8d89cdcb416107c349ad1e3a735d4c4af5e1cb8f5f727baca6350fdaea", size = 372069 }, + { url = "https://files.pythonhosted.org/packages/ad/5d/6e97c8a25167d4867694c7fb0b9bdbc9b096d6479c8e56c5bd41b49613f6/ninja-1.11.1.1-py2.py3-none-musllinux_1_1_i686.whl", hash = "sha256:7563ce1d9fe6ed5af0b8dd9ab4a214bf4ff1f2f6fd6dc29f480981f0f8b8b249", size = 418859 }, + { url = "https://files.pythonhosted.org/packages/43/78/34af88d753389a9412438d16142c77e587e0d69152faf0bbf99701063dd8/ninja-1.11.1.1-py2.py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:9df724344202b83018abb45cb1efc22efd337a1496514e7e6b3b59655be85205", size = 419782 }, + { url = "https://files.pythonhosted.org/packages/3b/74/de0633f8bced3b188942fca64a950e8f2206c60c10c97af465b356ae9b25/ninja-1.11.1.1-py2.py3-none-musllinux_1_1_s390x.whl", hash = "sha256:3e0f9be5bb20d74d58c66cc1c414c3e6aeb45c35b0d0e41e8d739c2c0d57784f", size = 415476 }, + { url = "https://files.pythonhosted.org/packages/9a/f3/3e4a56ff77739d1582749b93497bdebf11e003fbc7a66363ef6c772ebd0a/ninja-1.11.1.1-py2.py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:76482ba746a2618eecf89d5253c0d1e4f1da1270d41e9f54dfbd91831b0f6885", size = 379229 }, + { url = "https://files.pythonhosted.org/packages/c5/ee/53df34fcc9c0b1db62b2f2e2c848e28d9354e1c7f0dce029ee50b16ca157/ninja-1.11.1.1-py2.py3-none-win32.whl", hash = "sha256:fa2ba9d74acfdfbfbcf06fad1b8282de8a7a8c481d9dee45c859a8c93fcc1082", size = 265049 }, + { url = "https://files.pythonhosted.org/packages/b6/2f/a3bc50fa63fc4fe9348e15b53dc8c87febfd4e0c660fcf250c4b19a3aa3b/ninja-1.11.1.1-py2.py3-none-win_amd64.whl", hash = "sha256:95da904130bfa02ea74ff9c0116b4ad266174fafb1c707aa50212bc7859aebf1", size = 312958 }, + { url = "https://files.pythonhosted.org/packages/73/2a/f5b7b3b7ecd5cf4e31375580bf5c6a01a328ed1ebdfff90fab463e3f4bc7/ninja-1.11.1.1-py2.py3-none-win_arm64.whl", hash = "sha256:185e0641bde601e53841525c4196278e9aaf4463758da6dd1e752c0a0f54136a", size = 272686 }, +] + +[[package]] +name = "notebook" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, + { name = "jupyterlab" }, + { name = "jupyterlab-server" }, + { name = "notebook-shim" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/33/30b83c1c84e368087059bde1269549612584924db156bff53654e165a498/notebook-7.2.2.tar.gz", hash = "sha256:2ef07d4220421623ad3fe88118d687bc0450055570cdd160814a59cf3a1c516e", size = 4948876 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/77/53732fbf48196af9e51c2a61833471021c1d77d335d57b96ee3588c0c53d/notebook-7.2.2-py3-none-any.whl", hash = "sha256:c89264081f671bc02eec0ed470a627ed791b9156cad9285226b31611d3e9fe1c", size = 5037123 }, +] + +[[package]] +name = "notebook-shim" +version = "0.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307 }, +] + +[[package]] +name = "numpy" +version = "1.26.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/94/ace0fdea5241a27d13543ee117cbc65868e82213fb31a8eb7fe9ff23f313/numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", size = 20631468 }, + { url = "https://files.pythonhosted.org/packages/20/f7/b24208eba89f9d1b58c1668bc6c8c4fd472b20c45573cb767f59d49fb0f6/numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", size = 13966411 }, + { url = "https://files.pythonhosted.org/packages/fc/a5/4beee6488160798683eed5bdb7eead455892c3b4e1f78d79d8d3f3b084ac/numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", size = 14219016 }, + { url = "https://files.pythonhosted.org/packages/4b/d7/ecf66c1cd12dc28b4040b15ab4d17b773b87fa9d29ca16125de01adb36cd/numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f", size = 18240889 }, + { url = "https://files.pythonhosted.org/packages/24/03/6f229fe3187546435c4f6f89f6d26c129d4f5bed40552899fcf1f0bf9e50/numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", size = 13876746 }, + { url = "https://files.pythonhosted.org/packages/39/fe/39ada9b094f01f5a35486577c848fe274e374bbf8d8f472e1423a0bbd26d/numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", size = 18078620 }, + { url = "https://files.pythonhosted.org/packages/d5/ef/6ad11d51197aad206a9ad2286dc1aac6a378059e06e8cf22cd08ed4f20dc/numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", size = 5972659 }, + { url = "https://files.pythonhosted.org/packages/19/77/538f202862b9183f54108557bfda67e17603fc560c384559e769321c9d92/numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", size = 15808905 }, + { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554 }, + { url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127 }, + { url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994 }, + { url = "https://files.pythonhosted.org/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005 }, + { url = "https://files.pythonhosted.org/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297 }, + { url = "https://files.pythonhosted.org/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567 }, + { url = "https://files.pythonhosted.org/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812 }, + { url = "https://files.pythonhosted.org/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913 }, + { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901 }, + { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868 }, + { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109 }, + { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613 }, + { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172 }, + { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643 }, + { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803 }, + { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754 }, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.1.3.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/6d/121efd7382d5b0284239f4ab1fc1590d86d34ed4a4a2fdb13b30ca8e5740/nvidia_cublas_cu12-12.1.3.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:ee53ccca76a6fc08fb9701aa95b6ceb242cdaab118c3bb152af4e579af792728", size = 410594774 }, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.1.105" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/00/6b218edd739ecfc60524e585ba8e6b00554dd908de2c9c66c1af3e44e18d/nvidia_cuda_cupti_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:e54fde3983165c624cb79254ae9818a456eb6e87a7fd4d56a2352c24ee542d7e", size = 14109015 }, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.1.105" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/9f/c64c03f49d6fbc56196664d05dba14e3a561038a81a638eeb47f4d4cfd48/nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:339b385f50c309763ca65456ec75e17bbefcbbf2893f462cb8b90584cd27a1c2", size = 23671734 }, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.1.105" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/d5/c68b1d2cdfcc59e72e8a5949a37ddb22ae6cade80cd4a57a84d4c8b55472/nvidia_cuda_runtime_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:6e258468ddf5796e25f1dc591a31029fa317d97a0a94ed93468fc86301d61e40", size = 823596 }, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "8.9.2.26" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/74/a2e2be7fb83aaedec84f391f082cf765dfb635e7caa9b49065f73e4835d8/nvidia_cudnn_cu12-8.9.2.26-py3-none-manylinux1_x86_64.whl", hash = "sha256:5ccb288774fdfb07a7e7025ffec286971c06d8d7b4fb162525334616d7629ff9", size = 731725872 }, +] + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.0.2.54" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/94/eb540db023ce1d162e7bea9f8f5aa781d57c65aed513c33ee9a5123ead4d/nvidia_cufft_cu12-11.0.2.54-py3-none-manylinux1_x86_64.whl", hash = "sha256:794e3948a1aa71fd817c3775866943936774d1c14e7628c74f6f7417224cdf56", size = 121635161 }, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.2.106" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/31/4890b1c9abc496303412947fc7dcea3d14861720642b49e8ceed89636705/nvidia_curand_cu12-10.3.2.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:9d264c5036dde4e64f1de8c50ae753237c12e0b1348738169cd0f8a536c0e1e0", size = 56467784 }, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.4.5.107" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12" }, + { name = "nvidia-cusparse-cu12" }, + { name = "nvidia-nvjitlink-cu12" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/1d/8de1e5c67099015c834315e333911273a8c6aaba78923dd1d1e25fc5f217/nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl", hash = "sha256:8a7ec542f0412294b15072fa7dab71d31334014a69f953004ea7a118206fe0dd", size = 124161928 }, +] + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.1.0.106" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/5b/cfaeebf25cd9fdec14338ccb16f6b2c4c7fa9163aefcf057d86b9cc248bb/nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:f3b50f42cf363f86ab21f720998517a659a48131e8d538dc02f8768237bd884c", size = 195958278 }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.19.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/00/d0d4e48aef772ad5aebcf70b73028f88db6e5640b36c38e90445b7a57c45/nvidia_nccl_cu12-2.19.3-py3-none-manylinux1_x86_64.whl", hash = "sha256:a9734707a2c96443331c1e48c717024aa6678a0e2a4cb66b2c364d18cee6b48d", size = 165987969 }, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.6.77" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/8c/386018fdffdce2ff8d43fedf192ef7d14cab7501cbf78a106dd2e9f1fc1f/nvidia_nvjitlink_cu12-12.6.77-py3-none-manylinux2014_aarch64.whl", hash = "sha256:3bf10d85bb1801e9c894c6e197e44dd137d2a0a9e43f8450e9ad13f2df0dd52d", size = 19270432 }, + { url = "https://files.pythonhosted.org/packages/fe/e4/486de766851d58699bcfeb3ba6a3beb4d89c3809f75b9d423b9508a8760f/nvidia_nvjitlink_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:9ae346d16203ae4ea513be416495167a0101d33d2d14935aa9c1829a3fb45142", size = 19745114 }, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.1.105" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/d3/8057f0587683ed2fcd4dbfbdfdfa807b9160b809976099d36b8f60d08f03/nvidia_nvtx_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:dc21cf308ca5691e7c04d962e213f8a4aa9bbfa23d95412f452254c2caeb09e5", size = 99138 }, +] + +[[package]] +name = "omegaconf" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "antlr4-python3-runtime" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/48/6388f1bb9da707110532cb70ec4d2822858ddfb44f1cdf1233c20a80ea4b/omegaconf-2.3.0.tar.gz", hash = "sha256:d5d4b6d29955cc50ad50c46dc269bcd92c6e00f5f90d23ab5fee7bfca4ba4cc7", size = 3298120 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl", hash = "sha256:7b4df175cdb08ba400f45cae3bdcae7ba8365db4d165fc65fd04b050ab63b46b", size = 79500 }, +] + +[[package]] +name = "opt-einsum" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/b9/2ac072041e899a52f20cf9510850ff58295003aa75525e58343591b0cbfb/opt_einsum-3.4.0.tar.gz", hash = "sha256:96ca72f1b886d148241348783498194c577fa30a8faac108586b14f1ba4473ac", size = 63004 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl", hash = "sha256:69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd", size = 71932 }, +] + +[[package]] +name = "opt-einsum-fx" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opt-einsum" }, + { name = "packaging" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/de/856dab99be0360c7275fee075eb0450a2ec82a54c4c33689606f62e9615b/opt_einsum_fx-0.1.4.tar.gz", hash = "sha256:7eeb7f91ecb70be65e6179c106ea7f64fc1db6319e3d1289a4518b384f81e74f", size = 12969 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/4c/e0370709aaf9d7ceb68f975cac559751e75954429a77e83202e680606560/opt_einsum_fx-0.1.4-py3-none-any.whl", hash = "sha256:85f489f4c7c31fd88d5faf9669c09e61ec37a30098809fdcfe2a08a9e42f23c9", size = 13213 }, +] + +[[package]] +name = "ordered-set" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/ca/bfac8bc689799bcca4157e0e0ced07e70ce125193fc2e166d2e685b7e2fe/ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8", size = 12826 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/55/af02708f230eb77084a299d7b08175cff006dea4f2721074b92cdb0296c0/ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562", size = 7634 }, +] + +[[package]] +name = "orion" +version = "0.2.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appdirs" }, + { name = "cloudpickle" }, + { name = "falcon" }, + { name = "falcon-cors" }, + { name = "filelock" }, + { name = "gitpython" }, + { name = "gunicorn" }, + { name = "joblib" }, + { name = "kaleido" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "plotly" }, + { name = "psutil" }, + { name = "pymongo" }, + { name = "pytest" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "scikit-learn" }, + { name = "scikit-optimize" }, + { name = "scipy" }, + { name = "tabulate" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/da/a487ada73b43f3150012cea7c83071aa7d8b9018d6bdf5e556978a31b267/orion-0.2.7.tar.gz", hash = "sha256:e38abad90b9861bebf0b92fd54348c6d187a903072f7a8b79524464b1d563c7d", size = 29518707 } + +[[package]] +name = "overrides" +version = "7.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832 }, +] + +[[package]] +name = "ovito" +version = "3.10.6.post2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pyside6-essentials" }, + { name = "traits" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/a3/05f945e508837d2a2e1c8b931128f47a589b47b57d7f6ac308ce76d14b7a/ovito-3.10.6.post2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:be7ba8bf3de19473b8412d7b0e2717e26c156367656f570baa3be73e9e1530aa", size = 53061294 }, + { url = "https://files.pythonhosted.org/packages/50/29/c3c7f3e7793a6db5c1eaba658b8588b1b8ec1d9c0b39332d02e0941fe8bb/ovito-3.10.6.post2-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:e3d330f8573ee7071103ae18e342a936b61504c739e97a4b7d28923ee27c4167", size = 42430576 }, + { url = "https://files.pythonhosted.org/packages/73/2f/58896b2f08855d1e11c929e461675395d5baf984fd8e83b45f005939daf9/ovito-3.10.6.post2-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:075543430201a761cf3b35297b5338b864b624918c3ff49d6495edec666ec4c9", size = 78693520 }, + { url = "https://files.pythonhosted.org/packages/92/57/e3901ea5039f5bf8cff6a059c49561ee2dc567ac2964c25cb078e0a9f3e5/ovito-3.10.6.post2-cp310-cp310-win_amd64.whl", hash = "sha256:ef1a3c2ccd1ef5bf0b9f5dae500b46c481f538d78471a7baae0504a4579ba427", size = 65634332 }, + { url = "https://files.pythonhosted.org/packages/32/07/a5887d2090b4e9647d16e086bbfe41011afca4c38678f5fed65ddcfd23e8/ovito-3.10.6.post2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:fba273bf30d14f8f23ba32357c8ae541eb2915692cdc0dd9c8c52ddae586666e", size = 53062659 }, + { url = "https://files.pythonhosted.org/packages/a5/4f/63696bc514cf6000cff9f15bc31207da04dbd8740bd0e05f56247884b1ef/ovito-3.10.6.post2-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:d10b323917be2a173e26221ddd07bc8037c0d9df575191e95efa3899736af06c", size = 42432207 }, + { url = "https://files.pythonhosted.org/packages/c9/0f/587a6d58a21a0112a736e98174a752fe802a1fdca6c1e5bba83dd5b7cfc7/ovito-3.10.6.post2-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:dc2bb6d3b44040c224d192dd1c240e9f379c1ddb90f66ed60131caee9a18692a", size = 78697997 }, + { url = "https://files.pythonhosted.org/packages/f3/46/ba344def7b595dc9e7b19b8df9674dc7353f14bc95bb68c4aaf09cee9d32/ovito-3.10.6.post2-cp311-cp311-win_amd64.whl", hash = "sha256:11bfa6cf70c0b51ccb672c642d69e7d45971fc2d03b21739d67bf6b5f2308b37", size = 65635395 }, + { url = "https://files.pythonhosted.org/packages/38/38/e453ac3ce67178528c72220abe42b9ae57c75a846dd364198d49d99245e5/ovito-3.10.6.post2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:9033d7ce3d5e402c15a9a502cb1e486690043130706d23aa8c3feac66ad44343", size = 53102561 }, + { url = "https://files.pythonhosted.org/packages/39/88/c18c7fa0e29a2bd65117a9a9730b4917fae303dce03725b7448b41d7a344/ovito-3.10.6.post2-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:6faa2df509b04c1d912220289be05088edaaf5a9e8a78c4719620e9ecd43b69e", size = 42452425 }, + { url = "https://files.pythonhosted.org/packages/60/ab/effc7dae741d4435f42f5f6c88d28d2a4abb8f4e2b5188d7d7523844ddd9/ovito-3.10.6.post2-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:cf48ee6fe13e28581d0aa104f37eb8e1749863dbd9a7d8457628224636e399f1", size = 78666198 }, + { url = "https://files.pythonhosted.org/packages/3e/6b/166ab0e25b5b6c3d0a128c2a8ca3179108e2f426bc1e6e92f3551d8caa3c/ovito-3.10.6.post2-cp312-cp312-win_amd64.whl", hash = "sha256:aab8abaccce51d946af62f069ef8c0ecda34c558e96b99a2306017a3a1bd333d", size = 65636801 }, +] + +[[package]] +name = "packaging" +version = "24.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, +] + +[[package]] +name = "palettable" +version = "3.3.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/3d/a5854d60850485bff12f28abfe0e17f503e866763bed61aed4990b604530/palettable-3.3.3.tar.gz", hash = "sha256:094dd7d9a5fc1cca4854773e5c1fc6a315b33bd5b3a8f47064928facaf0490a8", size = 106639 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/f7/3367feadd4ab56783b0971c9b7edfbdd68e0c70ce877949a5dd2117ed4a0/palettable-3.3.3-py2.py3-none-any.whl", hash = "sha256:74e9e7d7fe5a9be065e02397558ed1777b2df0b793a6f4ce1a5ee74f74fb0caa", size = 332251 }, +] + +[[package]] +name = "pandas" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/70/c853aec59839bceed032d52010ff5f1b8d87dc3114b762e4ba2727661a3b/pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5", size = 12580827 }, + { url = "https://files.pythonhosted.org/packages/99/f2/c4527768739ffa4469b2b4fff05aa3768a478aed89a2f271a79a40eee984/pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348", size = 11303897 }, + { url = "https://files.pythonhosted.org/packages/ed/12/86c1747ea27989d7a4064f806ce2bae2c6d575b950be087837bdfcabacc9/pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed", size = 66480908 }, + { url = "https://files.pythonhosted.org/packages/44/50/7db2cd5e6373ae796f0ddad3675268c8d59fb6076e66f0c339d61cea886b/pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57", size = 13064210 }, + { url = "https://files.pythonhosted.org/packages/61/61/a89015a6d5536cb0d6c3ba02cebed51a95538cf83472975275e28ebf7d0c/pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42", size = 16754292 }, + { url = "https://files.pythonhosted.org/packages/ce/0d/4cc7b69ce37fac07645a94e1d4b0880b15999494372c1523508511b09e40/pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f", size = 14416379 }, + { url = "https://files.pythonhosted.org/packages/31/9e/6ebb433de864a6cd45716af52a4d7a8c3c9aaf3a98368e61db9e69e69a9c/pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645", size = 11598471 }, + { url = "https://files.pythonhosted.org/packages/a8/44/d9502bf0ed197ba9bf1103c9867d5904ddcaf869e52329787fc54ed70cc8/pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", size = 12602222 }, + { url = "https://files.pythonhosted.org/packages/52/11/9eac327a38834f162b8250aab32a6781339c69afe7574368fffe46387edf/pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", size = 11321274 }, + { url = "https://files.pythonhosted.org/packages/45/fb/c4beeb084718598ba19aa9f5abbc8aed8b42f90930da861fcb1acdb54c3a/pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", size = 15579836 }, + { url = "https://files.pythonhosted.org/packages/cd/5f/4dba1d39bb9c38d574a9a22548c540177f78ea47b32f99c0ff2ec499fac5/pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", size = 13058505 }, + { url = "https://files.pythonhosted.org/packages/b9/57/708135b90391995361636634df1f1130d03ba456e95bcf576fada459115a/pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", size = 16744420 }, + { url = "https://files.pythonhosted.org/packages/86/4a/03ed6b7ee323cf30404265c284cee9c65c56a212e0a08d9ee06984ba2240/pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", size = 14440457 }, + { url = "https://files.pythonhosted.org/packages/ed/8c/87ddf1fcb55d11f9f847e3c69bb1c6f8e46e2f40ab1a2d2abadb2401b007/pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", size = 11617166 }, + { url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893 }, + { url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475 }, + { url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645 }, + { url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445 }, + { url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235 }, + { url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756 }, + { url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248 }, + { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643 }, + { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573 }, + { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085 }, + { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809 }, + { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316 }, + { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055 }, + { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175 }, + { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650 }, + { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177 }, + { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526 }, + { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013 }, + { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620 }, + { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 }, +] + +[[package]] +name = "pandocfilters" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663 }, +] + +[[package]] +name = "parso" +version = "0.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, +] + +[[package]] +name = "pillow" +version = "11.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/26/0d95c04c868f6bdb0c447e3ee2de5564411845e36a858cfd63766bc7b563/pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739", size = 46737780 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/fb/a6ce6836bd7fd93fbf9144bf54789e02babc27403b50a9e1583ee877d6da/pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947", size = 3154708 }, + { url = "https://files.pythonhosted.org/packages/6a/1d/1f51e6e912d8ff316bb3935a8cda617c801783e0b998bf7a894e91d3bd4c/pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba", size = 2979223 }, + { url = "https://files.pythonhosted.org/packages/90/83/e2077b0192ca8a9ef794dbb74700c7e48384706467067976c2a95a0f40a1/pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086", size = 4183167 }, + { url = "https://files.pythonhosted.org/packages/0e/74/467af0146970a98349cdf39e9b79a6cc8a2e7558f2c01c28a7b6b85c5bda/pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9", size = 4283912 }, + { url = "https://files.pythonhosted.org/packages/85/b1/d95d4f7ca3a6c1ae120959605875a31a3c209c4e50f0029dc1a87566cf46/pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488", size = 4195815 }, + { url = "https://files.pythonhosted.org/packages/41/c3/94f33af0762ed76b5a237c5797e088aa57f2b7fa8ee7932d399087be66a8/pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f", size = 4366117 }, + { url = "https://files.pythonhosted.org/packages/ba/3c/443e7ef01f597497268899e1cca95c0de947c9bbf77a8f18b3c126681e5d/pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb", size = 4278607 }, + { url = "https://files.pythonhosted.org/packages/26/95/1495304448b0081e60c0c5d63f928ef48bb290acee7385804426fa395a21/pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97", size = 4410685 }, + { url = "https://files.pythonhosted.org/packages/45/da/861e1df971ef0de9870720cb309ca4d553b26a9483ec9be3a7bf1de4a095/pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50", size = 2249185 }, + { url = "https://files.pythonhosted.org/packages/d5/4e/78f7c5202ea2a772a5ab05069c1b82503e6353cd79c7e474d4945f4b82c3/pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c", size = 2566726 }, + { url = "https://files.pythonhosted.org/packages/77/e4/6e84eada35cbcc646fc1870f72ccfd4afacb0fae0c37ffbffe7f5dc24bf1/pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1", size = 2254585 }, + { url = "https://files.pythonhosted.org/packages/f0/eb/f7e21b113dd48a9c97d364e0915b3988c6a0b6207652f5a92372871b7aa4/pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc", size = 3154705 }, + { url = "https://files.pythonhosted.org/packages/25/b3/2b54a1d541accebe6bd8b1358b34ceb2c509f51cb7dcda8687362490da5b/pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a", size = 2979222 }, + { url = "https://files.pythonhosted.org/packages/20/12/1a41eddad8265c5c19dda8fb6c269ce15ee25e0b9f8f26286e6202df6693/pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3", size = 4190220 }, + { url = "https://files.pythonhosted.org/packages/a9/9b/8a8c4d07d77447b7457164b861d18f5a31ae6418ef5c07f6f878fa09039a/pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5", size = 4291399 }, + { url = "https://files.pythonhosted.org/packages/fc/e4/130c5fab4a54d3991129800dd2801feeb4b118d7630148cd67f0e6269d4c/pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b", size = 4202709 }, + { url = "https://files.pythonhosted.org/packages/39/63/b3fc299528d7df1f678b0666002b37affe6b8751225c3d9c12cf530e73ed/pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa", size = 4372556 }, + { url = "https://files.pythonhosted.org/packages/c6/a6/694122c55b855b586c26c694937d36bb8d3b09c735ff41b2f315c6e66a10/pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306", size = 4287187 }, + { url = "https://files.pythonhosted.org/packages/ba/a9/f9d763e2671a8acd53d29b1e284ca298bc10a595527f6be30233cdb9659d/pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9", size = 4418468 }, + { url = "https://files.pythonhosted.org/packages/6e/0e/b5cbad2621377f11313a94aeb44ca55a9639adabcaaa073597a1925f8c26/pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5", size = 2249249 }, + { url = "https://files.pythonhosted.org/packages/dc/83/1470c220a4ff06cd75fc609068f6605e567ea51df70557555c2ab6516b2c/pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291", size = 2566769 }, + { url = "https://files.pythonhosted.org/packages/52/98/def78c3a23acee2bcdb2e52005fb2810ed54305602ec1bfcfab2bda6f49f/pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9", size = 2254611 }, + { url = "https://files.pythonhosted.org/packages/1c/a3/26e606ff0b2daaf120543e537311fa3ae2eb6bf061490e4fea51771540be/pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923", size = 3147642 }, + { url = "https://files.pythonhosted.org/packages/4f/d5/1caabedd8863526a6cfa44ee7a833bd97f945dc1d56824d6d76e11731939/pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903", size = 2978999 }, + { url = "https://files.pythonhosted.org/packages/d9/ff/5a45000826a1aa1ac6874b3ec5a856474821a1b59d838c4f6ce2ee518fe9/pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4", size = 4196794 }, + { url = "https://files.pythonhosted.org/packages/9d/21/84c9f287d17180f26263b5f5c8fb201de0f88b1afddf8a2597a5c9fe787f/pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f", size = 4300762 }, + { url = "https://files.pythonhosted.org/packages/84/39/63fb87cd07cc541438b448b1fed467c4d687ad18aa786a7f8e67b255d1aa/pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9", size = 4210468 }, + { url = "https://files.pythonhosted.org/packages/7f/42/6e0f2c2d5c60f499aa29be14f860dd4539de322cd8fb84ee01553493fb4d/pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7", size = 4381824 }, + { url = "https://files.pythonhosted.org/packages/31/69/1ef0fb9d2f8d2d114db982b78ca4eeb9db9a29f7477821e160b8c1253f67/pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6", size = 4296436 }, + { url = "https://files.pythonhosted.org/packages/44/ea/dad2818c675c44f6012289a7c4f46068c548768bc6c7f4e8c4ae5bbbc811/pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc", size = 4429714 }, + { url = "https://files.pythonhosted.org/packages/af/3a/da80224a6eb15bba7a0dcb2346e2b686bb9bf98378c0b4353cd88e62b171/pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6", size = 2249631 }, + { url = "https://files.pythonhosted.org/packages/57/97/73f756c338c1d86bb802ee88c3cab015ad7ce4b838f8a24f16b676b1ac7c/pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47", size = 2567533 }, + { url = "https://files.pythonhosted.org/packages/0b/30/2b61876e2722374558b871dfbfcbe4e406626d63f4f6ed92e9c8e24cac37/pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25", size = 2254890 }, + { url = "https://files.pythonhosted.org/packages/63/24/e2e15e392d00fcf4215907465d8ec2a2f23bcec1481a8ebe4ae760459995/pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699", size = 3147300 }, + { url = "https://files.pythonhosted.org/packages/43/72/92ad4afaa2afc233dc44184adff289c2e77e8cd916b3ddb72ac69495bda3/pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38", size = 2978742 }, + { url = "https://files.pythonhosted.org/packages/9e/da/c8d69c5bc85d72a8523fe862f05ababdc52c0a755cfe3d362656bb86552b/pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2", size = 4194349 }, + { url = "https://files.pythonhosted.org/packages/cd/e8/686d0caeed6b998351d57796496a70185376ed9c8ec7d99e1d19ad591fc6/pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2", size = 4298714 }, + { url = "https://files.pythonhosted.org/packages/ec/da/430015cec620d622f06854be67fd2f6721f52fc17fca8ac34b32e2d60739/pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527", size = 4208514 }, + { url = "https://files.pythonhosted.org/packages/44/ae/7e4f6662a9b1cb5f92b9cc9cab8321c381ffbee309210940e57432a4063a/pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa", size = 4380055 }, + { url = "https://files.pythonhosted.org/packages/74/d5/1a807779ac8a0eeed57f2b92a3c32ea1b696e6140c15bd42eaf908a261cd/pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f", size = 4296751 }, + { url = "https://files.pythonhosted.org/packages/38/8c/5fa3385163ee7080bc13026d59656267daaaaf3c728c233d530e2c2757c8/pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb", size = 4430378 }, + { url = "https://files.pythonhosted.org/packages/ca/1d/ad9c14811133977ff87035bf426875b93097fb50af747793f013979facdb/pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798", size = 2249588 }, + { url = "https://files.pythonhosted.org/packages/fb/01/3755ba287dac715e6afdb333cb1f6d69740a7475220b4637b5ce3d78cec2/pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de", size = 2567509 }, + { url = "https://files.pythonhosted.org/packages/c0/98/2c7d727079b6be1aba82d195767d35fcc2d32204c7a5820f822df5330152/pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84", size = 2254791 }, + { url = "https://files.pythonhosted.org/packages/eb/38/998b04cc6f474e78b563716b20eecf42a2fa16a84589d23c8898e64b0ffd/pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b", size = 3150854 }, + { url = "https://files.pythonhosted.org/packages/13/8e/be23a96292113c6cb26b2aa3c8b3681ec62b44ed5c2bd0b258bd59503d3c/pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003", size = 2982369 }, + { url = "https://files.pythonhosted.org/packages/97/8a/3db4eaabb7a2ae8203cd3a332a005e4aba00067fc514aaaf3e9721be31f1/pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2", size = 4333703 }, + { url = "https://files.pythonhosted.org/packages/28/ac/629ffc84ff67b9228fe87a97272ab125bbd4dc462745f35f192d37b822f1/pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a", size = 4412550 }, + { url = "https://files.pythonhosted.org/packages/d6/07/a505921d36bb2df6868806eaf56ef58699c16c388e378b0dcdb6e5b2fb36/pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8", size = 4461038 }, + { url = "https://files.pythonhosted.org/packages/d6/b9/fb620dd47fc7cc9678af8f8bd8c772034ca4977237049287e99dda360b66/pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8", size = 2253197 }, + { url = "https://files.pythonhosted.org/packages/df/86/25dde85c06c89d7fc5db17940f07aae0a56ac69aa9ccb5eb0f09798862a8/pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904", size = 2572169 }, + { url = "https://files.pythonhosted.org/packages/51/85/9c33f2517add612e17f3381aee7c4072779130c634921a756c97bc29fb49/pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3", size = 2256828 }, + { url = "https://files.pythonhosted.org/packages/36/57/42a4dd825eab762ba9e690d696d894ba366e06791936056e26e099398cda/pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2", size = 3119239 }, + { url = "https://files.pythonhosted.org/packages/98/f7/25f9f9e368226a1d6cf3507081a1a7944eddd3ca7821023377043f5a83c8/pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2", size = 2950803 }, + { url = "https://files.pythonhosted.org/packages/59/01/98ead48a6c2e31e6185d4c16c978a67fe3ccb5da5c2ff2ba8475379bb693/pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b", size = 3281098 }, + { url = "https://files.pythonhosted.org/packages/51/c0/570255b2866a0e4d500a14f950803a2ec273bac7badc43320120b9262450/pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2", size = 3323665 }, + { url = "https://files.pythonhosted.org/packages/0e/75/689b4ec0483c42bfc7d1aacd32ade7a226db4f4fac57c6fdcdf90c0731e3/pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830", size = 3310533 }, + { url = "https://files.pythonhosted.org/packages/3d/30/38bd6149cf53da1db4bad304c543ade775d225961c4310f30425995cb9ec/pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734", size = 3414886 }, + { url = "https://files.pythonhosted.org/packages/ec/3d/c32a51d848401bd94cabb8767a39621496491ee7cd5199856b77da9b18ad/pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316", size = 2567508 }, +] + +[[package]] +name = "pip" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/87/fb90046e096a03aeab235e139436b3fe804cdd447ed2093b0d70eba3f7f8/pip-24.2.tar.gz", hash = "sha256:5b5e490b5e9cb275c879595064adce9ebd31b854e3e803740b72f9ccf34a45b8", size = 1922041 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/55/90db48d85f7689ec6f81c0db0622d704306c5284850383c090e6c7195a5c/pip-24.2-py3-none-any.whl", hash = "sha256:2cd581cf58ab7fcfca4ce8efa6dcacd0de5bf8d0a3eb9ec927e07405f4d9e2a2", size = 1815170 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + +[[package]] +name = "plotly" +version = "5.24.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "tenacity" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/4f/428f6d959818d7425a94c190a6b26fbc58035cbef40bf249be0b62a9aedd/plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae", size = 9479398 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/ae/580600f441f6fc05218bd6c9d5794f4aef072a7d9093b291f1c50a9db8bc/plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089", size = 19054220 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pockets" +version = "0.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/8e/0601097cfcce2e8c2297db5080e9719f549c2bd4b94420ddc8d3f848bbca/pockets-0.9.1.tar.gz", hash = "sha256:9320f1a3c6f7a9133fe3b571f283bcf3353cd70249025ae8d618e40e9f7e92b3", size = 24993 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/2f/a4583c70fbd8cd04910e2884bcc2bdd670e884061f7b4d70bc13e632a993/pockets-0.9.1-py2.py3-none-any.whl", hash = "sha256:68597934193c08a08eb2bf6a1d85593f627c22f9b065cc727a4f03f669d96d86", size = 26263 }, +] + +[[package]] +name = "prettytable" +version = "3.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/57/0a642bec16d5736b9baaac7e830bedccd10341dc2858075c34d5aec5c8b6/prettytable-3.11.0.tar.gz", hash = "sha256:7e23ca1e68bbfd06ba8de98bf553bf3493264c96d5e8a615c0471025deeba722", size = 57527 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/5a/bfdc26c0e19156992b1dc9de47f0b2e8992fe43db9981d814f860bdce2b3/prettytable-3.11.0-py3-none-any.whl", hash = "sha256:aa17083feb6c71da11a68b2c213b04675c4af4ce9c541762632ca3f2cb3546dd", size = 28734 }, +] + +[[package]] +name = "prometheus-client" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/54/a369868ed7a7f1ea5163030f4fc07d85d22d7a1d270560dab675188fb612/prometheus_client-0.21.0.tar.gz", hash = "sha256:96c83c606b71ff2b0a433c98889d275f51ffec6c5e267de37c7a2b5c9aa9233e", size = 78634 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/2d/46ed6436849c2c88228c3111865f44311cff784b4aabcdef4ea2545dbc3d/prometheus_client-0.21.0-py3-none-any.whl", hash = "sha256:4fa6b4dd0ac16d58bb587c04b1caae65b8c5043e85f778f42f5f632f6af2e166", size = 54686 }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.48" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/4f/feb5e137aff82f7c7f3248267b97451da3644f6cdc218edfe549fb354127/prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90", size = 424684 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/6a/fd08d94654f7e67c52ca30523a178b3f8ccc4237fce4be90d39c938a831a/prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e", size = 386595 }, +] + +[[package]] +name = "propcache" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/4d/5e5a60b78dbc1d464f8a7bbaeb30957257afdc8512cbb9dfd5659304f5cd/propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70", size = 40951 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/08/1963dfb932b8d74d5b09098507b37e9b96c835ba89ab8aad35aa330f4ff3/propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58", size = 80712 }, + { url = "https://files.pythonhosted.org/packages/e6/59/49072aba9bf8a8ed958e576182d46f038e595b17ff7408bc7e8807e721e1/propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b", size = 46301 }, + { url = "https://files.pythonhosted.org/packages/33/a2/6b1978c2e0d80a678e2c483f45e5443c15fe5d32c483902e92a073314ef1/propcache-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110", size = 45581 }, + { url = "https://files.pythonhosted.org/packages/43/95/55acc9adff8f997c7572f23d41993042290dfb29e404cdadb07039a4386f/propcache-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2", size = 208659 }, + { url = "https://files.pythonhosted.org/packages/bd/2c/ef7371ff715e6cd19ea03fdd5637ecefbaa0752fee5b0f2fe8ea8407ee01/propcache-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a", size = 222613 }, + { url = "https://files.pythonhosted.org/packages/5e/1c/fef251f79fd4971a413fa4b1ae369ee07727b4cc2c71e2d90dfcde664fbb/propcache-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577", size = 221067 }, + { url = "https://files.pythonhosted.org/packages/8d/e7/22e76ae6fc5a1708bdce92bdb49de5ebe89a173db87e4ef597d6bbe9145a/propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850", size = 208920 }, + { url = "https://files.pythonhosted.org/packages/04/3e/f10aa562781bcd8a1e0b37683a23bef32bdbe501d9cc7e76969becaac30d/propcache-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61", size = 200050 }, + { url = "https://files.pythonhosted.org/packages/d0/98/8ac69f638358c5f2a0043809c917802f96f86026e86726b65006830f3dc6/propcache-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37", size = 202346 }, + { url = "https://files.pythonhosted.org/packages/ee/78/4acfc5544a5075d8e660af4d4e468d60c418bba93203d1363848444511ad/propcache-0.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48", size = 199750 }, + { url = "https://files.pythonhosted.org/packages/a2/8f/90ada38448ca2e9cf25adc2fe05d08358bda1b9446f54a606ea38f41798b/propcache-0.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630", size = 201279 }, + { url = "https://files.pythonhosted.org/packages/08/31/0e299f650f73903da851f50f576ef09bfffc8e1519e6a2f1e5ed2d19c591/propcache-0.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394", size = 211035 }, + { url = "https://files.pythonhosted.org/packages/85/3e/e356cc6b09064bff1c06d0b2413593e7c925726f0139bc7acef8a21e87a8/propcache-0.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b", size = 215565 }, + { url = "https://files.pythonhosted.org/packages/8b/54/4ef7236cd657e53098bd05aa59cbc3cbf7018fba37b40eaed112c3921e51/propcache-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336", size = 207604 }, + { url = "https://files.pythonhosted.org/packages/1f/27/d01d7799c068443ee64002f0655d82fb067496897bf74b632e28ee6a32cf/propcache-0.2.0-cp310-cp310-win32.whl", hash = "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad", size = 40526 }, + { url = "https://files.pythonhosted.org/packages/bb/44/6c2add5eeafb7f31ff0d25fbc005d930bea040a1364cf0f5768750ddf4d1/propcache-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99", size = 44958 }, + { url = "https://files.pythonhosted.org/packages/e0/1c/71eec730e12aec6511e702ad0cd73c2872eccb7cad39de8ba3ba9de693ef/propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354", size = 80811 }, + { url = "https://files.pythonhosted.org/packages/89/c3/7e94009f9a4934c48a371632197406a8860b9f08e3f7f7d922ab69e57a41/propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de", size = 46365 }, + { url = "https://files.pythonhosted.org/packages/c0/1d/c700d16d1d6903aeab28372fe9999762f074b80b96a0ccc953175b858743/propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87", size = 45602 }, + { url = "https://files.pythonhosted.org/packages/2e/5e/4a3e96380805bf742712e39a4534689f4cddf5fa2d3a93f22e9fd8001b23/propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016", size = 236161 }, + { url = "https://files.pythonhosted.org/packages/a5/85/90132481183d1436dff6e29f4fa81b891afb6cb89a7306f32ac500a25932/propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb", size = 244938 }, + { url = "https://files.pythonhosted.org/packages/4a/89/c893533cb45c79c970834274e2d0f6d64383ec740be631b6a0a1d2b4ddc0/propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2", size = 243576 }, + { url = "https://files.pythonhosted.org/packages/8c/56/98c2054c8526331a05f205bf45cbb2cda4e58e56df70e76d6a509e5d6ec6/propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4", size = 236011 }, + { url = "https://files.pythonhosted.org/packages/2d/0c/8b8b9f8a6e1abd869c0fa79b907228e7abb966919047d294ef5df0d136cf/propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504", size = 224834 }, + { url = "https://files.pythonhosted.org/packages/18/bb/397d05a7298b7711b90e13108db697732325cafdcd8484c894885c1bf109/propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178", size = 224946 }, + { url = "https://files.pythonhosted.org/packages/25/19/4fc08dac19297ac58135c03770b42377be211622fd0147f015f78d47cd31/propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d", size = 217280 }, + { url = "https://files.pythonhosted.org/packages/7e/76/c79276a43df2096ce2aba07ce47576832b1174c0c480fe6b04bd70120e59/propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2", size = 220088 }, + { url = "https://files.pythonhosted.org/packages/c3/9a/8a8cf428a91b1336b883f09c8b884e1734c87f724d74b917129a24fe2093/propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db", size = 233008 }, + { url = "https://files.pythonhosted.org/packages/25/7b/768a8969abd447d5f0f3333df85c6a5d94982a1bc9a89c53c154bf7a8b11/propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b", size = 237719 }, + { url = "https://files.pythonhosted.org/packages/ed/0d/e5d68ccc7976ef8b57d80613ac07bbaf0614d43f4750cf953f0168ef114f/propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b", size = 227729 }, + { url = "https://files.pythonhosted.org/packages/05/64/17eb2796e2d1c3d0c431dc5f40078d7282f4645af0bb4da9097fbb628c6c/propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1", size = 40473 }, + { url = "https://files.pythonhosted.org/packages/83/c5/e89fc428ccdc897ade08cd7605f174c69390147526627a7650fb883e0cd0/propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71", size = 44921 }, + { url = "https://files.pythonhosted.org/packages/7c/46/a41ca1097769fc548fc9216ec4c1471b772cc39720eb47ed7e38ef0006a9/propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2", size = 80800 }, + { url = "https://files.pythonhosted.org/packages/75/4f/93df46aab9cc473498ff56be39b5f6ee1e33529223d7a4d8c0a6101a9ba2/propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7", size = 46443 }, + { url = "https://files.pythonhosted.org/packages/0b/17/308acc6aee65d0f9a8375e36c4807ac6605d1f38074b1581bd4042b9fb37/propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8", size = 45676 }, + { url = "https://files.pythonhosted.org/packages/65/44/626599d2854d6c1d4530b9a05e7ff2ee22b790358334b475ed7c89f7d625/propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793", size = 246191 }, + { url = "https://files.pythonhosted.org/packages/f2/df/5d996d7cb18df076debae7d76ac3da085c0575a9f2be6b1f707fe227b54c/propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09", size = 251791 }, + { url = "https://files.pythonhosted.org/packages/2e/6d/9f91e5dde8b1f662f6dd4dff36098ed22a1ef4e08e1316f05f4758f1576c/propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89", size = 253434 }, + { url = "https://files.pythonhosted.org/packages/3c/e9/1b54b7e26f50b3e0497cd13d3483d781d284452c2c50dd2a615a92a087a3/propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e", size = 248150 }, + { url = "https://files.pythonhosted.org/packages/a7/ef/a35bf191c8038fe3ce9a414b907371c81d102384eda5dbafe6f4dce0cf9b/propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9", size = 233568 }, + { url = "https://files.pythonhosted.org/packages/97/d9/d00bb9277a9165a5e6d60f2142cd1a38a750045c9c12e47ae087f686d781/propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4", size = 229874 }, + { url = "https://files.pythonhosted.org/packages/8e/78/c123cf22469bdc4b18efb78893e69c70a8b16de88e6160b69ca6bdd88b5d/propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c", size = 225857 }, + { url = "https://files.pythonhosted.org/packages/31/1b/fd6b2f1f36d028820d35475be78859d8c89c8f091ad30e377ac49fd66359/propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887", size = 227604 }, + { url = "https://files.pythonhosted.org/packages/99/36/b07be976edf77a07233ba712e53262937625af02154353171716894a86a6/propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57", size = 238430 }, + { url = "https://files.pythonhosted.org/packages/0d/64/5822f496c9010e3966e934a011ac08cac8734561842bc7c1f65586e0683c/propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23", size = 244814 }, + { url = "https://files.pythonhosted.org/packages/fd/bd/8657918a35d50b18a9e4d78a5df7b6c82a637a311ab20851eef4326305c1/propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348", size = 235922 }, + { url = "https://files.pythonhosted.org/packages/a8/6f/ec0095e1647b4727db945213a9f395b1103c442ef65e54c62e92a72a3f75/propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5", size = 40177 }, + { url = "https://files.pythonhosted.org/packages/20/a2/bd0896fdc4f4c1db46d9bc361c8c79a9bf08ccc08ba054a98e38e7ba1557/propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3", size = 44446 }, + { url = "https://files.pythonhosted.org/packages/a8/a7/5f37b69197d4f558bfef5b4bceaff7c43cc9b51adf5bd75e9081d7ea80e4/propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7", size = 78120 }, + { url = "https://files.pythonhosted.org/packages/c8/cd/48ab2b30a6b353ecb95a244915f85756d74f815862eb2ecc7a518d565b48/propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763", size = 45127 }, + { url = "https://files.pythonhosted.org/packages/a5/ba/0a1ef94a3412aab057bd996ed5f0ac7458be5bf469e85c70fa9ceb43290b/propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d", size = 44419 }, + { url = "https://files.pythonhosted.org/packages/b4/6c/ca70bee4f22fa99eacd04f4d2f1699be9d13538ccf22b3169a61c60a27fa/propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a", size = 229611 }, + { url = "https://files.pythonhosted.org/packages/19/70/47b872a263e8511ca33718d96a10c17d3c853aefadeb86dc26e8421184b9/propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b", size = 234005 }, + { url = "https://files.pythonhosted.org/packages/4f/be/3b0ab8c84a22e4a3224719099c1229ddfdd8a6a1558cf75cb55ee1e35c25/propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb", size = 237270 }, + { url = "https://files.pythonhosted.org/packages/04/d8/f071bb000d4b8f851d312c3c75701e586b3f643fe14a2e3409b1b9ab3936/propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf", size = 231877 }, + { url = "https://files.pythonhosted.org/packages/93/e7/57a035a1359e542bbb0a7df95aad6b9871ebee6dce2840cb157a415bd1f3/propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2", size = 217848 }, + { url = "https://files.pythonhosted.org/packages/f0/93/d1dea40f112ec183398fb6c42fde340edd7bab202411c4aa1a8289f461b6/propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f", size = 216987 }, + { url = "https://files.pythonhosted.org/packages/62/4c/877340871251145d3522c2b5d25c16a1690ad655fbab7bb9ece6b117e39f/propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136", size = 212451 }, + { url = "https://files.pythonhosted.org/packages/7c/bb/a91b72efeeb42906ef58ccf0cdb87947b54d7475fee3c93425d732f16a61/propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325", size = 212879 }, + { url = "https://files.pythonhosted.org/packages/9b/7f/ee7fea8faac57b3ec5d91ff47470c6c5d40d7f15d0b1fccac806348fa59e/propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44", size = 222288 }, + { url = "https://files.pythonhosted.org/packages/ff/d7/acd67901c43d2e6b20a7a973d9d5fd543c6e277af29b1eb0e1f7bd7ca7d2/propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83", size = 228257 }, + { url = "https://files.pythonhosted.org/packages/8d/6f/6272ecc7a8daad1d0754cfc6c8846076a8cb13f810005c79b15ce0ef0cf2/propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544", size = 221075 }, + { url = "https://files.pythonhosted.org/packages/7c/bd/c7a6a719a6b3dd8b3aeadb3675b5783983529e4a3185946aa444d3e078f6/propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032", size = 39654 }, + { url = "https://files.pythonhosted.org/packages/88/e7/0eef39eff84fa3e001b44de0bd41c7c0e3432e7648ffd3d64955910f002d/propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e", size = 43705 }, + { url = "https://files.pythonhosted.org/packages/3d/b6/e6d98278f2d49b22b4d033c9f792eda783b9ab2094b041f013fc69bcde87/propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036", size = 11603 }, +] + +[[package]] +name = "protobuf" +version = "5.28.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/a4/4579a61de526e19005ceeb93e478b61d77aa38c8a85ad958ff16a9906549/protobuf-5.28.2.tar.gz", hash = "sha256:59379674ff119717404f7454647913787034f03fe7049cbef1d74a97bb4593f0", size = 422494 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/30/231764750e0987755b7b8d66771f161e5f002e165d27b72154c776dbabf7/protobuf-5.28.2-cp310-abi3-win32.whl", hash = "sha256:eeea10f3dc0ac7e6b4933d32db20662902b4ab81bf28df12218aa389e9c2102d", size = 419662 }, + { url = "https://files.pythonhosted.org/packages/7d/46/3fdf7462160135aee6a530f1ec66665b5b4132fa2e1002ab971bc6ec2589/protobuf-5.28.2-cp310-abi3-win_amd64.whl", hash = "sha256:2c69461a7fcc8e24be697624c09a839976d82ae75062b11a0972e41fd2cd9132", size = 431479 }, + { url = "https://files.pythonhosted.org/packages/37/45/d2a760580f8f2ed2825ba44cb370e0a4011ddef85e728f46ea3dd565a8a5/protobuf-5.28.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a8b9403fc70764b08d2f593ce44f1d2920c5077bf7d311fefec999f8c40f78b7", size = 414736 }, + { url = "https://files.pythonhosted.org/packages/e6/23/ed718dc18e6a561445ece1e7a17d2dda0c634ad9cf663102b47f10005d8f/protobuf-5.28.2-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:35cfcb15f213449af7ff6198d6eb5f739c37d7e4f1c09b5d0641babf2cc0c68f", size = 316518 }, + { url = "https://files.pythonhosted.org/packages/23/08/a1ce0415a115c2b703bfa798f06f0e43ca91dbe29d6180bf86a9287b15e2/protobuf-5.28.2-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:5e8a95246d581eef20471b5d5ba010d55f66740942b95ba9b872d918c459452f", size = 316605 }, + { url = "https://files.pythonhosted.org/packages/9b/55/f24e3b801d2e108c48aa2b1b59bb791b5cffba89465cbbf66fc98de89270/protobuf-5.28.2-py3-none-any.whl", hash = "sha256:52235802093bd8a2811abbe8bf0ab9c5f54cca0a751fdd3f6ac2a21438bffece", size = 169566 }, +] + +[[package]] +name = "psutil" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/10/2a30b13c61e7cf937f4adf90710776b7918ed0a9c434e2c38224732af310/psutil-6.1.0.tar.gz", hash = "sha256:353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a", size = 508565 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/2b/f4dea5d993d9cd22ad958eea828a41d5d225556123d372f02547c29c4f97/psutil-6.1.0-cp27-none-win32.whl", hash = "sha256:9118f27452b70bb1d9ab3198c1f626c2499384935aaf55388211ad982611407e", size = 246648 }, + { url = "https://files.pythonhosted.org/packages/9f/14/4aa97a7f2e0ac33a050d990ab31686d651ae4ef8c86661fef067f00437b9/psutil-6.1.0-cp27-none-win_amd64.whl", hash = "sha256:a8506f6119cff7015678e2bce904a4da21025cc70ad283a53b099e7620061d85", size = 249905 }, + { url = "https://files.pythonhosted.org/packages/01/9e/8be43078a171381953cfee33c07c0d628594b5dbfc5157847b85022c2c1b/psutil-6.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6e2dcd475ce8b80522e51d923d10c7871e45f20918e027ab682f94f1c6351688", size = 247762 }, + { url = "https://files.pythonhosted.org/packages/1d/cb/313e80644ea407f04f6602a9e23096540d9dc1878755f3952ea8d3d104be/psutil-6.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0895b8414afafc526712c498bd9de2b063deaac4021a3b3c34566283464aff8e", size = 248777 }, + { url = "https://files.pythonhosted.org/packages/65/8e/bcbe2025c587b5d703369b6a75b65d41d1367553da6e3f788aff91eaf5bd/psutil-6.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dcbfce5d89f1d1f2546a2090f4fcf87c7f669d1d90aacb7d7582addece9fb38", size = 284259 }, + { url = "https://files.pythonhosted.org/packages/58/4d/8245e6f76a93c98aab285a43ea71ff1b171bcd90c9d238bf81f7021fb233/psutil-6.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:498c6979f9c6637ebc3a73b3f87f9eb1ec24e1ce53a7c5173b8508981614a90b", size = 287255 }, + { url = "https://files.pythonhosted.org/packages/27/c2/d034856ac47e3b3cdfa9720d0e113902e615f4190d5d1bdb8df4b2015fb2/psutil-6.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d905186d647b16755a800e7263d43df08b790d709d575105d419f8b6ef65423a", size = 288804 }, + { url = "https://files.pythonhosted.org/packages/ea/55/5389ed243c878725feffc0d6a3bc5ef6764312b6fc7c081faaa2cfa7ef37/psutil-6.1.0-cp37-abi3-win32.whl", hash = "sha256:1ad45a1f5d0b608253b11508f80940985d1d0c8f6111b5cb637533a0e6ddc13e", size = 250386 }, + { url = "https://files.pythonhosted.org/packages/11/91/87fa6f060e649b1e1a7b19a4f5869709fbf750b7c8c262ee776ec32f3028/psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be", size = 254228 }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, +] + +[[package]] +name = "py" +version = "1.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/ff/fec109ceb715d2a6b4c4a85a61af3b40c723a961e8828319fbcb15b868dc/py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", size = 207796 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378", size = 98708 }, +] + +[[package]] +name = "pyaml" +version = "24.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/a6/5b51160ff7ce60b0c60ec825359c0e818b0ce4a2504fa3dd1470f42f9b10/pyaml-24.9.0.tar.gz", hash = "sha256:e78dee8b0d4fed56bb9fa11a8a7858e6fade1ec70a9a122cee6736efac3e69b5", size = 28111 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/1b/16b79549afd3dec6d764456c07b8581134c74008d87303ba23906380fa57/pyaml-24.9.0-py3-none-any.whl", hash = "sha256:31080551502f1014852b3c966a96c796adc79b4cf86e165f28ed83455bf19c62", size = 24965 }, +] + +[[package]] +name = "pyarrow" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/f8/38a8498b294a6d3c74cd81bb411c510d52dfdd40d082651ded761fa7a964/pyarrow-15.0.1.tar.gz", hash = "sha256:21d812548d39d490e0c6928a7c663f37b96bf764034123d4b4ab4530ecc757a9", size = 1063808 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/da/1825450e660dcee5564d6eb7ccc8f8a11e5d21a25247fcf02e110fea78c5/pyarrow-15.0.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:c2ddb3be5ea938c329a84171694fc230b241ce1b6b0ff1a0280509af51c375fa", size = 27148846 }, + { url = "https://files.pythonhosted.org/packages/71/79/d19aa34807ed6ef327789d74febc2b2509f3ca0243558942fe84c16dbf65/pyarrow-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7543ea88a0ff72f8e6baaf9bfdbec2c62aeabdbede9e4a571c71cc3bc43b6302", size = 24193995 }, + { url = "https://files.pythonhosted.org/packages/cf/a3/0f98201e092d2932e84e1859b4aa5f196fe13b6d0d4e0569ab6e55279061/pyarrow-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1519e218a6941fc074e4501088d891afcb2adf77c236e03c34babcf3d6a0d1c7", size = 36150049 }, + { url = "https://files.pythonhosted.org/packages/73/4b/c9707d1d6f006407f93cb950887b4bd023f73253e70f605ccca3625162a7/pyarrow-15.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28cafa86e1944761970d3b3fc0411b14ff9b5c2b73cd22aaf470d7a3976335f5", size = 38400376 }, + { url = "https://files.pythonhosted.org/packages/e0/03/828ff920ba4ad75a8af34264606cf80c12e6393c8cdc83846b1f5453542d/pyarrow-15.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:be5c3d463e33d03eab496e1af7916b1d44001c08f0f458ad27dc16093a020638", size = 35651995 }, + { url = "https://files.pythonhosted.org/packages/e1/41/b0a9bf304d47c18cb4f14cf2f5431eeb4e2259cb74f6e866904f4eb783a5/pyarrow-15.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:47b1eda15d3aa3f49a07b1808648e1397e5dc6a80a30bf87faa8e2d02dad7ac3", size = 38328781 }, + { url = "https://files.pythonhosted.org/packages/7c/7d/f206e282698525be086e860c905e67d08a80a9c10e0a88a9b560e5491a17/pyarrow-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e524a31be7db22deebbbcf242b189063ab9a7652c62471d296b31bc6e3cae77b", size = 24811454 }, + { url = "https://files.pythonhosted.org/packages/cf/57/ed666a9c007fa846072d7b8949d80df924f67340bebf047a0bd3e27ab945/pyarrow-15.0.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:a476fefe8bdd56122fb0d4881b785413e025858803cc1302d0d788d3522b374d", size = 27185542 }, + { url = "https://files.pythonhosted.org/packages/11/d4/39ea7badb6c68b5a6d4c58cb069dfbe151140edc96d1d037919ba09c6bb7/pyarrow-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:309e6191be385f2e220586bfdb643f9bb21d7e1bc6dd0a6963dc538e347b2431", size = 24214908 }, + { url = "https://files.pythonhosted.org/packages/5f/4c/4b0386dbeb576c98dc5a004ff16532388961828e68e86ccfa1087e2dc842/pyarrow-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83bc586903dbeb4365cbc72b602f99f70b96c5882e5dfac5278813c7d624ca3c", size = 36148711 }, + { url = "https://files.pythonhosted.org/packages/a7/d0/80e390b1785c02cb3effff61032595337575248f712033d4aa6f951c9502/pyarrow-15.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e652daac6d8b05280cd2af31c0fb61a4490ec6a53dc01588014d9fa3fdbee9", size = 38394611 }, + { url = "https://files.pythonhosted.org/packages/ef/7f/2f627de324b38944b14fcdf8de039c268b087c3916142a0bcc91eb39b55c/pyarrow-15.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:abad2e08652df153a72177ce20c897d083b0c4ebeec051239e2654ddf4d3c996", size = 35670335 }, + { url = "https://files.pythonhosted.org/packages/a9/e1/8b93203a6733611e7a8bc07bfd4f2f9ba1f741d3af236ead52f492d772df/pyarrow-15.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cde663352bc83ad75ba7b3206e049ca1a69809223942362a8649e37bd22f9e3b", size = 38344610 }, + { url = "https://files.pythonhosted.org/packages/ed/f3/d4a8d6d3af6b67f8724412da3f4e261671bb614ddc7a724bbc14e72022de/pyarrow-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:1b6e237dd7a08482a8b8f3f6512d258d2460f182931832a8c6ef3953203d31e1", size = 24800366 }, + { url = "https://files.pythonhosted.org/packages/1d/a1/c606d8178eb2b41cce189f4b179138afcec30075defb15e719435f19cb18/pyarrow-15.0.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:7bd167536ee23192760b8c731d39b7cfd37914c27fd4582335ffd08450ff799d", size = 27098961 }, + { url = "https://files.pythonhosted.org/packages/02/a0/874a1d7488f5a57cdf5d536bebcefb3fee9b39a09b1d74624f79959263e6/pyarrow-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7c08bb31eb2984ba5c3747d375bb522e7e536b8b25b149c9cb5e1c49b0ccb736", size = 24184324 }, + { url = "https://files.pythonhosted.org/packages/41/a7/8d48b53306a47121126c2cdb02d1604fb04b916bd844df5f3fc9414a83d1/pyarrow-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0f9c1d630ed2524bd1ddf28ec92780a7b599fd54704cd653519f7ff5aec177a", size = 36148063 }, + { url = "https://files.pythonhosted.org/packages/2c/54/745a20814d4c0a111f2f0a7990b804e1d05c8355c197b71932862838ab18/pyarrow-15.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5186048493395220550bca7b524420471aac2d77af831f584ce132680f55c3df", size = 38410602 }, + { url = "https://files.pythonhosted.org/packages/06/83/225d577116e8b4bcba1f14a74fdba94a2fc3d4a4a433225d60f586bb3e9d/pyarrow-15.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:31dc30c7ec8958da3a3d9f31d6c3630429b2091ede0ecd0d989fd6bec129f0e4", size = 35648453 }, + { url = "https://files.pythonhosted.org/packages/15/d6/0f9a3045b96eefa43e8fb979b5b5c187fb2127562a579eb5411bafb9097f/pyarrow-15.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3f111a014fb8ac2297b43a74bf4495cc479a332908f7ee49cb7cbd50714cb0c1", size = 38343322 }, + { url = "https://files.pythonhosted.org/packages/e6/3e/641cc5b2b9eb50cbb8712b6f6ca2cdc944934165fd939064b30efe3d762d/pyarrow-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:a6d1f7c15d7f68f08490d0cb34611497c74285b8a6bbeab4ef3fc20117310983", size = 25274084 }, +] + +[[package]] +name = "pyarrow-hotfix" +version = "0.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/0a/71da7b0db0c7078d4cf34ecf0c70ded5ed29decc06612097474e0114f4cc/pyarrow_hotfix-0.6.tar.gz", hash = "sha256:79d3e030f7ff890d408a100ac16d6f00b14d44a502d7897cd9fc3e3a534e9945", size = 9754 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/f4/9ec2222f5f5f8ea04f66f184caafd991a39c8782e31f5b0266f101cb68ca/pyarrow_hotfix-0.6-py3-none-any.whl", hash = "sha256:dcc9ae2d220dff0083be6a9aa8e0cdee5182ad358d4931fce825c545e5c89178", size = 7888 }, +] + +[[package]] +name = "pybind11" +version = "2.13.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/c1/72b9622fcb32ff98b054f724e213c7f70d6898baa714f4516288456ceaba/pybind11-2.13.6.tar.gz", hash = "sha256:ba6af10348c12b24e92fa086b39cfba0eff619b61ac77c406167d813b096d39a", size = 218403 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/2f/0f24b288e2ce56f51c920137620b4434a38fd80583dbbe24fc2a1656c388/pybind11-2.13.6-py3-none-any.whl", hash = "sha256:237c41e29157b962835d356b370ededd57594a26d5894a795960f0047cb5caf5", size = 243282 }, +] + +[[package]] +name = "pybtex" +version = "0.24.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "latexcodec" }, + { name = "pyyaml" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/9b/fd39836a6397fb363446d83075a7b9c2cc562f4c449292e039ed36084376/pybtex-0.24.0.tar.gz", hash = "sha256:818eae35b61733e5c007c3fcd2cfb75ed1bc8b4173c1f70b56cc4c0802d34755", size = 402879 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/5f/40d8e90f985a05133a8895fc454c6127ecec3de8b095dd35bba91382f803/pybtex-0.24.0-py2.py3-none-any.whl", hash = "sha256:e1e0c8c69998452fea90e9179aa2a98ab103f3eed894405b7264e517cc2fcc0f", size = 561354 }, +] + +[[package]] +name = "pycnite" +version = "2024.7.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/55/7f412c01675a8c77a9ace64a37d557faebe6740cf7fd619c9bda82b33341/pycnite-2024.7.31.tar.gz", hash = "sha256:5125f1c95aef4a23b9bec3b32fae76873dcd46324fa68e39c10fa852ecdea340", size = 24189 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/af/7ba371f966657f6e7b1c9876cae7e9f1c5d3635c3df1329636b99e615494/pycnite-2024.7.31-py3-none-any.whl", hash = "sha256:9ff9c09d35056435b867e14ebf79626ca94b6017923a0bf9935377fa90d4cbb3", size = 22939 }, +] + +[[package]] +name = "pycodestyle" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/08/dc/b29daf0a202b03f57c19e7295b60d1d5e1281c45a6f5f573e41830819918/pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f", size = 102299 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/94/bc43a2efb7b8615e38acde2b6624cae8c9ec86faf718ff5676c5179a7714/pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20", size = 42112 }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + +[[package]] +name = "pydocstyle" +version = "6.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "snowballstemmer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/5c/d5385ca59fd065e3c6a5fe19f9bc9d5ea7f2509fa8c9c22fb6b2031dd953/pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1", size = 36796 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/ea/99ddefac41971acad68f14114f38261c1f27dac0b3ec529824ebc739bdaa/pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019", size = 38038 }, +] + +[[package]] +name = "pydot" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/10/4e4da8c271540dc35914e927546cbb821397f0f9477f4079cd8732946699/pydot-3.0.2.tar.gz", hash = "sha256:9180da540b51b3aa09fbf81140b3edfbe2315d778e8589a7d0a4a69c41332bae", size = 167979 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/e4/463fd46922e0b0b369305662f85f1c70dcc1cde1584906cf8defed8308a3/pydot-3.0.2-py3-none-any.whl", hash = "sha256:99cedaa55d04abb0b2bc56d9981a6da781053dd5ac75c428e8dd53db53f90b14", size = 35773 }, +] + +[[package]] +name = "pyflakes" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/60/c577e54518086e98470e9088278247f4af1d39cb43bcbd731e2c307acd6a/pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c", size = 69101 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/fb/38848eb494af7df9aeb2d7673ace8b213313eb7e391691a79dbaeb6a838f/pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e", size = 69704 }, +] + +[[package]] +name = "pygments" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, +] + +[[package]] +name = "pykeops" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "keopscore" }, + { name = "numpy" }, + { name = "pybind11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f9/64/2c7b49954aeac6b10e1fd553e190aef8e4737d48506aae60aa6f2b035809/pykeops-2.2.3.tar.gz", hash = "sha256:2e2cba1de5e05c35559957a14f41cb5165dc667cc51b3b2118d7e0027eb435a1", size = 92458 } + +[[package]] +name = "pymatgen" +version = "2024.2.23" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "matplotlib" }, + { name = "monty" }, + { name = "networkx" }, + { name = "numpy" }, + { name = "palettable" }, + { name = "pandas" }, + { name = "plotly" }, + { name = "pybtex" }, + { name = "requests" }, + { name = "ruamel-yaml" }, + { name = "scipy" }, + { name = "spglib" }, + { name = "sympy" }, + { name = "tabulate" }, + { name = "tqdm" }, + { name = "uncertainties" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/a0/903a79eeddd76e1913af8d7911dc7edc4b6898cc616642a8229124a1d21a/pymatgen-2024.2.23.tar.gz", hash = "sha256:48764e1bdf8d6b0209b650a96c86947b73d41bc7d2cc4ff13b2ac54dd1c308eb", size = 7183794 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/f4/9f9991dbf9f165861a3be695b4290e8544f5fa71cab454247ed59f22e0ea/pymatgen-2024.2.23-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbe11280e6fbd4309e1d82c72a6bf0ea15fa2399969a1ccb30150fd3070bd218", size = 7732929 }, + { url = "https://files.pythonhosted.org/packages/31/07/faa685a6971d3686d989a244901247e1408a945cdbec28c43f1cf370204c/pymatgen-2024.2.23-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d9be26a1d89aedd81687deee13d65c0b51fed2dd9da1d495c7835820c9f7f75", size = 10025771 }, + { url = "https://files.pythonhosted.org/packages/6d/90/3cc2e199fd1b1804acdf8a70d6ef4bd4e3a0a296c2685d3791ec88b87fed/pymatgen-2024.2.23-cp310-cp310-win32.whl", hash = "sha256:c682e86eced5f195fa33810135542b1d2b670e774ff08c42a29ecf5591fc27cb", size = 7683088 }, + { url = "https://files.pythonhosted.org/packages/2f/17/00360a48923bb6b888cf02f24bf6b2225ab56d7b75e48c72743310e0f398/pymatgen-2024.2.23-cp310-cp310-win_amd64.whl", hash = "sha256:0073d9b7745009d82cdbc19068ea9f497dc9951cccb5a4fd10e8f7f7eeaeffa4", size = 7726922 }, + { url = "https://files.pythonhosted.org/packages/93/c3/efba4b6b47f50f44dc8d317f6a77ed1c990bacfd5607e08e277c4ee41f1e/pymatgen-2024.2.23-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bbdb42c4cee63952595c2150261dc83d8e0635f1f77466b931949ec7bf9fa530", size = 7732724 }, + { url = "https://files.pythonhosted.org/packages/05/84/cfd8fe5822eedca2f5b0f45dc849aa9f6104f390354b556e82fd6960de7b/pymatgen-2024.2.23-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90e162398de7825f44b9b2136234ab8fc3778c663e6c48662dd2db824ba74fb3", size = 10146196 }, + { url = "https://files.pythonhosted.org/packages/7c/5d/069e33b9d4fe539d14a624a667bcd9739245abc9ff6b5d21dc3043e1d10d/pymatgen-2024.2.23-cp311-cp311-win32.whl", hash = "sha256:59b54b7ee1823c1bc5feaf1ad707ea5044b586216565b6e98f639aa6b515ae6a", size = 7682710 }, + { url = "https://files.pythonhosted.org/packages/a8/75/7424ce76f9c97da99f37008abd1b1b9cd13fc12e979b131c01b1209b912c/pymatgen-2024.2.23-cp311-cp311-win_amd64.whl", hash = "sha256:d3b2c3fb09fae67d7dc7691293f818b933b639776d3ec50e3c01de16a39a5ed4", size = 7727250 }, +] + +[[package]] +name = "pymongo" +version = "4.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/35/b62a3139f908c68b69aac6a6a3f8cc146869de0a7929b994600e2c587c77/pymongo-4.10.1.tar.gz", hash = "sha256:a9de02be53b6bb98efe0b9eda84ffa1ec027fcb23a2de62c4f941d9a2f2f3330", size = 1903902 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ca/f56b1dd84541de658d246f86828be27e32285f2151fab97efbce1db3ed57/pymongo-4.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e699aa68c4a7dea2ab5a27067f7d3e08555f8d2c0dc6a0c8c60cfd9ff2e6a4b1", size = 835459 }, + { url = "https://files.pythonhosted.org/packages/97/01/fe4ee34b33c6863be6a09d1e805ceb1122d9cd5d4a5d1664e360b91adf7e/pymongo-4.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:70645abc714f06b4ad6b72d5bf73792eaad14e3a2cfe29c62a9c81ada69d9e4b", size = 835716 }, + { url = "https://files.pythonhosted.org/packages/46/ff/9eb21c1d5861729ae1c91669b02f5bfbd23221ba9809fb97fade761f3f3b/pymongo-4.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae2fd94c9fe048c94838badcc6e992d033cb9473eb31e5710b3707cba5e8aee2", size = 1407173 }, + { url = "https://files.pythonhosted.org/packages/e5/d9/8cf042449d6804e00e38d3bb138b0e9acb8a8e0c9dd9dd989ffffd481c3b/pymongo-4.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ded27a4a5374dae03a92e084a60cdbcecd595306555bda553b833baf3fc4868", size = 1456455 }, + { url = "https://files.pythonhosted.org/packages/37/9a/da0d121f98c1413853e1172e2095fe77c1629c83a1db107d45a37ca935c2/pymongo-4.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ecc2455e3974a6c429687b395a0bc59636f2d6aedf5785098cf4e1f180f1c71", size = 1433360 }, + { url = "https://files.pythonhosted.org/packages/7d/6d/50480f0452e2fb59256d9d641d192366c0079920c36851b818ebeff0cec9/pymongo-4.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920fee41f7d0259f5f72c1f1eb331bc26ffbdc952846f9bd8c3b119013bb52c", size = 1410758 }, + { url = "https://files.pythonhosted.org/packages/cd/8f/b83b9910c54f63bfff34305074e79cd08cf5e12dda22d1a2b4ad009b32b3/pymongo-4.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0a15665b2d6cf364f4cd114d62452ce01d71abfbd9c564ba8c74dcd7bbd6822", size = 1380257 }, + { url = "https://files.pythonhosted.org/packages/ed/e3/8f381b576e5f912cf0fe34218c6b0ef23d7afdef13fed592900fb52f0ed4/pymongo-4.10.1-cp310-cp310-win32.whl", hash = "sha256:29e1c323c28a4584b7095378ff046815e39ff82cdb8dc4cc6dfe3acf6f9ad1f8", size = 812324 }, + { url = "https://files.pythonhosted.org/packages/ab/14/1cae5359e2c4677856527a2965c999c23f596cced4b7828d880cb8fc0f54/pymongo-4.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:88dc4aa45f8744ccfb45164aedb9a4179c93567bbd98a33109d7dc400b00eb08", size = 826774 }, + { url = "https://files.pythonhosted.org/packages/e4/a3/d6403ec53fa2fe922b4a5c86388ea5fada01dd51d803e17bb2a7c9cda839/pymongo-4.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:57ee6becae534e6d47848c97f6a6dff69e3cce7c70648d6049bd586764febe59", size = 889238 }, + { url = "https://files.pythonhosted.org/packages/29/a2/9643450424bcf241e80bb713497ec2e3273c183d548b4eca357f75d71885/pymongo-4.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6f437a612f4d4f7aca1812311b1e84477145e950fdafe3285b687ab8c52541f3", size = 889504 }, + { url = "https://files.pythonhosted.org/packages/ec/40/4759984f34415509e9111be8ee863034611affdc1e0b41016c9d53b2f1b3/pymongo-4.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a970fd3117ab40a4001c3dad333bbf3c43687d90f35287a6237149b5ccae61d", size = 1649069 }, + { url = "https://files.pythonhosted.org/packages/56/0f/b6e917478a3ada81e768475516cd544982cc42cbb7d3be325182768139e1/pymongo-4.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c4d0e7cd08ef9f8fbf2d15ba281ed55604368a32752e476250724c3ce36c72e", size = 1714927 }, + { url = "https://files.pythonhosted.org/packages/56/c5/4237d94dfa19ebdf9a92b1071e2139c91f48908c5782e592c571c33b67ab/pymongo-4.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca6f700cff6833de4872a4e738f43123db34400173558b558ae079b5535857a4", size = 1683454 }, + { url = "https://files.pythonhosted.org/packages/9a/16/dbffca9d4ad66f2a325c280f1177912fa23235987f7b9033e283da889b7a/pymongo-4.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cec237c305fcbeef75c0bcbe9d223d1e22a6e3ba1b53b2f0b79d3d29c742b45b", size = 1653840 }, + { url = "https://files.pythonhosted.org/packages/2b/4d/21df934ef5cf8f0e587bac922a129e13d4c0346c54e9bf2371b90dd31112/pymongo-4.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3337804ea0394a06e916add4e5fac1c89902f1b6f33936074a12505cab4ff05", size = 1613233 }, + { url = "https://files.pythonhosted.org/packages/24/07/dd9c3db30e754680606295d5574521956898005db0629411a89163cc6eee/pymongo-4.10.1-cp311-cp311-win32.whl", hash = "sha256:778ac646ce6ac1e469664062dfe9ae1f5c9961f7790682809f5ec3b8fda29d65", size = 857331 }, + { url = "https://files.pythonhosted.org/packages/02/68/b71c4106d03eef2482eade440c6f5737c2a4a42f6155726009f80ea38d06/pymongo-4.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:9df4ab5594fdd208dcba81be815fa8a8a5d8dedaf3b346cbf8b61c7296246a7a", size = 876473 }, + { url = "https://files.pythonhosted.org/packages/10/d1/60ad99fe3f64d45e6c71ac0e3078e88d9b64112b1bae571fc3707344d6d1/pymongo-4.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fbedc4617faa0edf423621bb0b3b8707836687161210d470e69a4184be9ca011", size = 943356 }, + { url = "https://files.pythonhosted.org/packages/ca/9b/21d4c6b4ee9c1fa9691c68dc2a52565e0acb644b9e95148569b4736a4ebd/pymongo-4.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7bd26b2aec8ceeb95a5d948d5cc0f62b0eb6d66f3f4230705c1e3d3d2c04ec76", size = 943142 }, + { url = "https://files.pythonhosted.org/packages/07/af/691b7454e219a8eb2d1641aecedd607e3a94b93650c2011ad8a8fd74ef9f/pymongo-4.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb104c3c2a78d9d85571c8ac90ec4f95bca9b297c6eee5ada71fabf1129e1674", size = 1909129 }, + { url = "https://files.pythonhosted.org/packages/0c/74/fd75d5ad4181d6e71ce0fca32404fb71b5046ac84d9a1a2f0862262dd032/pymongo-4.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4924355245a9c79f77b5cda2db36e0f75ece5faf9f84d16014c0a297f6d66786", size = 1987763 }, + { url = "https://files.pythonhosted.org/packages/8a/56/6d3d0ef63c6d8cb98c7c653a3a2e617675f77a95f3853851d17a7664876a/pymongo-4.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11280809e5dacaef4971113f0b4ff4696ee94cfdb720019ff4fa4f9635138252", size = 1950821 }, + { url = "https://files.pythonhosted.org/packages/70/ed/1603fa0c0e51444752c3fa91f16c3a97e6d92eb9fe5e553dae4f18df16f6/pymongo-4.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5d55f2a82e5eb23795f724991cac2bffbb1c0f219c0ba3bf73a835f97f1bb2e", size = 1912247 }, + { url = "https://files.pythonhosted.org/packages/c1/66/e98b2308971d45667cb8179d4d66deca47336c90663a7e0527589f1038b7/pymongo-4.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e974ab16a60be71a8dfad4e5afccf8dd05d41c758060f5d5bda9a758605d9a5d", size = 1862230 }, + { url = "https://files.pythonhosted.org/packages/6c/80/ba9b7ed212a5f8cf8ad7037ed5bbebc1c587fc09242108f153776e4a338b/pymongo-4.10.1-cp312-cp312-win32.whl", hash = "sha256:544890085d9641f271d4f7a47684450ed4a7344d6b72d5968bfae32203b1bb7c", size = 903045 }, + { url = "https://files.pythonhosted.org/packages/76/8b/5afce891d78159912c43726fab32641e3f9718f14be40f978c148ea8db48/pymongo-4.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:dcc07b1277e8b4bf4d7382ca133850e323b7ab048b8353af496d050671c7ac52", size = 926686 }, + { url = "https://files.pythonhosted.org/packages/83/76/df0fd0622a85b652ad0f91ec8a0ebfd0cb86af6caec8999a22a1f7481203/pymongo-4.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:90bc6912948dfc8c363f4ead54d54a02a15a7fee6cfafb36dc450fc8962d2cb7", size = 996981 }, + { url = "https://files.pythonhosted.org/packages/4c/39/fa50531de8d1d8af8c253caeed20c18ccbf1de5d970119c4a42c89f2bd09/pymongo-4.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:594dd721b81f301f33e843453638e02d92f63c198358e5a0fa8b8d0b1218dabc", size = 996769 }, + { url = "https://files.pythonhosted.org/packages/bf/50/6936612c1b2e32d95c30e860552d3bc9e55cfa79a4f73b73225fa05a028c/pymongo-4.10.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0783e0c8e95397c84e9cf8ab092ab1e5dd7c769aec0ef3a5838ae7173b98dea0", size = 2169159 }, + { url = "https://files.pythonhosted.org/packages/78/8c/45cb23096e66c7b1da62bb8d9c7ac2280e7c1071e13841e7fb71bd44fd9f/pymongo-4.10.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6fb6a72e88df46d1c1040fd32cd2d2c5e58722e5d3e31060a0393f04ad3283de", size = 2260569 }, + { url = "https://files.pythonhosted.org/packages/29/b6/e5ec697087e527a6a15c5f8daa5bcbd641edb8813487345aaf963d3537dc/pymongo-4.10.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e3a593333e20c87415420a4fb76c00b7aae49b6361d2e2205b6fece0563bf40", size = 2218142 }, + { url = "https://files.pythonhosted.org/packages/ad/8a/c0b45bee0f0c57732c5c36da5122c1796efd5a62d585fbc504e2f1401244/pymongo-4.10.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72e2ace7456167c71cfeca7dcb47bd5dceda7db2231265b80fc625c5e8073186", size = 2170623 }, + { url = "https://files.pythonhosted.org/packages/3b/26/6c0a5360a571df24c9bfbd51b1dae279f4f0c511bdbc0906f6df6d1543fa/pymongo-4.10.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ad05eb9c97e4f589ed9e74a00fcaac0d443ccd14f38d1258eb4c39a35dd722b", size = 2111112 }, + { url = "https://files.pythonhosted.org/packages/38/bc/5b91b728e1cf505d931f04e24cbac71ae519523785570ed046cdc31e6efc/pymongo-4.10.1-cp313-cp313-win32.whl", hash = "sha256:ee4c86d8e6872a61f7888fc96577b0ea165eb3bdb0d841962b444fa36001e2bb", size = 948727 }, + { url = "https://files.pythonhosted.org/packages/0d/2a/7c24a6144eaa06d18ed52822ea2b0f119fd9267cd1abbb75dae4d89a3803/pymongo-4.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:45ee87a4e12337353242bc758accc7fb47a2f2d9ecc0382a61e64c8f01e86708", size = 976873 }, +] + +[[package]] +name = "pyparsing" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/d5/e5aeee5387091148a19e1145f63606619cb5f20b83fccb63efae6474e7b2/pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c", size = 920984 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/ec/2eb3cd785efd67806c46c13a17339708ddc346cbb684eade7a6e6f79536a/pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84", size = 106921 }, +] + +[[package]] +name = "pyside6-essentials" +version = "6.8.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "shiboken6" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a7/0383a2c4771ca1008c2ee6b9fca76ad00e48053df6626fa17fe478522671/PySide6_Essentials-6.8.0.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:2ae533a318b563ef7b73b1d7de3254e966423215589329207e45ee9adcb2e83c", size = 164669460 }, + { url = "https://files.pythonhosted.org/packages/31/db/da4db014ed4b3b6d0ff148749d743d6e25692b9a66b9d5fbd10f4ecee6d6/PySide6_Essentials-6.8.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:327a0f08cb1ee5af7f7ed81527bbdb3058e2a39a300efcc806e0289b50192f98", size = 95183947 }, + { url = "https://files.pythonhosted.org/packages/7a/9d/ae433a47c1c8d63e1702c4055698e2f439c3e8961dcb0b60db595f424451/PySide6_Essentials-6.8.0.1-cp39-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:6f573331261889992d450145c0f3a9a317e798b63c1318cc271422c0a74b2298", size = 92322388 }, + { url = "https://files.pythonhosted.org/packages/e0/cd/1794fcc63c9f1b4d0349b2a7bb3ee925418054ec2ce92f95e69beb9d8382/PySide6_Essentials-6.8.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:800d26aa51003b41c73e9c4cd4fd6c96fe91fc21f9862c3d459a7e2209f9475f", size = 72518525 }, +] + +[[package]] +name = "pytest" +version = "7.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "atomicwrites", marker = "sys_platform == 'win32'" }, + { name = "attrs" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "py" }, + { name = "tomli" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/1f/34657c6ac56f3c58df650ba41f8ffb2620281ead8e11bcdc7db63cf72a78/pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45", size = 1256241 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/d0/bae533985f2338c5d02184b4a7083b819f6b3fc101da792e0d96e6e5299d/pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c", size = 297031 }, +] + +[[package]] +name = "pytest-cov" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/41/e046526849972555928a6d31c2068410e47a31fb5ab0a77f868596811329/pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470", size = 61440 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/49/b3e0edec68d81846f519c602ac38af9db86e1e71275528b3e814ae236063/pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6", size = 20981 }, +] + +[[package]] +name = "pytest-mock" +version = "3.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/96/25588c55fbe330b751bd7c7d723c3544957566bc090f6d506551b514f488/pytest-mock-3.12.0.tar.gz", hash = "sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9", size = 32139 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/25/b29fd10dd062cf41e66787a7951b3842881a2a2d7e3a41fcbb58a8466046/pytest_mock-3.12.0-py3-none-any.whl", hash = "sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f", size = 9771 }, +] + +[[package]] +name = "python-box" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/85/b02b80d74bdb95bfe491d49ad1627e9833c73d331edbe6eed0bdfe170361/python-box-6.1.0.tar.gz", hash = "sha256:6e7c243b356cb36e2c0f0e5ed7850969fede6aa812a7f501de7768996c7744d7", size = 41443 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/c7/5d18ef4960a67b8d8dad9a6aa6c739da15a92b6f1e550ab7ebe040493957/python_box-6.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:c14aa4e72bf30f4d573e62ff8030a86548603a100c3fb534561dbedf4a83f454", size = 1120226 }, + { url = "https://files.pythonhosted.org/packages/6f/32/3c865e7d62e481c46abffef3303db0d27bf2ca72e4f497d05d66939bfd4a/python_box-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab13208b053525ef154a36a4a52873b98a12b18b946edd4c939a4d5080e9a218", size = 3327115 }, + { url = "https://files.pythonhosted.org/packages/69/97/e43a8ab4487f923356a0ab8b0e540448aa453a8ec9314d49dd2098952185/python_box-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:d199cd289b4f4d053770eadd70217c76214aac30b92a23adfb9627fd8558d300", size = 962296 }, + { url = "https://files.pythonhosted.org/packages/d4/16/48bcaacf750fa2cc78882a53eef953c28a42e4a84f5e0b27e05d7188a92a/python_box-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ac44b3b85714a4575cc273b5dbd39ef739f938ef6c522d6757704a29e7797d16", size = 1571634 }, + { url = "https://files.pythonhosted.org/packages/8b/b4/ae3736cfc3970fe6ee348620780811c016fe4c01d2d0ff4a3a19f4eff5f7/python_box-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f0036f91e13958d2b37d2bc74c1197aa36ffd66755342eb64910f63d8a2990f", size = 3546030 }, + { url = "https://files.pythonhosted.org/packages/f3/7d/5cc1f3145792b803ee6debc82d1faf791659baa15c2de7b1d9318adbcd68/python_box-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:af6bcee7e1abe9251e9a41ca9ab677e1f679f6059321cfbae7e78a3831e0b736", size = 957417 }, + { url = "https://files.pythonhosted.org/packages/88/c6/6d1e368710cb6c458ed692d179d7e101ebce80a3e640b2e74cc7ae886d6f/python_box-6.1.0-py3-none-any.whl", hash = "sha256:bdec0a5f5a17b01fc538d292602a077aa8c641fb121e1900dff0591791af80e8", size = 27277 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "python-json-logger" +version = "2.0.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/da/95963cebfc578dabd323d7263958dfb68898617912bb09327dd30e9c8d13/python-json-logger-2.0.7.tar.gz", hash = "sha256:23e7ec02d34237c5aa1e29a070193a4ea87583bb4e7f8fd06d3de8264c4b2e1c", size = 10508 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/a6/145655273568ee78a581e734cf35beb9e33a370b29c5d3c8fee3744de29f/python_json_logger-2.0.7-py3-none-any.whl", hash = "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd", size = 8067 }, +] + +[[package]] +name = "pytorch-lightning" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec", extra = ["http"] }, + { name = "lightning-utilities" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "torch" }, + { name = "torchmetrics" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/f0/3207bd5019c43899efbb5444da263577497a5c4dc82719633a3bf63d8f45/pytorch-lightning-2.4.0.tar.gz", hash = "sha256:6aa897fd9d6dfa7b7b49f37c2f04e13592861831d08deae584dfda423fdb71c8", size = 625320 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/d2/ecd65ff1e0b1ca79f9785dd65d5ced7ec2643a828068aaa24e47e4c84a14/pytorch_lightning-2.4.0-py3-none-any.whl", hash = "sha256:9ac7935229ac022ef06994c928217ed37f525ac6700f7d4fc57009624570e655", size = 815151 }, +] + +[[package]] +name = "pytype" +version = "2024.2.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "importlab" }, + { name = "jinja2" }, + { name = "libcst" }, + { name = "networkx" }, + { name = "ninja" }, + { name = "pycnite" }, + { name = "pydot" }, + { name = "tabulate" }, + { name = "toml" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/7a/9ab6ee2588c1bd114bfe7d96015f90c9d11b3224d6a9915f35e7a481a495/pytype-2024.2.13.tar.gz", hash = "sha256:5a2bf755b6a62c3f4c2ae7a5102c8da04011d94646a4faf3c6bcc3b44fbb0ced", size = 2785643 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/2f/109389d134252a8113b5c957cdbd6e218cc9bcb36fb2aab7c77377685a2b/pytype-2024.2.13-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:0842b4e5611d1c2533e2c4f432bfe94483632f7b27eb5081573f90d8b939c1c3", size = 4194848 }, + { url = "https://files.pythonhosted.org/packages/14/cb/53ca77b1e6087c1c484782fae3eeaced63a494825689683b782a92050a70/pytype-2024.2.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3fc8ecda755e041b78e80966bc000eb5cc491b2912dfa3d20022b1ed5d205ec", size = 4328370 }, + { url = "https://files.pythonhosted.org/packages/be/f3/3727037a53ef738ce4124556b8c57c975eee84d10a17b9a0df5d49d2c607/pytype-2024.2.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dad172615419a21c6b2f37a8a4ff85d3d46d317137844f6b2721b732a415ef38", size = 4338124 }, + { url = "https://files.pythonhosted.org/packages/f7/dc/92391b25a09abbe9a96045e23b552cd3f567939157087bc1d59574eacdcc/pytype-2024.2.13-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5c739230fb52fcc4a52f46396627982ade99a349edccecc4dfd572c22279a382", size = 4341690 }, + { url = "https://files.pythonhosted.org/packages/b9/72/bf84d68b7878824d485dbab38dc14a9e94edd3f8e90a19d970e2ce9c7372/pytype-2024.2.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3c6c097ae2654db35c8c09b58dd63092fb7d5791ab937903e93bc2f6aa65c59", size = 4329530 }, + { url = "https://files.pythonhosted.org/packages/83/9e/6f47f68048a8b102b4e9ef51e94d00c75f5dfebd29a0d0174e309fbe416a/pytype-2024.2.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:108fbd615e2ed97ab7d7895d4e60c703af0534f60fc23826daa6b5c516155777", size = 4339418 }, +] + +[[package]] +name = "pytz" +version = "2024.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/31/3c70bf7603cc2dca0f19bdc53b4537a797747a58875b552c8c413d963a3f/pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", size = 319692 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 }, +] + +[[package]] +name = "pywin32" +version = "308" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/a6/3e9f2c474895c1bb61b11fa9640be00067b5c5b363c501ee9c3fa53aec01/pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e", size = 5927028 }, + { url = "https://files.pythonhosted.org/packages/d9/b4/84e2463422f869b4b718f79eb7530a4c1693e96b8a4e5e968de38be4d2ba/pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e", size = 6558484 }, + { url = "https://files.pythonhosted.org/packages/9f/8f/fb84ab789713f7c6feacaa08dad3ec8105b88ade8d1c4f0f0dfcaaa017d6/pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c", size = 7971454 }, + { url = "https://files.pythonhosted.org/packages/eb/e2/02652007469263fe1466e98439831d65d4ca80ea1a2df29abecedf7e47b7/pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a", size = 5928156 }, + { url = "https://files.pythonhosted.org/packages/48/ef/f4fb45e2196bc7ffe09cad0542d9aff66b0e33f6c0954b43e49c33cad7bd/pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b", size = 6559559 }, + { url = "https://files.pythonhosted.org/packages/79/ef/68bb6aa865c5c9b11a35771329e95917b5559845bd75b65549407f9fc6b4/pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6", size = 7972495 }, + { url = "https://files.pythonhosted.org/packages/00/7c/d00d6bdd96de4344e06c4afbf218bc86b54436a94c01c71a8701f613aa56/pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897", size = 5939729 }, + { url = "https://files.pythonhosted.org/packages/21/27/0c8811fbc3ca188f93b5354e7c286eb91f80a53afa4e11007ef661afa746/pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47", size = 6543015 }, + { url = "https://files.pythonhosted.org/packages/9d/0f/d40f8373608caed2255781a3ad9a51d03a594a1248cd632d6a298daca693/pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091", size = 7976033 }, + { url = "https://files.pythonhosted.org/packages/a9/a4/aa562d8935e3df5e49c161b427a3a2efad2ed4e9cf81c3de636f1fdddfd0/pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed", size = 5938579 }, + { url = "https://files.pythonhosted.org/packages/c7/50/b0efb8bb66210da67a53ab95fd7a98826a97ee21f1d22949863e6d588b22/pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4", size = 6542056 }, + { url = "https://files.pythonhosted.org/packages/26/df/2b63e3e4f2df0224f8aaf6d131f54fe4e8c96400eb9df563e2aae2e1a1f9/pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd", size = 7974986 }, +] + +[[package]] +name = "pywinpty" +version = "2.0.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/82/90f8750423cba4b9b6c842df227609fb60704482d7abf6dd47e2babc055a/pywinpty-2.0.14.tar.gz", hash = "sha256:18bd9529e4a5daf2d9719aa17788ba6013e594ae94c5a0c27e83df3278b0660e", size = 27769 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/09/56376af256eab8cc5f8982a3b138d387136eca27fa1a8a68660e8ed59e4b/pywinpty-2.0.14-cp310-none-win_amd64.whl", hash = "sha256:0b149c2918c7974f575ba79f5a4aad58bd859a52fa9eb1296cc22aa412aa411f", size = 1397115 }, + { url = "https://files.pythonhosted.org/packages/be/e2/af1a99c0432e4e58c9ac8e334ee191790ec9793d33559189b9d2069bdc1d/pywinpty-2.0.14-cp311-none-win_amd64.whl", hash = "sha256:cf2a43ac7065b3e0dc8510f8c1f13a75fb8fde805efa3b8cff7599a1ef497bc7", size = 1397223 }, + { url = "https://files.pythonhosted.org/packages/ad/79/759ae767a3b78d340446efd54dd1fe4f7dafa4fc7be96ed757e44bcdba54/pywinpty-2.0.14-cp312-none-win_amd64.whl", hash = "sha256:55dad362ef3e9408ade68fd173e4f9032b3ce08f68cfe7eacb2c263ea1179737", size = 1397207 }, + { url = "https://files.pythonhosted.org/packages/7d/34/b77b3c209bf2eaa6455390c8d5449241637f5957f41636a2204065d52bfa/pywinpty-2.0.14-cp313-none-win_amd64.whl", hash = "sha256:074fb988a56ec79ca90ed03a896d40707131897cefb8f76f926e3834227f2819", size = 1396698 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/e5/af35f7ea75cf72f2cd079c95ee16797de7cd71f29ea7c68ae5ce7be1eda0/PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", size = 125201 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/06/4beb652c0fe16834032e54f0956443d4cc797fe645527acee59e7deaa0a2/PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", size = 189447 }, + { url = "https://files.pythonhosted.org/packages/5b/07/10033a403b23405a8fc48975444463d3d10a5c2736b7eb2550b07b367429/PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f", size = 169264 }, + { url = "https://files.pythonhosted.org/packages/f1/26/55e4f21db1f72eaef092015d9017c11510e7e6301c62a6cfee91295d13c6/PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", size = 677003 }, + { url = "https://files.pythonhosted.org/packages/ba/91/090818dfa62e85181f3ae23dd1e8b7ea7f09684864a900cab72d29c57346/PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", size = 699070 }, + { url = "https://files.pythonhosted.org/packages/29/61/bf33c6c85c55bc45a29eee3195848ff2d518d84735eb0e2d8cb42e0d285e/PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", size = 705525 }, + { url = "https://files.pythonhosted.org/packages/07/91/45dfd0ef821a7f41d9d0136ea3608bb5b1653e42fd56a7970532cb5c003f/PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", size = 707514 }, + { url = "https://files.pythonhosted.org/packages/b6/a0/b6700da5d49e9fed49dc3243d3771b598dad07abb37cc32e524607f96adc/PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", size = 130488 }, + { url = "https://files.pythonhosted.org/packages/24/97/9b59b43431f98d01806b288532da38099cc6f2fea0f3d712e21e269c0279/PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", size = 145338 }, + { url = "https://files.pythonhosted.org/packages/ec/0d/26fb23e8863e0aeaac0c64e03fd27367ad2ae3f3cccf3798ee98ce160368/PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", size = 187867 }, + { url = "https://files.pythonhosted.org/packages/28/09/55f715ddbf95a054b764b547f617e22f1d5e45d83905660e9a088078fe67/PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", size = 167530 }, + { url = "https://files.pythonhosted.org/packages/5e/94/7d5ee059dfb92ca9e62f4057dcdec9ac08a9e42679644854dc01177f8145/PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", size = 732244 }, + { url = "https://files.pythonhosted.org/packages/06/92/e0224aa6ebf9dc54a06a4609da37da40bb08d126f5535d81bff6b417b2ae/PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", size = 752871 }, + { url = "https://files.pythonhosted.org/packages/7b/5e/efd033ab7199a0b2044dab3b9f7a4f6670e6a52c089de572e928d2873b06/PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", size = 757729 }, + { url = "https://files.pythonhosted.org/packages/03/5c/c4671451b2f1d76ebe352c0945d4cd13500adb5d05f5a51ee296d80152f7/PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", size = 748528 }, + { url = "https://files.pythonhosted.org/packages/73/9c/766e78d1efc0d1fca637a6b62cea1b4510a7fb93617eb805223294fef681/PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", size = 130286 }, + { url = "https://files.pythonhosted.org/packages/b3/34/65bb4b2d7908044963ebf614fe0fdb080773fc7030d7e39c8d3eddcd4257/PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", size = 144699 }, + { url = "https://files.pythonhosted.org/packages/bc/06/1b305bf6aa704343be85444c9d011f626c763abb40c0edc1cad13bfd7f86/PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", size = 178692 }, + { url = "https://files.pythonhosted.org/packages/84/02/404de95ced348b73dd84f70e15a41843d817ff8c1744516bf78358f2ffd2/PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", size = 165622 }, + { url = "https://files.pythonhosted.org/packages/c7/4c/4a2908632fc980da6d918b9de9c1d9d7d7e70b2672b1ad5166ed27841ef7/PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", size = 696937 }, + { url = "https://files.pythonhosted.org/packages/b4/33/720548182ffa8344418126017aa1d4ab4aeec9a2275f04ce3f3573d8ace8/PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", size = 724969 }, + { url = "https://files.pythonhosted.org/packages/4f/78/77b40157b6cb5f2d3d31a3d9b2efd1ba3505371f76730d267e8b32cf4b7f/PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", size = 712604 }, + { url = "https://files.pythonhosted.org/packages/2e/97/3e0e089ee85e840f4b15bfa00e4e63d84a3691ababbfea92d6f820ea6f21/PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", size = 126098 }, + { url = "https://files.pythonhosted.org/packages/2b/9f/fbade56564ad486809c27b322d0f7e6a89c01f6b4fe208402e90d4443a99/PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", size = 138675 }, +] + +[[package]] +name = "pyzmq" +version = "26.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/05/bed626b9f7bb2322cdbbf7b4bd8f54b1b617b0d2ab2d3547d6e39428a48e/pyzmq-26.2.0.tar.gz", hash = "sha256:070672c258581c8e4f640b5159297580a9974b026043bd4ab0470be9ed324f1f", size = 271975 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/a8/9837c39aba390eb7d01924ace49d761c8dbe7bc2d6082346d00c8332e431/pyzmq-26.2.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:ddf33d97d2f52d89f6e6e7ae66ee35a4d9ca6f36eda89c24591b0c40205a3629", size = 1340058 }, + { url = "https://files.pythonhosted.org/packages/a2/1f/a006f2e8e4f7d41d464272012695da17fb95f33b54342612a6890da96ff6/pyzmq-26.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dacd995031a01d16eec825bf30802fceb2c3791ef24bcce48fa98ce40918c27b", size = 1008818 }, + { url = "https://files.pythonhosted.org/packages/b6/09/b51b6683fde5ca04593a57bbe81788b6b43114d8f8ee4e80afc991e14760/pyzmq-26.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89289a5ee32ef6c439086184529ae060c741334b8970a6855ec0b6ad3ff28764", size = 673199 }, + { url = "https://files.pythonhosted.org/packages/c9/78/486f3e2e824f3a645238332bf5a4c4b4477c3063033a27c1e4052358dee2/pyzmq-26.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5506f06d7dc6ecf1efacb4a013b1f05071bb24b76350832c96449f4a2d95091c", size = 911762 }, + { url = "https://files.pythonhosted.org/packages/5e/3b/2eb1667c9b866f53e76ee8b0c301b0469745a23bd5a87b7ee3d5dd9eb6e5/pyzmq-26.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ea039387c10202ce304af74def5021e9adc6297067f3441d348d2b633e8166a", size = 868773 }, + { url = "https://files.pythonhosted.org/packages/16/29/ca99b4598a9dc7e468b5417eda91f372b595be1e3eec9b7cbe8e5d3584e8/pyzmq-26.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a2224fa4a4c2ee872886ed00a571f5e967c85e078e8e8c2530a2fb01b3309b88", size = 868834 }, + { url = "https://files.pythonhosted.org/packages/ad/e5/9efaeb1d2f4f8c50da04144f639b042bc52869d3a206d6bf672ab3522163/pyzmq-26.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:28ad5233e9c3b52d76196c696e362508959741e1a005fb8fa03b51aea156088f", size = 1202861 }, + { url = "https://files.pythonhosted.org/packages/c3/62/c721b5608a8ac0a69bb83cbb7d07a56f3ff00b3991a138e44198a16f94c7/pyzmq-26.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1c17211bc037c7d88e85ed8b7d8f7e52db6dc8eca5590d162717c654550f7282", size = 1515304 }, + { url = "https://files.pythonhosted.org/packages/87/84/e8bd321aa99b72f48d4606fc5a0a920154125bd0a4608c67eab742dab087/pyzmq-26.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b8f86dd868d41bea9a5f873ee13bf5551c94cf6bc51baebc6f85075971fe6eea", size = 1414712 }, + { url = "https://files.pythonhosted.org/packages/cd/cd/420e3fd1ac6977b008b72e7ad2dae6350cc84d4c5027fc390b024e61738f/pyzmq-26.2.0-cp310-cp310-win32.whl", hash = "sha256:46a446c212e58456b23af260f3d9fb785054f3e3653dbf7279d8f2b5546b21c2", size = 578113 }, + { url = "https://files.pythonhosted.org/packages/5c/57/73930d56ed45ae0cb4946f383f985c855c9b3d4063f26416998f07523c0e/pyzmq-26.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:49d34ab71db5a9c292a7644ce74190b1dd5a3475612eefb1f8be1d6961441971", size = 641631 }, + { url = "https://files.pythonhosted.org/packages/61/d2/ae6ac5c397f1ccad59031c64beaafce7a0d6182e0452cc48f1c9c87d2dd0/pyzmq-26.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:bfa832bfa540e5b5c27dcf5de5d82ebc431b82c453a43d141afb1e5d2de025fa", size = 543528 }, + { url = "https://files.pythonhosted.org/packages/12/20/de7442172f77f7c96299a0ac70e7d4fb78cd51eca67aa2cf552b66c14196/pyzmq-26.2.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:8f7e66c7113c684c2b3f1c83cdd3376103ee0ce4c49ff80a648643e57fb22218", size = 1340639 }, + { url = "https://files.pythonhosted.org/packages/98/4d/5000468bd64c7910190ed0a6c76a1ca59a68189ec1f007c451dc181a22f4/pyzmq-26.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3a495b30fc91db2db25120df5847d9833af237546fd59170701acd816ccc01c4", size = 1008710 }, + { url = "https://files.pythonhosted.org/packages/e1/bf/c67fd638c2f9fbbab8090a3ee779370b97c82b84cc12d0c498b285d7b2c0/pyzmq-26.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77eb0968da535cba0470a5165468b2cac7772cfb569977cff92e240f57e31bef", size = 673129 }, + { url = "https://files.pythonhosted.org/packages/86/94/99085a3f492aa538161cbf27246e8886ff850e113e0c294a5b8245f13b52/pyzmq-26.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ace4f71f1900a548f48407fc9be59c6ba9d9aaf658c2eea6cf2779e72f9f317", size = 910107 }, + { url = "https://files.pythonhosted.org/packages/31/1d/346809e8a9b999646d03f21096428453465b1bca5cd5c64ecd048d9ecb01/pyzmq-26.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92a78853d7280bffb93df0a4a6a2498cba10ee793cc8076ef797ef2f74d107cf", size = 867960 }, + { url = "https://files.pythonhosted.org/packages/ab/68/6fb6ae5551846ad5beca295b7bca32bf0a7ce19f135cb30e55fa2314e6b6/pyzmq-26.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:689c5d781014956a4a6de61d74ba97b23547e431e9e7d64f27d4922ba96e9d6e", size = 869204 }, + { url = "https://files.pythonhosted.org/packages/0f/f9/18417771dee223ccf0f48e29adf8b4e25ba6d0e8285e33bcbce078070bc3/pyzmq-26.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0aca98bc423eb7d153214b2df397c6421ba6373d3397b26c057af3c904452e37", size = 1203351 }, + { url = "https://files.pythonhosted.org/packages/e0/46/f13e67fe0d4f8a2315782cbad50493de6203ea0d744610faf4d5f5b16e90/pyzmq-26.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f3496d76b89d9429a656293744ceca4d2ac2a10ae59b84c1da9b5165f429ad3", size = 1514204 }, + { url = "https://files.pythonhosted.org/packages/50/11/ddcf7343b7b7a226e0fc7b68cbf5a5bb56291fac07f5c3023bb4c319ebb4/pyzmq-26.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5c2b3bfd4b9689919db068ac6c9911f3fcb231c39f7dd30e3138be94896d18e6", size = 1414339 }, + { url = "https://files.pythonhosted.org/packages/01/14/1c18d7d5b7be2708f513f37c61bfadfa62161c10624f8733f1c8451b3509/pyzmq-26.2.0-cp311-cp311-win32.whl", hash = "sha256:eac5174677da084abf378739dbf4ad245661635f1600edd1221f150b165343f4", size = 576928 }, + { url = "https://files.pythonhosted.org/packages/3b/1b/0a540edd75a41df14ec416a9a500b9fec66e554aac920d4c58fbd5756776/pyzmq-26.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:5a509df7d0a83a4b178d0f937ef14286659225ef4e8812e05580776c70e155d5", size = 642317 }, + { url = "https://files.pythonhosted.org/packages/98/77/1cbfec0358078a4c5add529d8a70892db1be900980cdb5dd0898b3d6ab9d/pyzmq-26.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:c0e6091b157d48cbe37bd67233318dbb53e1e6327d6fc3bb284afd585d141003", size = 543834 }, + { url = "https://files.pythonhosted.org/packages/28/2f/78a766c8913ad62b28581777ac4ede50c6d9f249d39c2963e279524a1bbe/pyzmq-26.2.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:ded0fc7d90fe93ae0b18059930086c51e640cdd3baebdc783a695c77f123dcd9", size = 1343105 }, + { url = "https://files.pythonhosted.org/packages/b7/9c/4b1e2d3d4065be715e007fe063ec7885978fad285f87eae1436e6c3201f4/pyzmq-26.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:17bf5a931c7f6618023cdacc7081f3f266aecb68ca692adac015c383a134ca52", size = 1008365 }, + { url = "https://files.pythonhosted.org/packages/4f/ef/5a23ec689ff36d7625b38d121ef15abfc3631a9aecb417baf7a4245e4124/pyzmq-26.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55cf66647e49d4621a7e20c8d13511ef1fe1efbbccf670811864452487007e08", size = 665923 }, + { url = "https://files.pythonhosted.org/packages/ae/61/d436461a47437d63c6302c90724cf0981883ec57ceb6073873f32172d676/pyzmq-26.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4661c88db4a9e0f958c8abc2b97472e23061f0bc737f6f6179d7a27024e1faa5", size = 903400 }, + { url = "https://files.pythonhosted.org/packages/47/42/fc6d35ecefe1739a819afaf6f8e686f7f02a4dd241c78972d316f403474c/pyzmq-26.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea7f69de383cb47522c9c208aec6dd17697db7875a4674c4af3f8cfdac0bdeae", size = 860034 }, + { url = "https://files.pythonhosted.org/packages/07/3b/44ea6266a6761e9eefaa37d98fabefa112328808ac41aa87b4bbb668af30/pyzmq-26.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7f98f6dfa8b8ccaf39163ce872bddacca38f6a67289116c8937a02e30bbe9711", size = 860579 }, + { url = "https://files.pythonhosted.org/packages/38/6f/4df2014ab553a6052b0e551b37da55166991510f9e1002c89cab7ce3b3f2/pyzmq-26.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e3e0210287329272539eea617830a6a28161fbbd8a3271bf4150ae3e58c5d0e6", size = 1196246 }, + { url = "https://files.pythonhosted.org/packages/38/9d/ee240fc0c9fe9817f0c9127a43238a3e28048795483c403cc10720ddef22/pyzmq-26.2.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6b274e0762c33c7471f1a7471d1a2085b1a35eba5cdc48d2ae319f28b6fc4de3", size = 1507441 }, + { url = "https://files.pythonhosted.org/packages/85/4f/01711edaa58d535eac4a26c294c617c9a01f09857c0ce191fd574d06f359/pyzmq-26.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:29c6a4635eef69d68a00321e12a7d2559fe2dfccfa8efae3ffb8e91cd0b36a8b", size = 1406498 }, + { url = "https://files.pythonhosted.org/packages/07/18/907134c85c7152f679ed744e73e645b365f3ad571f38bdb62e36f347699a/pyzmq-26.2.0-cp312-cp312-win32.whl", hash = "sha256:989d842dc06dc59feea09e58c74ca3e1678c812a4a8a2a419046d711031f69c7", size = 575533 }, + { url = "https://files.pythonhosted.org/packages/ce/2c/a6f4a20202a4d3c582ad93f95ee78d79bbdc26803495aec2912b17dbbb6c/pyzmq-26.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:2a50625acdc7801bc6f74698c5c583a491c61d73c6b7ea4dee3901bb99adb27a", size = 637768 }, + { url = "https://files.pythonhosted.org/packages/5f/0e/eb16ff731632d30554bf5af4dbba3ffcd04518219d82028aea4ae1b02ca5/pyzmq-26.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d29ab8592b6ad12ebbf92ac2ed2bedcfd1cec192d8e559e2e099f648570e19b", size = 540675 }, + { url = "https://files.pythonhosted.org/packages/04/a7/0f7e2f6c126fe6e62dbae0bc93b1bd3f1099cf7fea47a5468defebe3f39d/pyzmq-26.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9dd8cd1aeb00775f527ec60022004d030ddc51d783d056e3e23e74e623e33726", size = 1006564 }, + { url = "https://files.pythonhosted.org/packages/31/b6/a187165c852c5d49f826a690857684333a6a4a065af0a6015572d2284f6a/pyzmq-26.2.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:28c812d9757fe8acecc910c9ac9dafd2ce968c00f9e619db09e9f8f54c3a68a3", size = 1340447 }, + { url = "https://files.pythonhosted.org/packages/68/ba/f4280c58ff71f321602a6e24fd19879b7e79793fb8ab14027027c0fb58ef/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d80b1dd99c1942f74ed608ddb38b181b87476c6a966a88a950c7dee118fdf50", size = 665485 }, + { url = "https://files.pythonhosted.org/packages/77/b5/c987a5c53c7d8704216f29fc3d810b32f156bcea488a940e330e1bcbb88d/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c997098cc65e3208eca09303630e84d42718620e83b733d0fd69543a9cab9cb", size = 903484 }, + { url = "https://files.pythonhosted.org/packages/29/c9/07da157d2db18c72a7eccef8e684cefc155b712a88e3d479d930aa9eceba/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ad1bc8d1b7a18497dda9600b12dc193c577beb391beae5cd2349184db40f187", size = 859981 }, + { url = "https://files.pythonhosted.org/packages/43/09/e12501bd0b8394b7d02c41efd35c537a1988da67fc9c745cae9c6c776d31/pyzmq-26.2.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bea2acdd8ea4275e1278350ced63da0b166421928276c7c8e3f9729d7402a57b", size = 860334 }, + { url = "https://files.pythonhosted.org/packages/eb/ff/f5ec1d455f8f7385cc0a8b2acd8c807d7fade875c14c44b85c1bddabae21/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:23f4aad749d13698f3f7b64aad34f5fc02d6f20f05999eebc96b89b01262fb18", size = 1196179 }, + { url = "https://files.pythonhosted.org/packages/ec/8a/bb2ac43295b1950fe436a81fc5b298be0b96ac76fb029b514d3ed58f7b27/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a4f96f0d88accc3dbe4a9025f785ba830f968e21e3e2c6321ccdfc9aef755115", size = 1507668 }, + { url = "https://files.pythonhosted.org/packages/a9/49/dbc284ebcfd2dca23f6349227ff1616a7ee2c4a35fe0a5d6c3deff2b4fed/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ced65e5a985398827cc9276b93ef6dfabe0273c23de8c7931339d7e141c2818e", size = 1406539 }, + { url = "https://files.pythonhosted.org/packages/00/68/093cdce3fe31e30a341d8e52a1ad86392e13c57970d722c1f62a1d1a54b6/pyzmq-26.2.0-cp313-cp313-win32.whl", hash = "sha256:31507f7b47cc1ead1f6e86927f8ebb196a0bab043f6345ce070f412a59bf87b5", size = 575567 }, + { url = "https://files.pythonhosted.org/packages/92/ae/6cc4657148143412b5819b05e362ae7dd09fb9fe76e2a539dcff3d0386bc/pyzmq-26.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:70fc7fcf0410d16ebdda9b26cbd8bf8d803d220a7f3522e060a69a9c87bf7bad", size = 637551 }, + { url = "https://files.pythonhosted.org/packages/6c/67/fbff102e201688f97c8092e4c3445d1c1068c2f27bbd45a578df97ed5f94/pyzmq-26.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:c3789bd5768ab5618ebf09cef6ec2b35fed88709b104351748a63045f0ff9797", size = 540378 }, + { url = "https://files.pythonhosted.org/packages/3f/fe/2d998380b6e0122c6c4bdf9b6caf490831e5f5e2d08a203b5adff060c226/pyzmq-26.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:034da5fc55d9f8da09015d368f519478a52675e558c989bfcb5cf6d4e16a7d2a", size = 1007378 }, + { url = "https://files.pythonhosted.org/packages/4a/f4/30d6e7157f12b3a0390bde94d6a8567cdb88846ed068a6e17238a4ccf600/pyzmq-26.2.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:c92d73464b886931308ccc45b2744e5968cbaade0b1d6aeb40d8ab537765f5bc", size = 1329532 }, + { url = "https://files.pythonhosted.org/packages/82/86/3fe917870e15ee1c3ad48229a2a64458e36036e64b4afa9659045d82bfa8/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:794a4562dcb374f7dbbfb3f51d28fb40123b5a2abadee7b4091f93054909add5", size = 653242 }, + { url = "https://files.pythonhosted.org/packages/50/2d/242e7e6ef6c8c19e6cb52d095834508cd581ffb925699fd3c640cdc758f1/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aee22939bb6075e7afededabad1a56a905da0b3c4e3e0c45e75810ebe3a52672", size = 888404 }, + { url = "https://files.pythonhosted.org/packages/ac/11/7270566e1f31e4ea73c81ec821a4b1688fd551009a3d2bab11ec66cb1e8f/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ae90ff9dad33a1cfe947d2c40cb9cb5e600d759ac4f0fd22616ce6540f72797", size = 845858 }, + { url = "https://files.pythonhosted.org/packages/91/d5/72b38fbc69867795c8711bdd735312f9fef1e3d9204e2f63ab57085434b9/pyzmq-26.2.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:43a47408ac52647dfabbc66a25b05b6a61700b5165807e3fbd40063fcaf46386", size = 847375 }, + { url = "https://files.pythonhosted.org/packages/dd/9a/10ed3c7f72b4c24e719c59359fbadd1a27556a28b36cdf1cd9e4fb7845d5/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:25bf2374a2a8433633c65ccb9553350d5e17e60c8eb4de4d92cc6bd60f01d306", size = 1183489 }, + { url = "https://files.pythonhosted.org/packages/72/2d/8660892543fabf1fe41861efa222455811adac9f3c0818d6c3170a1153e3/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6", size = 1492932 }, + { url = "https://files.pythonhosted.org/packages/7b/d6/32fd69744afb53995619bc5effa2a405ae0d343cd3e747d0fbc43fe894ee/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0", size = 1392485 }, + { url = "https://files.pythonhosted.org/packages/53/fb/36b2b2548286e9444e52fcd198760af99fd89102b5be50f0660fcfe902df/pyzmq-26.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:706e794564bec25819d21a41c31d4df2d48e1cc4b061e8d345d7fb4dd3e94072", size = 906955 }, + { url = "https://files.pythonhosted.org/packages/77/8f/6ce54f8979a01656e894946db6299e2273fcee21c8e5fa57c6295ef11f57/pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b435f2753621cd36e7c1762156815e21c985c72b19135dac43a7f4f31d28dd1", size = 565701 }, + { url = "https://files.pythonhosted.org/packages/ee/1c/bf8cd66730a866b16db8483286078892b7f6536f8c389fb46e4beba0a970/pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160c7e0a5eb178011e72892f99f918c04a131f36056d10d9c1afb223fc952c2d", size = 794312 }, + { url = "https://files.pythonhosted.org/packages/71/43/91fa4ff25bbfdc914ab6bafa0f03241d69370ef31a761d16bb859f346582/pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4a71d5d6e7b28a47a394c0471b7e77a0661e2d651e7ae91e0cab0a587859ca", size = 752775 }, + { url = "https://files.pythonhosted.org/packages/ec/d2/3b2ab40f455a256cb6672186bea95cd97b459ce4594050132d71e76f0d6f/pyzmq-26.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:90412f2db8c02a3864cbfc67db0e3dcdbda336acf1c469526d3e869394fe001c", size = 550762 }, +] + +[[package]] +name = "qtconsole" +version = "5.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipykernel" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "qtpy" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/75/c8f60478f682f4ba408eddc35084fea0ffeffc8b18e671294726b00d4c58/qtconsole-5.6.0.tar.gz", hash = "sha256:4c82120a3b53a3d36e3f76e6a1a26ffddf4e1ce2359d56a19889c55e1d73a436", size = 440531 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/30/4b4d21319030d1574d28a503b8680e111b6d9a3e963cc664f50ef084477b/qtconsole-5.6.0-py3-none-any.whl", hash = "sha256:c36e0d497a473b67898b96dd38666e645e4594019244263da7b409b84fa2ebb5", size = 124660 }, +] + +[[package]] +name = "qtpy" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/9a/7ce646daefb2f85bf5b9c8ac461508b58fa5dcad6d40db476187fafd0148/QtPy-2.4.1.tar.gz", hash = "sha256:a5a15ffd519550a1361bdc56ffc07fda56a6af7292f17c7b395d4083af632987", size = 65492 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/a9/2146d5117ad8a81185331e0809a6b48933c10171f5bac253c6df9fce991c/QtPy-2.4.1-py3-none-any.whl", hash = "sha256:1c1d8c4fa2c884ae742b069151b0abe15b3f70491f3972698c683b8e38de839b", size = 93500 }, +] + +[[package]] +name = "referencing" +version = "0.35.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/73ca1f8e72fff6fa52119dbd185f73a907b1989428917b24cff660129b6d/referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c", size = 62991 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/59/2056f61236782a2c86b33906c025d4f4a0b17be0161b63b70fd9e8775d36/referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de", size = 26684 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481 }, +] + +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490 }, +] + +[[package]] +name = "rfc3986-validator" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242 }, +] + +[[package]] +name = "rich" +version = "13.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/01/c954e134dc440ab5f96952fe52b4fdc64225530320a910473c1fe270d9aa/rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432", size = 221248 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/67/a37f6214d0e9fe57f6ae54b2956d550ca8365857f42a1ce0392bb21d9410/rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", size = 240681 }, +] + +[[package]] +name = "rpds-py" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/55/64/b693f262791b818880d17268f3f8181ef799b0d187f6f731b1772e05a29a/rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121", size = 25814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/2d/a7e60483b72b91909e18f29a5c5ae847bac4e2ae95b77bb77e1f41819a58/rpds_py-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2", size = 318432 }, + { url = "https://files.pythonhosted.org/packages/b5/b4/f15b0c55a6d880ce74170e7e28c3ed6c5acdbbd118df50b91d1dabf86008/rpds_py-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f", size = 311333 }, + { url = "https://files.pythonhosted.org/packages/36/10/3f4e490fe6eb069c07c22357d0b4804cd94cb9f8d01345ef9b1d93482b9d/rpds_py-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150", size = 366697 }, + { url = "https://files.pythonhosted.org/packages/f5/c8/cd6ab31b4424c7fab3b17e153b6ea7d1bb0d7cabea5c1ef683cc8adb8bc2/rpds_py-0.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e", size = 368386 }, + { url = "https://files.pythonhosted.org/packages/60/5e/642a44fda6dda90b5237af7a0ef1d088159c30a504852b94b0396eb62125/rpds_py-0.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2", size = 395374 }, + { url = "https://files.pythonhosted.org/packages/7c/b5/ff18c093c9e72630f6d6242e5ccb0728ef8265ba0a154b5972f89d23790a/rpds_py-0.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3", size = 433189 }, + { url = "https://files.pythonhosted.org/packages/4a/6d/1166a157b227f2333f8e8ae320b6b7ea2a6a38fbe7a3563ad76dffc8608d/rpds_py-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf", size = 354849 }, + { url = "https://files.pythonhosted.org/packages/70/a4/70ea49863ea09ae4c2971f2eef58e80b757e3c0f2f618c5815bb751f7847/rpds_py-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140", size = 373233 }, + { url = "https://files.pythonhosted.org/packages/3b/d3/822a28152a1e7e2ba0dc5d06cf8736f4cd64b191bb6ec47fb51d1c3c5ccf/rpds_py-0.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f", size = 541852 }, + { url = "https://files.pythonhosted.org/packages/c6/a5/6ef91e4425dc8b3445ff77d292fc4c5e37046462434a0423c4e0a596a8bd/rpds_py-0.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce", size = 547630 }, + { url = "https://files.pythonhosted.org/packages/72/f8/d5625ee05c4e5c478954a16d9359069c66fe8ac8cd5ddf28f80d3b321837/rpds_py-0.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94", size = 525766 }, + { url = "https://files.pythonhosted.org/packages/94/3c/1ff1ed6ae323b3e16fdfcdae0f0a67f373a6c3d991229dc32b499edeffb7/rpds_py-0.20.0-cp310-none-win32.whl", hash = "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee", size = 199174 }, + { url = "https://files.pythonhosted.org/packages/ec/ba/5762c0aee2403dfea14ed74b0f8a2415cfdbb21cf745d600d9a8ac952c5b/rpds_py-0.20.0-cp310-none-win_amd64.whl", hash = "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399", size = 213543 }, + { url = "https://files.pythonhosted.org/packages/ab/2a/191374c52d7be0b056cc2a04d718d2244c152f915d4a8d2db2aacc526189/rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489", size = 318369 }, + { url = "https://files.pythonhosted.org/packages/0e/6a/2c9fdcc6d235ac0d61ec4fd9981184689c3e682abd05e3caa49bccb9c298/rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318", size = 311303 }, + { url = "https://files.pythonhosted.org/packages/d2/b2/725487d29633f64ef8f9cbf4729111a0b61702c8f8e94db1653930f52cce/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db", size = 366424 }, + { url = "https://files.pythonhosted.org/packages/7a/8c/668195ab9226d01b7cf7cd9e59c1c0be1df05d602df7ec0cf46f857dcf59/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5", size = 368359 }, + { url = "https://files.pythonhosted.org/packages/52/28/356f6a39c1adeb02cf3e5dd526f5e8e54e17899bef045397abcfbf50dffa/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5", size = 394886 }, + { url = "https://files.pythonhosted.org/packages/a2/65/640fb1a89080a8fb6f4bebd3dafb65a2edba82e2e44c33e6eb0f3e7956f1/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6", size = 432416 }, + { url = "https://files.pythonhosted.org/packages/a7/e8/85835077b782555d6b3416874b702ea6ebd7db1f145283c9252968670dd5/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209", size = 354819 }, + { url = "https://files.pythonhosted.org/packages/4f/87/1ac631e923d65cbf36fbcfc6eaa702a169496de1311e54be142f178e53ee/rpds_py-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3", size = 373282 }, + { url = "https://files.pythonhosted.org/packages/e4/ce/cb316f7970189e217b998191c7cf0da2ede3d5437932c86a7210dc1e9994/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272", size = 541540 }, + { url = "https://files.pythonhosted.org/packages/90/d7/4112d7655ec8aff168ecc91d4ceb51c557336edde7e6ccf6463691a2f253/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad", size = 547640 }, + { url = "https://files.pythonhosted.org/packages/ab/44/4f61d64dfed98cc71623f3a7fcb612df636a208b4b2c6611eaa985e130a9/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58", size = 525555 }, + { url = "https://files.pythonhosted.org/packages/35/f2/a862d81eacb21f340d584cd1c749c289979f9a60e9229f78bffc0418a199/rpds_py-0.20.0-cp311-none-win32.whl", hash = "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0", size = 199338 }, + { url = "https://files.pythonhosted.org/packages/cc/ec/77d0674f9af4872919f3738018558dd9d37ad3f7ad792d062eadd4af7cba/rpds_py-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c", size = 213585 }, + { url = "https://files.pythonhosted.org/packages/89/b7/f9682c5cc37fcc035f4a0fc33c1fe92ec9cbfdee0cdfd071cf948f53e0df/rpds_py-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6", size = 321468 }, + { url = "https://files.pythonhosted.org/packages/b8/ad/fc82be4eaceb8d444cb6fc1956ce972b3a0795104279de05e0e4131d0a47/rpds_py-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b", size = 313062 }, + { url = "https://files.pythonhosted.org/packages/0e/1c/6039e80b13a08569a304dc13476dc986352dca4598e909384db043b4e2bb/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739", size = 370168 }, + { url = "https://files.pythonhosted.org/packages/dc/c9/5b9aa35acfb58946b4b785bc8e700ac313669e02fb100f3efa6176a83e81/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c", size = 371376 }, + { url = "https://files.pythonhosted.org/packages/7b/dd/0e0dbeb70d8a5357d2814764d467ded98d81d90d3570de4fb05ec7224f6b/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee", size = 397200 }, + { url = "https://files.pythonhosted.org/packages/e4/da/a47d931eb688ccfd77a7389e45935c79c41e8098d984d87335004baccb1d/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96", size = 426824 }, + { url = "https://files.pythonhosted.org/packages/0f/f7/a59a673594e6c2ff2dbc44b00fd4ecdec2fc399bb6a7bd82d612699a0121/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4", size = 357967 }, + { url = "https://files.pythonhosted.org/packages/5f/61/3ba1905396b2cb7088f9503a460b87da33452da54d478cb9241f6ad16d00/rpds_py-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef", size = 378905 }, + { url = "https://files.pythonhosted.org/packages/08/31/6d0df9356b4edb0a3a077f1ef714e25ad21f9f5382fc490c2383691885ea/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821", size = 546348 }, + { url = "https://files.pythonhosted.org/packages/ae/15/d33c021de5cb793101df9961c3c746dfc476953dbbf5db337d8010dffd4e/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940", size = 553152 }, + { url = "https://files.pythonhosted.org/packages/70/2d/5536d28c507a4679179ab15aa0049440e4d3dd6752050fa0843ed11e9354/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174", size = 528807 }, + { url = "https://files.pythonhosted.org/packages/e3/62/7ebe6ec0d3dd6130921f8cffb7e34afb7f71b3819aa0446a24c5e81245ec/rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139", size = 200993 }, + { url = "https://files.pythonhosted.org/packages/ec/2f/b938864d66b86a6e4acadefdc56de75ef56f7cafdfd568a6464605457bd5/rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585", size = 214458 }, + { url = "https://files.pythonhosted.org/packages/99/32/43b919a0a423c270a838ac2726b1c7168b946f2563fd99a51aaa9692d00f/rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29", size = 321465 }, + { url = "https://files.pythonhosted.org/packages/58/a9/c4d899cb28e9e47b0ff12462e8f827381f243176036f17bef9c1604667f2/rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91", size = 312900 }, + { url = "https://files.pythonhosted.org/packages/8f/90/9e51670575b5dfaa8c823369ef7d943087bfb73d4f124a99ad6ef19a2b26/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24", size = 370973 }, + { url = "https://files.pythonhosted.org/packages/fc/c1/523f2a03f853fc0d4c1acbef161747e9ab7df0a8abf6236106e333540921/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7", size = 370890 }, + { url = "https://files.pythonhosted.org/packages/51/ca/2458a771f16b0931de4d384decbe43016710bc948036c8f4562d6e063437/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9", size = 397174 }, + { url = "https://files.pythonhosted.org/packages/00/7d/6e06807f6305ea2408b364efb0eef83a6e21b5e7b5267ad6b473b9a7e416/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8", size = 426449 }, + { url = "https://files.pythonhosted.org/packages/8c/d1/6c9e65260a819a1714510a7d69ac1d68aa23ee9ce8a2d9da12187263c8fc/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879", size = 357698 }, + { url = "https://files.pythonhosted.org/packages/5d/fb/ecea8b5286d2f03eec922be7173a03ed17278944f7c124348f535116db15/rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f", size = 378530 }, + { url = "https://files.pythonhosted.org/packages/e3/e3/ac72f858957f52a109c588589b73bd2fad4a0fc82387fb55fb34aeb0f9cd/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c", size = 545753 }, + { url = "https://files.pythonhosted.org/packages/b2/a4/a27683b519d5fc98e4390a3b130117d80fd475c67aeda8aac83c0e8e326a/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2", size = 552443 }, + { url = "https://files.pythonhosted.org/packages/a1/ed/c074d248409b4432b1ccb2056974175fa0af2d1bc1f9c21121f80a358fa3/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57", size = 528380 }, + { url = "https://files.pythonhosted.org/packages/d5/bd/04caf938895d2d78201e89c0c8a94dfd9990c34a19ff52fb01d0912343e3/rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a", size = 200540 }, + { url = "https://files.pythonhosted.org/packages/95/cc/109eb8b9863680411ae703664abacaa035820c7755acc9686d5dd02cdd2e/rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2", size = 214111 }, + { url = "https://files.pythonhosted.org/packages/06/39/bf1f664c347c946ef56cecaa896e3693d91acc741afa78ebb3fdb7aba08b/rpds_py-0.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045", size = 319444 }, + { url = "https://files.pythonhosted.org/packages/c1/71/876135d3cb90d62468540b84e8e83ff4dc92052ab309bfdea7ea0b9221ad/rpds_py-0.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc", size = 311699 }, + { url = "https://files.pythonhosted.org/packages/f7/da/8ccaeba6a3dda7467aebaf893de9eafd56275e2c90773c83bf15fb0b8374/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02", size = 367825 }, + { url = "https://files.pythonhosted.org/packages/04/b6/02a54c47c178d180395b3c9a8bfb3b93906e08f9acf7b4a1067d27c3fae0/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92", size = 369046 }, + { url = "https://files.pythonhosted.org/packages/a7/64/df4966743aa4def8727dc13d06527c8b13eb7412c1429def2d4701bee520/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d", size = 395896 }, + { url = "https://files.pythonhosted.org/packages/6f/d9/7ff03ff3642c600f27ff94512bb158a8d815fea5ed4162c75a7e850d6003/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855", size = 432427 }, + { url = "https://files.pythonhosted.org/packages/b8/c6/e1b886f7277b3454e55e85332e165091c19114eecb5377b88d892fd36ccf/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511", size = 355403 }, + { url = "https://files.pythonhosted.org/packages/e2/62/e26bd5b944e547c7bfd0b6ca7e306bfa430f8bd298ab72a1217976a7ca8d/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51", size = 374491 }, + { url = "https://files.pythonhosted.org/packages/c3/92/93c5a530898d3a5d1ce087455071ba714b77806ed9ffee4070d0c7a53b7e/rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075", size = 543622 }, + { url = "https://files.pythonhosted.org/packages/01/9e/d68fba289625b5d3c9d1925825d7da716fbf812bda2133ac409021d5db13/rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60", size = 548558 }, + { url = "https://files.pythonhosted.org/packages/bf/d6/4b2fad4898154365f0f2bd72ffd190349274a4c1d6a6f94f02a83bb2b8f1/rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344", size = 525753 }, + { url = "https://files.pythonhosted.org/packages/d2/ea/6f121d1802f3adae1981aea4209ea66f9d3c7f2f6d6b85ef4f13a61d17ef/rpds_py-0.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989", size = 213529 }, +] + +[[package]] +name = "ruamel-yaml" +version = "0.18.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ruamel-yaml-clib", marker = "python_full_version < '3.13' and platform_python_implementation == 'CPython'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/81/4dfc17eb6ebb1aac314a3eb863c1325b907863a1b8b1382cdffcb6ac0ed9/ruamel.yaml-0.18.6.tar.gz", hash = "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b", size = 143362 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/67/8ece580cc363331d9a53055130f86b096bf16e38156e33b1d3014fffda6b/ruamel.yaml-0.18.6-py3-none-any.whl", hash = "sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636", size = 117761 }, +] + +[[package]] +name = "ruamel-yaml-clib" +version = "0.2.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/84/80203abff8ea4993a87d823a5f632e4d92831ef75d404c9fc78d0176d2b5/ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f", size = 225315 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/57/40a958e863e299f0c74ef32a3bde9f2d1ea8d69669368c0c502a0997f57f/ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5", size = 131301 }, + { url = "https://files.pythonhosted.org/packages/98/a8/29a3eb437b12b95f50a6bcc3d7d7214301c6c529d8fdc227247fa84162b5/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969", size = 633728 }, + { url = "https://files.pythonhosted.org/packages/35/6d/ae05a87a3ad540259c3ad88d71275cbd1c0f2d30ae04c65dcbfb6dcd4b9f/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df", size = 722230 }, + { url = "https://files.pythonhosted.org/packages/7f/b7/20c6f3c0b656fe609675d69bc135c03aac9e3865912444be6339207b6648/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76", size = 686712 }, + { url = "https://files.pythonhosted.org/packages/cd/11/d12dbf683471f888d354dac59593873c2b45feb193c5e3e0f2ebf85e68b9/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6", size = 663936 }, + { url = "https://files.pythonhosted.org/packages/72/14/4c268f5077db5c83f743ee1daeb236269fa8577133a5cfa49f8b382baf13/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd", size = 696580 }, + { url = "https://files.pythonhosted.org/packages/80/29/c0a017b704aaf3cbf704989785cd9c5d5b8ccec2dae6ac0c53833c84e677/ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da", size = 100326 }, + { url = "https://files.pythonhosted.org/packages/3a/65/fa39d74db4e2d0cd252355732d966a460a41cd01c6353b820a0952432839/ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28", size = 118079 }, + { url = "https://files.pythonhosted.org/packages/fb/8f/683c6ad562f558cbc4f7c029abcd9599148c51c54b5ef0f24f2638da9fbb/ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6", size = 132224 }, + { url = "https://files.pythonhosted.org/packages/3c/d2/b79b7d695e2f21da020bd44c782490578f300dd44f0a4c57a92575758a76/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e", size = 641480 }, + { url = "https://files.pythonhosted.org/packages/68/6e/264c50ce2a31473a9fdbf4fa66ca9b2b17c7455b31ef585462343818bd6c/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e", size = 739068 }, + { url = "https://files.pythonhosted.org/packages/86/29/88c2567bc893c84d88b4c48027367c3562ae69121d568e8a3f3a8d363f4d/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52", size = 703012 }, + { url = "https://files.pythonhosted.org/packages/11/46/879763c619b5470820f0cd6ca97d134771e502776bc2b844d2adb6e37753/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642", size = 704352 }, + { url = "https://files.pythonhosted.org/packages/02/80/ece7e6034256a4186bbe50dee28cd032d816974941a6abf6a9d65e4228a7/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2", size = 737344 }, + { url = "https://files.pythonhosted.org/packages/67/58/b1f60a1d591b771298ffa0428237afb092c7f29ae23bad93420b1eb10703/ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4", size = 100205 }, + { url = "https://files.pythonhosted.org/packages/b4/4f/b52f634c9548a9291a70dfce26ca7ebce388235c93588a1068028ea23fcc/ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb", size = 118185 }, + { url = "https://files.pythonhosted.org/packages/48/41/e7a405afbdc26af961678474a55373e1b323605a4f5e2ddd4a80ea80f628/ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632", size = 133433 }, + { url = "https://files.pythonhosted.org/packages/ec/b0/b850385604334c2ce90e3ee1013bd911aedf058a934905863a6ea95e9eb4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d", size = 647362 }, + { url = "https://files.pythonhosted.org/packages/44/d0/3f68a86e006448fb6c005aee66565b9eb89014a70c491d70c08de597f8e4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c", size = 754118 }, + { url = "https://files.pythonhosted.org/packages/52/a9/d39f3c5ada0a3bb2870d7db41901125dbe2434fa4f12ca8c5b83a42d7c53/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd", size = 706497 }, + { url = "https://files.pythonhosted.org/packages/b0/fa/097e38135dadd9ac25aecf2a54be17ddf6e4c23e43d538492a90ab3d71c6/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31", size = 698042 }, + { url = "https://files.pythonhosted.org/packages/ec/d5/a659ca6f503b9379b930f13bc6b130c9f176469b73b9834296822a83a132/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680", size = 745831 }, + { url = "https://files.pythonhosted.org/packages/b1/82/85cb92f15a4231c89b95dfe08b09eb6adca929ef7df7e17ab59902b6f589/ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5", size = 98777 }, + { url = "https://files.pythonhosted.org/packages/d7/8f/c3654f6f1ddb75daf3922c3d8fc6005b1ab56671ad56ffb874d908bfa668/ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4", size = 115523 }, + { url = "https://files.pythonhosted.org/packages/29/00/4864119668d71a5fa45678f380b5923ff410701565821925c69780356ffa/ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a", size = 132011 }, + { url = "https://files.pythonhosted.org/packages/7f/5e/212f473a93ae78c669ffa0cb051e3fee1139cb2d385d2ae1653d64281507/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475", size = 642488 }, + { url = "https://files.pythonhosted.org/packages/1f/8f/ecfbe2123ade605c49ef769788f79c38ddb1c8fa81e01f4dbf5cf1a44b16/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef", size = 745066 }, + { url = "https://files.pythonhosted.org/packages/e2/a9/28f60726d29dfc01b8decdb385de4ced2ced9faeb37a847bd5cf26836815/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6", size = 701785 }, + { url = "https://files.pythonhosted.org/packages/84/7e/8e7ec45920daa7f76046578e4f677a3215fe8f18ee30a9cb7627a19d9b4c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf", size = 693017 }, + { url = "https://files.pythonhosted.org/packages/c5/b3/d650eaade4ca225f02a648321e1ab835b9d361c60d51150bac49063b83fa/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1", size = 741270 }, + { url = "https://files.pythonhosted.org/packages/30/8c/ed73f047a73638257aa9377ad356bea4d96125b305c34a28766f4445cc0f/ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6", size = 98583 }, + { url = "https://files.pythonhosted.org/packages/b0/85/e8e751d8791564dd333d5d9a4eab0a7a115f7e349595417fd50ecae3395c/ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3", size = 115190 }, +] + +[[package]] +name = "scikit-learn" +version = "1.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/59/44985a2bdc95c74e34fef3d10cb5d93ce13b0e2a7baefffe1b53853b502d/scikit_learn-1.5.2.tar.gz", hash = "sha256:b4237ed7b3fdd0a4882792e68ef2545d5baa50aca3bb45aa7df468138ad8f94d", size = 7001680 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/89/be41419b4bec629a4691183a5eb1796f91252a13a5ffa243fd958cad7e91/scikit_learn-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:299406827fb9a4f862626d0fe6c122f5f87f8910b86fe5daa4c32dcd742139b6", size = 12106070 }, + { url = "https://files.pythonhosted.org/packages/bf/e0/3b6d777d375f3b685f433c93384cdb724fb078e1dc8f8ff0950467e56c30/scikit_learn-1.5.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:2d4cad1119c77930b235579ad0dc25e65c917e756fe80cab96aa3b9428bd3fb0", size = 10971758 }, + { url = "https://files.pythonhosted.org/packages/7b/31/eb7dd56c371640753953277de11356c46a3149bfeebb3d7dcd90b993715a/scikit_learn-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c412ccc2ad9bf3755915e3908e677b367ebc8d010acbb3f182814524f2e5540", size = 12500080 }, + { url = "https://files.pythonhosted.org/packages/4c/1e/a7c7357e704459c7d56a18df4a0bf08669442d1f8878cc0864beccd6306a/scikit_learn-1.5.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a686885a4b3818d9e62904d91b57fa757fc2bed3e465c8b177be652f4dd37c8", size = 13347241 }, + { url = "https://files.pythonhosted.org/packages/48/76/154ebda6794faf0b0f3ccb1b5cd9a19f0a63cb9e1f3d2c61b6114002677b/scikit_learn-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:c15b1ca23d7c5f33cc2cb0a0d6aaacf893792271cddff0edbd6a40e8319bc113", size = 11000477 }, + { url = "https://files.pythonhosted.org/packages/ff/91/609961972f694cb9520c4c3d201e377a26583e1eb83bc5a334c893729214/scikit_learn-1.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03b6158efa3faaf1feea3faa884c840ebd61b6484167c711548fce208ea09445", size = 12088580 }, + { url = "https://files.pythonhosted.org/packages/cd/7a/19fe32c810c5ceddafcfda16276d98df299c8649e24e84d4f00df4a91e01/scikit_learn-1.5.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1ff45e26928d3b4eb767a8f14a9a6efbf1cbff7c05d1fb0f95f211a89fd4f5de", size = 10975994 }, + { url = "https://files.pythonhosted.org/packages/4c/75/62e49f8a62bf3c60b0e64d0fce540578ee4f0e752765beb2e1dc7c6d6098/scikit_learn-1.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f763897fe92d0e903aa4847b0aec0e68cadfff77e8a0687cabd946c89d17e675", size = 12465782 }, + { url = "https://files.pythonhosted.org/packages/49/21/3723de321531c9745e40f1badafd821e029d346155b6c79704e0b7197552/scikit_learn-1.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8b0ccd4a902836493e026c03256e8b206656f91fbcc4fde28c57a5b752561f1", size = 13322034 }, + { url = "https://files.pythonhosted.org/packages/17/1c/ccdd103cfcc9435a18819856fbbe0c20b8fa60bfc3343580de4be13f0668/scikit_learn-1.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:6c16d84a0d45e4894832b3c4d0bf73050939e21b99b01b6fd59cbb0cf39163b6", size = 11015224 }, + { url = "https://files.pythonhosted.org/packages/a4/db/b485c1ac54ff3bd9e7e6b39d3cc6609c4c76a65f52ab0a7b22b6c3ab0e9d/scikit_learn-1.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f932a02c3f4956dfb981391ab24bda1dbd90fe3d628e4b42caef3e041c67707a", size = 12110344 }, + { url = "https://files.pythonhosted.org/packages/54/1a/7deb52fa23aebb855431ad659b3c6a2e1709ece582cb3a63d66905e735fe/scikit_learn-1.5.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:3b923d119d65b7bd555c73be5423bf06c0105678ce7e1f558cb4b40b0a5502b1", size = 11033502 }, + { url = "https://files.pythonhosted.org/packages/a1/32/4a7a205b14c11225609b75b28402c196e4396ac754dab6a81971b811781c/scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f60021ec1574e56632be2a36b946f8143bf4e5e6af4a06d85281adc22938e0dd", size = 12085794 }, + { url = "https://files.pythonhosted.org/packages/c6/29/044048c5e911373827c0e1d3051321b9183b2a4f8d4e2f11c08fcff83f13/scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:394397841449853c2290a32050382edaec3da89e35b3e03d6cc966aebc6a8ae6", size = 12945797 }, + { url = "https://files.pythonhosted.org/packages/aa/ce/c0b912f2f31aeb1b756a6ba56bcd84dd1f8a148470526a48515a3f4d48cd/scikit_learn-1.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:57cc1786cfd6bd118220a92ede80270132aa353647684efa385a74244a41e3b1", size = 10985467 }, + { url = "https://files.pythonhosted.org/packages/a4/50/8891028437858cc510e13578fe7046574a60c2aaaa92b02d64aac5b1b412/scikit_learn-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9a702e2de732bbb20d3bad29ebd77fc05a6b427dc49964300340e4c9328b3f5", size = 12025584 }, + { url = "https://files.pythonhosted.org/packages/d2/79/17feef8a1c14149436083bec0e61d7befb4812e272d5b20f9d79ea3e9ab1/scikit_learn-1.5.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:b0768ad641981f5d3a198430a1d31c3e044ed2e8a6f22166b4d546a5116d7908", size = 10959795 }, + { url = "https://files.pythonhosted.org/packages/b1/c8/f08313f9e2e656bd0905930ae8bf99a573ea21c34666a813b749c338202f/scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:178ddd0a5cb0044464fc1bfc4cca5b1833bfc7bb022d70b05db8530da4bb3dd3", size = 12077302 }, + { url = "https://files.pythonhosted.org/packages/a7/48/fbfb4dc72bed0fe31fe045fb30e924909ad03f717c36694351612973b1a9/scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7284ade780084d94505632241bf78c44ab3b6f1e8ccab3d2af58e0e950f9c12", size = 13002811 }, + { url = "https://files.pythonhosted.org/packages/a5/e7/0c869f9e60d225a77af90d2aefa7a4a4c0e745b149325d1450f0f0ce5399/scikit_learn-1.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:b7b0f9a0b1040830d38c39b91b3a44e1b643f4b36e36567b80b7c6bd2202a27f", size = 10951354 }, +] + +[[package]] +name = "scikit-optimize" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyaml" }, + { name = "scikit-learn" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/95/1b433b9eb9eb653fb97fd525552fd027886e3812d7d20d843994263340aa/scikit_optimize-0.10.2.tar.gz", hash = "sha256:00a3d91bf9015e292b6e7aaefe7e6cb95e8d25ce19adafd2cd88849e1a0b0da0", size = 86202 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/cd/15c9ebea645cc9860aa71fe0474f4be981f10ed8e19e1fb0ef1027d4966e/scikit_optimize-0.10.2-py2.py3-none-any.whl", hash = "sha256:45bc7e879b086133984721f2f6735a86c085073f6c481c2ec665b5c67b44d723", size = 107794 }, +] + +[[package]] +name = "scipy" +version = "1.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/11/4d44a1f274e002784e4dbdb81e0ea96d2de2d1045b2132d5af62cc31fd28/scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417", size = 58620554 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/68/3bc0cfaf64ff507d82b1e5d5b64521df4c8bf7e22bc0b897827cbee9872c/scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389", size = 39069598 }, + { url = "https://files.pythonhosted.org/packages/43/a5/8d02f9c372790326ad405d94f04d4339482ec082455b9e6e288f7100513b/scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3", size = 29879676 }, + { url = "https://files.pythonhosted.org/packages/07/42/0e0bea9666fcbf2cb6ea0205db42c81b1f34d7b729ba251010edf9c80ebd/scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0", size = 23088696 }, + { url = "https://files.pythonhosted.org/packages/15/47/298ab6fef5ebf31b426560e978b8b8548421d4ed0bf99263e1eb44532306/scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3", size = 25470699 }, + { url = "https://files.pythonhosted.org/packages/d8/df/cdb6be5274bc694c4c22862ac3438cb04f360ed9df0aecee02ce0b798380/scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d", size = 35606631 }, + { url = "https://files.pythonhosted.org/packages/47/78/b0c2c23880dd1e99e938ad49ccfb011ae353758a2dc5ed7ee59baff684c3/scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69", size = 41178528 }, + { url = "https://files.pythonhosted.org/packages/5d/aa/994b45c34b897637b853ec04334afa55a85650a0d11dacfa67232260fb0a/scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad", size = 42784535 }, + { url = "https://files.pythonhosted.org/packages/e7/1c/8daa6df17a945cb1a2a1e3bae3c49643f7b3b94017ff01a4787064f03f84/scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5", size = 44772117 }, + { url = "https://files.pythonhosted.org/packages/b2/ab/070ccfabe870d9f105b04aee1e2860520460ef7ca0213172abfe871463b9/scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675", size = 39076999 }, + { url = "https://files.pythonhosted.org/packages/a7/c5/02ac82f9bb8f70818099df7e86c3ad28dae64e1347b421d8e3adf26acab6/scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2", size = 29894570 }, + { url = "https://files.pythonhosted.org/packages/ed/05/7f03e680cc5249c4f96c9e4e845acde08eb1aee5bc216eff8a089baa4ddb/scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617", size = 23103567 }, + { url = "https://files.pythonhosted.org/packages/5e/fc/9f1413bef53171f379d786aabc104d4abeea48ee84c553a3e3d8c9f96a9c/scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8", size = 25499102 }, + { url = "https://files.pythonhosted.org/packages/c2/4b/b44bee3c2ddc316b0159b3d87a3d467ef8d7edfd525e6f7364a62cd87d90/scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37", size = 35586346 }, + { url = "https://files.pythonhosted.org/packages/93/6b/701776d4bd6bdd9b629c387b5140f006185bd8ddea16788a44434376b98f/scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2", size = 41165244 }, + { url = "https://files.pythonhosted.org/packages/06/57/e6aa6f55729a8f245d8a6984f2855696c5992113a5dc789065020f8be753/scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2", size = 42817917 }, + { url = "https://files.pythonhosted.org/packages/ea/c2/5ecadc5fcccefaece775feadcd795060adf5c3b29a883bff0e678cfe89af/scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94", size = 44781033 }, + { url = "https://files.pythonhosted.org/packages/c0/04/2bdacc8ac6387b15db6faa40295f8bd25eccf33f1f13e68a72dc3c60a99e/scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d", size = 39128781 }, + { url = "https://files.pythonhosted.org/packages/c8/53/35b4d41f5fd42f5781dbd0dd6c05d35ba8aa75c84ecddc7d44756cd8da2e/scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07", size = 29939542 }, + { url = "https://files.pythonhosted.org/packages/66/67/6ef192e0e4d77b20cc33a01e743b00bc9e68fb83b88e06e636d2619a8767/scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5", size = 23148375 }, + { url = "https://files.pythonhosted.org/packages/f6/32/3a6dedd51d68eb7b8e7dc7947d5d841bcb699f1bf4463639554986f4d782/scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc", size = 25578573 }, + { url = "https://files.pythonhosted.org/packages/f0/5a/efa92a58dc3a2898705f1dc9dbaf390ca7d4fba26d6ab8cfffb0c72f656f/scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310", size = 35319299 }, + { url = "https://files.pythonhosted.org/packages/8e/ee/8a26858ca517e9c64f84b4c7734b89bda8e63bec85c3d2f432d225bb1886/scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066", size = 40849331 }, + { url = "https://files.pythonhosted.org/packages/a5/cd/06f72bc9187840f1c99e1a8750aad4216fc7dfdd7df46e6280add14b4822/scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1", size = 42544049 }, + { url = "https://files.pythonhosted.org/packages/aa/7d/43ab67228ef98c6b5dd42ab386eae2d7877036970a0d7e3dd3eb47a0d530/scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f", size = 44521212 }, + { url = "https://files.pythonhosted.org/packages/50/ef/ac98346db016ff18a6ad7626a35808f37074d25796fd0234c2bb0ed1e054/scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79", size = 39091068 }, + { url = "https://files.pythonhosted.org/packages/b9/cc/70948fe9f393b911b4251e96b55bbdeaa8cca41f37c26fd1df0232933b9e/scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e", size = 29875417 }, + { url = "https://files.pythonhosted.org/packages/3b/2e/35f549b7d231c1c9f9639f9ef49b815d816bf54dd050da5da1c11517a218/scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73", size = 23084508 }, + { url = "https://files.pythonhosted.org/packages/3f/d6/b028e3f3e59fae61fb8c0f450db732c43dd1d836223a589a8be9f6377203/scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e", size = 25503364 }, + { url = "https://files.pythonhosted.org/packages/a7/2f/6c142b352ac15967744d62b165537a965e95d557085db4beab2a11f7943b/scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d", size = 35292639 }, + { url = "https://files.pythonhosted.org/packages/56/46/2449e6e51e0d7c3575f289f6acb7f828938eaab8874dbccfeb0cd2b71a27/scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e", size = 40798288 }, + { url = "https://files.pythonhosted.org/packages/32/cd/9d86f7ed7f4497c9fd3e39f8918dd93d9f647ba80d7e34e4946c0c2d1a7c/scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06", size = 42524647 }, + { url = "https://files.pythonhosted.org/packages/f5/1b/6ee032251bf4cdb0cc50059374e86a9f076308c1512b61c4e003e241efb7/scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84", size = 44469524 }, +] + +[[package]] +name = "semantic-version" +version = "2.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/31/f2289ce78b9b473d582568c234e104d2a342fd658cc288a7553d83bb8595/semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c", size = 52289 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/23/8146aad7d88f4fcb3a6218f41a60f6c2d4e3a72de72da1825dc7c8f7877c/semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177", size = 15552 }, +] + +[[package]] +name = "send2trash" +version = "1.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/3a/aec9b02217bb79b87bbc1a21bc6abc51e3d5dcf65c30487ac96c0908c722/Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf", size = 17394 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072 }, +] + +[[package]] +name = "sentry-sdk" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/83/7d0956a71ac894717099be3669ca7b8f164bccbcfb570b2f02817d0a0068/sentry_sdk-2.17.0.tar.gz", hash = "sha256:dd0a05352b78ffeacced73a94e86f38b32e2eae15fff5f30ca5abb568a72eacf", size = 290959 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/63/8e80fff3aa15488bc332ede44165a397a29bb13ec4a4b2236299e3b66067/sentry_sdk-2.17.0-py2.py3-none-any.whl", hash = "sha256:625955884b862cc58748920f9e21efdfb8e0d4f98cca4ab0d3918576d5b606ad", size = 314520 }, +] + +[[package]] +name = "setuptools" +version = "75.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/07/37/b31be7e4b9f13b59cde9dcaeff112d401d49e0dc5b37ed4a9fc8fb12f409/setuptools-75.2.0.tar.gz", hash = "sha256:753bb6ebf1f465a1912e19ed1d41f403a79173a9acf66a42e7e6aec45c3c16ec", size = 1350308 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/2d/90165d51ecd38f9a02c6832198c13a4e48652485e2ccf863ebb942c531b6/setuptools-75.2.0-py3-none-any.whl", hash = "sha256:a7fcb66f68b4d9e8e66b42f9876150a3371558f98fa32222ffaa5bced76406f8", size = 1249825 }, +] + +[[package]] +name = "shiboken6" +version = "6.8.0.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/65/1c8a66e448ea1ee4745ba64b6a5604f9f40eeb807020baa181ae218a5d6d/shiboken6-6.8.0.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:99cbb8d53bd2abcb61717a675bb3c1d7513868298b56cdfcae8aa27ed367577a", size = 392836 }, + { url = "https://files.pythonhosted.org/packages/0f/7f/b81937dc3e414477e64f425a16a417a5d00cf8e2d954eee9264c5f4f14c0/shiboken6-6.8.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:77b6e2eae8d48fe32a058e234778c42642eca1755de8b793e2d57857d7d037cf", size = 199992 }, + { url = "https://files.pythonhosted.org/packages/5c/a1/1176683114bb7a1053ba2d3319c9f9f933ab4a132ef100c382fdb5f8ef42/shiboken6-6.8.0.1-cp39-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:eb2db6f0cac42f10dc5b4bcf4ab5a8e079acb870877d0cb3d95cac80c20e4554", size = 189340 }, + { url = "https://files.pythonhosted.org/packages/e0/ac/8c6e0a86ad84b8da61699d9e912850959fdee199cd3626c75759e35b57c4/shiboken6-6.8.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:3f2601c0db8d1e860a2a1265936e616361b073360eb15153806638fbc3b7ff26", size = 1146289 }, +] + +[[package]] +name = "simplejson" +version = "3.19.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/29/085111f19717f865eceaf0d4397bf3e76b08d60428b076b64e2a1903706d/simplejson-3.19.3.tar.gz", hash = "sha256:8e086896c36210ab6050f2f9f095a5f1e03c83fa0e7f296d6cba425411364680", size = 85237 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/24/260ad03435ce8ef2436031951134659c7161776ec3a78094b35b9375ceea/simplejson-3.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:50d8b742d74c449c4dcac570d08ce0f21f6a149d2d9cf7652dbf2ba9a1bc729a", size = 93660 }, + { url = "https://files.pythonhosted.org/packages/63/a1/dee207f357bcd6b106f2ca5129ee916c24993ba08b7dfbf9a37c22442ea9/simplejson-3.19.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd011fc3c1d88b779645495fdb8189fb318a26981eebcce14109460e062f209b", size = 75546 }, + { url = "https://files.pythonhosted.org/packages/80/7b/45ef1da43f54d209ce2ef59b7356cda13f810186c381f38ae23a4d2b1337/simplejson-3.19.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:637c4d4b81825c1f4d651e56210bd35b5604034b192b02d2d8f17f7ce8c18f42", size = 75602 }, + { url = "https://files.pythonhosted.org/packages/7f/4b/9a132382982f8127bc7ce5212a5585d83c174707c9dd698d0cb6a0d41882/simplejson-3.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f56eb03bc9e432bb81adc8ecff2486d39feb371abb442964ffb44f6db23b332", size = 138632 }, + { url = "https://files.pythonhosted.org/packages/76/37/012f5ad2f38afa28f8a6ad9da01dc0b64492ffbaf2a3f2f8a0e1fddf9c1d/simplejson-3.19.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ef59a53be400c1fad2c914b8d74c9d42384fed5174f9321dd021b7017fd40270", size = 146740 }, + { url = "https://files.pythonhosted.org/packages/69/b3/89640bd676e26ea2315b5aaf80712a6fbbb4338e4caf872d91448502a19b/simplejson-3.19.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72e8abbc86fcac83629a030888b45fed3a404d54161118be52cb491cd6975d3e", size = 134440 }, + { url = "https://files.pythonhosted.org/packages/61/20/0035a288deaff05397d6cc0145b33f3dd2429b99cdc880de4c5eca41ca72/simplejson-3.19.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8efb03ca77bd7725dfacc9254df00d73e6f43013cf39bd37ef1a8ed0ebb5165", size = 137949 }, + { url = "https://files.pythonhosted.org/packages/5d/de/5b03fafe3003e32d179588953d38183af6c3747e95c7dcc668c4f9eb886a/simplejson-3.19.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:add8850db04b98507a8b62d248a326ecc8561e6d24336d1ca5c605bbfaab4cad", size = 139992 }, + { url = "https://files.pythonhosted.org/packages/d1/ce/e493116ff49fd215f7baa25195b8f684c91e65c153e2a57e04dc3f3a466b/simplejson-3.19.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fc3dc9fb413fc34c396f52f4c87de18d0bd5023804afa8ab5cc224deeb6a9900", size = 140320 }, + { url = "https://files.pythonhosted.org/packages/86/f3/a18b98a7a27548829f672754dd3940fb637a27981399838128d3e560087f/simplejson-3.19.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4dfa420bb9225dd33b6efdabde7c6a671b51150b9b1d9c4e5cd74d3b420b3fe1", size = 148625 }, + { url = "https://files.pythonhosted.org/packages/0f/55/d3da33ee3e708133da079b9d537693d7fef281e6f0d27921cc7e5b3ec523/simplejson-3.19.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7b5c472099b39b274dcde27f1113db8d818c9aa3ba8f78cbb8ad04a4c1ac2118", size = 141287 }, + { url = "https://files.pythonhosted.org/packages/17/e8/56184ab4d66bb64a6ff569f069b3796dfd943f9b961268fe0d403526fc17/simplejson-3.19.3-cp310-cp310-win32.whl", hash = "sha256:817abad79241ed4a507b3caf4d3f2be5079f39d35d4c550a061988986bffd2ec", size = 74143 }, + { url = "https://files.pythonhosted.org/packages/be/8f/a0089eff060f10a925f08b0a0f50854321484f1ac54b1895bbf4c9213dfe/simplejson-3.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:dd5b9b1783e14803e362a558680d88939e830db2466f3fa22df5c9319f8eea94", size = 75643 }, + { url = "https://files.pythonhosted.org/packages/8c/bb/9ee3959e6929d228cf669b3f13f0edd43c5261b6cd69598640748b19ca35/simplejson-3.19.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e88abff510dcff903a18d11c2a75f9964e768d99c8d147839913886144b2065e", size = 91930 }, + { url = "https://files.pythonhosted.org/packages/ac/ae/a06523928af3a6783e2638cd4f6035c3e32de1c1063d563d9060c8d2f1ad/simplejson-3.19.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:934a50a614fb831614db5dbfba35127ee277624dda4d15895c957d2f5d48610c", size = 74787 }, + { url = "https://files.pythonhosted.org/packages/c3/58/fea732e48a7540035fe46d39e6fd77679f5810311d31da8661ce7a18210a/simplejson-3.19.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:212fce86a22188b0c7f53533b0f693ea9605c1a0f02c84c475a30616f55a744d", size = 74612 }, + { url = "https://files.pythonhosted.org/packages/ab/4d/15718f20cb0e3875b8af9597d6bb3bfbcf1383834b82b6385ee9ac0b72a9/simplejson-3.19.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d9e8f836688a8fabe6a6b41b334aa550a6823f7b4ac3d3712fc0ad8655be9a8", size = 143550 }, + { url = "https://files.pythonhosted.org/packages/93/44/815a4343774760f7a82459c8f6a4d8268b4b6d23f81e7b922a5e2ca79171/simplejson-3.19.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23228037dc5d41c36666384062904d74409a62f52283d9858fa12f4c22cffad1", size = 153284 }, + { url = "https://files.pythonhosted.org/packages/9d/52/d3202d9bba95444090d1c98e43da3c10907875babf63ed3c134d1b9437e3/simplejson-3.19.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0791f64fed7d4abad639491f8a6b1ba56d3c604eb94b50f8697359b92d983f36", size = 141518 }, + { url = "https://files.pythonhosted.org/packages/b7/d4/850948bcbcfe0b4a6c69dfde10e245d3a1ea45252f16a1e2308a3b06b1da/simplejson-3.19.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4f614581b61a26fbbba232a1391f6cee82bc26f2abbb6a0b44a9bba25c56a1c", size = 144688 }, + { url = "https://files.pythonhosted.org/packages/58/d2/b8dcb0a07d9cd54c47f9fe8733dbb83891d1efe4fc786d9dfc8781cc04f9/simplejson-3.19.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1df0aaf1cb787fdf34484ed4a1f0c545efd8811f6028623290fef1a53694e597", size = 144534 }, + { url = "https://files.pythonhosted.org/packages/a9/95/1e92d99039041f596e0923ec4f9153244acaf3830944dc69a7c11b23ceaa/simplejson-3.19.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:951095be8d4451a7182403354c22ec2de3e513e0cc40408b689af08d02611588", size = 146565 }, + { url = "https://files.pythonhosted.org/packages/21/04/c96aeb3a74031255e4cbcc0ca1b6ebfb5549902f0a065f06d65ce8447c0c/simplejson-3.19.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a954b30810988feeabde843e3263bf187697e0eb5037396276db3612434049b", size = 155014 }, + { url = "https://files.pythonhosted.org/packages/b7/41/e28a28593afc4a75d8999d057bfb7c73a103e35f927e66f4bb92571787ae/simplejson-3.19.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c40df31a75de98db2cdfead6074d4449cd009e79f54c1ebe5e5f1f153c68ad20", size = 148092 }, + { url = "https://files.pythonhosted.org/packages/2b/82/1c81a3af06f937afb6d2e9d74a465c0e0ae6db444d1bf2a436ea26de1965/simplejson-3.19.3-cp311-cp311-win32.whl", hash = "sha256:7e2a098c21ad8924076a12b6c178965d88a0ad75d1de67e1afa0a66878f277a5", size = 73942 }, + { url = "https://files.pythonhosted.org/packages/65/be/d8ab9717f471be3c114f16abd8be21d9a6a0a09b9b49177d93d64d3717d9/simplejson-3.19.3-cp311-cp311-win_amd64.whl", hash = "sha256:c9bedebdc5fdad48af8783022bae307746d54006b783007d1d3c38e10872a2c6", size = 75469 }, + { url = "https://files.pythonhosted.org/packages/20/15/513fea93fafbdd4993eacfcb762965b2ff3d29e618c029e2956174d68c4b/simplejson-3.19.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:66a0399e21c2112acacfebf3d832ebe2884f823b1c7e6d1363f2944f1db31a99", size = 92921 }, + { url = "https://files.pythonhosted.org/packages/a4/4f/998a907ae1a6c104dc0ee48aa248c2478490152808d34d8e07af57f396c3/simplejson-3.19.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6ef9383c5e05f445be60f1735c1816163c874c0b1ede8bb4390aff2ced34f333", size = 75311 }, + { url = "https://files.pythonhosted.org/packages/db/44/acd6122201e927451869d45952b9ab1d3025cdb5e61548d286d08fbccc08/simplejson-3.19.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:42e5acf80d4d971238d4df97811286a044d720693092b20a56d5e56b7dcc5d09", size = 74964 }, + { url = "https://files.pythonhosted.org/packages/27/ca/d0a1e8f16e1bbdc0b8c6d88166f45f565ed7285f53928cfef3b6ce78f14d/simplejson-3.19.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0b0efc7279d768db7c74d3d07f0b5c81280d16ae3fb14e9081dc903e8360771", size = 150106 }, + { url = "https://files.pythonhosted.org/packages/63/59/0554b78cf26c98e2b9cae3f44723bd72c2394e2afec1a14eedc6211f7187/simplejson-3.19.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0552eb06e7234da892e1d02365cd2b7b2b1f8233aa5aabdb2981587b7cc92ea0", size = 158347 }, + { url = "https://files.pythonhosted.org/packages/b2/fe/9f30890352e431e8508cc569912d3322147d3e7e4f321e48c0adfcb4c97d/simplejson-3.19.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf6a3b9a7d7191471b464fe38f684df10eb491ec9ea454003edb45a011ab187", size = 148456 }, + { url = "https://files.pythonhosted.org/packages/37/e3/663a09542ee021d4131162f7a164cb2e7f04ef48433a67591738afbf12ea/simplejson-3.19.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7017329ca8d4dca94ad5e59f496e5fc77630aecfc39df381ffc1d37fb6b25832", size = 152190 }, + { url = "https://files.pythonhosted.org/packages/31/20/4e0c4d35e10ff6465003bec304316d822a559a1c38c66ef6892ca199c207/simplejson-3.19.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:67a20641afebf4cfbcff50061f07daad1eace6e7b31d7622b6fa2c40d43900ba", size = 149846 }, + { url = "https://files.pythonhosted.org/packages/08/7a/46e2e072cac3987cbb05946f25167f0ad2fe536748e7405953fd6661a486/simplejson-3.19.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:dd6a7dabcc4c32daf601bc45e01b79175dde4b52548becea4f9545b0a4428169", size = 151714 }, + { url = "https://files.pythonhosted.org/packages/7f/7d/dbeeac10eb61d5d8858d0bb51121a21050d281dc83af4c557f86da28746c/simplejson-3.19.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:08f9b443a94e72dd02c87098c96886d35790e79e46b24e67accafbf13b73d43b", size = 158777 }, + { url = "https://files.pythonhosted.org/packages/fc/8f/a98bdbb799c6a4a884b5823db31785a96ba895b4b0f4d8ac345d6fe98bbf/simplejson-3.19.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa97278ae6614346b5ca41a45a911f37a3261b57dbe4a00602048652c862c28b", size = 154230 }, + { url = "https://files.pythonhosted.org/packages/b1/db/852eebceb85f969ae40e06babed1a93d3bacb536f187d7a80ff5823a5979/simplejson-3.19.3-cp312-cp312-win32.whl", hash = "sha256:ef28c3b328d29b5e2756903aed888960bc5df39b4c2eab157ae212f70ed5bf74", size = 74002 }, + { url = "https://files.pythonhosted.org/packages/fe/68/9f0e5df0651cb79ef83cba1378765a00ee8038e6201cc82b8e7178a7778e/simplejson-3.19.3-cp312-cp312-win_amd64.whl", hash = "sha256:1e662336db50ad665777e6548b5076329a94a0c3d4a0472971c588b3ef27de3a", size = 75596 }, + { url = "https://files.pythonhosted.org/packages/93/3a/5896821ed543899fcb9c4256c7e71bb110048047349a00f42bc8b8fb379f/simplejson-3.19.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0959e6cb62e3994b5a40e31047ff97ef5c4138875fae31659bead691bed55896", size = 92931 }, + { url = "https://files.pythonhosted.org/packages/39/15/5d33d269440912ee40d856db0c8be2b91aba7a219690ab01f86cb0edd590/simplejson-3.19.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a7bfad839c624e139a4863007233a3f194e7c51551081f9789cba52e4da5167", size = 75318 }, + { url = "https://files.pythonhosted.org/packages/2a/8d/2e7483a2bf7ec53acf7e012bafbda79d7b34f90471dda8e424544a59d484/simplejson-3.19.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afab2f7f2486a866ff04d6d905e9386ca6a231379181a3838abce1f32fbdcc37", size = 74971 }, + { url = "https://files.pythonhosted.org/packages/4d/9d/9bdf34437c8834a7cf7246f85e9d5122e30579f512c10a0c2560e994294f/simplejson-3.19.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00313681015ac498e1736b304446ee6d1c72c5b287cd196996dad84369998f7", size = 150112 }, + { url = "https://files.pythonhosted.org/packages/a7/e2/1f2ae2d89eaf85f6163c82150180aae5eaa18085cfaf892f8a57d4c51cbd/simplejson-3.19.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d936ae682d5b878af9d9eb4d8bb1fdd5e41275c8eb59ceddb0aeed857bb264a2", size = 158354 }, + { url = "https://files.pythonhosted.org/packages/60/83/26f610adf234c8492b3f30501e12f2271e67790f946c6898fe0c58aefe99/simplejson-3.19.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c6657485393f2e9b8177c77a7634f13ebe70d5e6de150aae1677d91516ce6b", size = 148455 }, + { url = "https://files.pythonhosted.org/packages/b5/4b/109af50006af77133653c55b5b91b4bd2d579ff8254ce11216c0b75f911b/simplejson-3.19.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a6a750d3c7461b1c47cfc6bba8d9e57a455e7c5f80057d2a82f738040dd1129", size = 152191 }, + { url = "https://files.pythonhosted.org/packages/75/dc/108872a8825cbd99ae6f4334e0490ff1580367baf12198bcaf988f6820ba/simplejson-3.19.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ea7a4a998c87c5674a27089e022110a1a08a7753f21af3baf09efe9915c23c3c", size = 149954 }, + { url = "https://files.pythonhosted.org/packages/eb/be/deec1d947a5d0472276ab4a4d1a9378dc5ee27f3dc9e54d4f62ffbad7a08/simplejson-3.19.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6300680d83a399be2b8f3b0ef7ef90b35d2a29fe6e9c21438097e0938bbc1564", size = 151812 }, + { url = "https://files.pythonhosted.org/packages/e9/58/4ee130702d36b1551ef66e7587eefe56651f3669255bf748cd71691e2434/simplejson-3.19.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ab69f811a660c362651ae395eba8ce84f84c944cea0df5718ea0ba9d1e4e7252", size = 158880 }, + { url = "https://files.pythonhosted.org/packages/0f/e1/59cc6a371b60f89e3498d9f4c8109f6b7359094d453f5fe80b2677b777b0/simplejson-3.19.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:256e09d0f94d9c3d177d9e95fd27a68c875a4baa2046633df387b86b652f5747", size = 154344 }, + { url = "https://files.pythonhosted.org/packages/79/45/1b36044670016f5cb25ebd92497427d2d1711ecb454d00f71eb9a00b77cc/simplejson-3.19.3-cp313-cp313-win32.whl", hash = "sha256:2c78293470313aefa9cfc5e3f75ca0635721fb016fb1121c1c5b0cb8cc74712a", size = 74002 }, + { url = "https://files.pythonhosted.org/packages/e2/58/b06226e6b0612f2b1fa13d5273551da259f894566b1eef32249ddfdcce44/simplejson-3.19.3-cp313-cp313-win_amd64.whl", hash = "sha256:3bbcdc438dc1683b35f7a8dc100960c721f922f9ede8127f63bed7dfded4c64c", size = 75599 }, + { url = "https://files.pythonhosted.org/packages/0d/e7/f9fafbd4f39793a20cc52e77bbd766f7384312526d402c382928dc7667f6/simplejson-3.19.3-py3-none-any.whl", hash = "sha256:49cc4c7b940d43bd12bf87ec63f28cbc4964fc4e12c031cc8cd01650f43eb94e", size = 57004 }, +] + +[[package]] +name = "six" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, +] + +[[package]] +name = "smmap" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/04/b5bf6d21dc4041000ccba7eb17dd3055feb237e7ffc2c20d3fae3af62baa/smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62", size = 22291 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/a5/10f97f73544edcdef54409f1d839f6049a0d79df68adbc1ceb24d1aaca42/smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da", size = 24282 }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 }, +] + +[[package]] +name = "soupsieve" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 }, +] + +[[package]] +name = "spglib" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/8e/2c53fa9027543c3624cdd8b7ac9f6d611464dfc86eb786465e422a8c024c/spglib-2.5.0.tar.gz", hash = "sha256:f8bb638897be91b9dbd4c085d9fde1f69048f5949e20f3832cb9438e57418d4b", size = 2822241 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/ab/f9436120f1282b5bc6af7d63c5b1c3296f86acdc09927069631b092c051b/spglib-2.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:993f4b2d2f0a133d22fcf2d6f3fad9beeee3527c5453d5619e6518d7af253c85", size = 1064068 }, + { url = "https://files.pythonhosted.org/packages/99/9f/e6465a4e0856e35576d0cef7e44e10bdf8aa010dea0969c1ad3c50112c7c/spglib-2.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b0b070ddbaad0e97ec18efef602af57caf0045c7187180b1eaea843cb5877c8b", size = 1052798 }, + { url = "https://files.pythonhosted.org/packages/67/2f/330e67f98932ac1433472ba224e5f5319efdacf3fde149f09ccae4ee82f8/spglib-2.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12c3eb5d302dd74d07d35ad53180f7f91c69b58473e48b510aae90567f2bb90f", size = 1069986 }, + { url = "https://files.pythonhosted.org/packages/c7/70/68c4d51809a558784b822d7d6e2469696d3efd6039f76e6a7b1b4f7a3422/spglib-2.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8cf1f4082d8685686d7b3cf8d6ade4182ba09f13cc49eb2481201c0aa4579c9", size = 1075722 }, + { url = "https://files.pythonhosted.org/packages/d6/43/941c82f6d0665cd6f2af29a4547b24e5e06367bec2e2b73ab903c62674b7/spglib-2.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:b16ffc3497736263a53b629768ec8aae707d595bbd702deac3a6dd39e045537b", size = 300736 }, + { url = "https://files.pythonhosted.org/packages/c9/06/c52cc06a44b265d49bf8e006c45de82de1aa6fbad113e9b84bb6e7f4e8b6/spglib-2.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6d9d812eea06e09a1f725755f21efa7451c48636cd24abb64ffa2cd82b8003dd", size = 1064065 }, + { url = "https://files.pythonhosted.org/packages/ca/41/0ea6f102558c70e8d86a7127a8cc6b1a5cd765931b2baba8b82d66a4df93/spglib-2.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c36c549442d6e422c932f528dff13af5eb766a5d1985d1d14d65777b1d69f136", size = 1052797 }, + { url = "https://files.pythonhosted.org/packages/22/9b/66891820345f06de8bd0723f9ea075ed62be40eda8d7a0a56b93d9dbf978/spglib-2.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67c67a3457dcafd8c533b2c3bebd2f25dcdbef3d7b6ec122f1e983218e0cdeee", size = 1069984 }, + { url = "https://files.pythonhosted.org/packages/b6/61/f301b774e5cdbc9b618d9946eaa269d8e563ccad06a34c0bd33f276be8db/spglib-2.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b2f6085d5f7d8fff63055fdea2955e9667f07934263f23f8db12d667228c2a2", size = 1075722 }, + { url = "https://files.pythonhosted.org/packages/b6/27/4412242308119edef9f525afdc17af91560e64945af299676538576b4848/spglib-2.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:d5bc51265dc3a4ca77026f668a1884427d1c7f5f32669948336105a71bde745b", size = 300719 }, + { url = "https://files.pythonhosted.org/packages/cd/62/b0147f156841fc361657393b644c096e4daa4ecd92b0c28d7026e102e977/spglib-2.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2fe0014743694bed8fad0c21df0083086dc8aa9a6f61106fc1831edb9e67d260", size = 1064057 }, + { url = "https://files.pythonhosted.org/packages/a6/f6/6eb33d67df543eca3038adb26f59b6309ee42b1cbf9365acc1b5ca587fad/spglib-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:80fc33a21df2097d5f2182550719f761b3c3230e60bfa5f91dcf5683dc44d6b0", size = 1052792 }, + { url = "https://files.pythonhosted.org/packages/63/d4/411dc94bba3e1ccff68c23b3a6b858aa75991f4d8af4e9a81fa6fe2719b8/spglib-2.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cb174c6e0d3e2f824a5b4c0f46c2460ce9052c5ef88909058fe982885e47939", size = 1069956 }, + { url = "https://files.pythonhosted.org/packages/14/c3/e90b94178e01e3af7b54c245216bcc3910f01dc2ec5ca7579c43ad3acdf9/spglib-2.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7b0b73ad5a352cd1e0544631b3ab98980a366e2b1bd7c804ab8c3f6ea45a6d8", size = 1075752 }, + { url = "https://files.pythonhosted.org/packages/95/5a/a4854a97c7e5e693bd6fd31212a5b3e66386847c89f984947f2113253fd3/spglib-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:2f3bd027c40b74cc5297de3898d4badee32f8e90d24ac0449cd4266ee5111ba3", size = 300721 }, +] + +[[package]] +name = "sphinx" +version = "7.2.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/8e/6e51da4b26665b4b92b1944ea18b2d9c825e753e19180cc5bdc818d0ed3b/sphinx-7.2.6.tar.gz", hash = "sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5", size = 7015183 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/b6/8ed35256aa530a9d3da15d20bdc0ba888d5364441bb50a5a83ee7827affe/sphinx-7.2.6-py3-none-any.whl", hash = "sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560", size = 3207959 }, +] + +[[package]] +name = "sphinx-autoapi" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyascii" }, + { name = "astroid" }, + { name = "jinja2" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/48/cf3538dda02ec6f62013f9baa01b11e6049a120e67fec159d79c028d341d/sphinx-autoapi-3.0.0.tar.gz", hash = "sha256:09ebd674a32b44467222b0fb8a917b97c89523f20dbf05b52cb8a3f0e15714de", size = 58081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/f8/489774c0ebcd96bec1e44e4c23ac741f74895efd5ba1da778b2984e0e3cb/sphinx_autoapi-3.0.0-py2.py3-none-any.whl", hash = "sha256:ea207793cba1feff7b2ded0e29364f2995a4d157303a98603cee0ce94cea2688", size = 35009 }, +] + +[[package]] +name = "sphinx-rtd-theme" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "sphinx" }, + { name = "sphinxcontrib-jquery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/33/2a35a9cdbfda9086bda11457bcc872173ab3565b16b6d7f6b3efaa6dc3d6/sphinx_rtd_theme-2.0.0.tar.gz", hash = "sha256:bd5d7b80622406762073a04ef8fadc5f9151261563d47027de09910ce03afe6b", size = 2785005 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/46/00fda84467815c29951a9c91e3ae7503c409ddad04373e7cfc78daad4300/sphinx_rtd_theme-2.0.0-py2.py3-none-any.whl", hash = "sha256:ec93d0856dc280cf3aee9a4c9807c60e027c7f7b461b77aeffed682e68f0e586", size = 2824721 }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300 }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530 }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705 }, +] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104 }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071 }, +] + +[[package]] +name = "sphinxcontrib-katex" +version = "0.8.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/1c/654ecc51960443c4e1959e0b0addb33393318a7428c1fd786d816a5ed071/sphinxcontrib-katex-0.8.6.tar.gz", hash = "sha256:c3dcdb2984626a0e6c1b11bc2580c7bbc6ab3711879b23bbf26c028a0f4fd4f2", size = 17410 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/39/6398797ccef2f9eaf348cfb75497c265b58ba715ad001b3cca447c3cfe47/sphinxcontrib_katex-0.8.6-py3-none-any.whl", hash = "sha256:17ecee8630cb5bc64bfc3d8aa69bf8992b01384db6931a4d19edd1f118a854f7", size = 10142 }, +] + +[[package]] +name = "sphinxcontrib-napoleon" +version = "0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pockets" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fa/eb/ad89500f4cee83187596e07f43ad561f293e8e6e96996005c3319653b89f/sphinxcontrib-napoleon-0.7.tar.gz", hash = "sha256:407382beed396e9f2d7f3043fad6afda95719204a1e1a231ac865f40abcbfcf8", size = 21232 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/f2/6b7627dfe7b4e418e295e254bb15c3a6455f11f8c0ad0d43113f678049c3/sphinxcontrib_napoleon-0.7-py2.py3-none-any.whl", hash = "sha256:711e41a3974bdf110a484aec4c1a556799eb0b3f3b897521a018ad7e2db13fef", size = 17151 }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743 }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, +] + +[[package]] +name = "sympy" +version = "1.13.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/11/8a/5a7fd6284fa8caac23a26c9ddf9c30485a48169344b4bd3b0f02fef1890f/sympy-1.13.3.tar.gz", hash = "sha256:b27fd2c6530e0ab39e275fc9b683895367e51d5da91baa8d3d64db2565fec4d9", size = 7533196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/ff/c87e0622b1dadea79d2fb0b25ade9ed98954c9033722eb707053d310d4f3/sympy-1.13.3-py3-none-any.whl", hash = "sha256:54612cf55a62755ee71824ce692986f23c88ffa77207b30c1368eda4a7060f73", size = 6189483 }, +] + +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252 }, +] + +[[package]] +name = "tenacity" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/94/91fccdb4b8110642462e653d5dcb27e7b674742ad68efd146367da7bdb10/tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b", size = 47421 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/cb/b86984bed139586d01532a587464b5805f12e397594f19f931c4c2fbfa61/tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539", size = 28169 }, +] + +[[package]] +name = "tensorboard" +version = "2.16.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py" }, + { name = "grpcio" }, + { name = "markdown" }, + { name = "numpy" }, + { name = "protobuf" }, + { name = "setuptools" }, + { name = "six" }, + { name = "tensorboard-data-server" }, + { name = "werkzeug" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/d0/b97889ffa769e2d1fdebb632084d5e8b53fc299d43a537acee7ec0c021a3/tensorboard-2.16.2-py3-none-any.whl", hash = "sha256:9f2b4e7dad86667615c0e5cd072f1ea8403fc032a299f0072d6f74855775cc45", size = 5490335 }, +] + +[[package]] +name = "tensorboard-data-server" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/13/e503968fefabd4c6b2650af21e110aa8466fe21432cd7c43a84577a89438/tensorboard_data_server-0.7.2-py3-none-any.whl", hash = "sha256:7e0610d205889588983836ec05dc098e80f97b7e7bbff7e994ebb78f578d0ddb", size = 2356 }, + { url = "https://files.pythonhosted.org/packages/b7/85/dabeaf902892922777492e1d253bb7e1264cadce3cea932f7ff599e53fea/tensorboard_data_server-0.7.2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:9fe5d24221b29625dbc7328b0436ca7fc1c23de4acf4d272f1180856e32f9f60", size = 4823598 }, + { url = "https://files.pythonhosted.org/packages/73/c6/825dab04195756cf8ff2e12698f22513b3db2f64925bdd41671bfb33aaa5/tensorboard_data_server-0.7.2-py3-none-manylinux_2_31_x86_64.whl", hash = "sha256:ef687163c24185ae9754ed5650eb5bc4d84ff257aabdc33f0cc6f74d8ba54530", size = 6590363 }, +] + +[[package]] +name = "terminado" +version = "0.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess", marker = "os_name != 'nt'" }, + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154 }, +] + +[[package]] +name = "threadpoolctl" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/55/b5148dcbf72f5cde221f8bfe3b6a540da7aa1842f6b491ad979a6c8b84af/threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107", size = 41936 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/2c/ffbf7a134b9ab11a67b0cf0726453cedd9c5043a4fe7a35d1cefa9a1bcfb/threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467", size = 18414 }, +] + +[[package]] +name = "tinycss2" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/6f/38d2335a2b70b9982d112bb177e3dbe169746423e33f718bf5e9c7b3ddd3/tinycss2-1.3.0.tar.gz", hash = "sha256:152f9acabd296a8375fbca5b84c961ff95971fcfc32e79550c8df8e29118c54d", size = 67360 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/4d/0db5b8a613d2a59bbc29bc5bb44a2f8070eb9ceab11c50d477502a8a0092/tinycss2-1.3.0-py3-none-any.whl", hash = "sha256:54a8dbdffb334d536851be0226030e9505965bb2f30f21a4a82c55fb2a80fae7", size = 22532 }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588 }, +] + +[[package]] +name = "tomli" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/b9/de2a5c0144d7d75a57ff355c0c24054f965b2dc3036456ae03a51ea6264b/tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed", size = 16096 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/db/ce8eda256fa131af12e0a76d481711abe4681b6923c27efb9a255c9e4594/tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38", size = 13237 }, +] + +[[package]] +name = "torch" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "sympy" }, + { name = "triton", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/67/fcc9b9e2369a9bae4da492aedc0c2dfa95d563ef0eaa9228b70c98395ec2/torch-2.2.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:d366158d6503a3447e67f8c0ad1328d54e6c181d88572d688a625fac61b13a97", size = 755505538 }, + { url = "https://files.pythonhosted.org/packages/2a/2a/b6064e03a71d2dc4936975c667703f333ce663977ce489b50090daee332f/torch-2.2.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:707f2f80402981e9f90d0038d7d481678586251e6642a7a6ef67fc93511cb446", size = 86592741 }, + { url = "https://files.pythonhosted.org/packages/c8/ed/f11e9eb1e21d7ea8fc82a9fd373f9ff2023a7ee9e47d07c9bc9efce46eca/torch-2.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:15c8f0a105c66b28496092fca1520346082e734095f8eaf47b5786bac24b8a31", size = 198565666 }, + { url = "https://files.pythonhosted.org/packages/e7/0a/e42e6012b710e49bc56b4e6ce501fa39baa46fd978de014244aae108e6e1/torch-2.2.0-cp310-none-macosx_10_9_x86_64.whl", hash = "sha256:0ca4df4b728515ad009b79f5107b00bcb2c63dc202d991412b9eb3b6a4f24349", size = 150796203 }, + { url = "https://files.pythonhosted.org/packages/6c/b6/18f8b358cab98a048b07cc049e1692231656fe2572443f2b4f56e75a8151/torch-2.2.0-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:3d3eea2d5969b9a1c9401429ca79efc668120314d443d3463edc3289d7f003c7", size = 59699382 }, + { url = "https://files.pythonhosted.org/packages/c8/02/d3adf4b4851d99a31c5a9cf7b668f171e84334945d05fb7b51c42bf41abf/torch-2.2.0-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:0d1c580e379c0d48f0f0a08ea28d8e373295aa254de4f9ad0631f9ed8bc04c24", size = 755522292 }, + { url = "https://files.pythonhosted.org/packages/4f/a7/098bdc65e141b29f571989c4561cbc7fe7c78c9a12dbe61cba540ca1d5ef/torch-2.2.0-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:9328e3c1ce628a281d2707526b4d1080eae7c4afab4f81cea75bde1f9441dc78", size = 86610755 }, + { url = "https://files.pythonhosted.org/packages/58/b8/51b956c2da9729390a3080397cd2f31171394543af7746681466e372f69a/torch-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:03c8e660907ac1b8ee07f6d929c4e15cd95be2fb764368799cca02c725a212b8", size = 198571687 }, + { url = "https://files.pythonhosted.org/packages/c7/4e/578c4e3c7ac486cddcce3e85e4704a474854835baea4eba8bc707d4a0823/torch-2.2.0-cp311-none-macosx_10_9_x86_64.whl", hash = "sha256:da0cefe7f84ece3e3b56c11c773b59d1cb2c0fd83ddf6b5f7f1fd1a987b15c3e", size = 150571355 }, + { url = "https://files.pythonhosted.org/packages/96/4e/970cd3e13ad95aed81102272f0678d8cc48101880b8be5bae8aad22e7f3b/torch-2.2.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:f81d23227034221a4a4ff8ef24cc6cec7901edd98d9e64e32822778ff01be85e", size = 59360869 }, + { url = "https://files.pythonhosted.org/packages/d5/2f/f7a1701f1bc6c48401bcdd16208da2b9c8a3c1227e171782a06e5c3a64ba/torch-2.2.0-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:dcbfb2192ac41ca93c756ebe9e2af29df0a4c14ee0e7a0dd78f82c67a63d91d4", size = 755447496 }, + { url = "https://files.pythonhosted.org/packages/49/0c/46ffc4377156b1126c1bec9e177e16bb0410b592c5391e690486b21e4f62/torch-2.2.0-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:9eeb42971619e24392c9088b5b6d387d896e267889d41d267b1fec334f5227c5", size = 86509406 }, + { url = "https://files.pythonhosted.org/packages/60/6a/5374d5be17d951714b5a8f7956a70aebf52c5e98f579dfad880bed98c787/torch-2.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:c718b2ca69a6cac28baa36d86d8c0ec708b102cebd1ceb1b6488e404cd9be1d1", size = 198514435 }, + { url = "https://files.pythonhosted.org/packages/a0/ef/c09d5e8739f99ed99c821a468830b06ac0af0d21e443afda8d2459fdc50a/torch-2.2.0-cp312-none-macosx_10_9_x86_64.whl", hash = "sha256:f11d18fceb4f9ecb1ac680dde7c463c120ed29056225d75469c19637e9f98d12", size = 150803751 }, + { url = "https://files.pythonhosted.org/packages/99/4d/1ac78e96fca1cc2846a42d5e11a08851ae577ef9f02d117f83f5ccbabaea/torch-2.2.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:ee1da852bfd4a7e674135a446d6074c2da7194c1b08549e31eae0b3138c6b4d2", size = 59672586 }, +] + +[[package]] +name = "torch-ema" +version = "0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/af/db7d0c8b26a13062d9b85bdcf8d977acd8a51057fb6edca9eb30613ef5ef/torch_ema-0.3.tar.gz", hash = "sha256:5a3595405fa311995f01291a1d4a9242d6be08a0fc9db29152ec6cfd586ea414", size = 5486 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/f1/a47c831436595cffbfd69a19ea129a23627120b13f4886499e58775329d1/torch_ema-0.3-py3-none-any.whl", hash = "sha256:823ad8da5c10bc1eebcec739cc3f521aa9573229fe04e5673c304d29f1433279", size = 5475 }, +] + +[[package]] +name = "torch-geometric" +version = "2.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "numpy" }, + { name = "psutil" }, + { name = "pyparsing" }, + { name = "requests" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/1b/c79538f5c7160e927e3e4089114ec10af6447b1bdcc83ec63016c7cbbd74/torch_geometric-2.5.3.tar.gz", hash = "sha256:ad0761650c8fa56cdc46ee61c564fd4995f07f079965fe732b3a76d109fd3edc", size = 741267 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/f0/66ad3a5263aa16efb534aaf4e7da23ffc28c84efbbd720b0c5ec174f6242/torch_geometric-2.5.3-py3-none-any.whl", hash = "sha256:8277abfc12600b0e8047e0c3ea2d55cc43f08c1448e73e924de827c15d0b5f85", size = 1097714 }, +] + +[[package]] +name = "torchmetrics" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lightning-utilities" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/11/3d/d14914f15b19ce971215973305d30b9a673b7fa0c6796076e149c11765a0/torchmetrics-1.5.0.tar.gz", hash = "sha256:c18e68bab4104ad7d2285af601ddc6dc04f9f3b7cafaa8ad13fa1dcc539e33b6", size = 521148 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/94/e5ee3f8a99d7494e58f5e608ae8af0906e47b0bbf91ea7c9d8076418887e/torchmetrics-1.5.0-py3-none-any.whl", hash = "sha256:a65628e0ea04c98bff84f2152c5a48416cbb8dc41c6e4ba44204cfc6ace2a30f", size = 890490 }, +] + +[[package]] +name = "torchode" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sympy" }, + { name = "torch" }, + { name = "torchtyping" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/4e/d1bea9668d1485a98d6b2d4297f8f5b7f73ceb1fd05c68c6b1e0cda11fea/torchode-0.2.0.tar.gz", hash = "sha256:fd2aeb376af779116933cad0ec2356386dae2f8dcaa3f1eed050c5da269ac6fd", size = 35898 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/cb/ae05f1fff8e86e4ef4808e7fbbc89624b82b8225225f364857479260cb6d/torchode-0.2.0-py3-none-any.whl", hash = "sha256:79d31288e94bfa79a9fbc95ddcb4295433bb74e391f9943442c53665655e3008", size = 30301 }, +] + +[[package]] +name = "torchsde" +version = "0.2.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "scipy" }, + { name = "torch" }, + { name = "trampoline" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/a5/ae18ee6de023b3a5462122a43a4c9812c11d275cc585a3d08bf24945c02a/torchsde-0.2.6.tar.gz", hash = "sha256:81d074d3504f9d190f1694fb526395afbe4608ee43a88adb1262a639e5b4778b", size = 48840 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/1f/b67ebd7e19ffe259f05d3cf4547326725c3113d640c277030be3e9998d6f/torchsde-0.2.6-py3-none-any.whl", hash = "sha256:19bf7ff02eec7e8e46ba1cdb4aa0f9db1c51d492524a16975234b467f7fc463b", size = 61232 }, +] + +[[package]] +name = "torchtyping" +version = "0.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "torch" }, + { name = "typeguard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/b2/9099ac2d76ebe538d527ef73844bcfa1296a5c75bd9f2d489e862d47bb42/torchtyping-0.1.5.tar.gz", hash = "sha256:fb51e1792536223e2497b1e106ed0cbe681f7324044599cb718d03be3f2b3851", size = 23067 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/33/3915d3a985bf3914aadfc4e24438a8499c0cd91af0c6d251ad5b773bc310/torchtyping-0.1.5-py3-none-any.whl", hash = "sha256:429d76e16b08a2409226320565d557d4ba3d0d6f544db3e5dc4cf690ffa2a3bd", size = 17992 }, +] + +[[package]] +name = "tornado" +version = "6.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/66/398ac7167f1c7835406888a386f6d0d26ee5dbf197d8a571300be57662d3/tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9", size = 500623 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/d9/c33be3c1a7564f7d42d87a8d186371a75fd142097076767a5c27da941fef/tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8", size = 435924 }, + { url = "https://files.pythonhosted.org/packages/2e/0f/721e113a2fac2f1d7d124b3279a1da4c77622e104084f56119875019ffab/tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14", size = 433883 }, + { url = "https://files.pythonhosted.org/packages/13/cf/786b8f1e6fe1c7c675e79657448178ad65e41c1c9765ef82e7f6f765c4c5/tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4", size = 437224 }, + { url = "https://files.pythonhosted.org/packages/e4/8e/a6ce4b8d5935558828b0f30f3afcb2d980566718837b3365d98e34f6067e/tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842", size = 436597 }, + { url = "https://files.pythonhosted.org/packages/22/d4/54f9d12668b58336bd30defe0307e6c61589a3e687b05c366f804b7faaf0/tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3", size = 436797 }, + { url = "https://files.pythonhosted.org/packages/cf/3f/2c792e7afa7dd8b24fad7a2ed3c2f24a5ec5110c7b43a64cb6095cc106b8/tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f", size = 437516 }, + { url = "https://files.pythonhosted.org/packages/71/63/c8fc62745e669ac9009044b889fc531b6f88ac0f5f183cac79eaa950bb23/tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4", size = 436958 }, + { url = "https://files.pythonhosted.org/packages/94/d4/f8ac1f5bd22c15fad3b527e025ce219bd526acdbd903f52053df2baecc8b/tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698", size = 436882 }, + { url = "https://files.pythonhosted.org/packages/4b/3e/a8124c21cc0bbf144d7903d2a0cadab15cadaf683fa39a0f92bc567f0d4d/tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d", size = 438092 }, + { url = "https://files.pythonhosted.org/packages/d9/2f/3f2f05e84a7aff787a96d5fb06821323feb370fe0baed4db6ea7b1088f32/tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7", size = 438532 }, +] + +[[package]] +name = "tqdm" +version = "4.64.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/2a/838de32e09bd511cf69fe4ae13ffc748ac143449bfc24bb3fd172d53a84f/tqdm-4.64.0.tar.gz", hash = "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d", size = 169499 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/c4/d15f1e627fff25443ded77ea70a7b5532d6371498f9285d44d62587e209c/tqdm-4.64.0-py2.py3-none-any.whl", hash = "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6", size = 78448 }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, +] + +[[package]] +name = "traits" +version = "6.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/08/ab5a95c4e3f7ebff53c4b58cdeee5ad161a8a285e02620928ff566185a48/traits-6.4.3.tar.gz", hash = "sha256:a9bbfd9e0c08b7de07e86ef64e69cb96a29c2105a43bf832cd8b162fa1e22f44", size = 9527655 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/bd/4f9d867b4a3e35a22d8ce91868cf8548201acd44304d64ff8029fd58516f/traits-6.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:64bfbd0fc4c8fa25eb55d6d46a8f98992852acaf904618879dbbba87a09289aa", size = 5033988 }, + { url = "https://files.pythonhosted.org/packages/1e/48/b57ddca3b6ea6fd393a8fa47cdf9fc5a53dcd7c400ffc770b9cd242aee53/traits-6.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1e97f8a45c161715dfb69d5248cb54210057b5bd649caf08041465374dadbc1", size = 5007307 }, + { url = "https://files.pythonhosted.org/packages/fd/c7/6dea603c29d4e7179d341d7920418a158ee84830006e1e7135921d3814a8/traits-6.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeb126a6485edab740b2f238e9b02c830f7c8f7c1112609b4b5d280aef3e9421", size = 5098959 }, + { url = "https://files.pythonhosted.org/packages/cb/a9/0212d95416b98d1140aea268cfa1c2375733f25b997636e8543bf088f974/traits-6.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35f0bd177409697a33e95d5e5066670db122f0b5451e7a0ddffc9752d2bf3f48", size = 5084271 }, + { url = "https://files.pythonhosted.org/packages/4d/f7/cb0572a8caa2c7add74a473dbc44a4308a0c3274d17f33b37cd05212e6ec/traits-6.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57109bca87c1e9ec125332a758c95cdf940540efc4c53a30df8d1dfa119472b9", size = 5096029 }, + { url = "https://files.pythonhosted.org/packages/06/a7/5ecb55d0619b2c9104bbd6a6b0725a451a76e71b1551f6d1420480db32d4/traits-6.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46cc41d2c65b374fbc600f39fe4bd68bbd01bfbdd6629c29e39686a854d4cfd0", size = 5101804 }, + { url = "https://files.pythonhosted.org/packages/eb/92/52ec6d9edf76aa1e9706f1ee1bcc5459f62db1d4fe5a32078e5421b96bb1/traits-6.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2faa1467ac1da29f295a90ef0867474be79edb5aea133811c0e2403ac645adf9", size = 5088232 }, + { url = "https://files.pythonhosted.org/packages/ca/f0/a834f71d44b458b4452c67d04724caa75c39f3cd305d0ff10fc896ebf0a8/traits-6.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:053d7c176cc83b228d497cf96171792fb96b91a342bb9666071d513b4e064e66", size = 5098630 }, + { url = "https://files.pythonhosted.org/packages/53/d1/397ab29659a7c67df242b2e93aff90a0a8128ed23522282cd4dae0708cf7/traits-6.4.3-cp310-cp310-win32.whl", hash = "sha256:371c2b24daa7534206c8353d2fc62663b7068c25eb5f9a2a865e115e940abedf", size = 5008001 }, + { url = "https://files.pythonhosted.org/packages/b8/6e/4555770ec52185d9af215050e043b3fb1a2b78f0d6e1445796068763aab8/traits-6.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:fa8bc65a24dc3638f94f4fbc7d60c1838bb4e569c73317d37ab57fb3f23abb08", size = 5010645 }, + { url = "https://files.pythonhosted.org/packages/03/dd/d4ad2cb97a6f2e193ddde01e4c19878351d7b4596ada368bf105020ce7a5/traits-6.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8b098ecde49f78bc2587f43880d1344a8a81c9862244ad7e63d48409a8855b5c", size = 5034010 }, + { url = "https://files.pythonhosted.org/packages/28/b4/154845f19206e59ff9678521889d782836c816ff93c07051018cfe55fe65/traits-6.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8348b7f54f4a4be6c60872c23c5ec00db2322591d12d4eee3f3c7f472585504a", size = 5007367 }, + { url = "https://files.pythonhosted.org/packages/ee/91/280ca4e829c55b0c333f7848124fdb1de677931d547b89cb407cf01fe2d1/traits-6.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f791cca287a428dda5adc833915789f60dea1241e9ec21bde47668538fb01f40", size = 5107451 }, + { url = "https://files.pythonhosted.org/packages/a9/8d/abd78a7972b954f8b7c704440587093a587acd4538c03d298b8216b85e51/traits-6.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5d8eaa97adfb079872e760fab29452506f3cbe03e37a055737dae620d06ad5c", size = 5093177 }, + { url = "https://files.pythonhosted.org/packages/52/20/c88f196dda25e23164365a034b5c328c0c38ff132f3ed721dd5475aae10e/traits-6.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f622915217b5edfb95bf043a9eb75e7ab7c2f0697b59a92b3481e58c883b1e7", size = 5104898 }, + { url = "https://files.pythonhosted.org/packages/9e/04/b637406391e144e24d074f822751738c82743c14a62746b820aec4f6904a/traits-6.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3ed9ea3e7f830460fe39aeb170816152df87d8e99947034e74f2b58178599253", size = 5108629 }, + { url = "https://files.pythonhosted.org/packages/9a/98/0fa504329caf0aa002bab7f9370a06a1d6e23d7896d049129dd83431c6b0/traits-6.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:22d17a47dead1d78fcd7c85dc961e646c4b924ee6f0005b5b7020b88aeeee2ad", size = 5095743 }, + { url = "https://files.pythonhosted.org/packages/da/8d/5b853235971fff9c7e2a3e412c264f12ad943f0bc0a892df431e15b8b374/traits-6.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:79f937db0b0bd61272867f4a104d86a132cffab6cf8046f590fed800d621d9bd", size = 5105842 }, + { url = "https://files.pythonhosted.org/packages/1e/9a/79458a4ac8f3d77deaaeb0be2f91e39ccc78055b512f59304b01a97aeb5f/traits-6.4.3-cp311-cp311-win32.whl", hash = "sha256:c51635d076b4c2919e7fd7e82198cd7c3d5d0beee2cc4a5c366f2706dee1e465", size = 5007979 }, + { url = "https://files.pythonhosted.org/packages/14/a1/936fa87e771998220d965ab3d25b230a7dad6f418d496156a21a3db96db7/traits-6.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:c0e8ef49bcf8ab4e880009de934ce06a4f3fe47b3a064807eebd0e80798c0ab5", size = 5010625 }, + { url = "https://files.pythonhosted.org/packages/e3/12/a8605a9f084118ed585c758dfedaed2f0e4327b12052250e6d625faa439f/traits-6.4.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:dba809d945d599980694b69c5d5756115959a3899afa9354f0994eebdc843e14", size = 5034748 }, + { url = "https://files.pythonhosted.org/packages/6e/43/6778b5a93d3ea3378b6deb628e8fab10e672ba5fed32100b10918b640be5/traits-6.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e1600875fc3b1cd0502ce0d4317479e0ae0b91e4fa6f14fff1cc525eb1ad088e", size = 5007931 }, + { url = "https://files.pythonhosted.org/packages/78/37/646223fdf1d84313c222fc993417b1fd93ff67435c7e30cfc8bf958a2ce8/traits-6.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92add65d02b4d9ebd6b607948eff84c3a4dfaa642335e3d63c5d8c5a52f08e98", size = 5110974 }, + { url = "https://files.pythonhosted.org/packages/e2/b3/655545952a5a2d7c8a39c9170cc8456f060b14253fe0809c37f3ee159b1c/traits-6.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13181f5433dc2d2fc2d9ee3f7ee3d0ede734c4eb53311ab9710500e7c45ae2c2", size = 5095967 }, + { url = "https://files.pythonhosted.org/packages/eb/7b/88f70954f63d4513bfa23a2cae5f02ba78f551e131d8a359b829fced194f/traits-6.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2203ab21df7fd58d0eefb26c56501654ababcafe5e9299bac83dc1ce559be2f4", size = 5110142 }, + { url = "https://files.pythonhosted.org/packages/6a/22/0237737233ac504894fd8656b42735b2efddd1ec14c6a538449c6ca53b50/traits-6.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:44a663117ebd197dbba4a9222ac0ecbb7a6bff7cf97e051924e987685025edd9", size = 5115445 }, + { url = "https://files.pythonhosted.org/packages/2c/14/c88e137eefc90cbdf60e5bbf61cb559ce7e901f33d66aeeb465a365b1ff5/traits-6.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:83f531a45d681bbe133953c5d5a5a4b572d06239672b3346fb927706bee0c9a9", size = 5102225 }, + { url = "https://files.pythonhosted.org/packages/1f/ab/8de13dc6c30cb8c72239725a15c2c3bed4a6dbef6b1b73afc05f0b740373/traits-6.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7b1f5763ae1e6b1cab0ce7c82f7da3a642170666fba32dd643181f564e576d4d", size = 5114149 }, + { url = "https://files.pythonhosted.org/packages/88/36/2c09faccc21e6d6b469e17736fb9e18d3f9237e430c6da4afce685dff4bd/traits-6.4.3-cp312-cp312-win32.whl", hash = "sha256:f1253043fb8f034c4342b78e56490a1f2386ed6a645cf5e33e3aa428b4b680b6", size = 5008475 }, + { url = "https://files.pythonhosted.org/packages/52/7c/ff400b76ae5d6a596894002d536c2fde758194a7771bab9c20cc6c33fb06/traits-6.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:a01968bff6b13a0dc64842caf598ffc628bd973610b309d14b50fa92ce493a56", size = 5010809 }, +] + +[[package]] +name = "trampoline" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/54/d2805324fb746d8da86d3844bee4f55c0cfd6c136de61b713772d44c5bea/trampoline-0.1.2-py3-none-any.whl", hash = "sha256:36cc9a4ff9811843d177fc0e0740efbd7da39eadfe6e50c9e2937cbc06d899d9", size = 5173 }, +] + +[[package]] +name = "triton" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/05/ed974ce87fe8c8843855daa2136b3409ee1c126707ab54a8b72815c08b49/triton-2.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2294514340cfe4e8f4f9e5c66c702744c4a117d25e618bd08469d0bfed1e2e5", size = 167900779 }, + { url = "https://files.pythonhosted.org/packages/bd/ac/3974caaa459bf2c3a244a84be8d17561f631f7d42af370fc311defeca2fb/triton-2.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da58a152bddb62cafa9a857dd2bc1f886dbf9f9c90a2b5da82157cd2b34392b0", size = 167928356 }, + { url = "https://files.pythonhosted.org/packages/0e/49/2e1bbae4542b8f624e409540b4197e37ab22a88e8685e99debe721cc2b50/triton-2.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af58716e721460a61886668b205963dc4d1e4ac20508cc3f623aef0d70283d5", size = 167933985 }, +] + +[[package]] +name = "typeguard" +version = "2.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/38/c61bfcf62a7b572b5e9363a802ff92559cb427ee963048e1442e3aef7490/typeguard-2.13.3.tar.gz", hash = "sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4", size = 40604 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/bb/d43e5c75054e53efce310e79d63df0ac3f25e34c926be5dffb7d283fb2a8/typeguard-2.13.3-py3-none-any.whl", hash = "sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1", size = 17605 }, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20241003" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/f8/f6ee4c803a7beccffee21bb29a71573b39f7037c224843eff53e5308c16e/types-python-dateutil-2.9.0.20241003.tar.gz", hash = "sha256:58cb85449b2a56d6684e41aeefb4c4280631246a0da1a719bdbe6f3fb0317446", size = 9210 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/d6/ba5f61958f358028f2e2ba1b8e225b8e263053bd57d3a79e2d2db64c807b/types_python_dateutil-2.9.0.20241003-py3-none-any.whl", hash = "sha256:250e1d8e80e7bbc3a6c99b907762711d1a1cdd00e978ad39cb5940f6f0a87f3d", size = 9693 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "tzdata" +version = "2024.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/34/943888654477a574a86a98e9896bae89c7aa15078ec29f490fef2f1e5384/tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", size = 193282 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 }, +] + +[[package]] +name = "uncertainties" +version = "3.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/b0/f926a3faf468b9784bdecb8d9328b531743937ead284b2e8d406d96e8b0f/uncertainties-3.2.2.tar.gz", hash = "sha256:e62c86fdc64429828229de6ab4e11466f114907e6bd343c077858994cc12e00b", size = 143865 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/fc/97711d2a502881d871e3cf2d2645e21e7f8e4d4fd9a56937557790cade6a/uncertainties-3.2.2-py3-none-any.whl", hash = "sha256:fd8543355952f4052786ed4150acaf12e23117bd0f5bd03ea07de466bce646e7", size = 58266 }, +] + +[[package]] +name = "uri-template" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140 }, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 }, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, +] + +[[package]] +name = "webcolors" +version = "24.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/f8/53150a5bda7e042840b14f0236e1c0a4819d403658e3d453237983addfac/webcolors-24.8.0.tar.gz", hash = "sha256:08b07af286a01bcd30d583a7acadf629583d1f79bfef27dd2c2c5c263817277d", size = 42392 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/33/12020ba99beaff91682b28dc0bbf0345bbc3244a4afbae7644e4fa348f23/webcolors-24.8.0-py3-none-any.whl", hash = "sha256:fc4c3b59358ada164552084a8ebee637c221e4059267d0f8325b3b560f6c7f0a", size = 15027 }, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, +] + +[[package]] +name = "websocket-client" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826 }, +] + +[[package]] +name = "werkzeug" +version = "3.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/e2/6dbcaab07560909ff8f654d3a2e5a60552d937c909455211b1b36d7101dc/werkzeug-3.0.4.tar.gz", hash = "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306", size = 803966 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/84/997bbf7c2bf2dc3f09565c6d0b4959fefe5355c18c4096cfd26d83e0785b/werkzeug-3.0.4-py3-none-any.whl", hash = "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c", size = 227554 }, +] + +[[package]] +name = "widgetsnbextension" +version = "4.0.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/fc/238c424fd7f4ebb25f8b1da9a934a3ad7c848286732ae04263661eb0fc03/widgetsnbextension-4.0.13.tar.gz", hash = "sha256:ffcb67bc9febd10234a362795f643927f4e0c05d9342c727b65d2384f8feacb6", size = 1164730 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/02/88b65cc394961a60c43c70517066b6b679738caf78506a5da7b88ffcb643/widgetsnbextension-4.0.13-py3-none-any.whl", hash = "sha256:74b2692e8500525cc38c2b877236ba51d34541e6385eeed5aec15a70f88a6c71", size = 2335872 }, +] + +[[package]] +name = "wrapt" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/4c/063a912e20bcef7124e0df97282a8af3ff3e4b603ce84c481d6d7346be0a/wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d", size = 53972 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/c6/5375258add3777494671d8cec27cdf5402abd91016dee24aa2972c61fedf/wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4", size = 37315 }, + { url = "https://files.pythonhosted.org/packages/32/12/e11adfde33444986135d8881b401e4de6cbb4cced046edc6b464e6ad7547/wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020", size = 38160 }, + { url = "https://files.pythonhosted.org/packages/70/7d/3dcc4a7e96f8d3e398450ec7703db384413f79bd6c0196e0e139055ce00f/wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440", size = 80419 }, + { url = "https://files.pythonhosted.org/packages/d1/c4/8dfdc3c2f0b38be85c8d9fdf0011ebad2f54e40897f9549a356bebb63a97/wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487", size = 72669 }, + { url = "https://files.pythonhosted.org/packages/49/83/b40bc1ad04a868b5b5bcec86349f06c1ee1ea7afe51dc3e46131e4f39308/wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf", size = 80271 }, + { url = "https://files.pythonhosted.org/packages/19/d4/cd33d3a82df73a064c9b6401d14f346e1d2fb372885f0295516ec08ed2ee/wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72", size = 84748 }, + { url = "https://files.pythonhosted.org/packages/ef/58/2fde309415b5fa98fd8f5f4a11886cbf276824c4c64d45a39da342fff6fe/wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0", size = 77522 }, + { url = "https://files.pythonhosted.org/packages/07/44/359e4724a92369b88dbf09878a7cde7393cf3da885567ea898e5904049a3/wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136", size = 84780 }, + { url = "https://files.pythonhosted.org/packages/88/8f/706f2fee019360cc1da652353330350c76aa5746b4e191082e45d6838faf/wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d", size = 35335 }, + { url = "https://files.pythonhosted.org/packages/19/2b/548d23362e3002ebbfaefe649b833fa43f6ca37ac3e95472130c4b69e0b4/wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2", size = 37528 }, + { url = "https://files.pythonhosted.org/packages/fd/03/c188ac517f402775b90d6f312955a5e53b866c964b32119f2ed76315697e/wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09", size = 37313 }, + { url = "https://files.pythonhosted.org/packages/0f/16/ea627d7817394db04518f62934a5de59874b587b792300991b3c347ff5e0/wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d", size = 38164 }, + { url = "https://files.pythonhosted.org/packages/7f/a7/f1212ba098f3de0fd244e2de0f8791ad2539c03bef6c05a9fcb03e45b089/wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389", size = 80890 }, + { url = "https://files.pythonhosted.org/packages/b7/96/bb5e08b3d6db003c9ab219c487714c13a237ee7dcc572a555eaf1ce7dc82/wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060", size = 73118 }, + { url = "https://files.pythonhosted.org/packages/6e/52/2da48b35193e39ac53cfb141467d9f259851522d0e8c87153f0ba4205fb1/wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1", size = 80746 }, + { url = "https://files.pythonhosted.org/packages/11/fb/18ec40265ab81c0e82a934de04596b6ce972c27ba2592c8b53d5585e6bcd/wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3", size = 85668 }, + { url = "https://files.pythonhosted.org/packages/0f/ef/0ecb1fa23145560431b970418dce575cfaec555ab08617d82eb92afc7ccf/wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956", size = 78556 }, + { url = "https://files.pythonhosted.org/packages/25/62/cd284b2b747f175b5a96cbd8092b32e7369edab0644c45784871528eb852/wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d", size = 85712 }, + { url = "https://files.pythonhosted.org/packages/e5/a7/47b7ff74fbadf81b696872d5ba504966591a3468f1bc86bca2f407baef68/wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362", size = 35327 }, + { url = "https://files.pythonhosted.org/packages/cf/c3/0084351951d9579ae83a3d9e38c140371e4c6b038136909235079f2e6e78/wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89", size = 37523 }, + { url = "https://files.pythonhosted.org/packages/92/17/224132494c1e23521868cdd57cd1e903f3b6a7ba6996b7b8f077ff8ac7fe/wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b", size = 37614 }, + { url = "https://files.pythonhosted.org/packages/6a/d7/cfcd73e8f4858079ac59d9db1ec5a1349bc486ae8e9ba55698cc1f4a1dff/wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36", size = 38316 }, + { url = "https://files.pythonhosted.org/packages/7e/79/5ff0a5c54bda5aec75b36453d06be4f83d5cd4932cc84b7cb2b52cee23e2/wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73", size = 86322 }, + { url = "https://files.pythonhosted.org/packages/c4/81/e799bf5d419f422d8712108837c1d9bf6ebe3cb2a81ad94413449543a923/wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809", size = 79055 }, + { url = "https://files.pythonhosted.org/packages/62/62/30ca2405de6a20448ee557ab2cd61ab9c5900be7cbd18a2639db595f0b98/wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b", size = 87291 }, + { url = "https://files.pythonhosted.org/packages/49/4e/5d2f6d7b57fc9956bf06e944eb00463551f7d52fc73ca35cfc4c2cdb7aed/wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81", size = 90374 }, + { url = "https://files.pythonhosted.org/packages/a6/9b/c2c21b44ff5b9bf14a83252a8b973fb84923764ff63db3e6dfc3895cf2e0/wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9", size = 83896 }, + { url = "https://files.pythonhosted.org/packages/14/26/93a9fa02c6f257df54d7570dfe8011995138118d11939a4ecd82cb849613/wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c", size = 91738 }, + { url = "https://files.pythonhosted.org/packages/a2/5b/4660897233eb2c8c4de3dc7cefed114c61bacb3c28327e64150dc44ee2f6/wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc", size = 35568 }, + { url = "https://files.pythonhosted.org/packages/5c/cc/8297f9658506b224aa4bd71906447dea6bb0ba629861a758c28f67428b91/wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8", size = 37653 }, + { url = "https://files.pythonhosted.org/packages/ff/21/abdedb4cdf6ff41ebf01a74087740a709e2edb146490e4d9beea054b0b7a/wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1", size = 23362 }, +] + +[[package]] +name = "wurlitzer" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/90/623f99c55c7d0727a58eb2b7dfb65cb406c561a5c2e9a95b0d6a450c473d/wurlitzer-3.1.1.tar.gz", hash = "sha256:bfb9144ab9f02487d802b9ff89dbd3fa382d08f73e12db8adc4c2fb00cd39bd9", size = 11867 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/24/93ce54550a9dd3fd996ed477f00221f215bf6da3580397fbc138d6036e2e/wurlitzer-3.1.1-py3-none-any.whl", hash = "sha256:0b2749c2cde3ef640bf314a9f94b24d929fe1ca476974719a6909dfc568c3aac", size = 8590 }, +] + +[[package]] +name = "xxhash" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/5e/d6e5258d69df8b4ed8c83b6664f2b47d30d2dec551a29ad72a6c69eafd31/xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f", size = 84241 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/8a/0e9feca390d512d293afd844d31670e25608c4a901e10202aa98785eab09/xxhash-3.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ece616532c499ee9afbb83078b1b952beffef121d989841f7f4b3dc5ac0fd212", size = 31970 }, + { url = "https://files.pythonhosted.org/packages/16/e6/be5aa49580cd064a18200ab78e29b88b1127e1a8c7955eb8ecf81f2626eb/xxhash-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3171f693dbc2cef6477054a665dc255d996646b4023fe56cb4db80e26f4cc520", size = 30801 }, + { url = "https://files.pythonhosted.org/packages/20/ee/b8a99ebbc6d1113b3a3f09e747fa318c3cde5b04bd9c197688fadf0eeae8/xxhash-3.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5d3e570ef46adaf93fc81b44aca6002b5a4d8ca11bd0580c07eac537f36680", size = 220927 }, + { url = "https://files.pythonhosted.org/packages/58/62/15d10582ef159283a5c2b47f6d799fc3303fe3911d5bb0bcc820e1ef7ff4/xxhash-3.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cb29a034301e2982df8b1fe6328a84f4b676106a13e9135a0d7e0c3e9f806da", size = 200360 }, + { url = "https://files.pythonhosted.org/packages/23/41/61202663ea9b1bd8e53673b8ec9e2619989353dba8cfb68e59a9cbd9ffe3/xxhash-3.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0d307d27099bb0cbeea7260eb39ed4fdb99c5542e21e94bb6fd29e49c57a23", size = 428528 }, + { url = "https://files.pythonhosted.org/packages/f2/07/d9a3059f702dec5b3b703737afb6dda32f304f6e9da181a229dafd052c29/xxhash-3.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0342aafd421795d740e514bc9858ebddfc705a75a8c5046ac56d85fe97bf196", size = 194149 }, + { url = "https://files.pythonhosted.org/packages/eb/58/27caadf78226ecf1d62dbd0c01d152ed381c14c1ee4ad01f0d460fc40eac/xxhash-3.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dbbd9892c5ebffeca1ed620cf0ade13eb55a0d8c84e0751a6653adc6ac40d0c", size = 207703 }, + { url = "https://files.pythonhosted.org/packages/b1/08/32d558ce23e1e068453c39aed7b3c1cdc690c177873ec0ca3a90d5808765/xxhash-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4cc2d67fdb4d057730c75a64c5923abfa17775ae234a71b0200346bfb0a7f482", size = 216255 }, + { url = "https://files.pythonhosted.org/packages/3f/d4/2b971e2d2b0a61045f842b622ef11e94096cf1f12cd448b6fd426e80e0e2/xxhash-3.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ec28adb204b759306a3d64358a5e5c07d7b1dd0ccbce04aa76cb9377b7b70296", size = 202744 }, + { url = "https://files.pythonhosted.org/packages/19/ae/6a6438864a8c4c39915d7b65effd85392ebe22710412902487e51769146d/xxhash-3.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1328f6d8cca2b86acb14104e381225a3d7b42c92c4b86ceae814e5c400dbb415", size = 210115 }, + { url = "https://files.pythonhosted.org/packages/48/7d/b3c27c27d1fc868094d02fe4498ccce8cec9fcc591825c01d6bcb0b4fc49/xxhash-3.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8d47ebd9f5d9607fd039c1fbf4994e3b071ea23eff42f4ecef246ab2b7334198", size = 414247 }, + { url = "https://files.pythonhosted.org/packages/a1/05/918f9e7d2fbbd334b829997045d341d6239b563c44e683b9a7ef8fe50f5d/xxhash-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b96d559e0fcddd3343c510a0fe2b127fbff16bf346dd76280b82292567523442", size = 191419 }, + { url = "https://files.pythonhosted.org/packages/08/29/dfe393805b2f86bfc47c290b275f0b7c189dc2f4e136fd4754f32eb18a8d/xxhash-3.5.0-cp310-cp310-win32.whl", hash = "sha256:61c722ed8d49ac9bc26c7071eeaa1f6ff24053d553146d5df031802deffd03da", size = 30114 }, + { url = "https://files.pythonhosted.org/packages/7b/d7/aa0b22c4ebb7c3ccb993d4c565132abc641cd11164f8952d89eb6a501909/xxhash-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9bed5144c6923cc902cd14bb8963f2d5e034def4486ab0bbe1f58f03f042f9a9", size = 30003 }, + { url = "https://files.pythonhosted.org/packages/69/12/f969b81541ee91b55f1ce469d7ab55079593c80d04fd01691b550e535000/xxhash-3.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:893074d651cf25c1cc14e3bea4fceefd67f2921b1bb8e40fcfeba56820de80c6", size = 26773 }, + { url = "https://files.pythonhosted.org/packages/b8/c7/afed0f131fbda960ff15eee7f304fa0eeb2d58770fade99897984852ef23/xxhash-3.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02c2e816896dc6f85922ced60097bcf6f008dedfc5073dcba32f9c8dd786f3c1", size = 31969 }, + { url = "https://files.pythonhosted.org/packages/8c/0c/7c3bc6d87e5235672fcc2fb42fd5ad79fe1033925f71bf549ee068c7d1ca/xxhash-3.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6027dcd885e21581e46d3c7f682cfb2b870942feeed58a21c29583512c3f09f8", size = 30800 }, + { url = "https://files.pythonhosted.org/packages/04/9e/01067981d98069eec1c20201f8c145367698e9056f8bc295346e4ea32dd1/xxhash-3.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1308fa542bbdbf2fa85e9e66b1077eea3a88bef38ee8a06270b4298a7a62a166", size = 221566 }, + { url = "https://files.pythonhosted.org/packages/d4/09/d4996de4059c3ce5342b6e1e6a77c9d6c91acce31f6ed979891872dd162b/xxhash-3.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28b2fdcee797e1c1961cd3bcd3d545cab22ad202c846235197935e1df2f8ef7", size = 201214 }, + { url = "https://files.pythonhosted.org/packages/62/f5/6d2dc9f8d55a7ce0f5e7bfef916e67536f01b85d32a9fbf137d4cadbee38/xxhash-3.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:924361811732ddad75ff23e90efd9ccfda4f664132feecb90895bade6a1b4623", size = 429433 }, + { url = "https://files.pythonhosted.org/packages/d9/72/9256303f10e41ab004799a4aa74b80b3c5977d6383ae4550548b24bd1971/xxhash-3.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89997aa1c4b6a5b1e5b588979d1da048a3c6f15e55c11d117a56b75c84531f5a", size = 194822 }, + { url = "https://files.pythonhosted.org/packages/34/92/1a3a29acd08248a34b0e6a94f4e0ed9b8379a4ff471f1668e4dce7bdbaa8/xxhash-3.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:685c4f4e8c59837de103344eb1c8a3851f670309eb5c361f746805c5471b8c88", size = 208538 }, + { url = "https://files.pythonhosted.org/packages/53/ad/7fa1a109663366de42f724a1cdb8e796a260dbac45047bce153bc1e18abf/xxhash-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbd2ecfbfee70bc1a4acb7461fa6af7748ec2ab08ac0fa298f281c51518f982c", size = 216953 }, + { url = "https://files.pythonhosted.org/packages/35/02/137300e24203bf2b2a49b48ce898ecce6fd01789c0fcd9c686c0a002d129/xxhash-3.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25b5a51dc3dfb20a10833c8eee25903fd2e14059e9afcd329c9da20609a307b2", size = 203594 }, + { url = "https://files.pythonhosted.org/packages/23/03/aeceb273933d7eee248c4322b98b8e971f06cc3880e5f7602c94e5578af5/xxhash-3.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a8fb786fb754ef6ff8c120cb96629fb518f8eb5a61a16aac3a979a9dbd40a084", size = 210971 }, + { url = "https://files.pythonhosted.org/packages/e3/64/ed82ec09489474cbb35c716b189ddc1521d8b3de12b1b5ab41ce7f70253c/xxhash-3.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a905ad00ad1e1c34fe4e9d7c1d949ab09c6fa90c919860c1534ff479f40fd12d", size = 415050 }, + { url = "https://files.pythonhosted.org/packages/71/43/6db4c02dcb488ad4e03bc86d70506c3d40a384ee73c9b5c93338eb1f3c23/xxhash-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:963be41bcd49f53af6d795f65c0da9b4cc518c0dd9c47145c98f61cb464f4839", size = 192216 }, + { url = "https://files.pythonhosted.org/packages/22/6d/db4abec29e7a567455344433d095fdb39c97db6955bb4a2c432e486b4d28/xxhash-3.5.0-cp311-cp311-win32.whl", hash = "sha256:109b436096d0a2dd039c355fa3414160ec4d843dfecc64a14077332a00aeb7da", size = 30120 }, + { url = "https://files.pythonhosted.org/packages/52/1c/fa3b61c0cf03e1da4767213672efe186b1dfa4fc901a4a694fb184a513d1/xxhash-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:b702f806693201ad6c0a05ddbbe4c8f359626d0b3305f766077d51388a6bac58", size = 30003 }, + { url = "https://files.pythonhosted.org/packages/6b/8e/9e6fc572acf6e1cc7ccb01973c213f895cb8668a9d4c2b58a99350da14b7/xxhash-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:c4dcb4120d0cc3cc448624147dba64e9021b278c63e34a38789b688fd0da9bf3", size = 26777 }, + { url = "https://files.pythonhosted.org/packages/07/0e/1bfce2502c57d7e2e787600b31c83535af83746885aa1a5f153d8c8059d6/xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00", size = 31969 }, + { url = "https://files.pythonhosted.org/packages/3f/d6/8ca450d6fe5b71ce521b4e5db69622383d039e2b253e9b2f24f93265b52c/xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9", size = 30787 }, + { url = "https://files.pythonhosted.org/packages/5b/84/de7c89bc6ef63d750159086a6ada6416cc4349eab23f76ab870407178b93/xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84", size = 220959 }, + { url = "https://files.pythonhosted.org/packages/fe/86/51258d3e8a8545ff26468c977101964c14d56a8a37f5835bc0082426c672/xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793", size = 200006 }, + { url = "https://files.pythonhosted.org/packages/02/0a/96973bd325412feccf23cf3680fd2246aebf4b789122f938d5557c54a6b2/xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be", size = 428326 }, + { url = "https://files.pythonhosted.org/packages/11/a7/81dba5010f7e733de88af9555725146fc133be97ce36533867f4c7e75066/xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6", size = 194380 }, + { url = "https://files.pythonhosted.org/packages/fb/7d/f29006ab398a173f4501c0e4977ba288f1c621d878ec217b4ff516810c04/xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90", size = 207934 }, + { url = "https://files.pythonhosted.org/packages/8a/6e/6e88b8f24612510e73d4d70d9b0c7dff62a2e78451b9f0d042a5462c8d03/xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27", size = 216301 }, + { url = "https://files.pythonhosted.org/packages/af/51/7862f4fa4b75a25c3b4163c8a873f070532fe5f2d3f9b3fc869c8337a398/xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2", size = 203351 }, + { url = "https://files.pythonhosted.org/packages/22/61/8d6a40f288f791cf79ed5bb113159abf0c81d6efb86e734334f698eb4c59/xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d", size = 210294 }, + { url = "https://files.pythonhosted.org/packages/17/02/215c4698955762d45a8158117190261b2dbefe9ae7e5b906768c09d8bc74/xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab", size = 414674 }, + { url = "https://files.pythonhosted.org/packages/31/5c/b7a8db8a3237cff3d535261325d95de509f6a8ae439a5a7a4ffcff478189/xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e", size = 192022 }, + { url = "https://files.pythonhosted.org/packages/78/e3/dd76659b2811b3fd06892a8beb850e1996b63e9235af5a86ea348f053e9e/xxhash-3.5.0-cp312-cp312-win32.whl", hash = "sha256:f7b58d1fd3551b8c80a971199543379be1cee3d0d409e1f6d8b01c1a2eebf1f8", size = 30170 }, + { url = "https://files.pythonhosted.org/packages/d9/6b/1c443fe6cfeb4ad1dcf231cdec96eb94fb43d6498b4469ed8b51f8b59a37/xxhash-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0cafd3a2af231b4e113fba24a65d7922af91aeb23774a8b78228e6cd785e3e", size = 30040 }, + { url = "https://files.pythonhosted.org/packages/0f/eb/04405305f290173acc0350eba6d2f1a794b57925df0398861a20fbafa415/xxhash-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:586886c7e89cb9828bcd8a5686b12e161368e0064d040e225e72607b43858ba2", size = 26796 }, + { url = "https://files.pythonhosted.org/packages/c9/b8/e4b3ad92d249be5c83fa72916c9091b0965cb0faeff05d9a0a3870ae6bff/xxhash-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37889a0d13b0b7d739cfc128b1c902f04e32de17b33d74b637ad42f1c55101f6", size = 31795 }, + { url = "https://files.pythonhosted.org/packages/fc/d8/b3627a0aebfbfa4c12a41e22af3742cf08c8ea84f5cc3367b5de2d039cce/xxhash-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97a662338797c660178e682f3bc180277b9569a59abfb5925e8620fba00b9fc5", size = 30792 }, + { url = "https://files.pythonhosted.org/packages/c3/cc/762312960691da989c7cd0545cb120ba2a4148741c6ba458aa723c00a3f8/xxhash-3.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f85e0108d51092bdda90672476c7d909c04ada6923c14ff9d913c4f7dc8a3bc", size = 220950 }, + { url = "https://files.pythonhosted.org/packages/fe/e9/cc266f1042c3c13750e86a535496b58beb12bf8c50a915c336136f6168dc/xxhash-3.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2fd827b0ba763ac919440042302315c564fdb797294d86e8cdd4578e3bc7f3", size = 199980 }, + { url = "https://files.pythonhosted.org/packages/bf/85/a836cd0dc5cc20376de26b346858d0ac9656f8f730998ca4324921a010b9/xxhash-3.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82085c2abec437abebf457c1d12fccb30cc8b3774a0814872511f0f0562c768c", size = 428324 }, + { url = "https://files.pythonhosted.org/packages/b4/0e/15c243775342ce840b9ba34aceace06a1148fa1630cd8ca269e3223987f5/xxhash-3.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07fda5de378626e502b42b311b049848c2ef38784d0d67b6f30bb5008642f8eb", size = 194370 }, + { url = "https://files.pythonhosted.org/packages/87/a1/b028bb02636dfdc190da01951d0703b3d904301ed0ef6094d948983bef0e/xxhash-3.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c279f0d2b34ef15f922b77966640ade58b4ccdfef1c4d94b20f2a364617a493f", size = 207911 }, + { url = "https://files.pythonhosted.org/packages/80/d5/73c73b03fc0ac73dacf069fdf6036c9abad82de0a47549e9912c955ab449/xxhash-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89e66ceed67b213dec5a773e2f7a9e8c58f64daeb38c7859d8815d2c89f39ad7", size = 216352 }, + { url = "https://files.pythonhosted.org/packages/b6/2a/5043dba5ddbe35b4fe6ea0a111280ad9c3d4ba477dd0f2d1fe1129bda9d0/xxhash-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bcd51708a633410737111e998ceb3b45d3dbc98c0931f743d9bb0a209033a326", size = 203410 }, + { url = "https://files.pythonhosted.org/packages/a2/b2/9a8ded888b7b190aed75b484eb5c853ddd48aa2896e7b59bbfbce442f0a1/xxhash-3.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ff2c0a34eae7df88c868be53a8dd56fbdf592109e21d4bfa092a27b0bf4a7bf", size = 210322 }, + { url = "https://files.pythonhosted.org/packages/98/62/440083fafbc917bf3e4b67c2ade621920dd905517e85631c10aac955c1d2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e28503dccc7d32e0b9817aa0cbfc1f45f563b2c995b7a66c4c8a0d232e840c7", size = 414725 }, + { url = "https://files.pythonhosted.org/packages/75/db/009206f7076ad60a517e016bb0058381d96a007ce3f79fa91d3010f49cc2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a6c50017518329ed65a9e4829154626f008916d36295b6a3ba336e2458824c8c", size = 192070 }, + { url = "https://files.pythonhosted.org/packages/1f/6d/c61e0668943a034abc3a569cdc5aeae37d686d9da7e39cf2ed621d533e36/xxhash-3.5.0-cp313-cp313-win32.whl", hash = "sha256:53a068fe70301ec30d868ece566ac90d873e3bb059cf83c32e76012c889b8637", size = 30172 }, + { url = "https://files.pythonhosted.org/packages/96/14/8416dce965f35e3d24722cdf79361ae154fa23e2ab730e5323aa98d7919e/xxhash-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:80babcc30e7a1a484eab952d76a4f4673ff601f54d5142c26826502740e70b43", size = 30041 }, + { url = "https://files.pythonhosted.org/packages/27/ee/518b72faa2073f5aa8e3262408d284892cb79cf2754ba0c3a5870645ef73/xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b", size = 26801 }, + { url = "https://files.pythonhosted.org/packages/ab/9a/233606bada5bd6f50b2b72c45de3d9868ad551e83893d2ac86dc7bb8553a/xxhash-3.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2014c5b3ff15e64feecb6b713af12093f75b7926049e26a580e94dcad3c73d8c", size = 29732 }, + { url = "https://files.pythonhosted.org/packages/0c/67/f75276ca39e2c6604e3bee6c84e9db8a56a4973fde9bf35989787cf6e8aa/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fab81ef75003eda96239a23eda4e4543cedc22e34c373edcaf744e721a163986", size = 36214 }, + { url = "https://files.pythonhosted.org/packages/0f/f8/f6c61fd794229cc3848d144f73754a0c107854372d7261419dcbbd286299/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e2febf914ace002132aa09169cc572e0d8959d0f305f93d5828c4836f9bc5a6", size = 32020 }, + { url = "https://files.pythonhosted.org/packages/79/d3/c029c99801526f859e6b38d34ab87c08993bf3dcea34b11275775001638a/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d3a10609c51da2a1c0ea0293fc3968ca0a18bd73838455b5bca3069d7f8e32b", size = 40515 }, + { url = "https://files.pythonhosted.org/packages/62/e3/bef7b82c1997579c94de9ac5ea7626d01ae5858aa22bf4fcb38bf220cb3e/xxhash-3.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a74f23335b9689b66eb6dbe2a931a88fcd7a4c2cc4b1cb0edba8ce381c7a1da", size = 30064 }, +] + +[[package]] +name = "yarl" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/52/e9766cc6c2eab7dd1e9749c52c9879317500b46fb97d4105223f86679f93/yarl-1.16.0.tar.gz", hash = "sha256:b6f687ced5510a9a2474bbae96a4352e5ace5fa34dc44a217b0537fec1db00b4", size = 176548 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/30/00b17348655202e4bd24f8d79cd062888e5d3bdbf2ba726615c5d21b54a5/yarl-1.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32468f41242d72b87ab793a86d92f885355bcf35b3355aa650bfa846a5c60058", size = 140016 }, + { url = "https://files.pythonhosted.org/packages/a5/15/9b7b85b72b81f180689257b2bb6e54d5d0764a399679aa06d5dec8ca6e2e/yarl-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:234f3a3032b505b90e65b5bc6652c2329ea7ea8855d8de61e1642b74b4ee65d2", size = 92953 }, + { url = "https://files.pythonhosted.org/packages/31/41/91848bbb76789336d3b786ff144030001b5027b17729b3afa32da668f5b0/yarl-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a0296040e5cddf074c7f5af4a60f3fc42c0237440df7bcf5183be5f6c802ed5", size = 90793 }, + { url = "https://files.pythonhosted.org/packages/6c/99/f1ada764e350ab054e14902f3f68589a7d77469ac47fbc512aa1a78a2f35/yarl-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de6c14dd7c7c0badba48157474ea1f03ebee991530ba742d381b28d4f314d6f3", size = 313155 }, + { url = "https://files.pythonhosted.org/packages/75/fd/998ccdb489ca97d9073d882265203a2fae4c5bff30eb9b8a0bbbed7aef2b/yarl-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b140e532fe0266003c936d017c1ac301e72ee4a3fd51784574c05f53718a55d8", size = 328624 }, + { url = "https://files.pythonhosted.org/packages/2d/5d/395bbae1f509f64e6d26b7ffffff178d70c5480f15af735dfb0afb8f0dc5/yarl-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:019f5d58093402aa8f6661e60fd82a28746ad6d156f6c5336a70a39bd7b162b9", size = 325163 }, + { url = "https://files.pythonhosted.org/packages/1d/25/65601d336189d122483f5ff0276b08278fa4778f833458cfcac5c6eddc87/yarl-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c42998fd1cbeb53cd985bff0e4bc25fbe55fd6eb3a545a724c1012d69d5ec84", size = 318076 }, + { url = "https://files.pythonhosted.org/packages/50/bb/0c9692ec457c1ed023654a9fba6d0c69a20c79b56275d972f6a24ab18547/yarl-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7c30fb38c300fe8140df30a046a01769105e4cf4282567a29b5cdb635b66c4", size = 309551 }, + { url = "https://files.pythonhosted.org/packages/a5/2f/d0ced2050a203241a3f2e05c5bb86038b071f216897defd824dd85333f9e/yarl-1.16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e49e0fd86c295e743fd5be69b8b0712f70a686bc79a16e5268386c2defacaade", size = 317678 }, + { url = "https://files.pythonhosted.org/packages/46/93/b7359aa2bd0567eca72491cd20059744ed6ee00f08cd58c861243f656a90/yarl-1.16.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:b9ca7b9147eb1365c8bab03c003baa1300599575effad765e0b07dd3501ea9af", size = 317003 }, + { url = "https://files.pythonhosted.org/packages/87/18/77ef4d45d19ecafad0f7c07d5cf13a757a90122383494bc5a3e8ee68e2f2/yarl-1.16.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:27e11db3f1e6a51081a981509f75617b09810529de508a181319193d320bc5c7", size = 322795 }, + { url = "https://files.pythonhosted.org/packages/28/a9/b38880bf79665d1c8a3d4c09d6f7a686a50f8c74caf07603a2b8e5314038/yarl-1.16.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8994c42f4ca25df5380ddf59f315c518c81df6a68fed5bb0c159c6cb6b92f120", size = 337022 }, + { url = "https://files.pythonhosted.org/packages/e9/79/865788b297fc17117e3ff6ea74d5f864185085d61adc3364444732095254/yarl-1.16.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:542fa8e09a581bcdcbb30607c7224beff3fdfb598c798ccd28a8184ffc18b7eb", size = 338357 }, + { url = "https://files.pythonhosted.org/packages/bd/5e/c5cba528448f73c7035c9d3c07261b54312d8caa8372eeeff5e1f07e43ec/yarl-1.16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2bd6a51010c7284d191b79d3b56e51a87d8e1c03b0902362945f15c3d50ed46b", size = 330470 }, + { url = "https://files.pythonhosted.org/packages/1a/e4/90757595d81ec328ad94afa62d0724903a6c72b76e0ee9c9af9d8a399dd2/yarl-1.16.0-cp310-cp310-win32.whl", hash = "sha256:178ccb856e265174a79f59721031060f885aca428983e75c06f78aa24b91d929", size = 82967 }, + { url = "https://files.pythonhosted.org/packages/01/5a/b82ec5e7557b0d938b9475cbb5dcbb1f98c8601101188d79e423dc215cd0/yarl-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe8bba2545427418efc1929c5c42852bdb4143eb8d0a46b09de88d1fe99258e7", size = 89159 }, + { url = "https://files.pythonhosted.org/packages/0a/00/b29affe83de95e403f8a2a669b5a33f1e7dfe686264008100052eb0b05fd/yarl-1.16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d8643975a0080f361639787415a038bfc32d29208a4bf6b783ab3075a20b1ef3", size = 140120 }, + { url = "https://files.pythonhosted.org/packages/3f/22/bcc9799950281a5d4f646536854839ccdbb965e900827ef0750680f81faf/yarl-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:676d96bafc8c2d0039cea0cd3fd44cee7aa88b8185551a2bb93354668e8315c2", size = 92956 }, + { url = "https://files.pythonhosted.org/packages/33/0f/1b76d853d9d921d68bd9991648be17d34e7ac51e2e20e7658f8ee7e2e2ad/yarl-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9525f03269e64310416dbe6c68d3b23e5d34aaa8f47193a1c45ac568cecbc49", size = 90891 }, + { url = "https://files.pythonhosted.org/packages/61/19/3666d990c24aae98c748e2c262adc9b3a71e38834df007ac5317f4bbd789/yarl-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b37d5ec034e668b22cf0ce1074d6c21fd2a08b90d11b1b73139b750a8b0dd97", size = 338857 }, + { url = "https://files.pythonhosted.org/packages/a0/3d/54acbb3cdfcfea03d6a3535cff1e060a2de23e419a4e3955c9661171b8a8/yarl-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f32c4cb7386b41936894685f6e093c8dfaf0960124d91fe0ec29fe439e201d0", size = 354005 }, + { url = "https://files.pythonhosted.org/packages/15/98/cd9fe3938422c88775c94578a6c145aca89ff8368ff64e6032213ac12403/yarl-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b8e265a0545637492a7e12fd7038370d66c9375a61d88c5567d0e044ded9202", size = 351195 }, + { url = "https://files.pythonhosted.org/packages/e2/13/b6eff6ea1667aee948ecd6b1c8fb6473234f8e48f49af97be93251869c51/yarl-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:789a3423f28a5fff46fbd04e339863c169ece97c827b44de16e1a7a42bc915d2", size = 342789 }, + { url = "https://files.pythonhosted.org/packages/fe/05/d98e65ea74a7e44bb033b2cf5bcc16edc1d5212bdc5ca7fbb5e380d89f8e/yarl-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1d1f45e3e8d37c804dca99ab3cf4ab3ed2e7a62cd82542924b14c0a4f46d243", size = 336478 }, + { url = "https://files.pythonhosted.org/packages/7d/47/43de2e94b75f36d84733a35c807d0e33aaf084e98f32e2cbc685102f4ba4/yarl-1.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:621280719c4c5dad4c1391160a9b88925bb8b0ff6a7d5af3224643024871675f", size = 346008 }, + { url = "https://files.pythonhosted.org/packages/e2/de/9c2f900ec5e2f2e20329cfe7dcd9452e326d08cb5ecd098c2d4e9987b65c/yarl-1.16.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ed097b26f18a1f5ff05f661dc36528c5f6735ba4ce8c9645e83b064665131349", size = 343745 }, + { url = "https://files.pythonhosted.org/packages/56/cd/b014dce22e37b77caa37f998c6c47434fd78d01e7be07119629f369f5ee1/yarl-1.16.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2f1fe2b2e3ee418862f5ebc0c0083c97f6f6625781382f828f6d4e9b614eba9b", size = 349705 }, + { url = "https://files.pythonhosted.org/packages/07/17/bb191a26f7189423964e008ccb5146ce5258454ef3979f9d4c6860d282c7/yarl-1.16.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:87dd10bc0618991c66cee0cc65fa74a45f4ecb13bceec3c62d78ad2e42b27a16", size = 360767 }, + { url = "https://files.pythonhosted.org/packages/19/09/7d777369e151991b708a5b35280ea7444621d65af5f0545bcdce5d840867/yarl-1.16.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4199db024b58a8abb2cfcedac7b1292c3ad421684571aeb622a02f242280e8d6", size = 364755 }, + { url = "https://files.pythonhosted.org/packages/00/32/7558997d1d2e53dab15f6db5db49fc6b412b63ede3cb8314e5dd7cff14fe/yarl-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:99a9dcd4b71dd5f5f949737ab3f356cfc058c709b4f49833aeffedc2652dac56", size = 357087 }, + { url = "https://files.pythonhosted.org/packages/28/20/c49a95a30c57224e5fb0fc83235295684b041300ce508b71821cb042527d/yarl-1.16.0-cp311-cp311-win32.whl", hash = "sha256:a9394c65ae0ed95679717d391c862dece9afacd8fa311683fc8b4362ce8a410c", size = 83030 }, + { url = "https://files.pythonhosted.org/packages/75/e3/2a746721d6f32886d9bafccdb80174349f180ccae0a287f25ba4312a2618/yarl-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:5b9101f528ae0f8f65ac9d64dda2bb0627de8a50344b2f582779f32fda747c1d", size = 89616 }, + { url = "https://files.pythonhosted.org/packages/3a/be/82f696c8ce0395c37f62b955202368086e5cc114d5bb9cb1b634cff5e01d/yarl-1.16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4ffb7c129707dd76ced0a4a4128ff452cecf0b0e929f2668ea05a371d9e5c104", size = 141230 }, + { url = "https://files.pythonhosted.org/packages/38/60/45caaa748b53c4b0964f899879fcddc41faa4e0d12c6f0ae3311e8c151ff/yarl-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1a5e9d8ce1185723419c487758d81ac2bde693711947032cce600ca7c9cda7d6", size = 93515 }, + { url = "https://files.pythonhosted.org/packages/54/bd/33aaca2f824dc1d630729e16e313797e8b24c8f7b6803307e5394274e443/yarl-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d743e3118b2640cef7768ea955378c3536482d95550222f908f392167fe62059", size = 91441 }, + { url = "https://files.pythonhosted.org/packages/af/fa/1ce8ca85489925aabdb8d2e7bbeaf74e7d3e6ac069779d6d6b9c7c62a8ed/yarl-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26768342f256e6e3c37533bf9433f5f15f3e59e3c14b2409098291b3efaceacb", size = 330871 }, + { url = "https://files.pythonhosted.org/packages/f1/2a/a8110a225e498b87315827f8b61d24de35f86041834cf8c9c5544380c46b/yarl-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1b0796168b953bca6600c5f97f5ed407479889a36ad7d17183366260f29a6b9", size = 340641 }, + { url = "https://files.pythonhosted.org/packages/d0/64/20cd1cb1f60b3ff49e7d75c1a2083352e7c5939368aafa960712c9e53797/yarl-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:858728086914f3a407aa7979cab743bbda1fe2bdf39ffcd991469a370dd7414d", size = 340245 }, + { url = "https://files.pythonhosted.org/packages/77/a8/7f38bbefb22eb925a68ad1d8193b05f51515614a6c0ebcadf26e9ae5e5ad/yarl-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5570e6d47bcb03215baf4c9ad7bf7c013e56285d9d35013541f9ac2b372593e7", size = 336054 }, + { url = "https://files.pythonhosted.org/packages/b4/a6/ac633ea3ea0c4eb1057e6800db1d077e77493b4b3449a4a97b2fbefadef4/yarl-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66ea8311422a7ba1fc79b4c42c2baa10566469fe5a78500d4e7754d6e6db8724", size = 324405 }, + { url = "https://files.pythonhosted.org/packages/93/cd/4fc87ce9b0df7afb610ffb904f4aef25f59e0ad40a49da19a475facf98b7/yarl-1.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:649bddcedee692ee8a9b7b6e38582cb4062dc4253de9711568e5620d8707c2a3", size = 342235 }, + { url = "https://files.pythonhosted.org/packages/9f/bc/38bae4b716da1206849d88e167d3d2c5695ae9b418a3915220947593e5ca/yarl-1.16.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3a91654adb7643cb21b46f04244c5a315a440dcad63213033826549fa2435f71", size = 340835 }, + { url = "https://files.pythonhosted.org/packages/dc/0f/b9efbc0075916a450cbad41299dff3bdd3393cb1d8378bb831c4a6a836e1/yarl-1.16.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b439cae82034ade094526a8f692b9a2b5ee936452de5e4c5f0f6c48df23f8604", size = 344323 }, + { url = "https://files.pythonhosted.org/packages/87/6d/dc483ea1574005f14ef4c5f5f726cf60327b07ac83bd417d98db23e5285f/yarl-1.16.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:571f781ae8ac463ce30bacebfaef2c6581543776d5970b2372fbe31d7bf31a07", size = 355112 }, + { url = "https://files.pythonhosted.org/packages/10/22/3b7c3728d26b3cc295c51160ae4e2612ab7d3f9df30beece44bf72861730/yarl-1.16.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:aa7943f04f36d6cafc0cf53ea89824ac2c37acbdb4b316a654176ab8ffd0f968", size = 361506 }, + { url = "https://files.pythonhosted.org/packages/ad/8d/b7b5d43cf22a020b564ddf7502d83df150d797e34f18f6bf5fe0f12cbd91/yarl-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1a5cf32539373ff39d97723e39a9283a7277cbf1224f7aef0c56c9598b6486c3", size = 355746 }, + { url = "https://files.pythonhosted.org/packages/d9/a6/a2098bf3f09d38eb540b2b192e180d9d41c2ff64b692783db2188f0a55e3/yarl-1.16.0-cp312-cp312-win32.whl", hash = "sha256:a5b6c09b9b4253d6a208b0f4a2f9206e511ec68dce9198e0fbec4f160137aa67", size = 82675 }, + { url = "https://files.pythonhosted.org/packages/ed/a6/0a54b382cfc336e772b72681d6816a99222dc2d21876e649474973b8d244/yarl-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:1208ca14eed2fda324042adf8d6c0adf4a31522fa95e0929027cd487875f0240", size = 88986 }, + { url = "https://files.pythonhosted.org/packages/57/56/eef0a7050fcd11d70c536453f014d4b2dfd83fb934c9857fa1a912832405/yarl-1.16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5ace0177520bd4caa99295a9b6fb831d0e9a57d8e0501a22ffaa61b4c024283", size = 139373 }, + { url = "https://files.pythonhosted.org/packages/3f/b2/88eb9e98c5a4549606ebf673cba0d701f13ec855021b781f8e3fd7c04190/yarl-1.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7118bdb5e3ed81acaa2095cba7ec02a0fe74b52a16ab9f9ac8e28e53ee299732", size = 92759 }, + { url = "https://files.pythonhosted.org/packages/95/1d/c3b794ef82a3b1894a9f8fc1012b073a85464b95c646ac217e8013137ea3/yarl-1.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38fec8a2a94c58bd47c9a50a45d321ab2285ad133adefbbadf3012c054b7e656", size = 90573 }, + { url = "https://files.pythonhosted.org/packages/7f/35/39a5dcbf7ef320607bcfd1c0498ce348181b97627c3901139b429d806cf1/yarl-1.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8791d66d81ee45866a7bb15a517b01a2bcf583a18ebf5d72a84e6064c417e64b", size = 332461 }, + { url = "https://files.pythonhosted.org/packages/36/29/2a468c8b44aa750d0f3416bc24d58464237b402388a8f03091a58537274a/yarl-1.16.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cf936ba67bc6c734f3aa1c01391da74ab7fc046a9f8bbfa230b8393b90cf472", size = 343045 }, + { url = "https://files.pythonhosted.org/packages/91/6a/002300c86ed7ef3cd5ac890a0e17101aee06c64abe2e43f9dad85bc32c70/yarl-1.16.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1aab176dd55b59f77a63b27cffaca67d29987d91a5b615cbead41331e6b7428", size = 344592 }, + { url = "https://files.pythonhosted.org/packages/ea/69/ca4228e0f560f0c5817e0ebd789690c78ab17e6a876b38a5d000889b2f63/yarl-1.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:995d0759004c08abd5d1b81300a91d18c8577c6389300bed1c7c11675105a44d", size = 338127 }, + { url = "https://files.pythonhosted.org/packages/81/df/32eea6e5199f7298ec15cf708895f35a7d2899177ed556e6bdf6819462aa/yarl-1.16.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1bc22e00edeb068f71967ab99081e9406cd56dbed864fc3a8259442999d71552", size = 326127 }, + { url = "https://files.pythonhosted.org/packages/9a/11/1a888df53acd3d1d4b8dc803e0c8ed4a4b6cabc2abe19e4de31aa6b86857/yarl-1.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:35b4f7842154176523e0a63c9b871168c69b98065d05a4f637fce342a6a2693a", size = 345219 }, + { url = "https://files.pythonhosted.org/packages/34/88/44fd8b372c4c50c010e66c62bfb34e67d6bd217c973599e0ee03f74e74ec/yarl-1.16.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:7ace71c4b7a0c41f317ae24be62bb61e9d80838d38acb20e70697c625e71f120", size = 339742 }, + { url = "https://files.pythonhosted.org/packages/ee/c8/eaa53bd40db61265cec09d3c432d8bcd8ab9fd3a9fc5b0afdd13ab27b4a8/yarl-1.16.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8f639e3f5795a6568aa4f7d2ac6057c757dcd187593679f035adbf12b892bb00", size = 344695 }, + { url = "https://files.pythonhosted.org/packages/1b/8f/b00aa91bd3bc8ef41781b13ac967c9c5c2e3ca0c516cffdd15ac035a1839/yarl-1.16.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e8be3aff14f0120ad049121322b107f8a759be76a6a62138322d4c8a337a9e2c", size = 353617 }, + { url = "https://files.pythonhosted.org/packages/f1/88/8e86a28a840b8dc30c880fdde127f9610c56e55796a2cc969949b4a60fe7/yarl-1.16.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:122d8e7986043d0549e9eb23c7fd23be078be4b70c9eb42a20052b3d3149c6f2", size = 359911 }, + { url = "https://files.pythonhosted.org/packages/ee/61/9d59f7096fd72d5f68168ed8134773982ee48a8cb4009ecb34344e064999/yarl-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0fd9c227990f609c165f56b46107d0bc34553fe0387818c42c02f77974402c36", size = 358847 }, + { url = "https://files.pythonhosted.org/packages/f7/25/c323097b066a2b5a554f77e27a35bc067aebfcd3a001a0a3a6bc14190460/yarl-1.16.0-cp313-cp313-win32.whl", hash = "sha256:595ca5e943baed31d56b33b34736461a371c6ea0038d3baec399949dd628560b", size = 308302 }, + { url = "https://files.pythonhosted.org/packages/52/76/ca2c3de3511a127fc4124723e7ccc641aef5e0ec56c66d25dbd11f19ab84/yarl-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:921b81b8d78f0e60242fb3db615ea3f368827a76af095d5a69f1c3366db3f596", size = 314035 }, + { url = "https://files.pythonhosted.org/packages/fb/f7/87a32867ddc1a9817018bfd6109ee57646a543acf0d272843d8393e575f9/yarl-1.16.0-py3-none-any.whl", hash = "sha256:e6980a558d8461230c457218bd6c92dfc1d10205548215c2c21d79dc8d0a96f3", size = 43746 }, +] From a001465a871ca85b2e9480226dd11dfd7472c045 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Tue, 22 Oct 2024 08:00:17 -0400 Subject: [PATCH 22/29] Updating the README file. --- README.md | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0e7cece3..1506ba4b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,30 @@ -# Crystal Diffusion +# Diffusion for Multiscale Molecular Dynamics -Replace this line with a short description about your project! +This project implements diffusion-based generative models for periodic atomistic systems (i.e., crystals). +The aim of this project is to be able to train such a model and use it as part of an active learning +framework, where a Machine Learning Interatomic Potential (MLIP) is continually fine-tuned on labels obtained +from a costly oracle such as Density Functional Theory (DFT). The generative model is used to create +few-atom configurations that are computationally tractable for the costly oracle by inpainting +around problematic atomic configurations. -## Instructions to setup the project +# Instructions to set up the project for development -### Install the dependencies: +## Creating a Virtual Environment +The project dependencies are stated in the `pyproject.toml` file. They must be installed in a virtual environment. + +### uv +The recommended way of creating a virtual environment is to use the tool [`uv`](https://docs.astral.sh/uv/). +Once `uv` is installed locally, the virtual environment can be created with the command + + uv sync + +which will install the exact environment described in file `uv.lock`. The environment can then be activated with +the command + + source .venv/bin/activate + + +### pip First, activate a virtual environment (recommended). Install the package in `editable` mode so you can modify the source directly: From ccdf8fba720041437c3d4d31dc3c16092ac75cf3 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Tue, 22 Oct 2024 10:36:11 -0400 Subject: [PATCH 23/29] Udpated the README file. --- README.md | 193 +++++++++++------------------------------------------- 1 file changed, 38 insertions(+), 155 deletions(-) diff --git a/README.md b/README.md index 1506ba4b..bba7f8cb 100644 --- a/README.md +++ b/README.md @@ -23,182 +23,65 @@ the command source .venv/bin/activate - ### pip -First, activate a virtual environment (recommended). -Install the package in `editable` mode so you can modify the source directly: - - pip install -e . - -To add new dependencies, simply add them to the setup.py. - -### Setup pre-commit hooks: -These hooks will: -* validate flake8 before any commit -* check that jupyter notebook outputs have been stripped - - cd .git/hooks/ && ln -s ../../hooks/pre-commit . - -Alternatively, to only lint files that have been staged in git, use - cd .git/hooks/ && ln -s ../../hooks/pre-commit_staged pre-commit - -### Setup Continuous Integration - -Continuous integration will run the following: -- Unit tests under `tests`. -- End-to-end test under `exmaples/local`. -- `flake8` to check the code syntax. -- Checks on documentation presence and format (using `sphinx`). - -We support the GitHub Actions for running CI. - -Github actions are already configured in `.github/workflows/tests.yml`. -Github actions are already enabled by default when using Github, so, when -pushing to github, they will be executed automatically for pull requests to -`main` and to `develop`. - -## Running the code - -### Run the tests -Just run (from the root folder): - - pytest - -### Run the code/examples. -Note that the code should already compile at this point. - -Running examples can be found under the `examples` folder. - -In particular, you will find examples for: -* local machine (e.g., your laptop). -* a slurm cluster. - -For both these cases, there is the possibility to run with or without Orion. -(Orion is a hyper-parameter search tool - see https://github.com/Epistimio/orion - -that is already configured in this project) - -#### Run locally - -For example, to run on your local machine without Orion: - - cd examples/local - sh run.sh - -This will run a simple MLP on a simple toy task: sum 5 float numbers. -You should see an almost perfect loss of 0 after a few epochs. -Note you have a new `output` folder which contains models and a summary of results: -* best_model: the best model checkpoint during training -* last_model: the last model checkpoint during training -* lightning_logs: contains the tensorboard logs. +Alternatively, `pip` can be used to create the virtual environment. Assuming `python` and `pip` are already +available on the system, create a virtual env folder in the root directory with the command -To view tensorboard logs, simply run: + python -m venv ./.venv/ - tensorboard --logdir output +The environment must then be activated with the command -#### Run on a remote cluster (with Slurm) - -First, bring you project on the cluster (assuming you didn't create your -project directly there). To do so, simply login on the cluster and git -clone your project: - - git clone git@github.com:${GITHUB_USERNAME}/${PROJECT_NAME}.git + source .venv/bin/activate -Then activate your virtual env, and install the dependencies: +and the environment should be created in `editable` mode so that the source code can be modified directly, - cd crystal_diffusion pip install -e . -To run with Slurm, just: - - cd examples/slurm - sh run.sh - -Check the log to see that you got an almost perfect loss (i.e., 0). - -#### Measure GPU time (and others) on the Mila cluster - -You can track down the GPU time (and other resources) of your jobs by -associating a tag to the job (when using `sbatch`). -To associate a tag to a job, replace `my_tag` with a proper tag, -and uncomment the line (i.e., remove one #) from the line: - - ##SBATCH --wckey=my_tag - -This line is inside the file `examples/slurm_mila/to_submit.sh`. - -To get a sumary for a particular tag, just run: - - sacct --allusers --wckeys=my_tag --format=JobID,JobName,Start,Elapsed -X -P --delimiter=',' - -(again, remember to change `my_tag` into the real tag name) - -#### GPU profiling on the Mila cluster +### Testing the Installation +The test suite should be executed to make sure that the environment is properly installed. After activating the +environment, the tests can be executed with the command -It can be useful to monitor and profile how you utilise your GPU (usage, memory, etc.). For the -time being, you can only monitor your profiling in real-time from the Mila cluster, i.e. while your -experiments are running. To monitor your GPU, you need to setup port-forwarding on the host your -experiments are running on. This can be done in the following way: + pytest [--quick] -Once you have launched your job on the mila cluster, open the log for your current experiment: +the argument `--quick` is optional; a few tests are a bit slow and will be skipped if this flag is present. -`head logs/crystal_diffusion/__.err` -You should see printed in the first few lines the hostname of your machine, e.g., +## Setting up the Development Tools +Various automated tools are used in order to maintain a high quality code base. These must be set up +to start developing. We use -``` -INFO:crystal_diffusion.utils.logging_utils:Experiment info: -hostname: leto35 -git code hash: a51bfc5447d188bd6d31fac3afbd5757650ef524 -data folder: ../data -data folder (abs): /network/tmp1/bronzimi/20191105_cookiecutter/crystal_diffusion/examples/data -``` +* [flake8](https://flake8.pycqa.org/en/latest/) to insure the coding style is enforced. +* [isort](https://pycqa.github.io/isort/) to insure that the imports are properly ordered. +* [black](https://pypi.org/project/black/) to format the code. -In a separate shell on your local computer, run the following command: +### Setup pre-commit hooks +The folder `./hooks/` contain "pre-commit" scripts that automate various checks at every git commit. +These hooks will +* validate flake8 before any commit; +* check that jupyter notebook outputs have been stripped. -`ssh -L 19999:.server.mila.quebec:19999 @login.server.mila.quebec -p 2222` +There are two pre-commit scripts, `pre-commit` and `pre-commit_staged`. Both scripts perform the same +checks; `pre-commit` is used within the continuous integration (CI), while `pre-commit_staged` only +validates files that are staged in git, making it more developer-friendly. -where `` is your user name on the Mila cluster and `` is the name of the machine your job is currenty running on (`leto35` in our example). You can then navigate your local browser to `http://localhost:19999/` to view the ressources being used on the cluster and monitor your job. You should see something like this: +To activate the pre-commit hook, -![image](https://user-images.githubusercontent.com/18450628/88088807-fe2acd80-cb58-11ea-8ab2-bd090e8a826c.png) -{%- endif %} - -#### Run with Orion on the Slurm cluster - -This example will run orion for 2 trials (see the orion config file). -To do so, go into `examples/slurm_orion`. -Here you can find the orion config file (`orion_config.yaml`), as well as the config -file (`config.yaml`) for your project (that contains the hyper-parameters). - -In general, you will want to run Orion in parallel over N slurm jobs. -To do so, simply run `sh run.sh` N times. - -When Orion has completed the trials, you will find the orion db file. - -You will also find the output of your experiments in `orion_working_dir`, which -will contain a folder for every trial. -Inside these folders, you can find the models (the best one and the last one), the config file with -the hyper-parameters for this trial, and the log file. - -You can check orion status with the following commands: -(to be run from `examples/slurm_orion`) - - export ORION_DB_ADDRESS='orion_db.pkl' - export ORION_DB_TYPE='pickleddb' - orion status - orion info --name my_exp - -### Building docs: + cd .git/hooks/ && ln -s ../../hooks/pre-commit . -Documentation is built using sphinx. It will automatically document all functions based on docstrings. -To automatically generate docs for your project, navigate to the `docs` folder and build the documentation: +Alternatively, to only lint files that have been staged in git, use - cd docs - make html + cd .git/hooks/ && ln -s ../../hooks/pre-commit_staged pre-commit -To view the docs locally, open `docs/_build/html/index.html` in your browser. +### Setup Continuous Integration +GitHub Actions is used for running continuous integration (CI) checks. +The cI workflow is described in `.github/workflows/ci.yml`. -## YOUR PROJECT README: +CI will run the following: +- check the code syntax with `flake8` +- execute the unit tests in `./tests/`. +- Checks on documentation presence and format (using `sphinx`). -* __TODO__ +Since the various tests are relatively costly, the CI actions will only be executed for +pull requests to the `main` branch. From aabb4cedca334f13f2762191c95057f6b91a3d5b Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Tue, 22 Oct 2024 10:40:21 -0400 Subject: [PATCH 24/29] Only deal with the main branch. --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 201faa28..f2d416b9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,11 +5,9 @@ on: push: branches: - main - - development pull_request: branches: - main - - development jobs: build: runs-on: ubuntu-latest From 1ea21006691afee5639f8306af26633b13d613bc Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Tue, 22 Oct 2024 10:42:52 -0400 Subject: [PATCH 25/29] Add the UV default python version file. --- .python-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .python-version diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..c8cfe395 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.10 From 0fbd6594dc385bc54f130aa3125fc799e7c69f23 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Tue, 22 Oct 2024 10:44:58 -0400 Subject: [PATCH 26/29] removed needless files --- requirements.txt | 42 ------------------------------------------ setup.py | 18 ------------------ 2 files changed, 60 deletions(-) delete mode 100644 requirements.txt delete mode 100644 setup.py diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index dd631c5b..00000000 --- a/requirements.txt +++ /dev/null @@ -1,42 +0,0 @@ -datasets==2.17.1 -flake8==4.0.1 -flake8-docstrings==1.6.0 -gitpython==3.1.27 -hydra-core==1.3.2 -isort==5.13.2 -jupyter==1.0.0 -jinja2==3.1.2 -lammps -mace-torch==0.3.4 -maml==2023.9.9 -monty==2024.2.2 -myst-parser==2.0.0 -orion==0.2.7 -pyarrow==15.0.1 -pymatgen==2024.2.23 -pyyaml==6.0.1 -pytest==7.1.2 -pytest-cov==3.0.0 -pytest-mock==3.12.0 -pytorch_lightning==2.2.1 -pytype==2024.2.13 -sphinx==7.2.6 -sphinx-autoapi==3.0.0 -sphinx-rtd-theme==2.0.0 -sphinxcontrib-napoleon==0.7 -sphinxcontrib-katex==0.8.6 -tensorboard==2.16.2 -tqdm==4.64.0 -torch==2.2.0 -torchvision>=0.17.0 -torch_geometric==2.5.3 -matplotlib==3.8.3 -rich==13.7.1 -deepdiff==7.0.1 -pykeops==2.2.3 -comet_ml -einops==0.8.0 -torchode==0.2.0 -torchsde==0.2.6 -ovito==3.10.6.post2 - diff --git a/setup.py b/setup.py deleted file mode 100644 index 90bab9aa..00000000 --- a/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -from setuptools import find_packages, setup - -with open('requirements.txt', 'r') as f: - requirements = f.readlines() - -setup( - name='crystal_diffusion', - version='0.0.1', - packages=find_packages(include=['crystal_diffusion', 'crystal_diffusion.*']), - python_requires='>=3.10', - install_requires=requirements, - entry_points={ - 'console_scripts': [ - 'cd-train=crystal_diffusion.train:main', - 'cd-eval=crystal_diffusion.evaluate:main', - ], - } -) From 9dbf6a0d1450b950ca3f064ad2ae0e984a8f92b8 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Tue, 22 Oct 2024 10:46:39 -0400 Subject: [PATCH 27/29] a few init files --- .../active_learning_loop/__init__.py | 0 src/diffusion_for_multi_scale_molecular_dynamics/mlip/__init__.py | 0 .../models/mlip/__init__.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/__init__.py create mode 100644 src/diffusion_for_multi_scale_molecular_dynamics/mlip/__init__.py create mode 100644 src/diffusion_for_multi_scale_molecular_dynamics/models/mlip/__init__.py diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/__init__.py b/src/diffusion_for_multi_scale_molecular_dynamics/active_learning_loop/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/mlip/__init__.py b/src/diffusion_for_multi_scale_molecular_dynamics/mlip/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/diffusion_for_multi_scale_molecular_dynamics/models/mlip/__init__.py b/src/diffusion_for_multi_scale_molecular_dynamics/models/mlip/__init__.py new file mode 100644 index 00000000..e69de29b From 56bb596c6e8ea20f1e31019932fe429c0fca8d46 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Tue, 22 Oct 2024 10:55:19 -0400 Subject: [PATCH 28/29] Fix imports. --- data/crop_lammps_outputs.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/data/crop_lammps_outputs.py b/data/crop_lammps_outputs.py index fee07fbf..79a75f78 100644 --- a/data/crop_lammps_outputs.py +++ b/data/crop_lammps_outputs.py @@ -5,8 +5,10 @@ import yaml -from crystal_diffusion.data.utils import crop_lammps_yaml -from crystal_diffusion.utils.logging_utils import setup_analysis_logger +from diffusion_for_multi_scale_molecular_dynamics.data.utils import \ + crop_lammps_yaml +from diffusion_for_multi_scale_molecular_dynamics.utils.logging_utils import \ + setup_analysis_logger logger = logging.getLogger(__name__) From 706a5b172124d52c78dbeff8d5dc15d285397ef8 Mon Sep 17 00:00:00 2001 From: Bruno Rousseau Date: Tue, 22 Oct 2024 11:17:20 -0400 Subject: [PATCH 29/29] fixed docs --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index f46158c0..c0a5b517 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,7 +16,7 @@ # -- Project information ----------------------------------------------------- -project = 'crystal_diffusion' +project = 'diffusion_for_multi_scale_molecular_dynamics' copyright = '2023, amlrt_team' author = 'amlrt_team' @@ -39,7 +39,7 @@ # autoapi extension for doc strings extensions.append('autoapi.extension') autoapi_type = 'python' -autoapi_dirs = ['../crystal_diffusion/'] +autoapi_dirs = ['../src/'] # Skip docstrings for loggers and tests