diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 000000000..e2531bb84 --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file records the configuration used when building these files. When it is not found, a full rebuild will be done. +config: f4bb06e3dabb16fa363ac1a1f26ce6a4 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.doctrees/about/changelog.doctree b/.doctrees/about/changelog.doctree new file mode 100644 index 000000000..77917a0b3 Binary files /dev/null and b/.doctrees/about/changelog.doctree differ diff --git a/.doctrees/about/contributing.doctree b/.doctrees/about/contributing.doctree new file mode 100644 index 000000000..e9ba63b7f Binary files /dev/null and b/.doctrees/about/contributing.doctree differ diff --git a/.doctrees/about/contributors.doctree b/.doctrees/about/contributors.doctree new file mode 100644 index 000000000..47c09fe38 Binary files /dev/null and b/.doctrees/about/contributors.doctree differ diff --git a/.doctrees/about/license.doctree b/.doctrees/about/license.doctree new file mode 100644 index 000000000..35f1d55ca Binary files /dev/null and b/.doctrees/about/license.doctree differ diff --git a/.doctrees/dev/contributing.doctree b/.doctrees/dev/contributing.doctree new file mode 100644 index 000000000..48e682dbf Binary files /dev/null and b/.doctrees/dev/contributing.doctree differ diff --git a/.doctrees/dev/dev_install.doctree b/.doctrees/dev/dev_install.doctree new file mode 100644 index 000000000..d787fb966 Binary files /dev/null and b/.doctrees/dev/dev_install.doctree differ diff --git a/.doctrees/environment.pickle b/.doctrees/environment.pickle new file mode 100644 index 000000000..eec49c0d2 Binary files /dev/null and b/.doctrees/environment.pickle differ diff --git a/.doctrees/glossary.doctree b/.doctrees/glossary.doctree new file mode 100644 index 000000000..b46cede25 Binary files /dev/null and b/.doctrees/glossary.doctree differ diff --git a/.doctrees/index.doctree b/.doctrees/index.doctree new file mode 100644 index 000000000..d61fa366c Binary files /dev/null and b/.doctrees/index.doctree differ diff --git a/.doctrees/reference/autoplex.auto.doctree b/.doctrees/reference/autoplex.auto.doctree new file mode 100644 index 000000000..872f28efd Binary files /dev/null and b/.doctrees/reference/autoplex.auto.doctree differ diff --git a/.doctrees/reference/autoplex.auto.phonons.doctree b/.doctrees/reference/autoplex.auto.phonons.doctree new file mode 100644 index 000000000..73bbcc6d5 Binary files /dev/null and b/.doctrees/reference/autoplex.auto.phonons.doctree differ diff --git a/.doctrees/reference/autoplex.auto.phonons.flows.CompleteDFTvsMLBenchmarkWorkflow.doctree b/.doctrees/reference/autoplex.auto.phonons.flows.CompleteDFTvsMLBenchmarkWorkflow.doctree new file mode 100644 index 000000000..764a086c1 Binary files /dev/null and b/.doctrees/reference/autoplex.auto.phonons.flows.CompleteDFTvsMLBenchmarkWorkflow.doctree differ diff --git a/.doctrees/reference/autoplex.auto.phonons.flows.CompleteDFTvsMLBenchmarkWorkflowMPSettings.doctree b/.doctrees/reference/autoplex.auto.phonons.flows.CompleteDFTvsMLBenchmarkWorkflowMPSettings.doctree new file mode 100644 index 000000000..653cd82a6 Binary files /dev/null and b/.doctrees/reference/autoplex.auto.phonons.flows.CompleteDFTvsMLBenchmarkWorkflowMPSettings.doctree differ diff --git a/.doctrees/reference/autoplex.auto.phonons.flows.DFTSupercellSettingsMaker.doctree b/.doctrees/reference/autoplex.auto.phonons.flows.DFTSupercellSettingsMaker.doctree new file mode 100644 index 000000000..09ee9fa8a Binary files /dev/null and b/.doctrees/reference/autoplex.auto.phonons.flows.DFTSupercellSettingsMaker.doctree differ diff --git a/.doctrees/reference/autoplex.auto.phonons.flows.IterativeCompleteDFTvsMLBenchmarkWorkflow.doctree b/.doctrees/reference/autoplex.auto.phonons.flows.IterativeCompleteDFTvsMLBenchmarkWorkflow.doctree new file mode 100644 index 000000000..bf36b53d1 Binary files /dev/null and b/.doctrees/reference/autoplex.auto.phonons.flows.IterativeCompleteDFTvsMLBenchmarkWorkflow.doctree differ diff --git a/.doctrees/reference/autoplex.auto.phonons.flows.doctree b/.doctrees/reference/autoplex.auto.phonons.flows.doctree new file mode 100644 index 000000000..4500109b5 Binary files /dev/null and b/.doctrees/reference/autoplex.auto.phonons.flows.doctree differ diff --git a/.doctrees/reference/autoplex.auto.phonons.jobs.complete_benchmark.doctree b/.doctrees/reference/autoplex.auto.phonons.jobs.complete_benchmark.doctree new file mode 100644 index 000000000..53bd297f9 Binary files /dev/null and b/.doctrees/reference/autoplex.auto.phonons.jobs.complete_benchmark.doctree differ diff --git a/.doctrees/reference/autoplex.auto.phonons.jobs.dft_phonopy_gen_data.doctree b/.doctrees/reference/autoplex.auto.phonons.jobs.dft_phonopy_gen_data.doctree new file mode 100644 index 000000000..94b371cb5 Binary files /dev/null and b/.doctrees/reference/autoplex.auto.phonons.jobs.dft_phonopy_gen_data.doctree differ diff --git a/.doctrees/reference/autoplex.auto.phonons.jobs.dft_random_gen_data.doctree b/.doctrees/reference/autoplex.auto.phonons.jobs.dft_random_gen_data.doctree new file mode 100644 index 000000000..9650795b7 Binary files /dev/null and b/.doctrees/reference/autoplex.auto.phonons.jobs.dft_random_gen_data.doctree differ diff --git a/.doctrees/reference/autoplex.auto.phonons.jobs.do_iterative_rattled_structures.doctree b/.doctrees/reference/autoplex.auto.phonons.jobs.do_iterative_rattled_structures.doctree new file mode 100644 index 000000000..971fa8056 Binary files /dev/null and b/.doctrees/reference/autoplex.auto.phonons.jobs.do_iterative_rattled_structures.doctree differ diff --git a/.doctrees/reference/autoplex.auto.phonons.jobs.doctree b/.doctrees/reference/autoplex.auto.phonons.jobs.doctree new file mode 100644 index 000000000..5f5012d18 Binary files /dev/null and b/.doctrees/reference/autoplex.auto.phonons.jobs.doctree differ diff --git a/.doctrees/reference/autoplex.auto.phonons.jobs.generate_supercells.doctree b/.doctrees/reference/autoplex.auto.phonons.jobs.generate_supercells.doctree new file mode 100644 index 000000000..5ba3ef58c Binary files /dev/null and b/.doctrees/reference/autoplex.auto.phonons.jobs.generate_supercells.doctree differ diff --git a/.doctrees/reference/autoplex.auto.phonons.jobs.get_iso_atom.doctree b/.doctrees/reference/autoplex.auto.phonons.jobs.get_iso_atom.doctree new file mode 100644 index 000000000..b1d652731 Binary files /dev/null and b/.doctrees/reference/autoplex.auto.phonons.jobs.get_iso_atom.doctree differ diff --git a/.doctrees/reference/autoplex.auto.phonons.jobs.get_phonon_output.doctree b/.doctrees/reference/autoplex.auto.phonons.jobs.get_phonon_output.doctree new file mode 100644 index 000000000..c050fa36d Binary files /dev/null and b/.doctrees/reference/autoplex.auto.phonons.jobs.get_phonon_output.doctree differ diff --git a/.doctrees/reference/autoplex.auto.phonons.jobs.run_supercells.doctree b/.doctrees/reference/autoplex.auto.phonons.jobs.run_supercells.doctree new file mode 100644 index 000000000..3bd499b27 Binary files /dev/null and b/.doctrees/reference/autoplex.auto.phonons.jobs.run_supercells.doctree differ diff --git a/.doctrees/reference/autoplex.auto.rss.doctree b/.doctrees/reference/autoplex.auto.rss.doctree new file mode 100644 index 000000000..e30f7e679 Binary files /dev/null and b/.doctrees/reference/autoplex.auto.rss.doctree differ diff --git a/.doctrees/reference/autoplex.auto.rss.flows.RssMaker.doctree b/.doctrees/reference/autoplex.auto.rss.flows.RssMaker.doctree new file mode 100644 index 000000000..0e3fddc35 Binary files /dev/null and b/.doctrees/reference/autoplex.auto.rss.flows.RssMaker.doctree differ diff --git a/.doctrees/reference/autoplex.auto.rss.flows.doctree b/.doctrees/reference/autoplex.auto.rss.flows.doctree new file mode 100644 index 000000000..a0c20f3ae Binary files /dev/null and b/.doctrees/reference/autoplex.auto.rss.flows.doctree differ diff --git a/.doctrees/reference/autoplex.auto.rss.jobs.do_rss_iterations.doctree b/.doctrees/reference/autoplex.auto.rss.jobs.do_rss_iterations.doctree new file mode 100644 index 000000000..bf54782c3 Binary files /dev/null and b/.doctrees/reference/autoplex.auto.rss.jobs.do_rss_iterations.doctree differ diff --git a/.doctrees/reference/autoplex.auto.rss.jobs.doctree b/.doctrees/reference/autoplex.auto.rss.jobs.doctree new file mode 100644 index 000000000..2996ad940 Binary files /dev/null and b/.doctrees/reference/autoplex.auto.rss.jobs.doctree differ diff --git a/.doctrees/reference/autoplex.auto.rss.jobs.initial_rss.doctree b/.doctrees/reference/autoplex.auto.rss.jobs.initial_rss.doctree new file mode 100644 index 000000000..5e69b18a4 Binary files /dev/null and b/.doctrees/reference/autoplex.auto.rss.jobs.initial_rss.doctree differ diff --git a/.doctrees/reference/autoplex.benchmark.doctree b/.doctrees/reference/autoplex.benchmark.doctree new file mode 100644 index 000000000..0c59d4bef Binary files /dev/null and b/.doctrees/reference/autoplex.benchmark.doctree differ diff --git a/.doctrees/reference/autoplex.benchmark.phonons.doctree b/.doctrees/reference/autoplex.benchmark.phonons.doctree new file mode 100644 index 000000000..7f04b4c6c Binary files /dev/null and b/.doctrees/reference/autoplex.benchmark.phonons.doctree differ diff --git a/.doctrees/reference/autoplex.benchmark.phonons.flows.PhononBenchmarkMaker.doctree b/.doctrees/reference/autoplex.benchmark.phonons.flows.PhononBenchmarkMaker.doctree new file mode 100644 index 000000000..53a8f8ab5 Binary files /dev/null and b/.doctrees/reference/autoplex.benchmark.phonons.flows.PhononBenchmarkMaker.doctree differ diff --git a/.doctrees/reference/autoplex.benchmark.phonons.flows.doctree b/.doctrees/reference/autoplex.benchmark.phonons.flows.doctree new file mode 100644 index 000000000..fc064d658 Binary files /dev/null and b/.doctrees/reference/autoplex.benchmark.phonons.flows.doctree differ diff --git a/.doctrees/reference/autoplex.benchmark.phonons.jobs.doctree b/.doctrees/reference/autoplex.benchmark.phonons.jobs.doctree new file mode 100644 index 000000000..0e0fcfc4e Binary files /dev/null and b/.doctrees/reference/autoplex.benchmark.phonons.jobs.doctree differ diff --git a/.doctrees/reference/autoplex.benchmark.phonons.jobs.write_benchmark_metrics.doctree b/.doctrees/reference/autoplex.benchmark.phonons.jobs.write_benchmark_metrics.doctree new file mode 100644 index 000000000..4dc72f1d6 Binary files /dev/null and b/.doctrees/reference/autoplex.benchmark.phonons.jobs.write_benchmark_metrics.doctree differ diff --git a/.doctrees/reference/autoplex.benchmark.phonons.utils.compare_plot.doctree b/.doctrees/reference/autoplex.benchmark.phonons.utils.compare_plot.doctree new file mode 100644 index 000000000..0259bf9fb Binary files /dev/null and b/.doctrees/reference/autoplex.benchmark.phonons.utils.compare_plot.doctree differ diff --git a/.doctrees/reference/autoplex.benchmark.phonons.utils.compute_bandstructure_benchmark_metrics.doctree b/.doctrees/reference/autoplex.benchmark.phonons.utils.compute_bandstructure_benchmark_metrics.doctree new file mode 100644 index 000000000..7e1d4c16b Binary files /dev/null and b/.doctrees/reference/autoplex.benchmark.phonons.utils.compute_bandstructure_benchmark_metrics.doctree differ diff --git a/.doctrees/reference/autoplex.benchmark.phonons.utils.doctree b/.doctrees/reference/autoplex.benchmark.phonons.utils.doctree new file mode 100644 index 000000000..0291e3542 Binary files /dev/null and b/.doctrees/reference/autoplex.benchmark.phonons.utils.doctree differ diff --git a/.doctrees/reference/autoplex.benchmark.phonons.utils.get_rmse.doctree b/.doctrees/reference/autoplex.benchmark.phonons.utils.get_rmse.doctree new file mode 100644 index 000000000..aba7ba10a Binary files /dev/null and b/.doctrees/reference/autoplex.benchmark.phonons.utils.get_rmse.doctree differ diff --git a/.doctrees/reference/autoplex.benchmark.phonons.utils.rmse_qdep_plot.doctree b/.doctrees/reference/autoplex.benchmark.phonons.utils.rmse_qdep_plot.doctree new file mode 100644 index 000000000..f297e8bc9 Binary files /dev/null and b/.doctrees/reference/autoplex.benchmark.phonons.utils.rmse_qdep_plot.doctree differ diff --git a/.doctrees/reference/autoplex.benchmark.rss.doctree b/.doctrees/reference/autoplex.benchmark.rss.doctree new file mode 100644 index 000000000..0797e1b34 Binary files /dev/null and b/.doctrees/reference/autoplex.benchmark.rss.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.doctree b/.doctrees/reference/autoplex.data.common.doctree new file mode 100644 index 000000000..901cbda5e Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.flows.DFTStaticLabelling.doctree b/.doctrees/reference/autoplex.data.common.flows.DFTStaticLabelling.doctree new file mode 100644 index 000000000..d980ebb37 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.flows.DFTStaticLabelling.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.flows.GenerateTrainingDataForTesting.doctree b/.doctrees/reference/autoplex.data.common.flows.GenerateTrainingDataForTesting.doctree new file mode 100644 index 000000000..8ad0668d4 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.flows.GenerateTrainingDataForTesting.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.flows.doctree b/.doctrees/reference/autoplex.data.common.flows.doctree new file mode 100644 index 000000000..59f40fbd7 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.flows.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.jobs.TYPE_CHECKING.doctree b/.doctrees/reference/autoplex.data.common.jobs.TYPE_CHECKING.doctree new file mode 100644 index 000000000..58a0faa18 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.jobs.TYPE_CHECKING.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.jobs.check_convergence_vasp.doctree b/.doctrees/reference/autoplex.data.common.jobs.check_convergence_vasp.doctree new file mode 100644 index 000000000..c9378c171 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.jobs.check_convergence_vasp.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.jobs.collect_dft_data.doctree b/.doctrees/reference/autoplex.data.common.jobs.collect_dft_data.doctree new file mode 100644 index 000000000..2a630ae9b Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.jobs.collect_dft_data.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.jobs.convert_to_extxyz.doctree b/.doctrees/reference/autoplex.data.common.jobs.convert_to_extxyz.doctree new file mode 100644 index 000000000..dfc99512b Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.jobs.convert_to_extxyz.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.jobs.doctree b/.doctrees/reference/autoplex.data.common.jobs.doctree new file mode 100644 index 000000000..3fb72d5e7 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.jobs.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.jobs.generate_randomized_structures.doctree b/.doctrees/reference/autoplex.data.common.jobs.generate_randomized_structures.doctree new file mode 100644 index 000000000..4b78ff399 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.jobs.generate_randomized_structures.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.jobs.get_supercell_job.doctree b/.doctrees/reference/autoplex.data.common.jobs.get_supercell_job.doctree new file mode 100644 index 000000000..69695e27c Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.jobs.get_supercell_job.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.jobs.plot_force_distribution.doctree b/.doctrees/reference/autoplex.data.common.jobs.plot_force_distribution.doctree new file mode 100644 index 000000000..e85362cf2 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.jobs.plot_force_distribution.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.jobs.preprocess_data.doctree b/.doctrees/reference/autoplex.data.common.jobs.preprocess_data.doctree new file mode 100644 index 000000000..effd998c7 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.jobs.preprocess_data.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.jobs.safe_strip_hostname.doctree b/.doctrees/reference/autoplex.data.common.jobs.safe_strip_hostname.doctree new file mode 100644 index 000000000..ef1dce829 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.jobs.safe_strip_hostname.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.jobs.sample_data.doctree b/.doctrees/reference/autoplex.data.common.jobs.sample_data.doctree new file mode 100644 index 000000000..3c4b0b4e5 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.jobs.sample_data.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.ElementCollection.doctree b/.doctrees/reference/autoplex.data.common.utils.ElementCollection.doctree new file mode 100644 index 000000000..eca1574c5 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.ElementCollection.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.GPa.doctree b/.doctrees/reference/autoplex.data.common.utils.GPa.doctree new file mode 100644 index 000000000..fb0ba98bd Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.GPa.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.boltzhist_cur_dual_iter.doctree b/.doctrees/reference/autoplex.data.common.utils.boltzhist_cur_dual_iter.doctree new file mode 100644 index 000000000..cdded153b Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.boltzhist_cur_dual_iter.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.boltzhist_cur_one_shot.doctree b/.doctrees/reference/autoplex.data.common.utils.boltzhist_cur_one_shot.doctree new file mode 100644 index 000000000..122aa1dc6 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.boltzhist_cur_one_shot.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.check_distances.doctree b/.doctrees/reference/autoplex.data.common.utils.check_distances.doctree new file mode 100644 index 000000000..f4ef7d441 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.check_distances.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.convexhull_cur.doctree b/.doctrees/reference/autoplex.data.common.utils.convexhull_cur.doctree new file mode 100644 index 000000000..93eacdaa3 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.convexhull_cur.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.create_soap_descriptor.doctree b/.doctrees/reference/autoplex.data.common.utils.create_soap_descriptor.doctree new file mode 100644 index 000000000..315a85c2d Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.create_soap_descriptor.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.cur_select.doctree b/.doctrees/reference/autoplex.data.common.utils.cur_select.doctree new file mode 100644 index 000000000..b50363b29 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.cur_select.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.data_distillation.doctree b/.doctrees/reference/autoplex.data.common.utils.data_distillation.doctree new file mode 100644 index 000000000..d59b0f035 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.data_distillation.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.doctree b/.doctrees/reference/autoplex.data.common.utils.doctree new file mode 100644 index 000000000..1ff13245b Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.energy_plot.doctree b/.doctrees/reference/autoplex.data.common.utils.energy_plot.doctree new file mode 100644 index 000000000..a291728ff Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.energy_plot.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.filter_outlier_energy.doctree b/.doctrees/reference/autoplex.data.common.utils.filter_outlier_energy.doctree new file mode 100644 index 000000000..34e9381c4 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.filter_outlier_energy.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.filter_outlier_forces.doctree b/.doctrees/reference/autoplex.data.common.utils.filter_outlier_forces.doctree new file mode 100644 index 000000000..4aae1babb Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.filter_outlier_forces.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.flatten.doctree b/.doctrees/reference/autoplex.data.common.utils.flatten.doctree new file mode 100644 index 000000000..d4d481aa4 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.flatten.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.flatten_list.doctree b/.doctrees/reference/autoplex.data.common.utils.flatten_list.doctree new file mode 100644 index 000000000..4ab42eb91 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.flatten_list.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.force_plot.doctree b/.doctrees/reference/autoplex.data.common.utils.force_plot.doctree new file mode 100644 index 000000000..ee68f7232 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.force_plot.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.handle_rss_trajectory.doctree b/.doctrees/reference/autoplex.data.common.utils.handle_rss_trajectory.doctree new file mode 100644 index 000000000..e63033045 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.handle_rss_trajectory.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.mc_rattle.doctree b/.doctrees/reference/autoplex.data.common.utils.mc_rattle.doctree new file mode 100644 index 000000000..b16d6872e Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.mc_rattle.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.parallel_calc_descriptor_vec.doctree b/.doctrees/reference/autoplex.data.common.utils.parallel_calc_descriptor_vec.doctree new file mode 100644 index 000000000..ad0efe634 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.parallel_calc_descriptor_vec.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.plot_energy_forces.doctree b/.doctrees/reference/autoplex.data.common.utils.plot_energy_forces.doctree new file mode 100644 index 000000000..326c1aea1 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.plot_energy_forces.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.random_vary_angle.doctree b/.doctrees/reference/autoplex.data.common.utils.random_vary_angle.doctree new file mode 100644 index 000000000..9c70e846a Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.random_vary_angle.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.rms_dict.doctree b/.doctrees/reference/autoplex.data.common.utils.rms_dict.doctree new file mode 100644 index 000000000..c7e66db5a Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.rms_dict.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.scale_cell.doctree b/.doctrees/reference/autoplex.data.common.utils.scale_cell.doctree new file mode 100644 index 000000000..91a26c96f Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.scale_cell.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.std_rattle.doctree b/.doctrees/reference/autoplex.data.common.utils.std_rattle.doctree new file mode 100644 index 000000000..c00c51117 Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.std_rattle.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.stratified_dataset_split.doctree b/.doctrees/reference/autoplex.data.common.utils.stratified_dataset_split.doctree new file mode 100644 index 000000000..8280edd9d Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.stratified_dataset_split.doctree differ diff --git a/.doctrees/reference/autoplex.data.common.utils.to_ase_trajectory.doctree b/.doctrees/reference/autoplex.data.common.utils.to_ase_trajectory.doctree new file mode 100644 index 000000000..80192c75d Binary files /dev/null and b/.doctrees/reference/autoplex.data.common.utils.to_ase_trajectory.doctree differ diff --git a/.doctrees/reference/autoplex.data.doctree b/.doctrees/reference/autoplex.data.doctree new file mode 100644 index 000000000..1177e5ba2 Binary files /dev/null and b/.doctrees/reference/autoplex.data.doctree differ diff --git a/.doctrees/reference/autoplex.data.phonons.doctree b/.doctrees/reference/autoplex.data.phonons.doctree new file mode 100644 index 000000000..e792769bb Binary files /dev/null and b/.doctrees/reference/autoplex.data.phonons.doctree differ diff --git a/.doctrees/reference/autoplex.data.phonons.flows.DFTPhononMaker.doctree b/.doctrees/reference/autoplex.data.phonons.flows.DFTPhononMaker.doctree new file mode 100644 index 000000000..a4861bae9 Binary files /dev/null and b/.doctrees/reference/autoplex.data.phonons.flows.DFTPhononMaker.doctree differ diff --git a/.doctrees/reference/autoplex.data.phonons.flows.IsoAtomMaker.doctree b/.doctrees/reference/autoplex.data.phonons.flows.IsoAtomMaker.doctree new file mode 100644 index 000000000..3d7d7a3a4 Binary files /dev/null and b/.doctrees/reference/autoplex.data.phonons.flows.IsoAtomMaker.doctree differ diff --git a/.doctrees/reference/autoplex.data.phonons.flows.IsoAtomStaticMaker.doctree b/.doctrees/reference/autoplex.data.phonons.flows.IsoAtomStaticMaker.doctree new file mode 100644 index 000000000..cdb6e4d69 Binary files /dev/null and b/.doctrees/reference/autoplex.data.phonons.flows.IsoAtomStaticMaker.doctree differ diff --git a/.doctrees/reference/autoplex.data.phonons.flows.MLPhononMaker.doctree b/.doctrees/reference/autoplex.data.phonons.flows.MLPhononMaker.doctree new file mode 100644 index 000000000..b6d9fd18c Binary files /dev/null and b/.doctrees/reference/autoplex.data.phonons.flows.MLPhononMaker.doctree differ diff --git a/.doctrees/reference/autoplex.data.phonons.flows.RandomStructuresDataGenerator.doctree b/.doctrees/reference/autoplex.data.phonons.flows.RandomStructuresDataGenerator.doctree new file mode 100644 index 000000000..7c6c677e7 Binary files /dev/null and b/.doctrees/reference/autoplex.data.phonons.flows.RandomStructuresDataGenerator.doctree differ diff --git a/.doctrees/reference/autoplex.data.phonons.flows.TightDFTStaticMaker.doctree b/.doctrees/reference/autoplex.data.phonons.flows.TightDFTStaticMaker.doctree new file mode 100644 index 000000000..45ff7e85c Binary files /dev/null and b/.doctrees/reference/autoplex.data.phonons.flows.TightDFTStaticMaker.doctree differ diff --git a/.doctrees/reference/autoplex.data.phonons.flows.doctree b/.doctrees/reference/autoplex.data.phonons.flows.doctree new file mode 100644 index 000000000..2bcce87df Binary files /dev/null and b/.doctrees/reference/autoplex.data.phonons.flows.doctree differ diff --git a/.doctrees/reference/autoplex.data.phonons.jobs.doctree b/.doctrees/reference/autoplex.data.phonons.jobs.doctree new file mode 100644 index 000000000..c3c53e3cd Binary files /dev/null and b/.doctrees/reference/autoplex.data.phonons.jobs.doctree differ diff --git a/.doctrees/reference/autoplex.data.phonons.jobs.reduce_supercell_size_job.doctree b/.doctrees/reference/autoplex.data.phonons.jobs.reduce_supercell_size_job.doctree new file mode 100644 index 000000000..fa7703a35 Binary files /dev/null and b/.doctrees/reference/autoplex.data.phonons.jobs.reduce_supercell_size_job.doctree differ diff --git a/.doctrees/reference/autoplex.data.phonons.utils.check_supercells.doctree b/.doctrees/reference/autoplex.data.phonons.utils.check_supercells.doctree new file mode 100644 index 000000000..b698a74fe Binary files /dev/null and b/.doctrees/reference/autoplex.data.phonons.utils.check_supercells.doctree differ diff --git a/.doctrees/reference/autoplex.data.phonons.utils.doctree b/.doctrees/reference/autoplex.data.phonons.utils.doctree new file mode 100644 index 000000000..337a674b0 Binary files /dev/null and b/.doctrees/reference/autoplex.data.phonons.utils.doctree differ diff --git a/.doctrees/reference/autoplex.data.phonons.utils.ml_phonon_maker_preparation.doctree b/.doctrees/reference/autoplex.data.phonons.utils.ml_phonon_maker_preparation.doctree new file mode 100644 index 000000000..c31c8d8d2 Binary files /dev/null and b/.doctrees/reference/autoplex.data.phonons.utils.ml_phonon_maker_preparation.doctree differ diff --git a/.doctrees/reference/autoplex.data.phonons.utils.reduce_supercell_size.doctree b/.doctrees/reference/autoplex.data.phonons.utils.reduce_supercell_size.doctree new file mode 100644 index 000000000..e8fa94e66 Binary files /dev/null and b/.doctrees/reference/autoplex.data.phonons.utils.reduce_supercell_size.doctree differ diff --git a/.doctrees/reference/autoplex.data.phonons.utils.update_phonon_displacement_maker.doctree b/.doctrees/reference/autoplex.data.phonons.utils.update_phonon_displacement_maker.doctree new file mode 100644 index 000000000..d72006776 Binary files /dev/null and b/.doctrees/reference/autoplex.data.phonons.utils.update_phonon_displacement_maker.doctree differ diff --git a/.doctrees/reference/autoplex.data.rss.doctree b/.doctrees/reference/autoplex.data.rss.doctree new file mode 100644 index 000000000..3b23d7935 Binary files /dev/null and b/.doctrees/reference/autoplex.data.rss.doctree differ diff --git a/.doctrees/reference/autoplex.data.rss.flows.BuildMultiRandomizedStructure.doctree b/.doctrees/reference/autoplex.data.rss.flows.BuildMultiRandomizedStructure.doctree new file mode 100644 index 000000000..6254ba6e6 Binary files /dev/null and b/.doctrees/reference/autoplex.data.rss.flows.BuildMultiRandomizedStructure.doctree differ diff --git a/.doctrees/reference/autoplex.data.rss.flows.doctree b/.doctrees/reference/autoplex.data.rss.flows.doctree new file mode 100644 index 000000000..935da4185 Binary files /dev/null and b/.doctrees/reference/autoplex.data.rss.flows.doctree differ diff --git a/.doctrees/reference/autoplex.data.rss.jobs.RandomizedStructure.doctree b/.doctrees/reference/autoplex.data.rss.jobs.RandomizedStructure.doctree new file mode 100644 index 000000000..5e3ff775b Binary files /dev/null and b/.doctrees/reference/autoplex.data.rss.jobs.RandomizedStructure.doctree differ diff --git a/.doctrees/reference/autoplex.data.rss.jobs.atomic_numbers.doctree b/.doctrees/reference/autoplex.data.rss.jobs.atomic_numbers.doctree new file mode 100644 index 000000000..f5096cd15 Binary files /dev/null and b/.doctrees/reference/autoplex.data.rss.jobs.atomic_numbers.doctree differ diff --git a/.doctrees/reference/autoplex.data.rss.jobs.covalent_radii.doctree b/.doctrees/reference/autoplex.data.rss.jobs.covalent_radii.doctree new file mode 100644 index 000000000..fcdd5637b Binary files /dev/null and b/.doctrees/reference/autoplex.data.rss.jobs.covalent_radii.doctree differ diff --git a/.doctrees/reference/autoplex.data.rss.jobs.do_rss_multi_node.doctree b/.doctrees/reference/autoplex.data.rss.jobs.do_rss_multi_node.doctree new file mode 100644 index 000000000..a4d336701 Binary files /dev/null and b/.doctrees/reference/autoplex.data.rss.jobs.do_rss_multi_node.doctree differ diff --git a/.doctrees/reference/autoplex.data.rss.jobs.do_rss_single_node.doctree b/.doctrees/reference/autoplex.data.rss.jobs.do_rss_single_node.doctree new file mode 100644 index 000000000..b208a3990 Binary files /dev/null and b/.doctrees/reference/autoplex.data.rss.jobs.do_rss_single_node.doctree differ diff --git a/.doctrees/reference/autoplex.data.rss.jobs.doctree b/.doctrees/reference/autoplex.data.rss.jobs.doctree new file mode 100644 index 000000000..eb6659b3c Binary files /dev/null and b/.doctrees/reference/autoplex.data.rss.jobs.doctree differ diff --git a/.doctrees/reference/autoplex.data.rss.utils.CustomPotential.doctree b/.doctrees/reference/autoplex.data.rss.utils.CustomPotential.doctree new file mode 100644 index 000000000..fe5e28c9e Binary files /dev/null and b/.doctrees/reference/autoplex.data.rss.utils.CustomPotential.doctree differ diff --git a/.doctrees/reference/autoplex.data.rss.utils.GPa.doctree b/.doctrees/reference/autoplex.data.rss.utils.GPa.doctree new file mode 100644 index 000000000..bb2551928 Binary files /dev/null and b/.doctrees/reference/autoplex.data.rss.utils.GPa.doctree differ diff --git a/.doctrees/reference/autoplex.data.rss.utils.HookeanRepulsion.doctree b/.doctrees/reference/autoplex.data.rss.utils.HookeanRepulsion.doctree new file mode 100644 index 000000000..c58f67a80 Binary files /dev/null and b/.doctrees/reference/autoplex.data.rss.utils.HookeanRepulsion.doctree differ diff --git a/.doctrees/reference/autoplex.data.rss.utils.atomic_numbers.doctree b/.doctrees/reference/autoplex.data.rss.utils.atomic_numbers.doctree new file mode 100644 index 000000000..16df9633c Binary files /dev/null and b/.doctrees/reference/autoplex.data.rss.utils.atomic_numbers.doctree differ diff --git a/.doctrees/reference/autoplex.data.rss.utils.chemical_symbols.doctree b/.doctrees/reference/autoplex.data.rss.utils.chemical_symbols.doctree new file mode 100644 index 000000000..60c890a4a Binary files /dev/null and b/.doctrees/reference/autoplex.data.rss.utils.chemical_symbols.doctree differ diff --git a/.doctrees/reference/autoplex.data.rss.utils.doctree b/.doctrees/reference/autoplex.data.rss.utils.doctree new file mode 100644 index 000000000..dd3656f73 Binary files /dev/null and b/.doctrees/reference/autoplex.data.rss.utils.doctree differ diff --git a/.doctrees/reference/autoplex.data.rss.utils.extract_pairstyle.doctree b/.doctrees/reference/autoplex.data.rss.utils.extract_pairstyle.doctree new file mode 100644 index 000000000..a7ce8082d Binary files /dev/null and b/.doctrees/reference/autoplex.data.rss.utils.extract_pairstyle.doctree differ diff --git a/.doctrees/reference/autoplex.data.rss.utils.minimize_structures.doctree b/.doctrees/reference/autoplex.data.rss.utils.minimize_structures.doctree new file mode 100644 index 000000000..59bbdb81a Binary files /dev/null and b/.doctrees/reference/autoplex.data.rss.utils.minimize_structures.doctree differ diff --git a/.doctrees/reference/autoplex.data.rss.utils.process_rss.doctree b/.doctrees/reference/autoplex.data.rss.utils.process_rss.doctree new file mode 100644 index 000000000..2d65c21fe Binary files /dev/null and b/.doctrees/reference/autoplex.data.rss.utils.process_rss.doctree differ diff --git a/.doctrees/reference/autoplex.data.rss.utils.split_structure_into_groups.doctree b/.doctrees/reference/autoplex.data.rss.utils.split_structure_into_groups.doctree new file mode 100644 index 000000000..e44b2e6ca Binary files /dev/null and b/.doctrees/reference/autoplex.data.rss.utils.split_structure_into_groups.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.doctree b/.doctrees/reference/autoplex.fitting.common.doctree new file mode 100644 index 000000000..bfc9eb565 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.flows.DataPreprocessing.doctree b/.doctrees/reference/autoplex.fitting.common.flows.DataPreprocessing.doctree new file mode 100644 index 000000000..b20540515 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.flows.DataPreprocessing.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.flows.MLIPFitMaker.doctree b/.doctrees/reference/autoplex.fitting.common.flows.MLIPFitMaker.doctree new file mode 100644 index 000000000..27e810888 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.flows.MLIPFitMaker.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.flows.doctree b/.doctrees/reference/autoplex.fitting.common.flows.doctree new file mode 100644 index 000000000..145184c86 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.flows.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.jobs.doctree b/.doctrees/reference/autoplex.fitting.common.jobs.doctree new file mode 100644 index 000000000..cf2ec3263 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.jobs.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.jobs.machine_learning_fit.doctree b/.doctrees/reference/autoplex.fitting.common.jobs.machine_learning_fit.doctree new file mode 100644 index 000000000..e165fedab Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.jobs.machine_learning_fit.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.regularization.calculate_hull_3d.doctree b/.doctrees/reference/autoplex.fitting.common.regularization.calculate_hull_3d.doctree new file mode 100644 index 000000000..4bac72cf6 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.regularization.calculate_hull_3d.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.regularization.calculate_hull_nd.doctree b/.doctrees/reference/autoplex.fitting.common.regularization.calculate_hull_nd.doctree new file mode 100644 index 000000000..467c57d9b Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.regularization.calculate_hull_nd.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.regularization.doctree b/.doctrees/reference/autoplex.fitting.common.regularization.doctree new file mode 100644 index 000000000..89fc2f408 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.regularization.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.regularization.get_convex_hull.doctree b/.doctrees/reference/autoplex.fitting.common.regularization.get_convex_hull.doctree new file mode 100644 index 000000000..4ea8cc7b9 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.regularization.get_convex_hull.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.regularization.get_e_distance_to_hull.doctree b/.doctrees/reference/autoplex.fitting.common.regularization.get_e_distance_to_hull.doctree new file mode 100644 index 000000000..e1b700bab Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.regularization.get_e_distance_to_hull.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.regularization.get_e_distance_to_hull_3d.doctree b/.doctrees/reference/autoplex.fitting.common.regularization.get_e_distance_to_hull_3d.doctree new file mode 100644 index 000000000..ce4d40c6d Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.regularization.get_e_distance_to_hull_3d.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.regularization.get_intersect.doctree b/.doctrees/reference/autoplex.fitting.common.regularization.get_intersect.doctree new file mode 100644 index 000000000..de8598292 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.regularization.get_intersect.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.regularization.get_mole_frac.doctree b/.doctrees/reference/autoplex.fitting.common.regularization.get_mole_frac.doctree new file mode 100644 index 000000000..b4979ef01 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.regularization.get_mole_frac.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.regularization.label_stoichiometry_volume.doctree b/.doctrees/reference/autoplex.fitting.common.regularization.label_stoichiometry_volume.doctree new file mode 100644 index 000000000..9ca772fae Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.regularization.label_stoichiometry_volume.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.regularization.piecewise_linear.doctree b/.doctrees/reference/autoplex.fitting.common.regularization.piecewise_linear.doctree new file mode 100644 index 000000000..3835c7127 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.regularization.piecewise_linear.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.regularization.point_in_triangle_2D.doctree b/.doctrees/reference/autoplex.fitting.common.regularization.point_in_triangle_2D.doctree new file mode 100644 index 000000000..64236197d Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.regularization.point_in_triangle_2D.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.regularization.point_in_triangle_nd.doctree b/.doctrees/reference/autoplex.fitting.common.regularization.point_in_triangle_nd.doctree new file mode 100644 index 000000000..1dd43625f Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.regularization.point_in_triangle_nd.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.regularization.set_custom_sigma.doctree b/.doctrees/reference/autoplex.fitting.common.regularization.set_custom_sigma.doctree new file mode 100644 index 000000000..c2e8d8343 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.regularization.set_custom_sigma.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.calculate_delta.doctree b/.doctrees/reference/autoplex.fitting.common.utils.calculate_delta.doctree new file mode 100644 index 000000000..346c398b9 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.calculate_delta.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.check_convergence.doctree b/.doctrees/reference/autoplex.fitting.common.utils.check_convergence.doctree new file mode 100644 index 000000000..37cc162a9 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.check_convergence.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.chemical_symbols.doctree b/.doctrees/reference/autoplex.fitting.common.utils.chemical_symbols.doctree new file mode 100644 index 000000000..d650bfafb Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.chemical_symbols.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.compute_pairs_triplets.doctree b/.doctrees/reference/autoplex.fitting.common.utils.compute_pairs_triplets.doctree new file mode 100644 index 000000000..fdb1681d4 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.compute_pairs_triplets.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.convert_xyz_to_structure.doctree b/.doctrees/reference/autoplex.fitting.common.utils.convert_xyz_to_structure.doctree new file mode 100644 index 000000000..dc577bfc8 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.convert_xyz_to_structure.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.doctree b/.doctrees/reference/autoplex.fitting.common.utils.doctree new file mode 100644 index 000000000..e186efa36 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.energy_remain.doctree b/.doctrees/reference/autoplex.fitting.common.utils.energy_remain.doctree new file mode 100644 index 000000000..88e397670 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.energy_remain.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.extract_gap_label.doctree b/.doctrees/reference/autoplex.fitting.common.utils.extract_gap_label.doctree new file mode 100644 index 000000000..5422477b7 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.extract_gap_label.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.flatten.doctree b/.doctrees/reference/autoplex.fitting.common.utils.flatten.doctree new file mode 100644 index 000000000..e22a87a8d Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.flatten.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.gap_fitting.doctree b/.doctrees/reference/autoplex.fitting.common.utils.gap_fitting.doctree new file mode 100644 index 000000000..64419b7f8 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.gap_fitting.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.gap_hyperparameter_constructor.doctree b/.doctrees/reference/autoplex.fitting.common.utils.gap_hyperparameter_constructor.doctree new file mode 100644 index 000000000..5ae8f17b1 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.gap_hyperparameter_constructor.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.gcm3_to_Vm.doctree b/.doctrees/reference/autoplex.fitting.common.utils.gcm3_to_Vm.doctree new file mode 100644 index 000000000..5387fda0d Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.gcm3_to_Vm.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.get_atomic_numbers.doctree b/.doctrees/reference/autoplex.fitting.common.utils.get_atomic_numbers.doctree new file mode 100644 index 000000000..b0ca3685e Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.get_atomic_numbers.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.get_list_of_vasp_calc_dirs.doctree b/.doctrees/reference/autoplex.fitting.common.utils.get_list_of_vasp_calc_dirs.doctree new file mode 100644 index 000000000..3902e9087 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.get_list_of_vasp_calc_dirs.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.jace_fitting.doctree b/.doctrees/reference/autoplex.fitting.common.utils.jace_fitting.doctree new file mode 100644 index 000000000..f7268449c Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.jace_fitting.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.load_mlip_hyperparameter_defaults.doctree b/.doctrees/reference/autoplex.fitting.common.utils.load_mlip_hyperparameter_defaults.doctree new file mode 100644 index 000000000..0a5083a47 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.load_mlip_hyperparameter_defaults.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.m3gnet_fitting.doctree b/.doctrees/reference/autoplex.fitting.common.utils.m3gnet_fitting.doctree new file mode 100644 index 000000000..d640bee2e Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.m3gnet_fitting.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.mace_fitting.doctree b/.doctrees/reference/autoplex.fitting.common.utils.mace_fitting.doctree new file mode 100644 index 000000000..1fceddca5 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.mace_fitting.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.mace_virial_format_conversion.doctree b/.doctrees/reference/autoplex.fitting.common.utils.mace_virial_format_conversion.doctree new file mode 100644 index 000000000..a8091efb3 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.mace_virial_format_conversion.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.nequip_fitting.doctree b/.doctrees/reference/autoplex.fitting.common.utils.nequip_fitting.doctree new file mode 100644 index 000000000..6fa1504ae Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.nequip_fitting.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.plot_convex_hull.doctree b/.doctrees/reference/autoplex.fitting.common.utils.plot_convex_hull.doctree new file mode 100644 index 000000000..fcefe20ee Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.plot_convex_hull.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.prepare_fit_environment.doctree b/.doctrees/reference/autoplex.fitting.common.utils.prepare_fit_environment.doctree new file mode 100644 index 000000000..5fa6edc5a Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.prepare_fit_environment.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.run_ace.doctree b/.doctrees/reference/autoplex.fitting.common.utils.run_ace.doctree new file mode 100644 index 000000000..e136a5f22 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.run_ace.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.run_gap.doctree b/.doctrees/reference/autoplex.fitting.common.utils.run_gap.doctree new file mode 100644 index 000000000..cff4b065c Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.run_gap.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.run_mace.doctree b/.doctrees/reference/autoplex.fitting.common.utils.run_mace.doctree new file mode 100644 index 000000000..004410676 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.run_mace.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.run_nequip.doctree b/.doctrees/reference/autoplex.fitting.common.utils.run_nequip.doctree new file mode 100644 index 000000000..ef6e35b53 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.run_nequip.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.run_quip.doctree b/.doctrees/reference/autoplex.fitting.common.utils.run_quip.doctree new file mode 100644 index 000000000..c61b83df5 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.run_quip.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.vaspoutput_2_extended_xyz.doctree b/.doctrees/reference/autoplex.fitting.common.utils.vaspoutput_2_extended_xyz.doctree new file mode 100644 index 000000000..f29469794 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.vaspoutput_2_extended_xyz.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.common.utils.write_after_distillation_data_split.doctree b/.doctrees/reference/autoplex.fitting.common.utils.write_after_distillation_data_split.doctree new file mode 100644 index 000000000..70502dcd6 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.common.utils.write_after_distillation_data_split.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.doctree b/.doctrees/reference/autoplex.fitting.doctree new file mode 100644 index 000000000..b0f83baa7 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.phonons.doctree b/.doctrees/reference/autoplex.fitting.phonons.doctree new file mode 100644 index 000000000..64d5326b3 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.phonons.doctree differ diff --git a/.doctrees/reference/autoplex.fitting.rss.doctree b/.doctrees/reference/autoplex.fitting.rss.doctree new file mode 100644 index 000000000..42987fee3 Binary files /dev/null and b/.doctrees/reference/autoplex.fitting.rss.doctree differ diff --git a/.doctrees/reference/index.doctree b/.doctrees/reference/index.doctree new file mode 100644 index 000000000..474e74fa3 Binary files /dev/null and b/.doctrees/reference/index.doctree differ diff --git a/.doctrees/user/index.doctree b/.doctrees/user/index.doctree new file mode 100644 index 000000000..6d2b9e6a4 Binary files /dev/null and b/.doctrees/user/index.doctree differ diff --git a/.doctrees/user/installation/installation.doctree b/.doctrees/user/installation/installation.doctree new file mode 100644 index 000000000..9e0cb01c1 Binary files /dev/null and b/.doctrees/user/installation/installation.doctree differ diff --git a/.doctrees/user/jobflowremote.doctree b/.doctrees/user/jobflowremote.doctree new file mode 100644 index 000000000..313b21502 Binary files /dev/null and b/.doctrees/user/jobflowremote.doctree differ diff --git a/.doctrees/user/mongodb.doctree b/.doctrees/user/mongodb.doctree new file mode 100644 index 000000000..adf030344 Binary files /dev/null and b/.doctrees/user/mongodb.doctree differ diff --git a/.doctrees/user/phonon/flows/benchmark/benchmark.doctree b/.doctrees/user/phonon/flows/benchmark/benchmark.doctree new file mode 100644 index 000000000..d36e796d3 Binary files /dev/null and b/.doctrees/user/phonon/flows/benchmark/benchmark.doctree differ diff --git a/.doctrees/user/phonon/flows/fitting/fitting.doctree b/.doctrees/user/phonon/flows/fitting/fitting.doctree new file mode 100644 index 000000000..f8e10de08 Binary files /dev/null and b/.doctrees/user/phonon/flows/fitting/fitting.doctree differ diff --git a/.doctrees/user/phonon/flows/flows.doctree b/.doctrees/user/phonon/flows/flows.doctree new file mode 100644 index 000000000..c817df64b Binary files /dev/null and b/.doctrees/user/phonon/flows/flows.doctree differ diff --git a/.doctrees/user/phonon/flows/generation/data.doctree b/.doctrees/user/phonon/flows/generation/data.doctree new file mode 100644 index 000000000..923bd47bc Binary files /dev/null and b/.doctrees/user/phonon/flows/generation/data.doctree differ diff --git a/.doctrees/user/phonon/index.doctree b/.doctrees/user/phonon/index.doctree new file mode 100644 index 000000000..ba49c399b Binary files /dev/null and b/.doctrees/user/phonon/index.doctree differ diff --git a/.doctrees/user/rss.doctree b/.doctrees/user/rss.doctree new file mode 100644 index 000000000..324dca323 Binary files /dev/null and b/.doctrees/user/rss.doctree differ diff --git a/.doctrees/user/rss/flow/example/example.doctree b/.doctrees/user/rss/flow/example/example.doctree new file mode 100644 index 000000000..b80add2b5 Binary files /dev/null and b/.doctrees/user/rss/flow/example/example.doctree differ diff --git a/.doctrees/user/rss/flow/input/input.doctree b/.doctrees/user/rss/flow/input/input.doctree new file mode 100644 index 000000000..ef3ca6a3d Binary files /dev/null and b/.doctrees/user/rss/flow/input/input.doctree differ diff --git a/.doctrees/user/rss/flow/introduction/intro.doctree b/.doctrees/user/rss/flow/introduction/intro.doctree new file mode 100644 index 000000000..1fdd5d040 Binary files /dev/null and b/.doctrees/user/rss/flow/introduction/intro.doctree differ diff --git a/.doctrees/user/rss/flow/quick_start/start.doctree b/.doctrees/user/rss/flow/quick_start/start.doctree new file mode 100644 index 000000000..1dfd65b70 Binary files /dev/null and b/.doctrees/user/rss/flow/quick_start/start.doctree differ diff --git a/.doctrees/user/rss/index.doctree b/.doctrees/user/rss/index.doctree new file mode 100644 index 000000000..348b9a056 Binary files /dev/null and b/.doctrees/user/rss/index.doctree differ diff --git a/.doctrees/user/setup.doctree b/.doctrees/user/setup.doctree new file mode 100644 index 000000000..0c64532fc Binary files /dev/null and b/.doctrees/user/setup.doctree differ diff --git a/.doctrees/user/tutorials.doctree b/.doctrees/user/tutorials.doctree new file mode 100644 index 000000000..b96c4b937 Binary files /dev/null and b/.doctrees/user/tutorials.doctree differ diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/_downloads/a4843ae081656414b03cd78a238508a0/test_project.yaml b/_downloads/a4843ae081656414b03cd78a238508a0/test_project.yaml new file mode 100644 index 000000000..c3ce3767f --- /dev/null +++ b/_downloads/a4843ae081656414b03cd78a238508a0/test_project.yaml @@ -0,0 +1,77 @@ +name: test_project +base_dir: /home/username/.jfremote/test_project +tmp_dir: /home/username/.jfremote/test_project/tmp +log_dir: /home/username/.jfremote/test_project/log +daemon_dir: /home/username/.jfremote/test_project/daemon +log_level: debug +runner: + delay_checkout: 30 + delay_check_run_status: 30 + delay_advance_status: 30 + delay_refresh_limited: 600 + delay_update_batch: 60 + lock_timeout: 86400 + delete_tmp_folder: true + max_step_attempts: 3 + delta_retry: + - 30 + - 300 + - 1200 +workers: + example_worker: + type: remote + scheduler_type: slurm + work_dir: /path/to/your/scratch/dir + resources: + pre_run: | + source activate autoplex + post_run: + timeout_execute: 120 + max_jobs: 10 + batch: + host: remote cluster + user: username + port: + password: + key_filename: + passphrase: + gateway: + forward_agent: + connect_timeout: + connect_kwargs: + inline_ssh_env: + keepalive: 60 + shell_cmd: bash + login_shell: true + interactive_login: true +queue: + store: + type: MongoStore + host: local machine + database: db name + username: user name + password: password + collection_name: jobs + flows_collection: flows + auxiliary_collection: jf_auxiliary + db_id_prefix: +exec_config: {} +jobstore: + docs_store: + type: MongoStore + database: db name + host: local machine + port: 27017 + username: user name + password: password + collection_name: outputs + additional_stores: + data: + type: GridFSStore + database: db name + host: local machine + port: 27017 + username: user name + password: password + collection_name: outputs_blobs +metadata: \ No newline at end of file diff --git a/_images/LiCl_band_comparison.png b/_images/LiCl_band_comparison.png new file mode 100644 index 000000000..12bb1e413 Binary files /dev/null and b/_images/LiCl_band_comparison.png differ diff --git a/_images/LiCl_rmse_phonons.png b/_images/LiCl_rmse_phonons.png new file mode 100644 index 000000000..018708cfd Binary files /dev/null and b/_images/LiCl_rmse_phonons.png differ diff --git a/_images/autoplex_logo.png b/_images/autoplex_logo.png new file mode 100644 index 000000000..5266eefa9 Binary files /dev/null and b/_images/autoplex_logo.png differ diff --git a/_images/energy_forces.png b/_images/energy_forces.png new file mode 100644 index 000000000..8866491ba Binary files /dev/null and b/_images/energy_forces.png differ diff --git a/_images/orcid.svg b/_images/orcid.svg new file mode 100644 index 000000000..83d027ab7 --- /dev/null +++ b/_images/orcid.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/_modules/autoplex/auto/phonons/flows.html b/_modules/autoplex/auto/phonons/flows.html new file mode 100644 index 000000000..aaa830c12 --- /dev/null +++ b/_modules/autoplex/auto/phonons/flows.html @@ -0,0 +1,1591 @@ + + + + + + + + + + autoplex.auto.phonons.flows — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+
+
+ + + + +
+ +

Source code for autoplex.auto.phonons.flows

+"""Flows to perform automatic data generation, fitting, and benchmarking of ML potentials."""
+
+import logging
+import warnings
+from dataclasses import dataclass, field
+from pathlib import Path
+
+from atomate2.common.schemas.phonons import PhononBSDOSDoc
+from atomate2.vasp.flows.mp import (
+    MPGGADoubleRelaxMaker,
+    MPGGARelaxMaker,
+    MPGGAStaticMaker,
+)
+from atomate2.vasp.jobs.base import BaseVaspMaker
+from jobflow import Flow, Maker
+from pymatgen.core.structure import Structure
+from pymatgen.io.vasp.sets import (
+    MPRelaxSet,
+    MPStaticSet,
+)
+
+from autoplex.auto.phonons.jobs import (
+    complete_benchmark,
+    dft_phonopy_gen_data,
+    dft_random_gen_data,
+    do_iterative_rattled_structures,
+    generate_supercells,
+    get_iso_atom,
+    get_phonon_output,
+    run_supercells,
+)
+from autoplex.benchmark.phonons.jobs import write_benchmark_metrics
+from autoplex.data.phonons.flows import IsoAtomStaticMaker, TightDFTStaticMaker
+from autoplex.data.phonons.jobs import reduce_supercell_size_job
+from autoplex.fitting.common.flows import MLIPFitMaker
+from autoplex.fitting.common.utils import (
+    MLIP_PHONON_DEFAULTS_FILE_PATH,
+    load_mlip_hyperparameter_defaults,
+)
+
+__all__ = [
+    "CompleteDFTvsMLBenchmarkWorkflow",
+    "CompleteDFTvsMLBenchmarkWorkflowMPSettings",
+    "DFTSupercellSettingsMaker",
+    "IterativeCompleteDFTvsMLBenchmarkWorkflow",
+]
+
+
+
+[docs] +@dataclass +class CompleteDFTvsMLBenchmarkWorkflow(Maker): + """ + Maker to construct a DFT (VASP) based dataset, composed of the following two configuration types. + + (1) single atom displaced supercells (based on the atomate2 PhononMaker subroutines) + (2) supercells with randomly displaced atoms (based on the ase rattled function). + + Machine-learned interatomic potential(s) are then fitted on the dataset, followed by + benchmarking the resulting potential(s) to DFT (VASP) level using the provided benchmark + structure(s) and comparing the respective DFT and MLIP-based Phonon calculations. + The benchmark metrics are provided in form of a phonon band structure comparison and + q-point-wise phonons RMSE plots, as well as a summary text file. + + Parameters + ---------- + name : str + Name of the flow produced by this maker. + add_dft_phonon_struct: bool. + If True, will add displaced supercells via phonopy for DFT calculation. + add_dft_rattled_struct: bool. + If True, will add rattled structures for DFT calculation. + add_rss_struct: bool. + If True, will add RSS generated structures for DFT calculation. + n_structures: int. + The total number of randomly displaced structures to be generated. + displacement_maker: BaseVaspMaker + Maker used for a static calculation for a supercell. + phonon_bulk_relax_maker: BaseVaspMaker + Maker used for the bulk relax unit cell calculation. + rattled_bulk_relax_maker: BaseVaspMaker + Maker used for the bulk relax unit cell calculation. + phonon_static_energy_maker: BaseVaspMaker + Maker used for the static energy unit cell calculation. + isolated_atom_maker: IsoAtomStaticMaker + VASP maker for the isolated atom calculation. + n_structures : int. + Total number of distorted structures to be generated. + Must be provided if distorting volume without specifying a range, or if distorting angles. + Default=10. + displacements: list[float] + Displacement distances for phonon data generation for the fiting. + Only 0.01 is used for the benchmark at the moment. + This value can currently not be changed. + symprec: float + Symmetry precision to use in the + reduction of symmetry to find the primitive/conventional cell + (use_primitive_standard_structure, use_conventional_standard_structure) + and to handle all symmetry-related tasks in phonopy. + uc: bool. + If True, will generate randomly distorted structures (unitcells) + and add static computation jobs to the flow. + distort_type : int. + 0- volume distortion, 1- angle distortion, 2- volume and angle distortion. Default=0. + volume_scale_factor_range : list[float] + [min, max] of volume scale factors. + e.g. [0.90, 1.10] will distort volume +-10%. + volume_custom_scale_factors : list[float] + Specify explicit scale factors (if range is not specified). + If None, will default to [0.90, 0.95, 0.98, 0.99, 1.01, 1.02, 1.05, 1.10]. + min_distance: float + Minimum separation allowed between any two atoms. + Default= 1.5A. + angle_percentage_scale: float + Angle scaling factor. + Default= 10 will randomly distort angles by +-10% of original value. + angle_max_attempts: int. + Maximum number of attempts to distort structure before aborting. + Default=1000. + w_angle: list[float] + List of angle indices to be changed i.e. 0=alpha, 1=beta, 2=gamma. + Default= [0, 1, 2]. + rattle_type: int. + 0- standard rattling, 1- Monte-Carlo rattling. Default=0. + rattle_std: float. + Rattle amplitude (standard deviation in normal distribution). + Default=0.01. + Note that for MC rattling, displacements generated will roughly be + rattle_mc_n_iter**0.5 * rattle_std for small values of n_iter. + rattle_mc_n_iter: int. + Number of Monte Carlo iterations. + Larger number of iterations will generate larger displacements. + Default=10. + ml_models: list[str] + List of the ML models to be used. Default is GAP. + force_max: float + Maximum allowed force in the dataset. + force_min: float + Minimal force cutoff value for atom-wise regularization. + split_ratio: float. + Parameter to divide the training set and the test set. + A value of 0.1 means that the ratio of the training set to the test set is 9:1. + regularization: bool + For using sigma regularization. + distillation: bool + For using data distillation. + separated: bool + Repeat the fit for each data_type available in the (combined) database. + num_processes_fit: int + Number of processes for fitting. + apply_data_preprocessing: bool + Apply data preprocessing. + atomwise_regularization_parameter: float + Regularization value for the atom-wise force components. + atom_wise_regularization: bool + For including atom-wise regularization. + auto_delta: bool + Automatically determines delta for 2b, 3b and soap terms. + hyper_para_loop: bool + Making it easier to loop through several hyperparameter sets. + atomwise_regularization_list: list + List of atom-wise regularization parameters that are checked. + soap_delta_list: list + List of SOAP delta values that are checked. + n_sparse_list: list + List of GAP n_sparse values that are checked. + supercell_settings: dict + Settings for supercell generation + benchmark_kwargs: dict + Keyword arguments for the benchmark flows + path_to_hyperparameters : str or Path. + Path to JSON file containing the MLIP hyperparameters. + summary_filename_prefix: str + Prefix of the result summary file. + glue_xml: bool + Use the glue.xml core potential instead of fitting 2b terms. + glue_file_path: str + Name of the glue.xml file path. + use_defaults_fitting: bool + Use the fit defaults. + """ + + name: str = "add_data" + add_dft_phonon_struct: bool = True + add_dft_rattled_struct: bool = True + add_rss_struct: bool = False + displacement_maker: BaseVaspMaker = None + phonon_bulk_relax_maker: BaseVaspMaker = None + phonon_static_energy_maker: BaseVaspMaker = None + rattled_bulk_relax_maker: BaseVaspMaker = None + isolated_atom_maker: IsoAtomStaticMaker | None = None + n_structures: int = 10 + displacements: list[float] = field(default_factory=lambda: [0.01]) + symprec: float = 1e-4 + uc: bool = False + volume_custom_scale_factors: list[float] | None = None + volume_scale_factor_range: list[float] | None = None + rattle_std: float = 0.01 + distort_type: int = 0 + min_distance: float = 1.5 + angle_percentage_scale: float = 10 + angle_max_attempts: int = 1000 + rattle_type: int = 0 + rattle_mc_n_iter: int = 10 + w_angle: list[float] | None = None + ml_models: list[str] = field(default_factory=lambda: ["GAP"]) + atomwise_regularization_parameter: float = 0.1 + atom_wise_regularization: bool = True + force_max: float = 40.0 + force_min: float = 0.01 # unit: eV Å-1 + split_ratio: float = 0.4 + regularization: bool = False + separated: bool = False + num_processes_fit: int | None = None + distillation: bool = True + apply_data_preprocessing: bool = True + auto_delta: bool = False + hyper_para_loop: bool = False + atomwise_regularization_list: list | None = None + soap_delta_list: list | None = None + n_sparse_list: list | None = None + supercell_settings: dict = field( + default_factory=lambda: {"min_length": 15, "max_length": 20} + ) + benchmark_kwargs: dict = field(default_factory=dict) + path_to_hyperparameters: Path | str = MLIP_PHONON_DEFAULTS_FILE_PATH + summary_filename_prefix: str = "results_" + glue_xml: bool = False + glue_file_path: str = "glue.xml" + use_defaults_fitting: bool = True + +
+[docs] + def make( + self, + structure_list: list[Structure], + mp_ids, + dft_references: list[PhononBSDOSDoc] | None = None, + benchmark_structures: list[Structure] | None = None, + benchmark_mp_ids: list[str] | None = None, + pre_database_dir: str | None = None, + pre_xyz_files: list[str] | None = None, + rattle_seed: int | None = 42, + fit_kwargs_list: list | None = None, + ) -> Flow: + """ + Make flow for constructing the dataset, fitting the potentials and performing the benchmarks. + + Parameters + ---------- + structure_list: + List of pymatgen structures. + mp_ids: + Materials Project IDs. + dft_references: list[PhononBSDOSDoc] | None + List of DFT reference files containing the PhononBSDOCDoc object. + Reference files have to refer to a finite displacement of 0.01. + For benchmarking, only 0.01 is supported + benchmark_structures: list[Structure] | None + The pymatgen structure for benchmarking. + benchmark_mp_ids: list[str] | None + Materials Project ID of the benchmarking structure. + pre_xyz_files: list[str] or None + Names of the pre-database train xyz file and test xyz file. + pre_database_dir: str or None + The pre-database directory. + rattle_seed: int | None + Random seed for structure generation. + fit_kwargs_list : list[dict]. + Dict including MLIP fit keyword args. + + """ + self.structure_list = structure_list + self.mp_ids = mp_ids + if rattle_seed is None: + rattle_seed = 42 + + flows = [] + fit_input = {} + bm_outputs = [] + + default_hyperparameters = load_mlip_hyperparameter_defaults( + mlip_fit_parameter_file_path=self.path_to_hyperparameters + ) + + soap_default_dict = next( + ( + { + key: value + for key, value in fit_kwargs["soap"].items() + if key in ["n_sparse", "delta"] + } + for fit_kwargs in (fit_kwargs_list or []) + if "soap" in fit_kwargs + ), + default_hyperparameters["GAP"]["soap"], + ) + + for structure, mp_id in zip(structure_list, mp_ids): + self.supercell_settings.setdefault(mp_id, {}) + logging.warning( + "Currently, " + "the same supercell settings for single-atom displaced and rattled supercells are used." + ) + supercell_matrix_job = reduce_supercell_size_job( + structure=structure, + min_length=self.supercell_settings.get("min_length", 15), + max_length=self.supercell_settings.get("max_length", 20), + fallback_min_length=self.supercell_settings.get( + "fallback_min_length", 12 + ), + max_atoms=self.supercell_settings.get("max_atoms", 500), + min_atoms=self.supercell_settings.get("min_atoms", 50), + step_size=self.supercell_settings.get("step_size", 1.0), + ) + flows.append(supercell_matrix_job) + self.supercell_settings[mp_id][ + "supercell_matrix" + ] = supercell_matrix_job.output + + # TODO: add a separate optimization here if add_dft_rattld_structu or add_dft_phonon_struct is activated + # TODO: then forward the optimized structures to the next step + + if self.add_dft_rattled_struct: + add_dft_ratt = self.add_dft_rattled( + structure=structure, + mp_id=mp_id, + displacement_maker=self.displacement_maker, + rattled_bulk_relax_maker=self.rattled_bulk_relax_maker, + n_structures=self.n_structures, + uc=self.uc, + volume_custom_scale_factors=self.volume_custom_scale_factors, + volume_scale_factor_range=self.volume_scale_factor_range, + rattle_std=self.rattle_std, + distort_type=self.distort_type, + min_distance=self.min_distance, + rattle_type=self.rattle_type, + rattle_seed=rattle_seed, + rattle_mc_n_iter=self.rattle_mc_n_iter, + angle_max_attempts=self.angle_max_attempts, + angle_percentage_scale=self.angle_percentage_scale, + w_angle=self.w_angle, + supercell_settings=self.supercell_settings, + ) + add_dft_ratt.append_name(f"_{mp_id}") + flows.append(add_dft_ratt) + if self.volume_custom_scale_factors is not None: + rattle_seed = rattle_seed + len(self.volume_custom_scale_factors) + elif self.n_structures is not None: + rattle_seed = rattle_seed + self.n_structures + fit_input.update({mp_id: add_dft_ratt.output}) + if self.add_dft_phonon_struct: + add_dft_phon = self.add_dft_phonons( + structure=structure, + mp_id=mp_id, + displacements=self.displacements, + symprec=self.symprec, + phonon_bulk_relax_maker=self.phonon_bulk_relax_maker, + phonon_static_energy_maker=self.phonon_static_energy_maker, + phonon_displacement_maker=self.displacement_maker, + supercell_settings=self.supercell_settings, + ) + flows.append(add_dft_phon) + add_dft_phon.append_name(f"_{mp_id}") + fit_input.update({mp_id: add_dft_phon.output}) + if self.add_dft_rattled_struct and self.add_dft_phonon_struct: + fit_input.update( + { + mp_id: { + "rattled_dir": add_dft_ratt.output["rattled_dir"], + "phonon_dir": add_dft_phon.output["phonon_dir"], + "phonon_data": add_dft_phon.output["phonon_data"], + } + } + ) + if self.add_rss_struct: + raise NotImplementedError + + isoatoms = get_iso_atom(structure_list, self.isolated_atom_maker) + flows.append(isoatoms) + + if pre_xyz_files is None: + fit_input.update( + {"IsolatedAtom": {"iso_atoms_dir": [isoatoms.output["dirs"]]}} + ) + + for ml_model, fit_kwargs in zip(self.ml_models, fit_kwargs_list or [{}]): + add_data_fit = MLIPFitMaker( + mlip_type=ml_model, + glue_xml=self.glue_xml, + glue_file_path=self.glue_file_path, + use_defaults=self.use_defaults_fitting, + split_ratio=self.split_ratio, + force_max=self.force_max, + pre_xyz_files=pre_xyz_files, + pre_database_dir=pre_database_dir, + path_to_hyperparameters=self.path_to_hyperparameters, + atomwise_regularization_parameter=self.atomwise_regularization_parameter, + force_min=self.force_min, + atom_wise_regularization=self.atom_wise_regularization, + auto_delta=self.auto_delta, + apply_data_preprocessing=self.apply_data_preprocessing, + num_processes_fit=self.num_processes_fit, + separated=self.separated, + regularization=self.regularization, + distillation=self.distillation, + ).make( + species_list=isoatoms.output["species"], + isolated_atom_energies=isoatoms.output["energies"], + fit_input=fit_input, + **fit_kwargs, + ) + flows.append(add_data_fit) + + if (benchmark_structures is not None) and (benchmark_mp_ids is not None): + dft_new_references = [] + for ibenchmark_structure, benchmark_structure in enumerate( + benchmark_structures + ): + # hard coded at the moment as other displacements + # are not treated correctly in benchmark part + complete_bm = complete_benchmark( + ibenchmark_structure=ibenchmark_structure, + benchmark_structure=benchmark_structure, + ml_model=ml_model, + ml_path=add_data_fit.output["mlip_path"], + mp_ids=mp_ids, + benchmark_mp_ids=benchmark_mp_ids, + add_dft_phonon_struct=self.add_dft_phonon_struct, + fit_input=fit_input, + symprec=self.symprec, + phonon_bulk_relax_maker=self.phonon_bulk_relax_maker, + phonon_static_energy_maker=self.phonon_static_energy_maker, + phonon_displacement_maker=self.displacement_maker, + dft_references=dft_references, + supercell_settings=self.supercell_settings, + displacement=0.01, + atomwise_regularization_parameter=self.atomwise_regularization_parameter, + soap_dict=soap_default_dict, + **self.benchmark_kwargs, + ) + complete_bm.append_name( + f"_{benchmark_mp_ids[ibenchmark_structure]}" + ) + flows.append(complete_bm) + bm_outputs.append(complete_bm.output["bm_output"]) + dft_new_references.append(complete_bm.output["dft_references"]) + + if self.hyper_para_loop: + if self.atomwise_regularization_list is None: + self.atomwise_regularization_list = [0.1, 0.01, 0.001, 0.0001] + if self.soap_delta_list is None: + self.soap_delta_list = [0.5, 1.0, 1.5] + if self.n_sparse_list is None: + self.n_sparse_list = [ + 1000, + 2000, + 3000, + 4000, + 5000, + 6000, + 7000, + 8000, + 9000, + ] + for atomwise_reg_parameter in self.atomwise_regularization_list: + for n_sparse in self.n_sparse_list: + for delta in self.soap_delta_list: + soap_dict = { + "n_sparse": n_sparse, + "delta": delta, + } + loop_data_fit = MLIPFitMaker( + mlip_type=ml_model, + glue_xml=self.glue_xml, + glue_file_path=self.glue_file_path, + split_ratio=self.split_ratio, + force_max=self.force_max, + pre_xyz_files=pre_xyz_files, + pre_database_dir=pre_database_dir, + path_to_hyperparameters=self.path_to_hyperparameters, + atomwise_regularization_parameter=atomwise_reg_parameter, + force_min=self.force_min, + auto_delta=self.auto_delta, + num_processes_fit=self.num_processes_fit, + separated=self.separated, + regularization=self.regularization, + distillation=self.distillation, + ).make( + species_list=isoatoms.output["species"], + isolated_atom_energies=isoatoms.output["energies"], + fit_input=fit_input, + soap=soap_dict, + ) + flows.append(loop_data_fit) + + if (benchmark_structures is not None) and ( + benchmark_mp_ids is not None + ): + dft_new_references = [] + for ( + ibenchmark_structure, + benchmark_structure, + ) in enumerate(benchmark_structures): + + complete_bm = complete_benchmark( + ibenchmark_structure=ibenchmark_structure, + benchmark_structure=benchmark_structure, + ml_model=ml_model, + ml_path=loop_data_fit.output["mlip_path"], + mp_ids=mp_ids, + benchmark_mp_ids=benchmark_mp_ids, + add_dft_phonon_struct=self.add_dft_phonon_struct, + fit_input=fit_input, + symprec=self.symprec, + phonon_bulk_relax_maker=self.phonon_bulk_relax_maker, + phonon_static_energy_maker=self.phonon_static_energy_maker, + phonon_displacement_maker=self.displacement_maker, + dft_references=dft_references, + supercell_settings=self.supercell_settings, + displacement=0.01, + atomwise_regularization_parameter=atomwise_reg_parameter, + soap_dict=soap_dict, + **self.benchmark_kwargs, + ) + complete_bm.append_name( + f"_{benchmark_mp_ids[ibenchmark_structure]}" + ) + flows.append(complete_bm) + bm_outputs.append(complete_bm.output["bm_output"]) + # save the dft references okay + + dft_new_references.append( + complete_bm.output["dft_references"] + ) + + collect_bm = write_benchmark_metrics( + benchmark_structures=benchmark_structures, + metrics=bm_outputs, + filename_prefix=self.summary_filename_prefix, + ) + # collect_bm must be extended in a way that we get access to all benchmark structures and the previous databases + + flows.append(collect_bm) + + output_flow = get_phonon_output( + metrics=collect_bm.output, + benchmark_structures=benchmark_structures, + benchmark_mp_ids=benchmark_mp_ids, + dft_references=dft_new_references, + pre_xyz_files=pre_xyz_files, + pre_database_dir=add_data_fit.output["database_dir"], + fit_kwargs_list=fit_kwargs_list, + ) + flows.append(output_flow) + return Flow(jobs=flows, output=output_flow.output, name=self.name)
+ + +
+[docs] + @staticmethod + def add_dft_phonons( + structure: Structure, + mp_id: str, + displacements: list[float], + symprec: float, + phonon_bulk_relax_maker: BaseVaspMaker, + phonon_static_energy_maker: BaseVaspMaker, + phonon_displacement_maker: BaseVaspMaker, + supercell_settings: dict, + ): + """Add DFT phonon runs for reference structures. + + Parameters + ---------- + structure: Structure + The pymatgen Structure object + mp_id: str + Materials Project ID + displacements: list[float] + Displacement distance for phonons + symprec: float + Symmetry precision to use in the + reduction of symmetry to find the primitive/conventional cell + (use_primitive_standard_structure, use_conventional_standard_structure) + and to handle all symmetry-related tasks in phonopy + phonon_displacement_maker: BaseVaspMaker + Maker used to compute the forces for a supercell. + phonon_bulk_relax_maker: BaseVaspMaker + Maker used for the bulk relax unit cell calculation. + phonon_static_energy_maker: BaseVaspMaker + Maker used for the static energy unit cell calculation. + supercell_settings: dict + Supercell settings + + """ + dft_phonons = dft_phonopy_gen_data( + structure=structure, + mp_id=mp_id, + displacements=displacements, + symprec=symprec, + phonon_bulk_relax_maker=phonon_bulk_relax_maker, + phonon_static_energy_maker=phonon_static_energy_maker, + phonon_displacement_maker=phonon_displacement_maker, + supercell_settings=supercell_settings, + ) + # let's append a name + dft_phonons.name = "single-atom displaced supercells" + return dft_phonons
+ + +
+[docs] + @staticmethod + def add_dft_rattled( + structure: Structure, + mp_id: str, + rattled_bulk_relax_maker: BaseVaspMaker, + displacement_maker: BaseVaspMaker, + uc: bool = False, + volume_custom_scale_factors: list[float] | None = None, + volume_scale_factor_range: list[float] | None = None, + rattle_std: float = 0.01, + distort_type: int = 0, + n_structures: int = 10, + min_distance: float = 1.5, + angle_percentage_scale: float = 10, + angle_max_attempts: int = 1000, + rattle_type: int = 0, + rattle_seed: int = 42, + rattle_mc_n_iter: int = 10, + w_angle: list[float] | None = None, + supercell_settings: dict | None = None, + ): + """Add DFT static runs for randomly displaced structures. + + Parameters + ---------- + structure: Structure + The pymatgen Structure object + mp_id: str + Materials Project ID + displacement_maker: BaseVaspMaker + Maker used for a static calculation for a supercell. + rattled_bulk_relax_maker: BaseVaspMaker + Maker used for the bulk relax unit cell calculation. + uc: bool. + If True, will generate randomly distorted structures (unitcells) + and add static computation jobs to the flow. + distort_type : int. + 0- volume distortion, 1- angle distortion, 2- volume and angle distortion. Default=0. + n_structures : int. + Total number of distorted structures to be generated. + Must be provided if distorting volume without specifying a range, or if distorting angles. + Default=10. + volume_scale_factor_range : list[float] + [min, max] of volume scale factors. + e.g. [0.90, 1.10] will distort volume +-10%. + volume_custom_scale_factors : list[float] + Specify explicit scale factors (if range is not specified). + If None, will default to [0.90, 0.95, 0.98, 0.99, 1.01, 1.02, 1.05, 1.10]. + min_distance: float + Minimum separation allowed between any two atoms. + Default= 1.5A. + angle_percentage_scale: float + Angle scaling factor. + Default= 10 will randomly distort angles by +-10% of original value. + angle_max_attempts: int. + Maximum number of attempts to distort structure before aborting. + Default=1000. + w_angle: list[float] + List of angle indices to be changed i.e. 0=alpha, 1=beta, 2=gamma. + Default= [0, 1, 2]. + rattle_type: int. + 0- standard rattling, 1- Monte-Carlo rattling. Default=0. + rattle_std: float. + Rattle amplitude (standard deviation in normal distribution). + Default=0.01. + Note that for MC rattling, displacements generated will roughly be + rattle_mc_n_iter**0.5 * rattle_std for small values of n_iter. + rattle_seed: int. + Seed for setting up NumPy random state from which random numbers are generated. + Default=42. + rattle_mc_n_iter: int. + Number of Monte Carlo iterations. + Larger number of iterations will generate larger displacements. + Default=10. + supercell_settings: dict + Settings for supercells + """ + additonal_dft_random = dft_random_gen_data( + structure=structure, + mp_id=mp_id, + rattled_bulk_relax_maker=rattled_bulk_relax_maker, + displacement_maker=displacement_maker, + n_structures=n_structures, + uc=uc, + volume_custom_scale_factors=volume_custom_scale_factors, + volume_scale_factor_range=volume_scale_factor_range, + rattle_std=rattle_std, + distort_type=distort_type, + rattle_seed=rattle_seed, + rattle_mc_n_iter=rattle_mc_n_iter, + rattle_type=rattle_type, + angle_max_attempts=angle_max_attempts, + angle_percentage_scale=angle_percentage_scale, + w_angle=w_angle, + min_distance=min_distance, + supercell_settings=supercell_settings, + ) + additonal_dft_random.name = "rattled supercells" + return additonal_dft_random
+
+ + + +
+[docs] +@dataclass +class CompleteDFTvsMLBenchmarkWorkflowMPSettings(CompleteDFTvsMLBenchmarkWorkflow): + """ + Maker to construct a DFT (VASP) based dataset, composed of the following two configuration types. + + (1) single atom displaced supercells (based on the atomate2 PhononMaker subroutines) + (2) supercells with randomly displaced atoms (based on the ase rattled function). + + Machine-learned interatomic potential(s) are then fitted on the dataset, followed by + benchmarking the resulting potential(s) to DFT (VASP) level using the provided benchmark + structure(s) and comparing the respective DFT and MLIP-based Phonon calculations. + The benchmark metrics are provided in form of a phonon band structure comparison and + q-point-wise phonons RMSE plots, as well as a summary text file. + + Parameters + ---------- + name : str + Name of the flow produced by this maker. + add_dft_phonon_struct: bool. + If True, will add displaced supercells via phonopy for DFT calculation. + add_dft_rattled_struct: bool. + If True, will add randomly distorted structures for DFT calculation. + add_rss_struct: bool. + If True, will add RSS generated structures for DFT calculation. + n_structures: int. + The total number of randomly displaced structures to be generated. + displacement_maker: BaseVaspMaker + Maker used for a static calculation for a supercell. + phonon_bulk_relax_maker: BaseVaspMaker + Maker used for the bulk relax unit cell calculation. + rattled_bulk_relax_maker: BaseVaspMaker + Maker used for the bulk relax unit cell calculation. + phonon_static_energy_maker: BaseVaspMaker + Maker used for the static energy unit cell calculation. + isolated_atom_maker: IsoAtomStaticMaker + VASP maker for the isolated atom calculation. + n_structures : int. + Total number of distorted structures to be generated. + Must be provided if distorting volume without specifying a range, or if distorting angles. + Default=10. + displacements: list[float] + Displacement distances for phonons + symprec: float + Symmetry precision to use in the + reduction of symmetry to find the primitive/conventional cell + (use_primitive_standard_structure, use_conventional_standard_structure) + and to handle all symmetry-related tasks in phonopy. + uc: bool. + If True, will generate randomly distorted structures (unitcells) + and add static computation jobs to the flow. + distort_type : int. + 0- volume distortion, 1- angle distortion, 2- volume and angle distortion. Default=0. + volume_scale_factor_range : list[float] + [min, max] of volume scale factors. + e.g. [0.90, 1.10] will distort volume +-10%. + volume_custom_scale_factors : list[float] + Specify explicit scale factors (if range is not specified). + If None, will default to [0.90, 0.95, 0.98, 0.99, 1.01, 1.02, 1.05, 1.10]. + min_distance: float + Minimum separation allowed between any two atoms. + Default= 1.5A. + angle_percentage_scale: float + Angle scaling factor. + Default= 10 will randomly distort angles by +-10% of original value. + angle_max_attempts: int. + Maximum number of attempts to distort structure before aborting. + Default=1000. + w_angle: list[float] + List of angle indices to be changed i.e. 0=alpha, 1=beta, 2=gamma. + Default= [0, 1, 2]. + rattle_type: int. + 0- standard rattling, 1- Monte-Carlo rattling. Default=0. + rattle_std: float. + Rattle amplitude (standard deviation in normal distribution). + Default=0.01. + Note that for MC rattling, displacements generated will roughly be + rattle_mc_n_iter**0.5 * rattle_std for small values of n_iter. + rattle_seed: int. + Seed for setting up NumPy random state from which random numbers are generated. + Default=42. + rattle_mc_n_iter: int. + Number of Monte Carlo iterations. + Larger number of iterations will generate larger displacements. + Default=10. + ml_models: list[str] + List of the ML models to be used. Default is GAP. + force_max: float + Maximum allowed force in the dataset. + force_min: float + Minimal force cutoff value for atom-wise regularization. + split_ratio: float. + Parameter to divide the training set and the test set. + A value of 0.1 means that the ratio of the training set to the test set is 9:1. + apply_data_preprocessing: bool + Apply data preprocessing. + atomwise_regularization_parameter: float + Regularization value for the atom-wise force components. + atom_wise_regularization: bool + For including atom-wise regularization. + auto_delta: bool + Automatically determines delta for 2b, 3b and soap terms. + hyper_para_loop: bool + If true, performs several fits using the provided hyperparameter sets. + atomwise_regularization_list: list + List of atom-wise regularization parameters that are checked. + soap_delta_list: list + List of SOAP delta values that are checked. + n_sparse_list: list + List of GAP n_sparse values that are checked. + supercell_settings: dict + Settings for supercell generation + benchmark_kwargs: dict + Keyword arguments for the benchmark flows + summary_filename_prefix: str + Prefix of the result summary file. + glue_file_path: str + Name of the glue.xml file path. + use_defaults_fitting: bool + Use the fit defaults. + """ + + phonon_bulk_relax_maker: BaseVaspMaker = field( + default_factory=lambda: MPGGADoubleRelaxMaker.from_relax_maker( + MPGGARelaxMaker( + run_vasp_kwargs={"handlers": ()}, + input_set_generator=MPRelaxSet( + force_gamma=True, + auto_metal_kpoints=True, + inherit_incar=False, + user_incar_settings={ + "NPAR": 4, + "EDIFF": 1e-7, + "EDIFFG": 1e-6, + "ALGO": "NORMAL", + "ISPIN": 1, + "LREAL": False, + "LCHARG": False, + "ISMEAR": 0, + "KSPACING": 0.2, + }, + ), + ) + ) + ) + rattled_bulk_relax_maker: BaseVaspMaker = field( + default_factory=lambda: MPGGADoubleRelaxMaker.from_relax_maker( + MPGGARelaxMaker( + run_vasp_kwargs={"handlers": ()}, + input_set_generator=MPRelaxSet( + force_gamma=True, + auto_metal_kpoints=True, + inherit_incar=False, + user_incar_settings={ + "NPAR": 4, + "EDIFF": 1e-7, + "EDIFFG": 1e-6, + "ALGO": "NORMAL", + "ISPIN": 1, + "LREAL": False, + "LCHARG": False, + "ISMEAR": 0, + "KSPACING": 0.2, + }, + ), + ) + ) + ) + displacement_maker: BaseVaspMaker = field( + default_factory=lambda: MPGGAStaticMaker( + run_vasp_kwargs={"handlers": ()}, + name="dft phonon static", + input_set_generator=MPStaticSet( + force_gamma=True, + auto_metal_kpoints=True, + inherit_incar=False, + user_incar_settings={ + "NPAR": 4, + "EDIFF": 1e-7, + "EDIFFG": 1e-6, + "ALGO": "NORMAL", + "ISPIN": 1, + "LREAL": False, + "LCHARG": False, + "ISMEAR": 0, + "KSPACING": 0.2, + }, + ), + ) + ) + isolated_atom_maker: BaseVaspMaker = field( + default_factory=lambda: MPGGAStaticMaker( + run_vasp_kwargs={"handlers": ()}, + input_set_generator=MPStaticSet( + user_kpoints_settings={"reciprocal_density": 1}, + force_gamma=True, + auto_metal_kpoints=True, + inherit_incar=False, + user_incar_settings={ + "NPAR": 4, + "EDIFF": 1e-7, + "EDIFFG": 1e-6, + "ALGO": "NORMAL", + "ISPIN": 1, + "LREAL": False, + "LCHARG": False, + "ISMEAR": 0, + }, + ), + ) + ) + + phonon_static_energy_maker: BaseVaspMaker = field( + default_factory=lambda: MPGGAStaticMaker( + run_vasp_kwargs={"handlers": ()}, + name="dft phonon static", + input_set_generator=MPStaticSet( + force_gamma=True, + auto_metal_kpoints=True, + inherit_incar=False, + user_incar_settings={ + "NPAR": 4, + "EDIFF": 1e-7, + "EDIFFG": 1e-6, + "ALGO": "NORMAL", + "ISPIN": 1, + "LREAL": False, + "LCHARG": False, + "ISMEAR": 0, + "KSPACING": 0.2, + }, + ), + ) + )
+ + + +
+[docs] +@dataclass +class DFTSupercellSettingsMaker(Maker): + """ + Maker to test the DFT and supercell settings. + + This maker is used to test your queue settings for the rattled and phonon supercells. + Although the cells are not displaced, it provides an impression of the required memory + and other resources as the process runs without symmetry considerations. + + Parameters + ---------- + name (str): The name of the maker. Default is "test dft and supercell settings". + supercell_settings (dict): Settings for the supercells. Default is {"min_length": 15}. + DFT_Maker (BaseVaspMaker): The DFT maker to be used. Default is TightDFTStaticMaker. + + """ + + name: str = "test dft and supercell settings" + supercell_settings: dict = field(default_factory=lambda: {"min_length": 15}) + DFT_Maker: BaseVaspMaker = field(default_factory=TightDFTStaticMaker) + +
+[docs] + def make(self, structure_list: list[Structure], mp_ids: list[str]) -> Flow: + """ + Generate and runs supercell jobs for the given list of structures. + + Args: + structure_list (list[Structure]): List of structures to process. + mp_ids (list[str]): List of MP IDs. + + Returns + ------- + Flow: A Flow object containing the jobs and their output. + """ + job_list = [] + + # Modify to run for more than one cell + supercell_job = generate_supercells(structure_list, self.supercell_settings) + job_list.append(supercell_job) + + supercell_job = run_supercells( + structure_list, supercell_job.output, mp_ids, self.DFT_Maker + ) + job_list.append(supercell_job) + + return Flow(jobs=job_list, output=supercell_job.output, name=self.name)
+
+ + + +
+[docs] +@dataclass +class IterativeCompleteDFTvsMLBenchmarkWorkflow: + """ + Iterative Version of CompleteDFTvsMLBenchmarkWorkflow. + + Maker to run CompleteDFTvsMLBenchmarkWorkflow in an iterative + fashion to ensure convergence of the potentials. + + Parameters + ---------- + name : str + Name of the flow produced by this maker. + max_iterations: int. + Maximum number of iterations to run. + rms_max: float. + Will stop once the best potential has a max rmse below this value. + The RMSE value is in THz. + complete_dft_vs_ml_benchmark_workflow_0: CompleteDFTvsMLBenchmarkWorkflow. + First Iteration will be performed with this flow. + complete_dft_vs_ml_benchmark_workflow_1: CompleteDFTvsMLBenchmarkWorkflow. + All Iterations after the first one will be performed with this flow. + """ + + name: str = "IterativeCompleteDFTvsMLBenchmarkWorkflow" + max_iterations: int = 10 + rms_max: float = 0.2 + complete_dft_vs_ml_benchmark_workflow_0: CompleteDFTvsMLBenchmarkWorkflow | None = ( + field(default_factory=CompleteDFTvsMLBenchmarkWorkflow) + ) + complete_dft_vs_ml_benchmark_workflow_1: CompleteDFTvsMLBenchmarkWorkflow | None = ( + field( + default_factory=lambda: CompleteDFTvsMLBenchmarkWorkflow( + add_dft_phonon_struct=False + ) + ) + ) + +
+[docs] + def make( + self, + structure_list: list[Structure], + mp_ids: list[str] | None = None, + dft_references: list[PhononBSDOSDoc] | None = None, + benchmark_structures: list[Structure] | None = None, + benchmark_mp_ids: list[str] | None = None, + pre_database_dir: str | None = None, + pre_xyz_files: list[str] | None = None, + rattle_seed: int = 0, + fit_kwargs_list: list | None = None, + ) -> Flow: + """Make flow for constructing the dataset, fitting the potentials and performing the benchmarks. + + Parameters + ---------- + structure_list: + List of pymatgen structures. + mp_ids: + Materials Project IDs. + dft_references: list[PhononBSDOSDoc] | None + List of DFT reference files containing the PhononBSDOCDoc object. + Reference files have to refer to a finite displacement of 0.01. + For benchmarking, only 0.01 is supported + benchmark_structures: list[Structure] | None + The pymatgen structure for benchmarking. + benchmark_mp_ids: list[str] | None + Materials Project ID of the benchmarking structure. + pre_xyz_files: list[str] or None + Names of the pre-database train xyz file and test xyz file. + pre_database_dir: str or None + The pre-database directory. + rattle_seed: int | None + Random seed. + fit_kwargs_list : list[dict]. + Dict including MLIP fit keyword args. + + """ + flow = do_iterative_rattled_structures( + workflow_maker_gen_0=self.complete_dft_vs_ml_benchmark_workflow_0, + workflow_maker_gen_1=self.complete_dft_vs_ml_benchmark_workflow_1, + structure_list=structure_list, + mp_ids=mp_ids, + benchmark_structures=benchmark_structures, + benchmark_mp_ids=benchmark_mp_ids, + number_of_iteration=0, + rms=None, + max_iteration=self.max_iterations, + rms_max=self.rms_max, + rattle_seed=rattle_seed, + dft_references=dft_references, + fit_kwargs_list=fit_kwargs_list, + pre_database_dir=pre_database_dir, + pre_xyz_files=pre_xyz_files, + previous_output=None, + ) + return Flow(flow, flow.output)
+ + + def __post_init__(self) -> None: + """Test settings during the initialisation.""" + if self.complete_dft_vs_ml_benchmark_workflow_1.add_dft_phonon_struct: + warnings.warn( + "Phonon Data is generated in the second iteration. This will likely" + "waste resources. It is recommended to switch this off.", + stacklevel=2, + )
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/autoplex/auto/phonons/jobs.html b/_modules/autoplex/auto/phonons/jobs.html new file mode 100644 index 000000000..c0e04db3b --- /dev/null +++ b/_modules/autoplex/auto/phonons/jobs.html @@ -0,0 +1,1357 @@ + + + + + + + + + + autoplex.auto.phonons.jobs — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+
+
+ + + + +
+ +

Source code for autoplex.auto.phonons.jobs

+"""General AutoPLEX automation jobs."""
+
+from collections.abc import Iterable
+from dataclasses import field
+from pathlib import Path
+
+import numpy as np
+from atomate2.common.schemas.phonons import ForceConstants, PhononBSDOSDoc
+from atomate2.vasp.flows.core import DoubleRelaxMaker
+from atomate2.vasp.jobs.base import BaseVaspMaker
+from atomate2.vasp.jobs.core import StaticMaker, TightRelaxMaker
+from atomate2.vasp.sets.core import StaticSetGenerator, TightRelaxSetGenerator
+from jobflow import Flow, Response, job
+from pymatgen.core.structure import Structure
+from pymatgen.phonon.bandstructure import PhononBandStructure
+from pymatgen.phonon.dos import PhononDos
+
+from autoplex.benchmark.phonons.flows import PhononBenchmarkMaker
+from autoplex.data.phonons.flows import (
+    DFTPhononMaker,
+    IsoAtomMaker,
+    IsoAtomStaticMaker,
+    MLPhononMaker,
+    RandomStructuresDataGenerator,
+    TightDFTStaticMaker,
+)
+from autoplex.data.phonons.jobs import reduce_supercell_size
+
+
+
+[docs] +@job( + data=[ + PhononBSDOSDoc, + "dft_references", + "metrics", + "benchmark_structures", + PhononDos, + PhononBandStructure, + ForceConstants, + Structure, + ] +) +def do_iterative_rattled_structures( + workflow_maker_gen_0, + workflow_maker_gen_1, + structure_list: list[Structure], + mp_ids: list[str], + dft_references: list[PhononBSDOSDoc] | None = None, + benchmark_structures: list[Structure] | None = None, + benchmark_mp_ids: list[str] | None = None, + pre_xyz_files: list[str] | None = None, + pre_database_dir: str | None = None, + rattle_seed: int | None = None, + fit_kwargs_list: list | None = None, + number_of_iteration=0, + rms=0.2, + max_iteration=5, + rms_max=0.2, + previous_output=None, +): + """ + Job to run CompleteDFTvsMLBenchmarkWorkflow in an iterative manner. + + Parameters + ---------- + workflow_maker_gen_0: CompleteDFTvsMLBenchmarkWorkflow. + First Iteration will be performed with this flow. + workflow_maker_gen_1: CompleteDFTvsMLBenchmarkWorkflow. + All Iterations after the first one will be performed with this flow. + structure_list: + List of pymatgen structures. + mp_ids: + Materials Project IDs. + dft_references: list[PhononBSDOSDoc] | None + List of DFT reference files containing the PhononBSDOCDoc object. + Reference files have to refer to a finite displacement of 0.01. + For benchmarking, only 0.01 is supported + benchmark_structures: list[Structure] | None + The pymatgen structure for benchmarking. + benchmark_mp_ids: list[str] | None + Materials Project ID of the benchmarking structure. + pre_xyz_files: list[str] or None + Names of the pre-database train xyz file and test xyz file. + pre_database_dir: str or None + The pre-database directory. + rattle_seed: int | None + Random seed. + fit_kwargs_list : list[dict]. + Dict including MLIP fit keyword args. + number_of_iteration: int + Number of iterations. + rms: float + current maximum rms value + max_iteration: int. + Maximum number of iterations to run. + rms_max: float. + Will stop once the best potential has a max rmse below this value. + previous_output: dict | None. + Dict including the output of the previous flow. + """ + if rms is None or (number_of_iteration < max_iteration and rms > rms_max): + jobs = [] + + if number_of_iteration == 0: + workflow_maker = workflow_maker_gen_0 + job1 = workflow_maker_gen_0.make( + structure_list=structure_list, + mp_ids=mp_ids, + dft_references=dft_references, + benchmark_structures=benchmark_structures, + benchmark_mp_ids=benchmark_mp_ids, + pre_xyz_files=pre_xyz_files, + pre_database_dir=pre_database_dir, + rattle_seed=rattle_seed, + fit_kwargs_list=fit_kwargs_list, + ) + else: + workflow_maker = workflow_maker_gen_1 + job1 = workflow_maker_gen_1.make( + structure_list=structure_list, + mp_ids=mp_ids, + dft_references=dft_references, + benchmark_structures=benchmark_structures, + benchmark_mp_ids=benchmark_mp_ids, + pre_xyz_files=pre_xyz_files, + pre_database_dir=pre_database_dir, + rattle_seed=rattle_seed, + fit_kwargs_list=fit_kwargs_list, + ) + + # rms needs to be computed somehow + job1.append_name("_" + str(number_of_iteration)) + jobs.append(job1) + # order is the same as in the scaling "scale_cells" + if workflow_maker.volume_custom_scale_factors is not None: + rattle_seed = rattle_seed + ( + len(workflow_maker.volume_custom_scale_factors) + * len(workflow_maker.structure_list) + ) + elif workflow_maker.n_structures is not None: + rattle_seed = rattle_seed + (workflow_maker.n_structures) * len( + workflow_maker.structure_list + ) + + job2 = do_iterative_rattled_structures( + workflow_maker_gen_0=workflow_maker_gen_0, + workflow_maker_gen_1=workflow_maker_gen_1, + structure_list=structure_list, + mp_ids=mp_ids, + dft_references=job1.output["dft_references"], + benchmark_structures=job1.output["benchmark_structures"], + benchmark_mp_ids=job1.output["benchmark_mp_ids"], + pre_xyz_files=job1.output["pre_xyz_files"], + pre_database_dir=job1.output["pre_database_dir"], + rattle_seed=rattle_seed, + fit_kwargs_list=fit_kwargs_list, + number_of_iteration=number_of_iteration + 1, + rms=job1.output["rms"], + max_iteration=max_iteration, + rms_max=rms_max, + previous_output=job1.output, + ) + jobs.append(job2) + # recreate the output to make sure all is correctly put into data: + output_dict = { + "dft_references": job2.output["dft_references"], + "benchmark_structures": job2.output["benchmark_structures"], + "benchmark_mp_ids": job2.output["benchmark_mp_ids"], + "pre_xyz_files": job2.output["pre_xyz_files"], + "pre_database_dir": job2.output["pre_database_dir"], + "rms": job2.output["rms"], + "metrics": job2.output["metrics"], + "fit_kwargs_list": job2.output["fit_kwargs_list"], + } + + return Response(replace=Flow(jobs), output=output_dict) + + return { + "dft_references": previous_output["dft_references"], + "benchmark_structures": previous_output["benchmark_structures"], + "benchmark_mp_ids": previous_output["benchmark_mp_ids"], + "pre_xyz_files": previous_output["pre_xyz_files"], + "pre_database_dir": previous_output["pre_database_dir"], + "rms": previous_output["rms"], + "metrics": previous_output["metrics"], + "fit_kwargs_list": previous_output["fit_kwargs_list"], + }
+ + + +
+[docs] +@job(data=[PhononBSDOSDoc]) +def complete_benchmark( # this function was put here to prevent circular import + ml_path: list, + ml_model: str, + ibenchmark_structure: int, + benchmark_structure: Structure, + mp_ids, + benchmark_mp_ids, # list[str] mypy: Value of type "list[Any] | None" is not indexable + add_dft_phonon_struct: bool, + fit_input, + symprec, + phonon_bulk_relax_maker: BaseVaspMaker, + phonon_static_energy_maker: BaseVaspMaker, + phonon_displacement_maker: BaseVaspMaker, + atomwise_regularization_parameter: float, + dft_references=None, + soap_dict=None, + displacement: float = 0.01, + supercell_settings: dict | None = None, + relax_maker_kwargs: dict | None = None, + static_maker_kwargs: dict | None = None, + **ml_phonon_maker_kwargs, +): + """ + Construct a complete flow for benchmarking the MLIP fit quality using a DFT based phonon structure. + + The complete benchmark flow starts by calculating the MLIP based phonon structure for each structure that has to be + benchmarked. + Then, depending on if the user provided a DFT reference dataset or the DFT reference structure is + already given from a previous loop, the existing or to-be-calculated DFT reference is used to generate the phonon + bandstructure comparison plots, the q-point wise RMSE plots and to calculate the overall RMSE. + This process is repeated with the default ML potential as well as the potentials from the different active user + settings like sigma regularization, separated datasets or looping through several sets of hyperparameters. + + Parameters + ---------- + ml_path: str + Path to MLIP file. + Default is path to gap_file.xml + ml_model: str + ML model to be used. + Default is GAP. + ibenchmark_structure: int + The ith benchmark structure. + benchmark_structure: Structure + The pymatgen structure for benchmarking. + benchmark_mp_ids: list[str] + Materials Project ID of the benchmarking structure. + mp_ids: + Materials Project IDs. + add_dft_phonon_struct: bool. + If True, will add displaced supercells via phonopy for DFT calculation. + fit_input : dict. + CompletePhononDFTMLDataGenerationFlow output. + symprec: float + Symmetry precision to use in the + reduction of symmetry to find the primitive/conventional cell + (use_primitive_standard_structure, use_conventional_standard_structure) + and to handle all symmetry-related tasks in phonopy. + phonon_bulk_relax_maker: BaseVaspMaker + Maker used for the bulk relax unit cell calculation. + phonon_static_energy_maker: BaseVaspMaker + Maker used for the static energy unit cell calculation. + phonon_displacement_maker: BaseVaspMaker + Maker used to compute the forces for a supercell. + dft_references: + List of DFT reference files containing the PhononBSDOCDoc object. + Default None. + supercell_settings: dict + Settings for supercell generation + relax_maker_kwargs: dict + Keyword arguments that can be passed to the RelaxMaker. + static_maker_kwargs: dict + Keyword arguments that can be passed to the StaticMaker. + ml_phonon_maker_kwargs: dict + Keyword arguments that can be passed to the MLPhononMaker. + displacement: float + Displacement used in the finite displacement method. + atomwise_regularization_parameter: float + Regularization value for the atom-wise force components. + soap_dict: dict + Dictionary containing SOAP parameters. + + """ + jobs = [] + collect_output = [] + + for path in ml_path: + suffix = Path(path).name + print(suffix) + if suffix == "without_regularization": + suffix = "without_reg" + if suffix not in ["phonon", "rattled"]: + suffix = "" + + if phonon_displacement_maker is None: + phonon_displacement_maker = TightDFTStaticMaker(name="dft phonon static") + + if ml_model == "GAP": + ml_potential = ( + Path(path) / "gap_file.xml" + ) # TODO account for user-specific gap file name? + elif ml_model == "J-ACE": + raise UserWarning("No atomate2 ACE.jl PhononMaker implemented.") + elif ml_model in ["M3GNET"]: + ml_potential = Path( + path + ) # M3GNet requires path and fit already returns the path + # also need to find a different solution for separated fit then (name to path could be modified) + elif ml_model in ["NEQUIP"]: + ml_potential = Path(path) / "deployed_nequip_model.pth" + else: # MACE + # treat finetuned potentials + # TODO: fix this naming issue (depends on input) + ml_potential_fine = Path(path) / "MACE_final.model" + ml_potential = ( + ml_potential_fine + if ml_potential_fine.exists() + else Path(path) / "MACE_model.model" + ) + if Path(ml_potential).exists(): + add_data_ml_phonon = MLPhononMaker( + relax_maker_kwargs=relax_maker_kwargs, + static_maker_kwargs=static_maker_kwargs, + displacement=displacement, + ).make_from_ml_model( + structure=benchmark_structure, + ml_model=ml_model, + potential_file=ml_potential, + supercell_settings=supercell_settings, + **ml_phonon_maker_kwargs, + ) + jobs.append(add_data_ml_phonon) + + # DFT benchmark reference preparations + if dft_references is None and benchmark_mp_ids is not None: + # runs only the first time, then dft_references is added + if ( + benchmark_mp_ids[ibenchmark_structure] in mp_ids + ) and add_dft_phonon_struct: + + dft_references = fit_input[benchmark_mp_ids[ibenchmark_structure]][ + "phonon_data" + ][f"{int(displacement * 100):03d}"] + else: + dft_phonons = dft_phonopy_gen_data( + structure=benchmark_structure, + mp_id=benchmark_mp_ids[ibenchmark_structure], + displacements=[displacement], + symprec=symprec, + phonon_bulk_relax_maker=phonon_bulk_relax_maker, + phonon_static_energy_maker=phonon_static_energy_maker, + phonon_displacement_maker=phonon_displacement_maker, + supercell_settings=supercell_settings, + ) + jobs.append(dft_phonons) + dft_references = dft_phonons.output["phonon_data"][ + f"{int(displacement * 100):03d}" + ] + + add_data_bm = PhononBenchmarkMaker(name="Benchmark").make( + ml_model=ml_model, + structure=benchmark_structure, + benchmark_mp_id=benchmark_mp_ids[ibenchmark_structure], + ml_phonon_task_doc=add_data_ml_phonon.output, + dft_phonon_task_doc=dft_references, + displacement=displacement, + atomwise_regularization_parameter=atomwise_regularization_parameter, + soap_dict=soap_dict, + suffix=suffix, + ) + elif ( + dft_references is not None + and not isinstance(dft_references, list) + and benchmark_mp_ids is not None + ): + add_data_bm = PhononBenchmarkMaker(name="Benchmark").make( + # this is important for re-using the same internally calculated DFT reference + # for looping through several settings + ml_model=ml_model, + structure=benchmark_structure, + benchmark_mp_id=benchmark_mp_ids[ibenchmark_structure], + ml_phonon_task_doc=add_data_ml_phonon.output, + dft_phonon_task_doc=dft_references, + displacement=displacement, + atomwise_regularization_parameter=atomwise_regularization_parameter, + soap_dict=soap_dict, + suffix=suffix, + ) + else: + add_data_bm = PhononBenchmarkMaker(name="Benchmark").make( + # this is important for using a provided DFT reference + ml_model=ml_model, + structure=benchmark_structure, + benchmark_mp_id=benchmark_mp_ids[ibenchmark_structure], + ml_phonon_task_doc=add_data_ml_phonon.output, + dft_phonon_task_doc=dft_references[ibenchmark_structure], + displacement=displacement, + atomwise_regularization_parameter=atomwise_regularization_parameter, + soap_dict=soap_dict, + suffix=suffix, + ) + jobs.append(add_data_bm) + collect_output.append(add_data_bm.output) + + if isinstance(dft_references, list): + return Response( + replace=Flow(jobs), + output={ + "bm_output": collect_output, + "dft_references": dft_references[ibenchmark_structure], + }, + ) + return Response( + replace=Flow(jobs), + output={"bm_output": collect_output, "dft_references": dft_references}, + )
+ + + +
+[docs] +@job +def generate_supercells( + structures: list[Structure], + supercell_settings: dict, +) -> list[Iterable]: + """ + Run phonon displacements. + + Note, this job will replace itself with N displacement calculations, + or a single socket calculation for all displacements. + + Parameters + ---------- + structures : list[Structure] + List of supercells. + supercell_settings: dict + Settings for supercells. + + """ + return [ + reduce_supercell_size(structure, **supercell_settings) + for structure in structures + ]
+ + + +
+[docs] +@job +def run_supercells( + structures: list[Structure], + supercell_matrices: list[int], + mp_ids: list[str], + dft_maker: BaseVaspMaker = None, +) -> Response: + """ + Run supercell calculations. + + Note, this job will replace itself with supercell calculations. + + Parameters + ---------- + structures: list[Structure] + List of supercells. + supercell_matrices: list[int] + List of supercell matrices. + mp_ids: list[str] + List of Materials Project IDs. + dft_maker : .BaseVaspMaker or .ForceFieldStaticMaker or .BaseAimsMaker + Maker to use to generate dispacement calculations + """ + if dft_maker is None: + dft_maker = field(default_factory=TightDFTStaticMaker) + dft_jobs = [] + outputs: dict[str, list] = { + "uuids": [], + "dirs": [], + "mp-id": [], + } + + for structure, supercell_matrix, mp_id in zip( + structures, supercell_matrices, mp_ids + ): + structure.make_supercell(np.array(supercell_matrix).transpose()) + dft_job = dft_maker.make(structure=structure) + dft_jobs.append(dft_job) + outputs["uuids"].append(dft_job.output.uuid) + outputs["dirs"].append(dft_job.output.dir_name) + outputs["mp-id"].append(mp_id) + + displacement_flow = Flow(dft_jobs, outputs) + return Response(replace=displacement_flow)
+ + + +
+[docs] +@job(data=["phonon_data"]) +def dft_phonopy_gen_data( + structure: Structure, + mp_id: str, + displacements, + symprec, + phonon_bulk_relax_maker, + phonon_static_energy_maker, + phonon_displacement_maker, + supercell_settings, +): + """ + Job to generate DFT reference database using phonopy to be used for fitting ML potentials. + + Parameters + ---------- + structure: Structure + The pymatgen Structure object. + mp_id: str + Materials Project ID. + phonon_displacement_maker : .BaseVaspMaker or None + Maker used to compute the forces for a supercell. + phonon_bulk_relax_maker: BaseVaspMaker + Maker used for the bulk relax unit cell calculation. + phonon_static_energy_maker: BaseVaspMaker + Maker used for the static energy unit cell calculation. + displacements: list[float] + List of phonon displacement. + symprec : float + Symmetry precision to use in the + reduction of symmetry to find the primitive/conventional cell + (use_primitive_standard_structure, use_conventional_standard_structure) + and to handle all symmetry-related tasks in phonopy. + supercell_settings: + Settings for supercell generation. + """ + jobs = [] + dft_phonons_output = {} + dft_phonons_dir_output = [] + if supercell_settings is None: + supercell_settings = field( + default_factory=lambda: {"min_length": 15, "max_length": 20} + ) + supercell_matrix = supercell_settings.get(mp_id, {}).get("supercell_matrix") + if not supercell_matrix: + filtered_settings = { # mismatching mp_ids would lead to a key error + key: value + for key, value in supercell_settings.items() + if key + in [ + "min_length", + "max_length", + "fallback_min_length", + "max_atoms", + "min_atoms", + "step_size", + ] + } + supercell_matrix = reduce_supercell_size(structure, **filtered_settings) + + if phonon_displacement_maker is None: + phonon_displacement_maker = TightDFTStaticMaker(name="dft phonon static") + if phonon_bulk_relax_maker is None: + phonon_bulk_relax_maker = DoubleRelaxMaker.from_relax_maker( + TightRelaxMaker( + run_vasp_kwargs={"handlers": {}}, + input_set_generator=TightRelaxSetGenerator( + user_incar_settings={ + "ALGO": "Normal", + "ISPIN": 1, + "LAECHG": False, + "ISMEAR": 0, + "ENCUT": 700, + "ISYM": 0, + "SIGMA": 0.05, + "LCHARG": False, # Do not write the CHGCAR file + "LWAVE": False, # Do not write the WAVECAR file + "LVTOT": False, # Do not write LOCPOT file + "LORBIT": None, # No output of projected or partial DOS in EIGENVAL, PROCAR and DOSCAR + "LOPTICS": False, # No PCDAT file + "NSW": 200, + "NELM": 500, + # to be removed + "NPAR": 4, + } + ), + ) + ) + + if phonon_static_energy_maker is None: + phonon_static_energy_maker = StaticMaker( + input_set_generator=StaticSetGenerator( + auto_ispin=False, + user_incar_settings={ + "ALGO": "Normal", + "ISPIN": 1, + "LAECHG": False, + "ISMEAR": 0, + "ENCUT": 700, + "SIGMA": 0.05, + "LCHARG": False, # Do not write the CHGCAR file + "LWAVE": False, # Do not write the WAVECAR file + "LVTOT": False, # Do not write LOCPOT file + "LORBIT": None, # No output of projected or partial DOS in EIGENVAL, PROCAR and DOSCAR + "LOPTICS": False, # No PCDAT file + # to be removed + "NPAR": 4, + }, + ) + ) + + # always set autoplex default as job name + phonon_displacement_maker.name = "dft phonon static" + phonon_bulk_relax_maker.name = "tight relax" + phonon_static_energy_maker.name = "static" + + for displacement in displacements: + dft_phonons = DFTPhononMaker( + symprec=symprec, + static_energy_maker=phonon_static_energy_maker, + bulk_relax_maker=phonon_bulk_relax_maker, + phonon_displacement_maker=phonon_displacement_maker, + born_maker=None, + displacement=displacement, + ).make(structure=structure, supercell_matrix=supercell_matrix) + jobs.append(dft_phonons) + dft_phonons_output[ + f"{displacement}".replace(".", "") # key must not contain '.' + ] = dft_phonons.output + dft_phonons_dir_output.append(dft_phonons.output.jobdirs.displacements_job_dirs) + + flow = Flow( + jobs, + {"phonon_dir": dft_phonons_dir_output, "phonon_data": dft_phonons_output}, + name="dft_phononpy_gen_data", + ) + return Response(replace=flow)
+ + + +
+[docs] +@job +def dft_random_gen_data( + structure: Structure, + mp_id, + rattled_bulk_relax_maker, + displacement_maker, + uc: bool = False, + volume_custom_scale_factors: list[float] | None = None, + volume_scale_factor_range: list[float] | None = None, + rattle_std: float = 0.01, + distort_type: int = 0, + n_structures: int = 10, + min_distance: float = 1.5, + angle_percentage_scale: float = 10, + angle_max_attempts: int = 1000, + rattle_type: int = 0, + rattle_seed: int = 42, + rattle_mc_n_iter: int = 10, + w_angle: list[float] | None = None, + supercell_settings: dict | None = None, +): + """ + Job to generate random structured DFT reference database to be used for fitting ML potentials. + + Parameters + ---------- + structure: Structure + The pymatgen Structure object + displacement_maker : .BaseVaspMaker or None + Maker used for a static calculation for a supercell. + rattled_bulk_relax_maker: BaseVaspMaker + Maker used for the bulk relax unit cell calculation. + mp_id: + Materials Project ID. + uc: bool. + If True, will generate randomly distorted structures (unitcells) + and add static computation jobs to the flow. + distort_type : int. + 0- volume distortion, 1- angle distortion, 2- volume and angle distortion. Default=0. + distort_type : int. + 0- volume distortion, 1- angle distortion, 2- volume and angle distortion. Default=0. + n_structures : int. + Total number of distorted structures to be generated. + Must be provided if distorting volume without specifying a range, or if distorting angles. + Default=10. + volume_scale_factor_range : list[float] + [min, max] of volume scale factors. + e.g. [0.90, 1.10] will distort volume +-10%. + volume_custom_scale_factors : list[float] + Specify explicit scale factors (if range is not specified). + If None, will default to [0.90, 0.95, 0.98, 0.99, 1.01, 1.02, 1.05, 1.10]. + min_distance: float + Minimum separation allowed between any two atoms. + Default= 1.5A. + angle_percentage_scale: float + Angle scaling factor. + Default= 10 will randomly distort angles by +-10% of original value. + angle_max_attempts: int. + Maximum number of attempts to distort structure before aborting. + Default=1000. + w_angle: list[float] + List of angle indices to be changed i.e. 0=alpha, 1=beta, 2=gamma. + Default= [0, 1, 2]. + rattle_type: int. + 0- standard rattling, 1- Monte-Carlo rattling. Default=0. + rattle_std: float. + Rattle amplitude (standard deviation in normal distribution). + Default=0.01. + Note that for MC rattling, displacements generated will roughly be + rattle_mc_n_iter**0.5 * rattle_std for small values of n_iter. + rattle_seed: int. + Seed for setting up NumPy random state from which random numbers are generated. + Default=42. + rattle_mc_n_iter: int. + Number of Monte Carlo iterations. + Larger number of iterations will generate larger displacements. + Default=10. + supercell_settings: dict + Settings for supercells. + """ + jobs = [] + + if displacement_maker is None: + displacement_maker = TightDFTStaticMaker(name="dft rattle static") + if rattled_bulk_relax_maker is None: + rattled_bulk_relax_maker = TightRelaxMaker( + run_vasp_kwargs={"handlers": {}}, + input_set_generator=TightRelaxSetGenerator( + user_incar_settings={ + "ALGO": "Normal", + "ISPIN": 1, + "LAECHG": False, + "ISYM": 0, # to be changed + "ISMEAR": 0, + "SIGMA": 0.05, # to be changed back + "LCHARG": False, # Do not write the CHGCAR file + "LWAVE": False, # Do not write the WAVECAR file + "LVTOT": False, # Do not write LOCPOT file + "LORBIT": None, # No output of projected or partial DOS in EIGENVAL, PROCAR and DOSCAR + "LOPTICS": False, # No PCDAT file + "NSW": 200, + "NELM": 500, + # to be removed + "NPAR": 4, + } + ), + ) + + # always set autoplex default as job name + displacement_maker.name = "dft rattle static" + rattled_bulk_relax_maker.name = "tight relax" + + # TODO: decide if we should remove the additional response here as well + # looks like only the output is changing + random_datagen = RandomStructuresDataGenerator( + name="RandomDataGen", + bulk_relax_maker=rattled_bulk_relax_maker, + displacement_maker=displacement_maker, + n_structures=n_structures, + uc=uc, + rattle_std=rattle_std, + distort_type=distort_type, + min_distance=min_distance, + rattle_seed=rattle_seed, + rattle_type=rattle_type, + angle_max_attempts=angle_max_attempts, + angle_percentage_scale=angle_percentage_scale, + rattle_mc_n_iter=rattle_mc_n_iter, + w_angle=w_angle, + supercell_settings=supercell_settings, + ).make( + structure=structure, + mp_id=mp_id, + volume_custom_scale_factors=volume_custom_scale_factors, + volume_scale_factor_range=volume_scale_factor_range, + ) + jobs.append(random_datagen) + + flow = Flow(jobs, {"rattled_dir": random_datagen.output}) + return Response(replace=flow)
+ + + +
+[docs] +@job +def get_iso_atom( + structure_list: list[Structure], + isolated_atom_maker: IsoAtomStaticMaker, +): + """ + Job to collect all atomic species of the structures and starting VASP calculation of isolated atoms. + + Parameters + ---------- + structure_list: list[Structure] + List of pymatgen Structure objects + isolated_atom_maker: IsoAtomStaticMaker + VASP maker for the isolated atom calculation. + """ + jobs = [] + iso_atoms_dict = {} + all_species = list( + {specie for s in structure_list for specie in s.types_of_species} + ) + + isoatoms = IsoAtomMaker().make( + all_species=all_species, + isolated_atom_maker=isolated_atom_maker, + ) + jobs.append(isoatoms) + + for i, species in enumerate(all_species): + iso_atoms_dict.update({species.number: isoatoms.output["energies"][i]}) + + flow = Flow( + jobs, + { + "species": all_species, + "energies": iso_atoms_dict, + "dirs": isoatoms.output["dirs"], + }, + ) + return Response(replace=flow)
+ + + +
+[docs] +@job(data=[PhononBSDOSDoc, "dft_references"]) +def get_phonon_output( + metrics: list, + benchmark_structures: list[Structure] | None = None, + benchmark_mp_ids: list[str] | None = None, + dft_references: list[PhononBSDOSDoc] | None = None, + pre_xyz_files: list[str] | None = None, + pre_database_dir: str | None = None, + fit_kwargs_list: list | None = None, +): + """ + Job to collect and process all phonon-related output information for a potential restart of the flow. + + This function aggregates benchmark results, DFT reference data, and other input parameters + to determine the best fit RMSE from phonon calculations across all benchmark fits. + + Parameters + ---------- + metrics: list[dict] + List of metric dictionaries from complete_benchmark jobs. + dft_references: list[PhononBSDOSDoc] | None + List of DFT reference files containing the PhononBSDOCDoc object. + Reference files have to refer to a finite displacement of 0.01. + For benchmarking, only 0.01 is supported + benchmark_structures: list[Structure] | None + The pymatgen structure for benchmarking. + benchmark_mp_ids: list[str] | None + Materials Project ID of the benchmarking structure. + pre_xyz_files: list[str] or None + Names of the pre-database train xyz file and test xyz file. + pre_database_dir: str or None + The pre-database directory. + fit_kwargs_list : list[dict]. + Dict including MLIP fit keyword args. + """ + # TODO: potentially evaluation of imaginary modes + try: + rms_max_values = [] # get the largest rms in each fit + + for i in range(len(metrics[0])): + rms_max_value = max( + sublist[i]["benchmark_phonon_rmse"] for sublist in metrics + ) + rms_max_values.append(rms_max_value) + rms = min(rms_max_values) + except TypeError: + # Set a large value as a fall back if None is discovered + rms = 1000.0 + return { + "metrics": metrics, + "rms": rms, # get the best fit RMSE + "benchmark_structures": benchmark_structures, + "benchmark_mp_ids": benchmark_mp_ids, + "dft_references": dft_references, + "pre_xyz_files": pre_xyz_files, + "pre_database_dir": pre_database_dir, + "fit_kwargs_list": fit_kwargs_list, + }
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/autoplex/auto/rss/flows.html b/_modules/autoplex/auto/rss/flows.html new file mode 100644 index 000000000..7accdaae4 --- /dev/null +++ b/_modules/autoplex/auto/rss/flows.html @@ -0,0 +1,839 @@ + + + + + + + + + + autoplex.auto.rss.flows — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+
+
+ + + + +
+ +

Source code for autoplex.auto.rss.flows

+"""RSS (random structure searching) flow for exploring and learning potential energy surfaces from scratch."""
+
+import os
+from dataclasses import dataclass
+from pathlib import Path
+
+from jobflow import Flow, Maker, Response, job
+from ruamel.yaml import YAML
+
+from autoplex.auto.rss.jobs import do_rss_iterations, initial_rss
+
+
+
+[docs] +@dataclass +class RssMaker(Maker): + """ + Maker to set up and run RSS for exploring and learning potential-energy surfaces (from scratch). + + Parameters + ---------- + name: str + Name of the flow. + path_to_default_config_parameters: Path | str | None + Path to the default RSS configuration file 'rss_default_configuration.yaml'. + If None, the default path will be used. + """ + + name: str = "ml-driven rss" + path_to_default_config_parameters: Path | str | None = None + +
+[docs] + @job + def make(self, config_file: str | None = None, **kwargs): + """ + Make a rss workflow using the specified configuration file and additional keyword arguments. + + Parameters + ---------- + config_file: str | None + Path to the configuration file that defines the setup parameters for the whole RSS workflow. + If not provided, the default file 'rss_default_configuration.yaml' will be used. + kwargs: dict, optional + Additional optional keyword arguments to customize the job execution. + + Keyword Arguments + ----------------- + tag: str + Tag of systems. It can also be used for setting up elements and stoichiometry. + For example, the tag of 'SiO2' will be recognized as a 1:2 ratio of Si to O and + passed into the parameters of buildcell. However, note that this will be overwritten + if the stoichiometric ratio of elements is defined in the 'buildcell_options'. + train_from_scratch: bool + If True, it starts the workflow from scratch. + If False, it resumes from a previous state. + resume_from_previous_state: dict | None + A dictionary containing the state information required to resume a previously interrupted + or saved RSS workflow. When 'train_from_scratch' is set to False, this parameter is mandatory + for the workflow to pick up from a saved state.Expected keys within this dictionary are as follows + + - 'test_error': float, The test error from the last completed training step. + - 'pre_database_dir': str, Path to the directory containing the pre-existing database for resuming. + - 'mlip_path': str, Path to the file of a previous MLIP model. + - 'isolated_atom_energies': dict, A dictionary with isolated atom energy values mapped to atomic numbers. + + generated_struct_numbers: list[int] + Expected number of generated randomized unit cells by buildcell. + buildcell_options: list[dict] | None + Customized parameters for buildcell. Default is None. + fragment: Atoms | list[Atoms] | None + Fragment(s) for random structures, e.g., molecules, to be placed indivudally intact. + atoms.arrays should have a 'fragment_id' key with unique identifiers for each fragment if in same Atoms. + atoms.cell must be defined (e.g., Atoms.cell = np.eye(3)*20). + fragment_numbers: list[str] | None + Numbers of each fragment to be included in the random structures. Defaults to 1 for all specified. + num_processes_buildcell: int + Number of processes to use for parallel computation during buildcell generation. + num_of_initial_selected_structs: list[int] | None + Number of structures to be sampled directly from the buildcell-generated randomized cells. + num_of_rss_selected_structs: int + Number of structures to be selected from each RSS iteration. + initial_selection_enabled: bool + If true, sample structures from initially generated randomized cells using CUR. + rss_selection_method: str + Method for selecting samples from the RSS trajectories: + Boltzmann flat histogram in enthalpy first, then CUR. + Options are as follows + + - 'bcur1s': Execute bcur with one shot (1s) + - 'bcur2i': Execute bcur with two iterations (2i) + + bcur_params: dict | None + Parameters for Boltzmann CUR selection. The default dictionary includes following keys + + soap_paras: dict + SOAP descriptor parameters dict with following acceptable keys + + - 'l_max': int, Maximum degree of spherical harmonics (default 12). + - 'n_max': int, Maximum number of radial basis functions (default 12). + - 'atom_sigma': float, Width of Gaussian smearing (default 0.0875). + - 'cutoff': float, Radial cutoff distance (default 10.5). + - 'cutoff_transition_width': float, Width of the transition region (default 1.0). + - 'zeta': float,Exponent for dot-product SOAP kernel (default 4.0). + - 'average': bool, Whether to average the SOAP vectors (default True). + - 'species': bool, Whether to consider species information (default True). + + kb_temp: float + Temperature in eV for Boltzmann weighting (default 0.3). + frac_of_bcur: float + Fraction of Boltzmann CUR selections (default 0.8). + bolt_max_num: int + Maximum number of Boltzmann selections (default 3000). + kernel_exp: float + Exponent for the kernel (default 4.0). + energy_label: str + Label for the energy data (default 'energy'). + random_seed: int | None + A seed to ensure reproducibility of CUR selection. Default is None. + include_isolated_atom: bool + If true, perform single-point calculations for isolated atoms. + isolatedatom_box: list[float] + List of the lattice constants for an isolated atom configuration. + e0_spin: bool + If true, include spin polarization in isolated atom and dimer calculations. Default is False. + include_dimer: bool + If true, perform single-point calculations for dimers only once. Default is False. + dimer_box: list[float] + The lattice constants of a dimer box. + dimer_range: list[float] + Range of distances for dimer calculations. + dimer_num: int + Number of different distances to consider for dimer calculations. Default is 21. + custom_incar: dict | None + Dictionary of custom VASP input parameters. If provided, will update the + default parameters. Default is None. + custom_potcar: dict | None + Dictionary of POTCAR settings to update. Keys are element symbols, values are the desired POTCAR labels. + Default is None. + vasp_ref_file: str + Reference file for VASP data. Default is 'vasp_ref.extxyz'. + config_types: list[str] + Configuration types for the VASP calculations. Default is None. + rss_group: list[str] + Group name for RSS to setting up regularization. + test_ratio: float + The proportion of the test set after splitting the data. The value is allowed to be set to 0; + in this case, the testing error would not be meaningful anymore. + regularization: bool + If True, apply regularization. This only works for GAP to date. Default is False. + scheme: str + Method to use for regularization. Options are + + - 'linear_hull': for single-composition system, use 2D convex hull (E, V) + - 'volume-stoichiometry': for multi-composition system, use 3D convex hull of (E, V, mole-fraction) + + reg_minmax: list[tuple] + List of tuples of (min, max) values for energy, force, virial sigmas for regularization. + distillation: bool + If true, apply data distillation. Default is True. + force_max: float | None + Maximum force value to exclude structures. Default is 50. + force_label: str | None + The label of force values to use for distillation. Default is 'REF_forces'. + pre_database_dir: str | None + Directory where the previous database was saved. + mlip_type: str + Choose one specific MLIP type to be fitted: 'GAP' | 'J-ACE' | 'NEQUIP' | 'M3GNET' | 'MACE'. + Default is 'GAP'. + ref_energy_name: str + Reference energy name. Default is 'REF_energy'. + ref_force_name: str + Reference force name. Default is 'REF_forces'. + ref_virial_name: str + Reference virial name. Default is 'REF_virial'. + auto_delta: bool + If true, apply automatic determination of delta for GAP terms. Default is False. + num_processes_fit: int + Number of processes used for fitting. Default is 1. + device_for_fitting: str + Device to be used for model fitting, either "cpu" or "cuda". + scalar_pressure_method: str + Method for adding external pressures. Acceptable options are as follows + + - 'exp': Applies pressure using an exponential distribution. + - 'uniform': Applies pressure using a uniform distribution. + + scalar_exp_pressure: float + Scalar exponential pressure. Default is 100. + scalar_pressure_exponential_width: float + Width for scalar pressure exponential. Default is 0.2. + scalar_pressure_low: float + Low limit for scalar pressure. Default is 0. + scalar_pressure_high: float + High limit for scalar pressure. Default is 50. + max_steps: int + Maximum number of steps for relaxation. Default is 200. + force_tol: float + Force residual tolerance for relaxation. Default is 0.05. + stress_tol: float + Stress residual tolerance for relaxation. Default is 0.05. + hookean_repul: bool + If true, apply Hookean repulsion. Default is False. + hookean_paras: dict[tuple[int, int], tuple[float, float]] | None + Parameters for Hookean repulsion as a dictionary of tuples. Default is None. + keep_symmetry: bool + If true, preserve symmetry during relaxation. Default is False. + write_traj: bool + If true, write trajectory of RSS. Default is True. + num_processes_rss: int + Number of processes used for running RSS. Default is 1. + device_for_rss: str + Specify device to use "cuda" or "cpu" for running RSS. Default is "cpu". + stop_criterion: float + Convergence criterion for stopping RSS iterations. Default is 0.01. + max_iteration_number: int + Maximum number of RSS iterations to perform. Default is 25. + num_groups: int + Number of structure groups, used for assigning tasks across multiple nodes. + For example, if there are 10,000 trajectories to relax and 'num_groups=10', + the trajectories will be divided into 10 groups and 10 independent jobs will be created, + with each job handling 1,000 trajectories. + initial_kb_temp: float + Initial temperature (in eV) for Boltzmann sampling. Default is 0.3. + current_iter_index: int + Index for the current RSS iteration. Default is 1. + **fit_kwargs: + Additional keyword arguments for the MLIP fitting process. + + Returns + ------- + dict: + A dictionary with following information + + - 'test_error': float, The test error of the fitted MLIP. + - 'pre_database_dir': str, The directory of the latest RSS database. + - 'mlip_path': str, The path to the latest fitted MLIP. + - 'isolated_atom_energies': dict, The isolated energy values. + - 'current_iter': int, The current iteration index. + - 'kb_temp': float, The temperature (in eV) for Boltzmann sampling. + """ + rss_default_config_path = ( + self.path_to_default_config_parameters + or Path(__file__).absolute().parent / "rss_default_configuration.yaml" + ) + + yaml = YAML(typ="safe", pure=True) + + with open(rss_default_config_path) as f: + config = yaml.load(f) + + if config_file and os.path.exists(config_file): + with open(config_file) as f: + new_config = yaml.load(f) + config.update(new_config) + + config.update(kwargs) + self._process_hookean_paras(config) + + config_params = config.copy() + + if "train_from_scratch" not in config_params: + raise ValueError( + "'train_from_scratch' must be set in the configuration file or passed as a keyword argument!!" + ) + + rss_flow = [] + + if config_params["train_from_scratch"]: + initial_exclude_keys = [ + "train_from_scratch", + "resume_from_previous_state", + "config_types", + "rss_group", + "num_of_rss_selected_structs", + "rss_selection_method", + "scalar_pressure_method", + "scalar_exp_pressure", + "scalar_pressure_exponential_width", + "scalar_pressure_low", + "scalar_pressure_high", + "max_steps", + "force_tol", + "stress_tol", + "stop_criterion", + "max_iteration_number", + "num_groups", + "initial_kb_temp", + "current_iter_index", + "hookean_repul", + "hookean_paras", + "keep_symmetry", + "write_traj", + "num_processes_rss", + "device_for_rss", + ] + initial_params = { + k: v for k, v in config_params.items() if k not in initial_exclude_keys + } + initial_params.update( + { + "config_type": config_params["config_types"][0], + "rss_group": config_params["rss_group"][0], + } + ) + initial_rss_job = initial_rss(**initial_params) + rss_flow.append(initial_rss_job) + + rss_group = config_params["rss_group"] + config_types = config_params["config_types"] + do_rss_group = rss_group[0] if len(rss_group) == 1 else rss_group[-1] + rss_config_type = ( + config_types[0] if len(config_types) == 1 else config_types[1:] + ) + + rss_exclude_keys = [ + "train_from_scratch", + "resume_from_previous_state", + "pre_database_dir", + ] + + rss_params = { + k: v for k, v in config_params.items() if k not in rss_exclude_keys + } + rss_params.update( + { + "num_of_initial_selected_structs": None, + "initial_selection_enabled": False, + "rss_group": do_rss_group, + "config_types": rss_config_type, + } + ) + + if config_params["train_from_scratch"]: + rss_params.update({"include_isolated_atom": False}) + rss_params.update({"include_dimer": False}) + + do_rss_job = do_rss_iterations( + input=initial_rss_job.output, + **rss_params, + ) + else: + if "resume_from_previous_state" not in config_params: + raise ValueError( + "The parameter 'resume_from_previous_state' must be specified when 'train_from_scratch' is False." + ) + resume_from_previous_state = config_params["resume_from_previous_state"] + do_rss_job = do_rss_iterations( + input=resume_from_previous_state, + **rss_params, + ) + + rss_flow.append(do_rss_job) + + return Response(replace=Flow(rss_flow), output=do_rss_job.output)
+ + + def _process_hookean_paras(self, config): + if "hookean_paras" in config: + config["hookean_paras"] = { + tuple(map(int, k.strip("()").split(", "))): tuple(v) + for k, v in config["hookean_paras"].items() + }
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/autoplex/auto/rss/jobs.html b/_modules/autoplex/auto/rss/jobs.html new file mode 100644 index 000000000..3727f5fba --- /dev/null +++ b/_modules/autoplex/auto/rss/jobs.html @@ -0,0 +1,1171 @@ + + + + + + + + + + autoplex.auto.rss.jobs — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+
+
+ + + + +
+ +

Source code for autoplex.auto.rss.jobs

+"""RSS Jobs include the generation of the initial potential model as well as iterative RSS exploration."""
+
+import logging
+
+from jobflow import Flow, Response, job
+
+from autoplex.data.common.flows import DFTStaticLabelling
+from autoplex.data.common.jobs import (
+    collect_dft_data,
+    preprocess_data,
+    sample_data,
+)
+from autoplex.data.rss.flows import BuildMultiRandomizedStructure
+from autoplex.data.rss.jobs import do_rss_multi_node
+from autoplex.fitting.common.flows import MLIPFitMaker
+
+__all__ = ["do_rss_iterations", "initial_rss"]
+
+logging.basicConfig(
+    level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
+)
+
+
+
+[docs] +@job +def initial_rss( + tag: str, + generated_struct_numbers: list[int], + num_of_initial_selected_structs: list[int] | None = None, + buildcell_options: list[dict] | None = None, + fragment_file: str | None = None, + fragment_numbers: list[str] | None = None, + num_processes_buildcell: int = 1, + initial_selection_enabled: bool = False, + bcur_params: dict | None = None, + random_seed: int | None = None, + include_isolated_atom: bool = False, + isolatedatom_box: list[float] | None = None, + e0_spin: bool = False, + include_dimer: bool = False, + dimer_box: list[float] | None = None, + dimer_range: list | None = None, + dimer_num: int = 21, + custom_incar: dict | None = None, + custom_potcar: dict | None = None, + config_type: str | None = None, + vasp_ref_file: str = "vasp_ref.extxyz", + rss_group: str = "initial", + test_ratio: float = 0.1, + regularization: bool = False, + retain_existing_sigma: bool = False, + scheme: str | None = None, + reg_minmax: list[tuple] | None = None, + distillation: bool = False, + force_max: float | None = None, + force_label: str = "REF_forces", + pre_database_dir: str | None = None, + mlip_type: str = "GAP", + ref_energy_name: str = "REF_energy", + ref_force_name: str = "REF_forces", + ref_virial_name: str = "REF_virial", + auto_delta: bool = False, + num_processes_fit: int = 1, + device_for_fitting: str = "cpu", + **fit_kwargs, +): + """ + Run initial Random Structure Searching (RSS) workflow from scratch. + + Parameters + ---------- + tag: str + Tag of systems. It can also be used for setting up elements and stoichiometry. + For example, 'SiO2' will generate structures with a 2:1 ratio of Si to O. + generated_struct_numbers: list[int] + Expected number of generated randomized unit cells. + num_of_initial_selected_structs: list[int] | None + Number of structures to be sampled. Default is None. + buildcell_options: list[dict] | None + Customized parameters for buildcell. Default is None. + fragment_file: Atoms | list[Atoms] | None + Fragment(s) for random structures, e.g. molecules, to be placed indivudally intact. + atoms.arrays should have a 'fragment_id' key with unique identifiers for each fragment if in same Atoms. + atoms.cell must be defined (e.g. Atoms.cell = np.eye(3)*20). + fragment_numbers: list[str] | None + Numbers of each fragment to be included in the random structures. Defaults to 1 for all specified. + num_processes_buildcell: int + Number of processes to use for parallel computation during buildcell generation. Default is 1. + initial_selection_enabled: bool + If true, sample structures using CUR. Default is False. + bcur_params: dict | None + Parameters for Boltzmann CUR selection. Default is None. + random_seed: int | None + A seed to ensure reproducibility of CUR selection. Default is None. + include_isolated_atom: bool + If true, perform single-point calculations for isolated atoms. Default is False. + isolatedatom_box: list[float] | None + List of the lattice constants for an isolated atom configuration. Default is None. + e0_spin: bool + If true, include spin polarization in isolated atom and dimer calculations. Default is False. + include_dimer: bool + If true, perform single-point calculations for dimers. Default is False. + dimer_box: list[float] | None + The lattice constants of a dimer box. Default is None. + dimer_range: list[float] | None + Range of distances for dimer calculations. Default is None. + dimer_num: int + Number of different distances to consider for dimer calculations. Default is 21. + custom_incar: dict | None + Dictionary of custom VASP input parameters. If provided, will update the + default parameters. Default is None. + custom_potcar: dict | None + Dictionary of POTCAR settings to update. Keys are element symbols, values are the desired POTCAR labels. + Default is None. + config_type: str | None + Configuration type for the VASP calculations. Default is None. + vasp_ref_file: str + Reference file for VASP data. Default is 'vasp_ref.extxyz'. + rss_group: str + Group name for GAP RSS. Default is 'initial'. + test_ratio: float + The proportion of the test set after splitting the data. + If None, no splitting will be performed. Default is 0.1. + regularization: bool + If true, apply regularization. This only works for GAP. Default is False. + retain_existing_sigma: bool + Whether to keep the current sigma values for specific configuration types. + If set to True, existing sigma values for specific configurations will remain unchanged. + scheme: str | None + Scheme to use for regularization. Default is None. + reg_minmax: list[tuple] | None + A list of tuples representing the minimum and maximum values for regularization. + distillation: bool + If true, apply data distillation. Default is False. + force_max: float | None + Maximum force value to exclude structures. Default is None. + force_label: str + The label of force values to use for distillation. Default is 'REF_forces'. + pre_database_dir: str | None + Directory where the previous database was saved. Default is None. + mlip_type: str + Choose one specific MLIP type to be fitted: 'GAP' | 'J-ACE' | 'NEQUIP' | 'M3GNET' | 'MACE'. + Default is 'GAP'. + ref_energy_name: str + Reference energy name. Default is 'REF_energy'. + ref_force_name: str + Reference force name. Default is 'REF_forces'. + ref_virial_name: str + Reference virial name. Default is 'REF_virial'. + auto_delta: bool + If true, apply automatic determination of delta for GAP terms. Default is False. + num_processes_fit: int + Number of processes used for fitting. Default is 1. + device_for_fitting: str + Device to be used for model fitting, either "cpu" or "cuda". + fit_kwargs: + Additional keyword arguments for the MLIP fitting process. + + Returns + ------- + dict: + A dictionary with following information + + - 'test_error': float, The test error of the fitted MLIP. + - 'pre_database_dir': str, The directory of the preprocessed database. + - 'mlip_path': str, The path to the fitted MLIP. + - 'isolated_atom_energies': dict, The isolated energy values. + - 'current_iter': int, The current iteration index, set to 0. + """ + if isolatedatom_box is None: + isolatedatom_box = [20.0, 20.0, 20.0] + if dimer_box is None: + dimer_box = [20.0, 20.0, 20.0] + + do_randomized_structure_generation = BuildMultiRandomizedStructure( + generated_struct_numbers=generated_struct_numbers, + buildcell_options=buildcell_options, + fragment_file=fragment_file, + fragment_numbers=fragment_numbers, + selected_struct_numbers=num_of_initial_selected_structs, + tag=tag, + num_processes=num_processes_buildcell, + initial_selection_enabled=initial_selection_enabled, + bcur_params=bcur_params, + random_seed=random_seed, + ).make() + do_dft_static = DFTStaticLabelling( + e0_spin=e0_spin, + isolatedatom_box=isolatedatom_box, + isolated_atom=include_isolated_atom, + dimer=include_dimer, + dimer_box=dimer_box, + dimer_range=dimer_range, + dimer_num=dimer_num, + custom_incar=custom_incar, + custom_potcar=custom_potcar, + ).make( + structures=do_randomized_structure_generation.output, config_type=config_type + ) + do_data_collection = collect_dft_data( + vasp_ref_file=vasp_ref_file, rss_group=rss_group, vasp_dirs=do_dft_static.output + ) + do_data_preprocessing = preprocess_data( + test_ratio=test_ratio, + regularization=regularization, + retain_existing_sigma=retain_existing_sigma, + scheme=scheme, + distillation=distillation, + force_max=force_max, + force_label=force_label, + vasp_ref_dir=do_data_collection.output["vasp_ref_dir"], + pre_database_dir=pre_database_dir, + reg_minmax=reg_minmax, + isolated_atom_energies=do_data_collection.output["isolated_atom_energies"], + ) + do_mlip_fit = MLIPFitMaker( + mlip_type=mlip_type, + ref_energy_name=ref_energy_name, + ref_force_name=ref_force_name, + ref_virial_name=ref_virial_name, + num_processes_fit=num_processes_fit, + apply_data_preprocessing=False, + auto_delta=auto_delta, + glue_xml=False, + ).make( + database_dir=do_data_preprocessing.output, + isolated_atom_energies=do_data_collection.output["isolated_atom_energies"], + device=device_for_fitting, + **fit_kwargs, + ) + + job_list = [ + do_randomized_structure_generation, + do_dft_static, + do_data_collection, + do_data_preprocessing, + do_mlip_fit, + ] + + (mlip_path,) = do_mlip_fit.output["mlip_path"] + + return Response( + replace=Flow(job_list), + output={ + "test_error": do_mlip_fit.output["test_error"], + "pre_database_dir": do_data_preprocessing.output, + "mlip_path": mlip_path, + "isolated_atom_energies": do_data_collection.output[ + "isolated_atom_energies" + ], + }, + )
+ + + +
+[docs] +@job +def do_rss_iterations( + input: dict, + tag: str, + generated_struct_numbers: list[int], + num_of_initial_selected_structs: list[int] | None = None, + buildcell_options: list[dict] | None = None, + fragment_file: str | None = None, + fragment_numbers: list[str] | None = None, + num_processes_buildcell: int = 1, + initial_selection_enabled: bool = False, + rss_selection_method: str = None, + num_of_rss_selected_structs: int = 100, + bcur_params: dict | None = None, + random_seed: int | None = None, + include_isolated_atom: bool = False, + isolatedatom_box: list[float] | None = None, + e0_spin: bool = False, + include_dimer: bool = False, + dimer_box: list[float] | None = None, + dimer_range: list | None = None, + dimer_num: int = 21, + custom_incar: dict | None = None, + custom_potcar: dict | None = None, + config_types: list[str] | None = None, + vasp_ref_file: str = "vasp_ref.extxyz", + rss_group: str = "rss", + test_ratio: float = 0.1, + regularization: bool = False, + retain_existing_sigma: bool = False, + scheme: str | None = None, + reg_minmax: list[tuple] | None = None, + distillation: bool = True, + force_max: float = 200, + force_label: str = "REF_forces", + mlip_type: str = "GAP", + ref_energy_name: str = "REF_energy", + ref_force_name: str = "REF_forces", + ref_virial_name: str = "REF_virial", + auto_delta: bool = False, + num_processes_fit: int = 1, + device_for_fitting: str = "cpu", + scalar_pressure_method: str = "exp", + scalar_exp_pressure: float = 100, + scalar_pressure_exponential_width: float = 0.2, + scalar_pressure_low: float = 0, + scalar_pressure_high: float = 50, + max_steps: int = 200, + force_tol: float = 0.05, + stress_tol: float = 0.05, + hookean_repul: bool = False, + hookean_paras: dict[tuple[int, int], tuple[float, float]] | None = None, + keep_symmetry: bool = False, + write_traj: bool = True, + num_processes_rss: int = 1, + device_for_rss: str = "cpu", + stop_criterion: float = 0.01, + max_iteration_number: int = 5, + num_groups: int = 1, + initial_kt: float = 0.3, + current_iter_index: int = 1, + **fit_kwargs, +): + """ + Perform iterative RSS to improve the accuracy of a MLIP. + + Each iteration involves generating new structures, sampling, running + VASP calculations, collecting data, preprocessing data, and fitting a new MLIP. + + Parameters + ---------- + input : dict + A dictionary parameter used to pass specific input data required during the RSS iterations. + The keys in this dictionary should be one of the following valid keys: + + test_error: float + The test error of the fitted MLIP. + pre_database_dir: str + The directory of the preprocessed database. + mlip_path: str + The path to the fitted MLIP. + isolated_atom_energies: dict + The isolated energy values. + current_iter: int + The current iteration index. + kt: float + The value of kt. + + tag: str + Tag of systems. It can also be used for setting up elements and stoichiometry. + For example, 'SiO2' will generate structures with a 2:1 ratio of Si to O. + generated_struct_numbers: list[int] + Expected number of generated randomized unit cells. + num_of_initial_selected_structs: list[int] | None + Number of structures to be sampled. Default is None. + buildcell_options: list[dict] | None + Customized parameters for buildcell. Default is None. + fragment_file: Atoms | list[Atoms] | None + Fragment(s) for random structures, e.g. molecules, to be placed indivudally intact. + atoms.arrays should have a 'fragment_id' key with unique identifiers for each fragment if in same Atoms. + atoms.cell must be defined (e.g. Atoms.cell = np.eye(3)*20). + fragment_numbers: list[str] | None + Numbers of each fragment to be included in the random structures. Defaults to 1 for all specified. + num_processes_buildcell: int + Number of processes to use for parallel computation during buildcell generation. Default is 1. + initial_selection_enabled: bool + If true, sample structures using CUR. Default is False. + rss_selection_method: str + Method for selecting samples from the generated structures. Default is None. + num_of_rss_selected_structs: int + Number of structures to be selected. + bcur_params: dict | None + Parameters for Boltzmann CUR selection. Default is None. + random_seed: int | None + A seed to ensure reproducibility of CUR selection. Default is None. + include_isolated_atom: bool + If true, perform single-point calculations for isolated atoms. Default is False. + isolatedatom_box: list[float] | None + List of the lattice constants for an isolated atom configuration. Default is None. + e0_spin: bool + If true, include spin polarization in isolated atom and dimer calculations. Default is False. + include_dimer: bool + If true, perform single-point calculations for dimers only once. Default is False. + dimer_box: list[float] | None + The lattice constants of a dimer box. Default is None. + dimer_range: list[float] | None + Range of distances for dimer calculations. Default is None. + dimer_num: int + Number of different distances to consider for dimer calculations. Default is 21. + custom_incar: dict | None + Dictionary of custom VASP input parameters. If provided, will update the + default parameters. Default is None. + custom_potcar: dict | None + Dictionary of POTCAR settings to update. Keys are element symbols, values are the desired POTCAR labels. + Default is None. + config_types: list[str] | None + Configuration types for the VASP calculations. Default is None. + vasp_ref_file: str + Reference file for VASP data. Default is 'vasp_ref.extxyz'. + rss_group: str + Group name for GAP RSS. Default is 'rss'. + test_ratio: float + The proportion of the test set after splitting the data. Default is 0.1. + regularization: bool + If true, apply regularization. This only works for GAP. Default is False. + retain_existing_sigma: bool + Whether to keep the current sigma values for specific configuration types. + If set to True, existing sigma values for specific configurations will remain unchanged. + scheme: str | None + Scheme to use for regularization. Default is None. + reg_minmax: list[tuple] | None + A list of tuples representing the minimum and maximum values for regularization. + distillation: bool + If true, apply data distillation. Default is True. + force_max: float + Maximum force value to exclude structures. Default is 200. + force_label: str + The label of force values to use for distillation. Default is 'REF_forces'. + mlip_type: str + Choose one specific MLIP type: 'GAP' | 'J-ACE' | 'NequIP' | 'M3GNet' | 'MACE'. Default is 'GAP'. + ref_energy_name: str + Reference energy name. Default is 'REF_energy'. + ref_force_name: str + Reference force name. Default is 'REF_forces'. + ref_virial_name: str + Reference virial name. Default is 'REF_virial'. + auto_delta: bool + If true, apply automatic determination of delta for GAP terms. Default is False. + num_processes_fit: int + Number of processes used for fitting. Default is 1. + device_for_fitting: str + Device to be used for model fitting, either "cpu" or "cuda". + scalar_pressure_method: str + Method for adding external pressures. Default is 'exp'. + scalar_exp_pressure: float + Scalar exponential pressure. Default is 100. + scalar_pressure_exponential_width: float + Width for scalar pressure exponential. Default is 0.2. + scalar_pressure_low: float + Low limit for scalar pressure. Default is 0. + scalar_pressure_high: float + High limit for scalar pressure. Default is 50. + max_steps: int + Maximum number of steps for relaxation. Default is 200. + force_tol: float + Force residual tolerance for relaxation. Default is 0.05. + stress_tol: float + Stress residual tolerance for relaxation. Default is 0.05. + hookean_repul: bool + If true, apply Hookean repulsion. Default is False. + hookean_paras: dict[tuple[int, int], tuple[float, float]] | None + Parameters for Hookean repulsion as a dictionary of tuples. Default is None. + keep_symmetry: bool + If true, preserve symmetry during relaxation. Default is False. + write_traj: bool + If true, write trajectory of RSS. Default is True. + num_processes_rss: int + Number of processes used for running RSS. Default is 1. + device_for_rss: str + Specify device to use "cuda" or "cpu" for running RSS. Default is "cpu". + stop_criterion: float + Convergence criterion for stopping RSS iterations. Default is 0.01. + max_iteration_number: int + Maximum number of RSS iterations to perform. Default is 5. + num_groups: int + Number of structure groups, used for assigning tasks across multiple nodes. Default is 1. + initial_kt: float + Initial temperature (in eV) for Boltzmann sampling. Default is 0.3. + current_iter_index: int + Index for the current RSS iteration. Default is 1. + fit_kwargs: + Additional keyword arguments for the MLIP fitting process. + + Returns + ------- + dict: + A dictionary with following information + + - 'test_error': float, The test error of the fitted MLIP. + - 'pre_database_dir': str, The directory of the preprocessed database. + - 'mlip_path': str, The path to the fitted MLIP. + - 'isolated_atom_energies': dict, The isolated energy values. + - 'current_iter': int, The current iteration index. + - 'kt': float, The temperature (in eV) for Boltzmann sampling. + """ + test_error = input.get("test_error") + current_iter = input.get("current_iter", current_iter_index) + current_kt = input.get("kt", initial_kt) + + config_type = ( + (config_types[0] if current_kt > 0.1 else config_types[-1]) + if config_types + else None + ) + + if isolatedatom_box is None: + isolatedatom_box = [20.0, 20.0, 20.0] + if dimer_box is None: + dimer_box = [20.0, 20.0, 20.0] + + logging.info( + f"The configuration type of structures generated in the current iteration will be {config_type}!" + ) + + if ( + test_error is not None + and test_error > stop_criterion + and current_iter is not None + and current_iter < max_iteration_number + ): + logging.info(f"Current kt: {current_kt}") + logging.info(f"Current iter index: {current_iter}") + logging.info(f"The error of {current_iter}th iteration: {test_error}") + + if bcur_params is None: + bcur_params = {} + bcur_params["kt"] = current_kt + + do_randomized_structure_generation = BuildMultiRandomizedStructure( + generated_struct_numbers=generated_struct_numbers, + buildcell_options=buildcell_options, + fragment_file=fragment_file, + fragment_numbers=fragment_numbers, + selected_struct_numbers=num_of_initial_selected_structs, + tag=tag, + num_processes=num_processes_buildcell, + initial_selection_enabled=initial_selection_enabled, + bcur_params=bcur_params, + random_seed=random_seed, + ).make() + do_rss = do_rss_multi_node( + mlip_type=mlip_type, + iteration_index=f"{current_iter}th", + mlip_path=input["mlip_path"], + structure_paths=do_randomized_structure_generation.output, + scalar_pressure_method=scalar_pressure_method, + scalar_exp_pressure=scalar_exp_pressure, + scalar_pressure_exponential_width=scalar_pressure_exponential_width, + scalar_pressure_low=scalar_pressure_low, + scalar_pressure_high=scalar_pressure_high, + max_steps=max_steps, + force_tol=force_tol, + stress_tol=stress_tol, + hookean_repul=hookean_repul, + hookean_paras=hookean_paras, + keep_symmetry=keep_symmetry, + write_traj=write_traj, + num_processes_rss=num_processes_rss, + device=device_for_rss, + num_groups=num_groups, + config_type=config_type, + ) + do_data_sampling = sample_data( + selection_method=rss_selection_method, + num_of_selection=num_of_rss_selected_structs, + bcur_params=bcur_params, + traj_path=do_rss.output, + random_seed=random_seed, + isolated_atom_energies=input["isolated_atom_energies"], + ) + do_dft_static = DFTStaticLabelling( + e0_spin=e0_spin, + isolatedatom_box=isolatedatom_box, + isolated_atom=include_isolated_atom, + dimer=include_dimer, + dimer_box=dimer_box, + dimer_range=dimer_range, + dimer_num=dimer_num, + custom_incar=custom_incar, + custom_potcar=custom_potcar, + ).make(structures=do_data_sampling.output, config_type=config_type) + do_data_collection = collect_dft_data( + vasp_ref_file=vasp_ref_file, + rss_group=rss_group, + vasp_dirs=do_dft_static.output, + ) + do_data_preprocessing = preprocess_data( + test_ratio=test_ratio, + regularization=regularization, + retain_existing_sigma=retain_existing_sigma, + scheme=scheme, + distillation=distillation, + force_max=force_max, + force_label=force_label, + vasp_ref_dir=do_data_collection.output["vasp_ref_dir"], + pre_database_dir=input["pre_database_dir"], + reg_minmax=reg_minmax, + isolated_atom_energies=input["isolated_atom_energies"], + ) + do_mlip_fit = MLIPFitMaker( + mlip_type=mlip_type, + ref_energy_name=ref_energy_name, + ref_force_name=ref_force_name, + ref_virial_name=ref_virial_name, + num_processes_fit=num_processes_fit, + apply_data_preprocessing=False, + auto_delta=auto_delta, + glue_xml=False, + ).make( + database_dir=do_data_preprocessing.output, + isolated_atom_energies=input["isolated_atom_energies"], + device=device_for_fitting, + **fit_kwargs, + ) + + kt = current_kt - 0.1 if (current_kt - 0.1) > 0.1 else 0.1 + current_iter += 1 + if include_isolated_atom: + include_isolated_atom = False + if include_dimer: + include_dimer = False + + (mlip_path,) = do_mlip_fit.output["mlip_path"] + + do_iteration = do_rss_iterations( + input={ + "test_error": do_mlip_fit.output["test_error"], + "pre_database_dir": do_data_preprocessing.output, + "mlip_path": mlip_path, + "isolated_atom_energies": input["isolated_atom_energies"], + "current_iter": current_iter, + "kt": kt, + }, + generated_struct_numbers=generated_struct_numbers, + num_of_initial_selected_structs=num_of_initial_selected_structs, + tag=tag, + buildcell_options=buildcell_options, + fragment_file=fragment_file, + fragment_numbers=fragment_numbers, + num_processes_buildcell=num_processes_buildcell, + initial_selection_enabled=initial_selection_enabled, + rss_selection_method=rss_selection_method, + num_of_rss_selected_structs=num_of_rss_selected_structs, + bcur_params=bcur_params, + random_seed=random_seed, + e0_spin=e0_spin, + isolatedatom_box=isolatedatom_box, + include_isolated_atom=include_isolated_atom, + include_dimer=include_dimer, + dimer_box=dimer_box, + dimer_range=dimer_range, + dimer_num=dimer_num, + custom_incar=custom_incar, + custom_potcar=custom_potcar, + config_types=config_types, + vasp_ref_file=vasp_ref_file, + rss_group=rss_group, + test_ratio=test_ratio, + regularization=regularization, + retain_existing_sigma=retain_existing_sigma, + scheme=scheme, + reg_minmax=reg_minmax, + distillation=distillation, + force_max=force_max, + force_label=force_label, + mlip_type=mlip_type, + ref_energy_name=ref_energy_name, + ref_force_name=ref_force_name, + ref_virial_name=ref_virial_name, + auto_delta=auto_delta, + num_processes_fit=num_processes_fit, + scalar_pressure_method=scalar_pressure_method, + scalar_exp_pressure=scalar_exp_pressure, + scalar_pressure_exponential_width=scalar_pressure_exponential_width, + scalar_pressure_low=scalar_pressure_low, + scalar_pressure_high=scalar_pressure_high, + max_steps=max_steps, + force_tol=force_tol, + stress_tol=stress_tol, + hookean_repul=hookean_repul, + hookean_paras=hookean_paras, + keep_symmetry=keep_symmetry, + write_traj=write_traj, + num_processes_rss=num_processes_rss, + device_for_rss=device_for_rss, + stop_criterion=stop_criterion, + max_iteration_number=max_iteration_number, + num_groups=num_groups, + initial_kt=initial_kt, + current_iter_index=current_iter_index, + **fit_kwargs, + ) + + job_list = [ + do_randomized_structure_generation, + do_rss, + do_data_sampling, + do_dft_static, + do_data_collection, + do_data_preprocessing, + do_mlip_fit, + do_iteration, + ] + + return Response(detour=job_list, output=do_iteration.output) + + return input
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/autoplex/benchmark/phonons/flows.html b/_modules/autoplex/benchmark/phonons/flows.html new file mode 100644 index 000000000..c409d74ad --- /dev/null +++ b/_modules/autoplex/benchmark/phonons/flows.html @@ -0,0 +1,561 @@ + + + + + + + + + + autoplex.benchmark.phonons.flows — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+
+
+ + + + +
+ +

Source code for autoplex.benchmark.phonons.flows

+"""Flows to benchmark ML potentials."""
+
+from dataclasses import dataclass
+
+from atomate2.common.schemas.phonons import PhononBSDOSDoc
+from jobflow import Maker, job
+from pymatgen.core.structure import Structure
+
+from autoplex.benchmark.phonons.utils import compute_bandstructure_benchmark_metrics
+
+__all__ = ["PhononBenchmarkMaker"]
+
+
+
+[docs] +@dataclass +class PhononBenchmarkMaker(Maker): + """ + Maker to benchmark all chosen ML potentials on the DFT (VASP) reference data. + + Produces a phonon band structure comparison and q-point-wise phonons RMSE plots, + as well as a summary text file. + + Parameters + ---------- + name : str + Name of the flow produced by this maker. + """ + + name: str = "PhononBenchmark" + +
+[docs] + @job + def make( + self, + ml_model: str, + structure: Structure, + benchmark_mp_id: str, + ml_phonon_task_doc: PhononBSDOSDoc, + dft_phonon_task_doc: PhononBSDOSDoc, + displacement: float, + atomwise_regularization_parameter: float, + soap_dict: dict, + suffix: str, + ): + """ + Make flow for benchmarking. + + Parameters + ---------- + ml_model: str + ML model to be used. Default is GAP. + structure: + The pymatgen structures drawn from the Materials Project. + benchmark_mp_id: str. + Materials Project IDs for the structure. + ml_phonon_task_doc: PhononBSDOSDoc + Phonon task doc from ML potential consisting of pymatgen band-structure object. + dft_phonon_task_doc: PhononBSDOSDoc + Phonon task doc from DFT runs consisting of pymatgen band-structure object. + displacement: float + Displacement for finite displacement method. + atomwise_regularization_parameter: float + Regularization value for the atom-wise force components. + suffix: str + GAP potential file path suffix. + soap_dict: dict + Dictionary containing SOAP parameters. + + """ + return compute_bandstructure_benchmark_metrics( + ml_model=ml_model, + ml_phonon_bs=ml_phonon_task_doc.phonon_bandstructure, + dft_phonon_bs=dft_phonon_task_doc.phonon_bandstructure, + dft_imag_modes=dft_phonon_task_doc.has_imaginary_modes, + ml_imag_modes=ml_phonon_task_doc.has_imaginary_modes, + structure=structure, + displacement=displacement, + atomwise_regularization_parameter=atomwise_regularization_parameter, + soap_dict=soap_dict, + suffix=suffix, + mp_id=benchmark_mp_id, + )
+
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/autoplex/benchmark/phonons/jobs.html b/_modules/autoplex/benchmark/phonons/jobs.html new file mode 100644 index 000000000..40236096c --- /dev/null +++ b/_modules/autoplex/benchmark/phonons/jobs.html @@ -0,0 +1,555 @@ + + + + + + + + + + autoplex.benchmark.phonons.jobs — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+
+
+ + + + +
+ +

Source code for autoplex.benchmark.phonons.jobs

+"""Atomistic Jobs to Benchmark Potentials."""
+
+from jobflow import Response, job
+
+
+
+[docs] +@job +def write_benchmark_metrics( + benchmark_structures: list, + metrics: list, + filename_prefix: str = "results_", +): + """ + Generate a text file with evaluated benchmark metrics. + + Parameters + ---------- + benchmark_structures: List[Structure]. + List of benchmark Structure used for benchmarking. + metrics: List[float] + Root mean squared error between band structures, imagmodesdft-bool and imagmodesml-bool. + filename_prefix: str + Prefix of the result summary file. + + Returns + ------- + A text file with root mean squared error between DFT and ML potential phonon band-structure + """ + metrics_flattened = [item for sublist in metrics for item in sublist] + + # the following code assumes all benchmark structures have the same composition + structure_composition = benchmark_structures[0].composition.reduced_formula + with open( + f"{filename_prefix}{structure_composition}.txt", + "a", + encoding="utf-8", + ) as file: + file.write( + f"{'Potential':<11}{'Structure':<11}{'MPID':<12}{'Displacement (Å)':<18}" + f"{'RMSE (THz)':<12}{'imagmodes(pot)':<16}{'imagmodes(dft)':<16}" + f"{'Database type':<16}{'(Hyper-)Parameters':<18}" + ) + + for metric in metrics_flattened: + with open( + f"{filename_prefix}{structure_composition}.txt", + "a", + encoding="utf-8", + ) as file: + # Build the SOAP dictionary or suffix value + soap_params = { # (atom-wise f, n_sparse, SOAP delta) + f"f={metric['atomwise_regularization_parameter']}": metric["soap_dict"] + } + + if metric["ml_model"] == "GAP": + key = next(iter(soap_params.keys())) + value = next(iter(soap_params.values())) + pretty_hyper_params = f"atom-wise {key}: n_sparse = {value['n_sparse']}, SOAP delta = {value['delta']}" + else: + pretty_hyper_params = "user defined" + + if not metric["suffix"]: + metric["suffix"] = "full" + if metric["benchmark_phonon_rmse"] is not None: + file.write( + f"\n{metric['ml_model']:<11}{structure_composition:<11}{metric['mp_id']:<12}" + f"{metric['displacement']:<18.2f}{metric['benchmark_phonon_rmse']:<12.5f}" + f"{metric['ml_imaginary_modes']!s:<16}{metric['dft_imaginary_modes']!s:<16}" + f"{metric['suffix']!s:<16}{pretty_hyper_params!s:<50}" + ) + else: + file.write( + f"\n{metric['ml_model']:<11}{structure_composition:<11}{metric['mp_id']:<12}" + f"{metric['displacement']:<18.2f}{'None':<12} " + f"{metric['ml_imaginary_modes']!s:<16}{metric['dft_imaginary_modes']!s:<16}" + f"{metric['suffix']!s:<16}{pretty_hyper_params!s:<50}" + ) + return Response(output=metrics)
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/autoplex/benchmark/phonons/utils.html b/_modules/autoplex/benchmark/phonons/utils.html new file mode 100644 index 000000000..0529174c2 --- /dev/null +++ b/_modules/autoplex/benchmark/phonons/utils.html @@ -0,0 +1,702 @@ + + + + + + + + + + autoplex.benchmark.phonons.utils — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+
+
+ + + + +
+ +

Source code for autoplex.benchmark.phonons.utils

+"""Utility functions for benchmarking jobs."""
+
+import matplotlib.pyplot as plt
+import numpy as np
+from matplotlib.figure import Figure
+from pymatgen.core.structure import Structure
+from pymatgen.phonon.bandstructure import PhononBandStructureSymmLine
+from pymatgen.phonon.plotter import PhononBSPlotter
+
+
+
+[docs] +def compute_bandstructure_benchmark_metrics( + ml_model: str, + structure: Structure, + mp_id: str, + ml_phonon_bs: PhononBandStructureSymmLine, + dft_phonon_bs: PhononBandStructureSymmLine, + ml_imag_modes: bool, + dft_imag_modes: bool, + atomwise_regularization_parameter: float, + soap_dict: dict, + suffix: str, + displacement: float = 0.01, +): + """ + Compute phonon band-structure benchmark metrics and generate associated plots. + + Parameters + ---------- + ml_model: str + ML model to be used. Default is GAP. + structure : .Structure + A structure object. + mp_id: + Materials Project ID. + ml_phonon_bs: PhononBandStructureSymmLine. + ML generated pymatgen phonon band-structure object. + dft_phonon_bs: PhononBandStructureSymmLine. + DFT generated pymatgen phonon band-structure object. + ml_imag_modes: bool + Whether the ML-based phonon band structure shows imaginary modes. + dft_imag_modes: bool + Whether the DFT-based phonon band structure shows imaginary modes. + displacement: float + Displacement distance for phonons + atomwise_regularization_parameter: float + Regularization value for the atom-wise force components. + suffix: str + GAP potential file suffix. + soap_dict: dict + Dictionary containing SOAP parameters. + + + Returns + ------- + dict including + Overall root mean squared error between DFT and ML phonon band-structure. + """ + # might fail if band structures are not the same + # TODO: Robust alternative would be a mesh computation + try: + # compute overall root mean squared error + overall_rmse = get_rmse(ml_bs=ml_phonon_bs, dft_bs=dft_phonon_bs) + + # saves rmse k-dependent plot + file_name = f"{structure.composition.reduced_formula}_rmse_phonons.pdf" + _ = rmse_qdep_plot( + ml_bs=ml_phonon_bs, + dft_bs=dft_phonon_bs, + which_q_path=2, + file_name=file_name, + img_format="pdf", + ) + + # saves DFT and ML phonon band-structure overlay plot + file_name = f"{structure.composition.reduced_formula}_band_comparison.pdf" + _ = compare_plot( + ml_model=ml_model, + ml_bs=ml_phonon_bs, + dft_bs=dft_phonon_bs, + file_name=file_name, + ) + except ValueError: + overall_rmse = None + return { + "benchmark_phonon_rmse": overall_rmse, + "dft_imaginary_modes": dft_imag_modes, + "ml_imaginary_modes": ml_imag_modes, + "ml_model": ml_model, + "mp_id": mp_id, + "structure": structure, + "displacement": displacement, + "atomwise_regularization_parameter": atomwise_regularization_parameter, + "soap_dict": soap_dict, + "suffix": suffix, + }
+ + + +
+[docs] +def get_rmse( + ml_bs: PhononBandStructureSymmLine, + dft_bs: PhononBandStructureSymmLine, + q_dependent_rmse: bool = False, +) -> float | list[float]: + """ + Compute root mean squared error (rmse) between DFT and ML phonon band-structure. + + Parameters + ---------- + ml_bs : PhononBandStructureSymmLine. + ML generated pymatgen phonon band-structure object + dft_bs : PhononBandStructureSymmLine. + DFT generated pymatgen phonon band-structure object + q_dependent_rmse : bool. + If true, method returns k-dependent rmse between the band-structures + + Returns + ------- + float or list[float] + Root mean squared error between DFT and ML phonon band-structure + """ + diff = np.sort(ml_bs.bands, axis=0) - np.sort(dft_bs.bands, axis=0) + + rmse = np.sqrt(np.mean(diff**2)) + + if q_dependent_rmse: + diff_here = np.transpose(diff) + rmse = [np.sqrt(np.mean(diff_here[i] ** 2)) for i in range(len(diff_here))] + + return rmse
+ + + +
+[docs] +def rmse_qdep_plot( + ml_bs: PhononBandStructureSymmLine, + dft_bs: PhononBandStructureSymmLine, + which_q_path=1, + file_name="rms.pdf", + img_format="pdf", +) -> plt: + """ + Save q dependent root mean squared error plot between DFT and ML phonon band-structure. + + Parameters + ---------- + ml_bs : PhononBandStructureSymmLine. + ML generated pymatgen phonon band-structure object. + dft_bs : PhononBandStructureSymmLine. + DFT generated pymatgen phonon band-structure object. + which_q_path : int. + If set 1, use ML band-structure as reference, if 2, uses DFT band-structure as reference. + file_name: str. + Name of the saved plot + img_format: str + File extension of plot to be saved, default is pdf. + + Returns + ------- + matplotlib.plt + A matplotlib figure with q-dependent RMSE. + """ + rmse_qp = get_rmse(ml_bs=ml_bs, dft_bs=dft_bs, q_dependent_rmse=True) + if which_q_path == 1: + plotter = PhononBSPlotter(bs=ml_bs) + elif which_q_path == 2: + plotter = PhononBSPlotter(bs=dft_bs) + + distances = [] + for element in plotter.bs_plot_data()["distances"]: + distances.extend(element) + + plt.close("all") + plt.plot(distances, rmse_qp) + plt.xticks( + ticks=plotter.bs_plot_data()["ticks"]["distance"], + labels=plotter.bs_plot_data()["ticks"]["label"], + ) + plt.xlabel("Wave vector") + plt.ylabel("Phonons RMS (THz)") + plt.savefig(file_name, format=img_format) + + return plt
+ + + +
+[docs] +def compare_plot( + ml_model: str, + ml_bs: PhononBandStructureSymmLine, + dft_bs: PhononBandStructureSymmLine, + file_name: str = "band_comparison.pdf", +) -> Figure: + """ + Save DFT and ML phonon band-structure overlay plot for visual comparison. + + Parameters + ---------- + ml_model: str + ML model to be used. Default is GAP. + ml_bs : PhononBandStructureSymmLine. + ML generated pymatgen phonon band-structure object + dft_bs : PhononBandStructureSymmLine. + DFT generated pymatgen phonon band-structure object + file_name: str. + Name of the saved plot + + Returns + ------- + matplotlib.plt + A matplotlib figure with DFT and ML generated phonon band-structure overlay + """ + plotter = PhononBSPlotter(bs=ml_bs) + plotter2 = PhononBSPlotter(bs=dft_bs) + new_plotter = plotter.plot_compare( + other_plotter={"DFT": plotter2}, self_label=ml_model + ) + + new_plotter.figure.savefig(fname=file_name) + + return new_plotter.figure
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/autoplex/data/common/flows.html b/_modules/autoplex/data/common/flows.html new file mode 100644 index 000000000..6ec8acbe3 --- /dev/null +++ b/_modules/autoplex/data/common/flows.html @@ -0,0 +1,942 @@ + + + + + + + + + + autoplex.data.common.flows — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+
+
+ + + + +
+ +

Source code for autoplex.data.common.flows

+"""Flows to create and check training data."""
+
+import logging
+import traceback
+from dataclasses import dataclass, field
+
+from atomate2.forcefields.jobs import (
+    ForceFieldRelaxMaker,
+    ForceFieldStaticMaker,
+)
+from atomate2.vasp.jobs.core import StaticMaker
+from atomate2.vasp.powerups import (
+    update_user_incar_settings,
+    update_user_potcar_settings,
+)
+from atomate2.vasp.sets.core import StaticSetGenerator
+from emmet.core.math import Matrix3D
+from jobflow import Flow, Maker, Response, job
+from pymatgen.core import Lattice
+from pymatgen.core.structure import Structure
+from pymatgen.io.ase import AseAtomsAdaptor
+
+from autoplex.data.common.jobs import (
+    convert_to_extxyz,
+    generate_randomized_structures,
+    get_supercell_job,
+    plot_force_distribution,
+)
+from autoplex.data.common.utils import (
+    ElementCollection,
+    flatten,
+)
+
+__all__ = ["DFTStaticLabelling", "GenerateTrainingDataForTesting"]
+
+logging.basicConfig(level=logging.DEBUG, format="[%(levelname)s] %(message)s")
+
+
+
+[docs] +@dataclass +class GenerateTrainingDataForTesting(Maker): + """Maker for generating training data to test it and check the forces. + + This Maker will first generate training data based on the chosen ML model (default is GAP) + by randomizing (ase rattle) atomic displacements in supercells of the provided input structures. + Then it will proceed with MLIP-based Phonon calculations (based on atomate2 PhononMaker), collect + all structure data in extended xyz files and plot the forces in histograms (per rescaling cell_factor + and total). + + Parameters + ---------- + name: str + Name of the flow. + bulk_relax_maker: ForceFieldRelaxMaker | None + Maker for the relax jobs. + static_energy_maker: ForceFieldStaticMaker | ForceFieldRelaxMaker | None + Maker for the static jobs. + + """ + + name: str = "generate_training_data_for_testing" + bulk_relax_maker: ForceFieldRelaxMaker | None = None + static_energy_maker: ForceFieldStaticMaker | ForceFieldRelaxMaker | None = None + +
+[docs] + def make( + self, + train_structure_list: list[Structure], + cell_factor_sequence: list[float] | None = None, + potential_filename: str = "gap.xml", + n_structures: int = 50, + rattle_std: float = 0.01, + relax_cell: bool = True, + steps: int = 1000, + supercell_matrix: Matrix3D | None = None, + config_type: str = "train", + x_min: int = 0, + x_max: int = 5, + bin_width: float = 0.125, + **relax_kwargs, + ): + """ + Generate ase.rattled structures from the training data and returns histogram plots of the forces. + + Parameters + ---------- + train_structure_list: list[Structure]. + List of pymatgen structures object. + cell_factor_sequence: list[float] + List of factor to resize cell parameters. + potential_filename: str + The param_file_name for :obj:`quippy.potential.Potential()'`. + n_structures : int. + Total number of randomly displaced structures to be generated. + rattle_std: float. + Rattle amplitude (standard deviation in normal distribution). + Default=0.01. + relax_cell : bool + Whether to allow the cell shape/volume to change during relaxation. + steps : int + Maximum number of ionic steps allowed during relaxation. + supercell_matrix: Matrix3D | None + The matrix to generate the supercell. + config_type: str + Configuration type of the data. + x_min: int + Minimum value for the plot x-axis. + x_max: int + Maximum value for the plot x-axis. + bin_width: float + Width of the plot bins. + relax_kwargs : dict + Keyword arguments that will get passed to :obj:`Relaxer.relax`. + + Returns + ------- + Matplotlib plots "count vs. forces". + """ + jobs = [] + if cell_factor_sequence is None: + cell_factor_sequence = [0.975, 1.0, 1.025, 1.05] + for structure in train_structure_list: + if self.bulk_relax_maker is None: + self.bulk_relax_maker = ForceFieldRelaxMaker( + calculator_kwargs={ + "args_str": "IP GAP", + "param_filename": str(potential_filename), + }, + force_field_name="GAP", + relax_cell=relax_cell, + steps=steps, + ) + if supercell_matrix is None: + supercell_matrix = [[3, 0, 0], [0, 3, 0], [0, 0, 3]] + + bulk_relax = self.bulk_relax_maker.make(structure=structure) + jobs.append(bulk_relax) + supercell = get_supercell_job( + structure=bulk_relax.output.structure, + supercell_matrix=supercell_matrix, + ) + jobs.append(supercell) + + for cell_factor in cell_factor_sequence: + rattled_job = generate_randomized_structures( + structure=supercell.output, + n_structures=n_structures, + volume_custom_scale_factors=[cell_factor], + rattle_std=rattle_std, + ) + jobs.append(rattled_job) + static_conv_jobs = self.static_run_and_convert( + rattled_job.output, + cell_factor, + config_type, + potential_filename, + **relax_kwargs, + ) + jobs.append(static_conv_jobs) + plots = plot_force_distribution( + cell_factor, static_conv_jobs.output, x_min, x_max, bin_width + ) + jobs.append(plots) + + return Flow(jobs=jobs, name=self.name) # , plots.output)
+ + +
+[docs] + @job + def static_run_and_convert( + self, + structure_list: list[Structure], + cell_factor: float, + config_type, + potential_filename, + **relax_kwargs, + ): + """ + Job for the static runs and the data conversion to the extxyz format. + + Parameters + ---------- + structure_list: list[Structure]. + List of pymatgen structures object. + cell_factor: float + Factor to resize cell parameters. + config_type: str + Configuration type of the data. + potential_filename: str + The param_file_name for :obj:`quippy.potential.Potential()'`. + relax_kwargs : dict + Keyword arguments that will get passed to :obj:`Relaxer.relax`. + + """ + jobs = [] + for rattled in structure_list: + if relax_kwargs == {}: + relax_kwargs = { + "interval": 50000, + "fmax": 0.5, + "traj_file": rattled.reduced_formula + + "_" + + f"{cell_factor}".replace(".", "") + + ".pkl", + } + if self.static_energy_maker is None: + self.static_energy_maker = ForceFieldRelaxMaker( + calculator_kwargs={ + "args_str": "IP GAP", + "param_filename": str(potential_filename), + }, + force_field_name="GAP", + relax_cell=False, + relax_kwargs=relax_kwargs, + steps=1, + ) + static_run = self.static_energy_maker.make(structure=rattled) + jobs.append(static_run) + conv_job = convert_to_extxyz( + static_run.output, + rattled.reduced_formula + + "_" + + f"{cell_factor}".replace(".", "") + + ".pkl", + config_type, + f"{cell_factor}".replace(".", ""), + ) + jobs.append(conv_job) + + return Response(replace=Flow(jobs), output=conv_job.output)
+
+ + + +
+[docs] +@dataclass +class DFTStaticLabelling(Maker): + """ + Maker to set up and run VASP static calculations for input structures, including bulk, isolated atoms, and dimers. + + It supports custom VASP input parameters and error handlers. + + Parameters + ---------- + name: str + Name of the flow. + isolated_atom: bool + If true, perform single-point calculations for isolated atoms. Default is False. + isolated_species: list[str] + List of species for which to perform isolated atom calculations. If None, + species will be automatically derived from the 'structures' list. Default is None. + e0_spin: bool + If true, include spin polarization in isolated atom and dimer calculations. + Default is False. + isolatedatom_box: list[float] + List of the lattice constants for a isolated_atom configuration. + dimer: bool + If true, perform single-point calculations for dimers. Default is False. + dimer_box: list[float] + The lattice constants of a dimer box. + dimer_species: list[str] + List of species for which to perform dimer calculations. If None, species + will be derived from the 'structures' list. Default is None. + dimer_range: list[float] + Range of distances for dimer calculations. + dimer_num: int + Number of different distances to consider for dimer calculations. + custom_incar: dict + Dictionary of custom VASP input parameters. If provided, will update the + default parameters. Default is None. + custom_potcar: dict + Dictionary of POTCAR settings to update. Keys are element symbols, values are the desired POTCAR labels. + Default is None. + + Returns + ------- + dict + A dictionary containing: + - 'dirs_of_vasp': List of directories containing VASP data. + - 'config_type': List of configuration types corresponding to each directory. + """ + + name: str = "do_dft_labelling" + isolated_atom: bool = False + isolated_species: list[str] | None = None + e0_spin: bool = False + isolatedatom_box: list[float] = field(default_factory=lambda: [20, 20, 20]) + dimer: bool = False + dimer_box: list[float] = field(default_factory=lambda: [20, 20, 20]) + dimer_species: list[str] | None = None + dimer_range: list[float] | None = None + dimer_num: int = 21 + custom_incar: dict | None = None + custom_potcar: dict | None = None + +
+[docs] + @job + def make( + self, + structures: list, + config_type: str | None = None, + ): + """ + Maker to set up and run VASP static calculations. + + Parameters + ---------- + structures : list[Structure] | list[list[Structure]] + List of structures for which to run the VASP static calculations. If None, + no bulk calculations will be performed. Default is None. + config_type : str + Configuration types corresponding to the structures. If None, defaults + to 'bulk'. Default is None. + """ + job_list = [] + + if isinstance(structures[0], list): + structures = flatten(structures, recursive=False) + + dirs: dict[str, list[str]] = {"dirs_of_vasp": [], "config_type": []} + + default_custom_set = { + "ADDGRID": "True", + "ENCUT": 520, + "EDIFF": 1e-06, + "ISMEAR": 0, + "SIGMA": 0.01, + "PREC": "Accurate", + "ISYM": None, + "KSPACING": 0.2, + "NPAR": 8, + "LWAVE": "False", + "LCHARG": "False", + "ENAUG": None, + "GGA": None, + "ISPIN": None, + "LAECHG": None, + "LELF": None, + "LORBIT": None, + "LVTOT": None, + "NSW": None, + "SYMPREC": None, + "NELM": 100, + "LMAXMIX": None, + "LASPH": None, + "AMIN": None, + } + + if self.custom_incar is not None: + default_custom_set.update(self.custom_incar) + + custom_set = default_custom_set + + st_m = StaticMaker( + input_set_generator=StaticSetGenerator(user_incar_settings=custom_set), + run_vasp_kwargs={"handlers": ()}, + ) + + if self.custom_potcar is not None: + st_m = update_user_potcar_settings(st_m, potcar_updates=self.custom_potcar) + + if structures: + for idx, struct in enumerate(structures): + static_job = st_m.make(structure=struct) + static_job.name = f"static_bulk_{idx}" + dirs["dirs_of_vasp"].append(static_job.output.dir_name) + if config_type: + dirs["config_type"].append(config_type) + else: + dirs["config_type"].append("bulk") + job_list.append(static_job) + + if self.isolated_atom: + try: + if self.isolated_species is not None: + syms = self.isolated_species + + elif (self.isolated_species is None) and (structures is not None): + # Get the species from the database + atoms = [AseAtomsAdaptor().get_atoms(at) for at in structures] + syms = ElementCollection(atoms).get_species() + + for idx, sym in enumerate(syms): + lattice = Lattice.orthorhombic( + self.isolatedatom_box[0], + self.isolatedatom_box[1], + self.isolatedatom_box[2], + ) + isolated_atom_struct = Structure(lattice, [sym], [[0.0, 0.0, 0.0]]) + static_job = st_m.make(structure=isolated_atom_struct) + static_job.name = f"static_isolated_{idx}" + static_job = update_user_incar_settings( + static_job, + {"KSPACING": 100.0, "ALGO": "All", "KPAR": 1}, + ) + + if self.e0_spin: + static_job = update_user_incar_settings( + static_job, {"ISPIN": 2} + ) + + dirs["dirs_of_vasp"].append(static_job.output.dir_name) + dirs["config_type"].append("IsolatedAtom") + job_list.append(static_job) + + except ValueError as e: + logging.error(f"Unknown species of isolated atoms! Exception: {e}") + traceback.print_exc() + + if self.dimer: + try: + atoms = [AseAtomsAdaptor().get_atoms(at) for at in structures] + if self.dimer_species is not None: + dimer_syms = self.dimer_species + elif (self.dimer_species is None) and (structures is not None): + # Get the species from the database + dimer_syms = ElementCollection(atoms).get_species() + pairs_list = ElementCollection(atoms).find_element_pairs(dimer_syms) + for pair in pairs_list: + for dimer_i in range(self.dimer_num): + if self.dimer_range is not None: + dimer_distance = self.dimer_range[0] + ( + self.dimer_range[1] - self.dimer_range[0] + ) * float(dimer_i) / float( + self.dimer_num - 1 + 0.000000000001 + ) + + lattice = Lattice.orthorhombic( + self.dimer_box[0], + self.dimer_box[1], + self.dimer_box[2], + ) + dimer_struct = Structure( + lattice, + [pair[0], pair[1]], + [[0.0, 0.0, 0.0], [dimer_distance, 0.0, 0.0]], + coords_are_cartesian=True, + ) + + static_job = st_m.make(structure=dimer_struct) + static_job.name = f"static_dimer_{dimer_i}" + static_job = update_user_incar_settings( + static_job, + {"KSPACING": 100.0, "ALGO": "All", "KPAR": 1}, + ) + + if self.e0_spin: + static_job = update_user_incar_settings( + static_job, {"ISPIN": 2} + ) + + dirs["dirs_of_vasp"].append(static_job.output.dir_name) + dirs["config_type"].append("dimer") + job_list.append(static_job) + + except ValueError: + logging.error("Unknown atom types in dimers!") + traceback.print_exc() + + return Response(replace=Flow(job_list), output=dirs)
+
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/autoplex/data/common/jobs.html b/_modules/autoplex/data/common/jobs.html new file mode 100644 index 000000000..fe1905a10 --- /dev/null +++ b/_modules/autoplex/data/common/jobs.html @@ -0,0 +1,1293 @@ + + + + + + + + + + autoplex.data.common.jobs — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+
+
+ + + + +
+ +

Source code for autoplex.data.common.jobs

+"""Jobs to create training data for ML potentials."""
+
+import logging
+import os
+import pickle
+import shutil
+import traceback
+from itertools import chain
+from pathlib import Path
+from typing import TYPE_CHECKING, Literal
+
+import matplotlib.pyplot as plt
+import numpy as np
+from ase.constraints import voigt_6_to_full_3x3_stress
+from ase.io import read, write
+from atomate2.utils.path import strip_hostname
+from emmet.core.math import Matrix3D
+from jobflow.core.job import job
+from phonopy.structure.cells import get_supercell
+from pymatgen.core.structure import Structure
+from pymatgen.io.ase import AseAtomsAdaptor
+from pymatgen.io.phonopy import get_phonopy_structure, get_pmg_structure
+from pymatgen.io.vasp.outputs import Vasprun
+
+from autoplex.data.common.utils import (
+    ElementCollection,
+    boltzhist_cur_dual_iter,
+    boltzhist_cur_one_shot,
+    create_soap_descriptor,
+    cur_select,
+    data_distillation,
+    flatten,
+    handle_rss_trajectory,
+    mc_rattle,
+    random_vary_angle,
+    scale_cell,
+    std_rattle,
+    stratified_dataset_split,
+    to_ase_trajectory,
+)
+from autoplex.fitting.common.regularization import set_custom_sigma
+
+if TYPE_CHECKING:
+    from ase import Atoms
+
+logging.basicConfig(level=logging.INFO, format="[%(levelname)s] %(message)s")
+
+
+
+[docs] +@job +def convert_to_extxyz(job_output, pkl_file, config_type, factor): + """ + Convert data and write extxyt file. + + Parameters + ---------- + job_output: + The (static) job output object. + pkl_file: str + A pickle file. + config_type: str + Configuration type of the data. + factor: str + String of a factor to resize cell parameters. + + """ + with open(Path(job_output.dir_name) / Path(pkl_file), "rb") as file: + traj_obj = pickle.load(file) + data = to_ase_trajectory(traj_obj=traj_obj) + data[-1].write("tmp.xyz") + file = read("tmp.xyz", index=":") + for i in file: + # TODO: enable switching to stress + virial_list = -voigt_6_to_full_3x3_stress(i.get_stress()) * i.get_volume() + i.info["REF_virial"] = " ".join(map(str, virial_list.flatten())) + del i.calc.results["stress"] + i.arrays["REF_forces"] = i.calc.results["forces"] + del i.calc.results["forces"] + i.info["REF_energy"] = i.calc.results["energy"] + del i.calc.results["energy"] + i.info["config_type"] = config_type + i.pbc = True + write("ref_" + factor + ".extxyz", file, append=True) + + return os.getcwd()
+ + + +
+[docs] +@job +def plot_force_distribution( + cell_factor: float, + path: str, + x_min: int = 0, + x_max: int = 5, + bin_width: float = 0.125, +): + """ + Plotter for the force distribution. + + Parameters + ---------- + cell_factor: float + Factor to resize cell parameters. + path: + Path to the ref_XYZ.extxyz file. + x_min: int + Minimum value for the plot x-axis. + x_max: int + Maximum value for the plot x-axis. + bin_width: float + Width of the plot bins. + + """ + plt.xlabel("Forces") + plt.ylabel("Count") + bins = np.arange(x_min, x_max + bin_width, bin_width) + plot_total = [] + + # TODO split data collection and plotting + + plot_data = [] + with open(path + "/ref_" + str(cell_factor).replace(".", "") + ".extxyz") as file: + for line in file: + # Split the line into columns + columns = line.split() + + # Check if the line has exactly 10 columns + if len(columns) == 10: + # Extract the last three columns + data = columns[-3:] + norm_data = np.linalg.norm(data, axis=-1) + plot_data.append(norm_data) + + plt.hist(plot_data, bins=bins, edgecolor="black") + plt.title(f"Data for factor {cell_factor}") + + plt.savefig("data_factor_" + str(cell_factor).replace(".", "") + ".png") + + plot_total += plot_data + plt.hist(plot_total, bins=bins, edgecolor="black") + plt.title("Data") + + plt.savefig("total_data.png")
+ + + +
+[docs] +@job +def get_supercell_job(structure: Structure, supercell_matrix: Matrix3D): + """ + Create a job to get the supercell. + + Parameters + ---------- + structure: Structure + The pymatgen structure object. + supercell_matrix: Matrix3D + The matrix to generate the supercell. + + Returns + ------- + supercell: Structure + A pymatgen structure object. + + """ + supercell = get_supercell( + unitcell=get_phonopy_structure(structure), supercell_matrix=supercell_matrix + ) + return get_pmg_structure(supercell)
+ + + +
+[docs] +@job(data=[Structure]) +def generate_randomized_structures( + structure: Structure, + supercell_matrix: Matrix3D | None = None, + distort_type: int = 0, + n_structures: int = 10, + volume_scale_factor_range: list[float] | None = None, + volume_custom_scale_factors: list[float] | None = None, + min_distance: float = 1.5, + angle_percentage_scale: float = 10, + angle_max_attempts: int = 1000, + rattle_type: int = 0, + rattle_std: float = 0.01, + rattle_seed: int = 42, + rattle_mc_n_iter: int = 10, + w_angle: list[float] | None = None, +): + """ + Take in a pymatgen Structure object and generates angle/volume distorted + rattled structures. + + Parameters + ---------- + structure: Structure. + Pymatgen structures object. + supercell_matrix: Matrix3D. + Matrix for obtaining the supercell. + distort_type: int. + 0- volume distortion, 1- angle distortion, 2- volume and angle distortion. Default=0. + n_structures: int. + Total number of distorted structures to be generated. + Must be provided if distorting volume without specifying a range, or if distorting angles. + Default=10. + volume_scale_factor_range: list[float] + [min, max] of volume scale factors. + e.g. [0.90, 1.10] will distort volume +-10%. + volume_custom_scale_factors: list[float] + Specify explicit scale factors (if range is not specified). + If None, will default to [0.90, 0.95, 0.98, 0.99, 1.01, 1.02, 1.05, 1.10]. + min_distance: float + Minimum separation allowed between any two atoms. + Default= 1.5A. + angle_percentage_scale: float + Angle scaling factor. + Default= 10 will randomly distort angles by +-10% of original value. + angle_max_attempts: int. + Maximum number of attempts to distort structure before aborting. + Default=1000. + w_angle: list[float] + List of angle indices to be changed i.e. 0=alpha, 1=beta, 2=gamma. + Default= [0, 1, 2]. + rattle_type: int. + 0- standard rattling, 1- Monte-Carlo rattling. Default=0. + rattle_std: float. + Rattle amplitude (standard deviation in normal distribution). + Default=0.01. + Note that for MC rattling, displacements generated will roughly be + rattle_mc_n_iter**0.5 * rattle_std for small values of n_iter. + rattle_seed: int. + Seed for setting up NumPy random state from which random numbers are generated. + Default=42. + rattle_mc_n_iter: int. + Number of Monte Carlo iterations. + Larger number of iterations will generate larger displacements. + Default=10. + + Returns + ------- + Response.output. + Volume or angle-distorted structures with rattled atoms. + """ + if supercell_matrix is None: + supercell_matrix = [[2, 0, 0], [0, 2, 0], [0, 0, 2]] + + supercell = get_supercell( + unitcell=get_phonopy_structure(structure), + supercell_matrix=supercell_matrix, + ) + structure = get_pmg_structure(supercell) + + # distort cells by volume or angle + if distort_type == 0: + distorted_cells = scale_cell( + structure=structure, + volume_scale_factor_range=volume_scale_factor_range, + n_structures=n_structures, + volume_custom_scale_factors=volume_custom_scale_factors, + ) + elif distort_type == 1: + distorted_cells = random_vary_angle( + structure=structure, + min_distance=min_distance, + angle_percentage_scale=angle_percentage_scale, + w_angle=w_angle, + n_structures=n_structures, + angle_max_attempts=angle_max_attempts, + ) + elif distort_type == 2: + initial_distorted_cells = scale_cell( + structure=structure, + volume_scale_factor_range=volume_scale_factor_range, + n_structures=n_structures, + volume_custom_scale_factors=volume_custom_scale_factors, + ) + distorted_cells = [] + for cell in initial_distorted_cells: + distorted_cell = random_vary_angle( + structure=cell, + min_distance=min_distance, + angle_percentage_scale=angle_percentage_scale, + w_angle=w_angle, + n_structures=1, + angle_max_attempts=angle_max_attempts, + ) + distorted_cells.append(distorted_cell) + distorted_cells = list(chain.from_iterable(distorted_cells)) + else: + raise TypeError("distort_type is not recognised") + + # distorted_cells=list(chain.from_iterable(distorted_cells)) + + # rattle cells by standard or mc + rattled_cells = ( + [ + std_rattle( + structure=cell, + n_structures=1, + rattle_std=rattle_std, + rattle_seed=rattle_seed + icell, + ) + for icell, cell in enumerate(distorted_cells) + ] + if rattle_type == 0 + else ( + [ + mc_rattle( + structure=cell, + n_structures=1, + rattle_std=rattle_std, + min_distance=min_distance, + rattle_seed=rattle_seed + icell, + rattle_mc_n_iter=rattle_mc_n_iter, + ) + for icell, cell in enumerate(distorted_cells) + ] + if rattle_type == 1 + else None + ) + ) + + if rattled_cells is None: + raise TypeError("rattle_type is not recognized") + + return list(chain.from_iterable(rattled_cells))
+ + + +
+[docs] +@job +def sample_data( + selection_method: Literal[ + "cur", "bcur1s", "bcur2i", "random", "uniform" + ] = "random", + num_of_selection: int = 5, + bcur_params: dict | None = None, + dir: list[str] | str | None = None, + structure: list[Structure] | list[list[Structure]] | None = None, + traj_path: list | None = None, + isolated_atom_energies: dict | None = None, + random_seed: int = None, + remove_traj_files: bool = False, +) -> list[Structure]: + """ + Job to sample training configurations from trajectories of MD/RSS. + + Parameters + ---------- + selection_method : Literal['cur', 'bcur1s', 'bcur2s', 'random', 'uniform'] + Method for selecting samples. Options include: + + - 'cur': Pure CUR selection. + + - 'bcur': Boltzmann flat histogram in enthalpy, then CUR. + + - 'bcur1s': Execute bcur with one shot (1s) + - 'bcur2i': Execute bcur with two iterations (2i) + + - 'random': Random selection. + - 'uniform': Uniform selection. + + num_of_selection: int + Number of structures to be sampled. + bcur_params: dict + Parameters for Boltzmann CUR selection. The default dictionary includes: + + - 'soap_paras': SOAP descriptor parameters + + - 'l_max': int, Maximum degree of spherical harmonics (default 12). + - 'n_max': int, Maximum number of radial basis functions (default 12). + - 'atom_sigma': float, Width of Gaussian smearing (default 0.0875). + - 'cutoff': float, Radial cutoff distance (default 10.5). + - 'cutoff_transition_width': float, Width of the transition region (default 1.0). + - 'zeta': float, Exponent for dot-product SOAP kernel (default 4.0). + - 'average': bool, Whether to average the SOAP vectors (default True). + - 'species': bool, Whether to consider species information (default True). + + - 'kt': float, Temperature in eV for Boltzmann weighting (default 0.3). + - 'frac_of_bcur': float, Fraction of Boltzmann CUR selections (default 0.8). + - 'bolt_max_num': int, Maximum number of Boltzmann selections (default 3000). + - 'kernel_exp': float, Exponent for the kernel (default 4.0). + - 'energy_label': str, Label for the energy data (default 'energy'). + + dir: str + Directory containing trajectory files for MD/RSS simulations. Default is None. + structure: list[Structure] + List of structures for sampling. Default is None. + traj_path: list[list[str]] + List of lists containing trajectory paths. Default is None. + isolated_atom_energies: dict + Dictionary of isolated energy values for species. Required for 'boltzhist_cur' + selection method. Default is None. + random_seed: int, optional + Seed for random number generation, ensuring reproducibility of sampling. + remove_traj_files: bool + Remove all trajectory files raised by RSS to save memory + + Returns + ------- + list of ase.Atoms + The selected atoms. + """ + default_bcur_params = { + "soap_paras": { + "l_max": 12, + "n_max": 12, + "atom_sigma": 0.0875, + "cutoff": 10.5, + "cutoff_transition_width": 1.0, + "zeta": 4.0, + "average": True, + "species": True, + }, + "kt": 0.3, + "frac_of_bcur": 0.8, + "bolt_max_num": 3000, + "kernel_exp": 4.0, + "energy_label": "energy", + } + + if bcur_params is not None: + default_bcur_params.update(bcur_params) + + bcur_params = default_bcur_params + pressures = None + + if dir is not None: + if isinstance(dir, list): + atoms = [read(i, index=":") for i in dir] + atoms = flatten(atoms, recursive=True) + else: + atoms = read(dir, index=":") + + elif structure is not None: + if isinstance(structure[0], list): + structure = flatten(structure, recursive=False) + atoms = [AseAtomsAdaptor().get_atoms(at) for at in structure] + + else: + atoms, pressures = handle_rss_trajectory(traj_path, remove_traj_files) + + if selection_method in {"cur", "bcur1s", "bcur2i"}: + n_species = ElementCollection( + flatten(atoms, recursive=True) + ).get_number_of_species() + species_Z = ElementCollection(flatten(atoms, recursive=True)).get_species_Z() + + if not isinstance(bcur_params["soap_paras"], dict): + raise TypeError("soap_paras must be a dictionary") + if not isinstance(bcur_params["kt"], float): + raise TypeError("kt must be a float") + if not isinstance(bcur_params["frac_of_bcur"], float): + raise TypeError("frac_of_bcur must be a float") + if not isinstance(bcur_params["bolt_max_num"], int): + raise TypeError("bolt_max_num must be an integer") + if not isinstance(bcur_params["kernel_exp"], float): + raise TypeError("kernel_exp must be a float") + if not isinstance(bcur_params["energy_label"], str): + raise TypeError("energy_label must be a string") + + soap_paras = bcur_params["soap_paras"] + descriptor = create_soap_descriptor(soap_paras, n_species, species_Z) + + if selection_method == "cur": + selected_atoms = cur_select( + atoms=atoms, + selected_descriptor=descriptor, + kernel_exp=bcur_params["kernel_exp"], + select_nums=num_of_selection, + stochastic=True, + random_seed=random_seed, + ) + + elif selection_method in {"bcur1s", "bcur2i"}: + if isolated_atom_energies is not None: + isolated_atom_energies = { + int(k): v for k, v in isolated_atom_energies.items() + } + else: + raise ValueError("Please provide the energy of isolated atoms!") + + if selection_method == "bcur1s": + selected_atoms = boltzhist_cur_one_shot( + atoms=atoms, + isolated_atom_energies=isolated_atom_energies, + bolt_frac=bcur_params["frac_of_bcur"], + bolt_max_num=bcur_params["bolt_max_num"], + cur_num=num_of_selection, + kernel_exp=bcur_params["kernel_exp"], + kt=bcur_params["kt"], + energy_label=bcur_params["energy_label"], + pressures=pressures, + descriptor=descriptor, + random_seed=random_seed, + ) + else: + selected_atoms = boltzhist_cur_dual_iter( + atoms=atoms, + isolated_atom_energies=isolated_atom_energies, + bolt_frac=bcur_params["frac_of_bcur"], + bolt_max_num=bcur_params["bolt_max_num"], + cur_num=num_of_selection, + kernel_exp=bcur_params["kernel_exp"], + kt=bcur_params["kt"], + energy_label=bcur_params["energy_label"], + pressures=pressures, + descriptor=descriptor, + random_seed=random_seed, + ) + + if selected_atoms is None: + raise ValueError( + "Unable to sample correctly. Please recheck the parameters!" + ) + + selected_atoms = [AseAtomsAdaptor().get_structure(at) for at in selected_atoms] + + elif selection_method == "random": + if random_seed is not None: + np.random.seed(random_seed) + + structure = [AseAtomsAdaptor().get_structure(at) for at in atoms] + + try: + selection = np.random.choice( + len(structure), num_of_selection, replace=False + ) + selected_atoms = [at for i, at in enumerate(structure) if i in selection] + + except ValueError: + logging.error( + "The number of selected structures must be less than the total!" + ) + traceback.print_exc() + + elif selection_method == "uniform": + try: + indices = np.linspace(0, len(atoms) - 1, num_of_selection, dtype=int) + structure = [AseAtomsAdaptor().get_structure(at) for at in atoms] + selected_atoms = [structure[idx] for idx in indices] + + except ValueError: + logging.error( + "The number of selected structures must be less than the total!" + ) + traceback.print_exc() + + if selected_atoms is None: + raise ValueError("Unable to sample correctly. Please recheck the parameters!") + + return selected_atoms
+ + + +
+[docs] +@job +def collect_dft_data( + vasp_ref_file: str = "vasp_ref.extxyz", + rss_group: str = "RSS", + vasp_dirs: dict | None = None, +) -> dict: + """ + Collect VASP data from specified directories. + + Parameters + ---------- + vasp_ref_file : str + Reference file for VASP data. Default is 'vasp_ref.extxyz'. + rss_group : str + Group name for GAP RSS. Default is 'RSS'. + vasp_dirs : dict + Dictionary containing VASP directories and configuration types. Should have keys: + + - 'dirs_of_vasp': list + List of directories containing VASP data. + - 'config_type': list + List of configuration types corresponding to each directory. + + Returns + ------- + dict: + A dictionary containing + + - 'vasp_ref_dir': Directory of the VASP reference file. + - 'isolated_atom_energies': Isolated energy values. + """ + if vasp_dirs is None: + raise ValueError( + "vasp_dirs must be provided and should contain 'dirs_of_vasp' and 'config_type' keys." + ) + + if "dirs_of_vasp" not in vasp_dirs or "config_type" not in vasp_dirs: + raise ValueError( + "vasp_dirs must contain 'dirs_of_vasp' and 'config_type' keys." + ) + + dirs = [safe_strip_hostname(value) for value in vasp_dirs["dirs_of_vasp"]] + config_types = vasp_dirs["config_type"] + + logging.info("Attempting collecting VASP...") + + if dirs is None: + raise ValueError("dft_dir must be specified if collect_vasp is True") + + atoms = [] + isolated_atom_energies = {} + + for i, val in enumerate(dirs): + if os.path.exists(os.path.join(val, "vasprun.xml.gz")): + + converged = check_convergence_vasp(os.path.join(val, "vasprun.xml.gz")) + + if converged: + at = read(os.path.join(val, "vasprun.xml.gz"), index=":") + for at_i in at: + virial_list = ( + -voigt_6_to_full_3x3_stress(at_i.get_stress()) + * at_i.get_volume() + ) + at_i.info["REF_virial"] = " ".join(map(str, virial_list.flatten())) + del at_i.calc.results["stress"] + at_i.arrays["REF_forces"] = at_i.calc.results["forces"] + del at_i.calc.results["forces"] + at_i.info["REF_energy"] = at_i.calc.results["free_energy"] + del at_i.calc.results["energy"] + del at_i.calc.results["free_energy"] + atoms.append(at_i) + at_i.info["config_type"] = config_types[i] + if ( + at_i.info["config_type"] != "dimer" + and at_i.info["config_type"] != "IsolatedAtom" + ): + at_i.pbc = True + at_i.info["rss_group"] = rss_group + else: + at_i.info["rss_nonperiodic"] = "T" + + if at_i.info["config_type"] == "IsolatedAtom": + at_ids = at_i.get_atomic_numbers() + # array_key = at_ids.tostring() + isolated_atom_energies[int(at_ids[0])] = at_i.info["REF_energy"] + + else: + logging.warning( + f"Calculation did not converge for path: {os.path.join(val, 'vasprun.xml.gz')}" + ) + + logging.info(f"Total {len(atoms)} structures from VASP are exactly collected.") + + write(vasp_ref_file, atoms, format="extxyz", parallel=False) + + dir_path = Path.cwd() + + vasp_ref_dir = os.path.join(dir_path, vasp_ref_file) + + return { + "vasp_ref_dir": vasp_ref_dir, + "isolated_atom_energies": isolated_atom_energies, + }
+ + + +
+[docs] +def check_convergence_vasp(path: str) -> bool: + """ + Check if VASP calculation has converged. + + Parameters + ---------- + path: Path + Path to the vasp output file to check convergence. + + Return + ------ + bool + True if a run is converged both ionically and electronically. + """ + vasprun = Vasprun(path) + converged_e = vasprun.converged_electronic + converged_i = vasprun.converged_ionic + + return converged_e and converged_i
+ + + +
+[docs] +def safe_strip_hostname(value): + """ + Strip the hostname from a given path or URL. + + Parameters + ---------- + value: str + The path or URL from which to strip the hostname. + + Returns + ------- + Optional[str] + The path or URL without the hostname if the operation is successful, + otherwise None. + """ + try: + return strip_hostname(value) + except Exception as e: + print(f"Error processing '{value}': {e}") + return None
+ + + +
+[docs] +@job +def preprocess_data( + vasp_ref_dir: str, + test_ratio: float | None = None, + regularization: bool = False, + retain_existing_sigma: bool = False, + scheme: str = "linear-hull", + distillation: bool = False, + force_max: float = 40, + force_label: str = "REF_forces", + pre_database_dir: str | None = None, + reg_minmax: list[tuple] | None = None, + isolated_atom_energies: dict | None = None, +) -> Path: + """ + Preprocesse data to before fiting machine learning models. + + This function handles tasks such as splitting the dataset, + applying regularization, accumulating database, and filtering + structures based on maximum force values. + + Parameters + ---------- + vasp_ref_dir: str + Path to the directory containing the reference VASP calculation data. + test_ratio: float + The proportion of the test set after splitting the data. + If None, no splitting will be performed. + regularization: bool + If true, apply regularization. This only works for GAP. + retain_existing_sigma: bool + Whether to keep the current sigma values for specific configuration types. + If set to True, existing sigma values for specific configurations will remain unchanged. + scheme: str + Scheme to use for regularization. + distillation: bool + If True, apply data distillation. + force_max: float + Maximum force value to exclude structures. + force_label: str + The label of force values to use for distillation. + pre_database_dir : str + Directory where the previous database was saved. + reg_minmax: list[tuple] + A list of tuples representing the minimum and maximum + values for regularization. + isolated_atom_energies: dict + A dictionary containing isolated energy values for different species. + + Returns + ------- + Path + The current working directory. + """ + atoms = ( + data_distillation(vasp_ref_dir, force_max, force_label) + if distillation + else read(vasp_ref_dir, index=":") + ) + + if test_ratio == 0 or test_ratio is None: + train_structures, test_structures = atoms, atoms + else: + train_structures, test_structures = stratified_dataset_split(atoms, test_ratio) + + if pre_database_dir and os.path.exists(pre_database_dir): + files_to_copy = ["train.extxyz", "test.extxyz"] + current_working_directory = os.getcwd() + + for file_name in files_to_copy: + source_file_path = os.path.join(pre_database_dir, file_name) + destination_file_path = os.path.join(current_working_directory, file_name) + if os.path.exists(source_file_path): + shutil.copy(source_file_path, destination_file_path) + print(f"File {file_name} has been copied to {destination_file_path}") + + write("train.extxyz", train_structures, format="extxyz", append=True) + write("test.extxyz", test_structures, format="extxyz", append=True) + + if regularization: + atoms_reg: list[Atoms] = read("train.extxyz", index=":") + + if reg_minmax is None: + reg_minmax = [(0.1, 1), (0.001, 0.1), (0.0316, 0.316), (0.0632, 0.632)] + + atom_with_sigma = set_custom_sigma( + atoms=atoms_reg, + reg_minmax=reg_minmax, + isolated_atom_energies=isolated_atom_energies, + scheme=scheme, + retain_existing_sigma=retain_existing_sigma, + ) + + write("train.extxyz", atom_with_sigma, format="extxyz") + + return Path.cwd()
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/autoplex/data/common/utils.html b/_modules/autoplex/data/common/utils.html new file mode 100644 index 000000000..28e8cbf82 --- /dev/null +++ b/_modules/autoplex/data/common/utils.html @@ -0,0 +1,2195 @@ + + + + + + + + + + autoplex.data.common.utils — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+
+
+ + + + +
+ +

Source code for autoplex.data.common.utils

+"""Utility functions for training data jobs."""
+
+import logging
+import os
+import random
+import shutil
+import warnings
+from collections.abc import Iterable
+from itertools import chain
+from multiprocessing import Pool
+from pathlib import Path
+
+import ase.io
+import matplotlib.pyplot as plt
+import numpy as np
+import pandas as pd
+from ase import Atoms
+from ase.atoms import Atom
+from ase.calculators.singlepoint import SinglePointCalculator
+from ase.io import Trajectory as AseTrajectory
+from ase.io import write
+from ase.units import GPa
+from hiphive.structure_generation import generate_mc_rattled_structures
+from pymatgen.core import Structure
+from pymatgen.io.ase import AseAtomsAdaptor
+from quippy import descriptors
+from scipy.sparse.linalg import LinearOperator, svds
+from sklearn.model_selection import StratifiedShuffleSplit
+
+from autoplex.fitting.common.regularization import (
+    calculate_hull_3d,
+    get_convex_hull,
+    get_e_distance_to_hull,
+    get_e_distance_to_hull_3d,
+    label_stoichiometry_volume,
+)
+
+logging.basicConfig(level=logging.INFO, format="[%(levelname)s] %(message)s")
+
+
+
+[docs] +def flatten(atoms_object: Atoms | Iterable, recursive: bool = False) -> list[Atoms]: + """ + Flatten an iterable fully, but excluding Atoms objects. + + Parameters + ---------- + atoms_object: Atoms or Iterable + An Atoms object or an iterable containing Atoms objects. + recursive: bool + If set to True, the function will recursively flatten the iterable. + + Returns + ------- + A flattened list containing only Atoms objects. + + """ + iteration_list = [] + + if recursive: + for element in atoms_object: + if isinstance(element, Iterable) and not isinstance( + element, (str, bytes, ase.atoms.Atoms, ase.Atoms) + ): + iteration_list.extend(flatten(element, recursive=True)) + else: + iteration_list.append(element) + return iteration_list + + return [item for sublist in atoms_object for item in sublist]
+ + + +
+[docs] +def rms_dict(x_ref: np.ndarray | list, x_pred: np.ndarray | list) -> dict: + """Compute RMSE and standard deviation of predictions with reference data. + + Adapted and adjusted from libatoms GAP tutorial page + https://libatoms.github.io/GAP/gap_fitting_tutorial.html#make-simple-plots-of-the-energies-and-forces-on-the-EMT-and-GAP-datas + + Parameters + ---------- + x_ref: np.ndarray. + List of reference data. + x_pred: np.ndarray. + List of prediction. + Note that x_ref and x_pred should be of same shape. + + Returns + ------- + dict + A dict with RMSE and std deviation of predictions. + """ + x_ref = np.array(x_ref) + x_pred = np.array(x_pred) + if np.shape(x_pred) != np.shape(x_ref): + raise ValueError("WARNING: not matching shapes in rms") + error_2 = (x_ref - x_pred) ** 2 + average = np.sqrt(np.average(error_2)) + std_ = np.sqrt(np.var(error_2)) + return {"rmse": average, "std": std_}
+ + + +
+[docs] +def to_ase_trajectory( + traj_obj, filename: str = "atoms.traj", store_magmoms=False +) -> AseTrajectory: # adapted from ase + """ + Convert to an ASE .Trajectory. + + Parameters + ---------- + traj_obj: + Trajectory object. + filename : str | None + Name of the file to write the ASE trajectory to. + If None, no file is written. + store_magmoms: + The bool to store magnetic moments. + """ + for idx in range(len(traj_obj["atom_positions"])): + atoms = Atoms(symbols=list(traj_obj["atomic_number"])) # .atoms.copy() + atoms.set_positions(traj_obj["atom_positions"][idx]) + atoms.set_cell(traj_obj["cell"][idx]) + atoms.set_pbc("T T T") + + kwargs = { + "energy": traj_obj["energy"][idx], + "forces": traj_obj["forces"][idx], + "stress": traj_obj["stresses"][idx], + } + if store_magmoms: + kwargs["magmom"] = traj_obj.magmoms[idx] + + atoms.calc = SinglePointCalculator(atoms=atoms, **kwargs) + with AseTrajectory(filename, "a" if idx > 0 else "w", atoms=atoms) as f: + f.write() + + return AseTrajectory(filename, "r")
+ + + +
+[docs] +def scale_cell( + structure: Structure, + volume_scale_factor_range: list[float] | None = None, + n_structures: int = 10, + volume_custom_scale_factors: list[float] | None = None, +) -> list[Structure]: + """ + Take in a pymatgen Structure object and generates stretched or compressed structures. + + Parameters + ---------- + structure : Structure. + Pymatgen structures object. + volume_scale_factor_range : list[float] + [min, max] of volume scale factors. + n_structures : int. + If specified a range, the number of structures to be generated with + volume distortions equally spaced between min and max. + volume_custom_scale_factors : list[float] + Specify explicit scale factors (if range is not specified). + If None, will default to [0.90, 0.95, 0.98, 0.99, 1.01, 1.02, 1.05, 1.10]. + + Returns + ------- + Response.output. + Stretched or compressed structures. + """ + atoms = AseAtomsAdaptor.get_atoms(structure) + distorted_cells = [] + + if volume_custom_scale_factors is not None: + scale_factors_defined = volume_custom_scale_factors + warnings.warn("Using your custom lattice scale factors", stacklevel=2) + if volume_custom_scale_factors is None: + if volume_scale_factor_range is None: + volume_scale_factor_range = [0.90, 1.1] + + scale_factors_defined = np.arange( + volume_scale_factor_range[0], + volume_scale_factor_range[1] + + (volume_scale_factor_range[1] - volume_scale_factor_range[0]) + / (n_structures - 1), + (volume_scale_factor_range[1] - volume_scale_factor_range[0]) + / (n_structures - 1), + ) + + if not np.isclose(scale_factors_defined, 1.0).any(): + scale_factors_defined = np.append(scale_factors_defined, 1) + scale_factors_defined = np.sort(scale_factors_defined) + + warnings.warn( + f"Generated lattice scale factors {scale_factors_defined} within your range", + stacklevel=2, + ) + + for scale_factor in scale_factors_defined: + # make copy of ground state + cell = atoms.copy() + # set lattice parameter scale factor + lattice_scale_factor = scale_factor ** (1 / 3) + # scale cell volume and atomic positions + cell.set_cell(lattice_scale_factor * atoms.get_cell(), scale_atoms=True) + # store scaled cell + distorted_cells.append(AseAtomsAdaptor.get_structure(cell)) + return distorted_cells
+ + + +
+[docs] +def check_distances(structure: Structure, min_distance: float = 1.5) -> bool: + """ + Take in a pymatgen Structure object and check minimum distances between atoms using minimum image convention. + + Useful after distorting cell angles and rattling to check atoms aren't too close. + + Parameters + ---------- + structure : Structure. + Pymatgen structures object. + min_distance: float + Minimum separation allowed between any two atoms. Default= 1.5A. + + Returns + ------- + Response.output. + "True" if atoms are sufficiently spaced out i.e. all pairwise interatomic distances > min_distance. + """ + atoms = AseAtomsAdaptor.get_atoms(structure) + + for i in range(len(atoms)): + indices = [j for j in range(len(atoms)) if j != i] + distances = atoms.get_distances(i, indices, mic=True) + for distance in distances: + if distance < min_distance: + warnings.warn("Atoms too close.", stacklevel=2) + return False + return True
+ + + +
+[docs] +def random_vary_angle( + structure: Structure, + min_distance: float = 1.5, + angle_percentage_scale: float = 10, + w_angle: list[float] | None = None, + n_structures: int = 8, + angle_max_attempts: int = 1000, +) -> list[Structure]: + """ + Take in a pymatgen Structure object and generates angle-distorted structures. + + Parameters + ---------- + structure : Structure. + Pymatgen structures object. + min_distance: float + Minimum separation allowed between atoms. Default= 1.5A. + angle_percentage_scale: float + Angle scaling factor. + Default= 10 will randomly distort angles by +-10% of original value. + w_angle: list[float] + List of angle indices to be changed i.e. 0=alpha, 1=beta, 2=gamma. + Default= [0, 1, 2]. + n_structures: int. + Number of angle-distorted structures to generate. + angle_max_attempts: int. + Maximum number of attempts to distort structure before aborting. + Default=1000. + + Returns + ------- + Response.output. + Angle-distorted structures. + """ + atoms = AseAtomsAdaptor.get_atoms(structure) + distorted_angle_cells = [] + generated_structures = 0 # counter to keep track of generated structures + + if w_angle is None: + w_angle = [0, 1, 2] + + while generated_structures < n_structures: + attempts = 0 + # make copy of ground state + atoms_copy = atoms.copy() + + # stretch lattice parameters by 3% before changing angles + # helps atoms to not be too close + distorted_cells = scale_cell( + AseAtomsAdaptor.get_structure(atoms_copy), + volume_custom_scale_factors=[1.03], + ) + + distorted_supercells: Atoms = AseAtomsAdaptor.get_atoms(distorted_cells[0]) + + # getting stretched supercell out of array + newcell = distorted_supercells.cell.cellpar() + + # current angles + alpha = atoms_copy.cell.cellpar()[3] + beta = atoms_copy.cell.cellpar()[4] + gamma = atoms_copy.cell.cellpar()[5] + + # convert angle distortion scale + angle_percentage_scale = angle_percentage_scale / 100 + min_scale = 1 - angle_percentage_scale + max_scale = 1 + angle_percentage_scale + + while attempts < angle_max_attempts: + attempts += 1 + # new random angles within +-10% (default) of current angle + new_alpha = random.randint(int(alpha * min_scale), int(alpha * max_scale)) + new_beta = random.randint(int(beta * min_scale), int(beta * max_scale)) + new_gamma = random.randint(int(gamma * min_scale), int(gamma * max_scale)) + + newvalues = [new_alpha, new_beta, new_gamma] + + for wang, newv in zip(w_angle, newvalues): + newcell[wang + 3] = newv + + # converting newcell back into an Atoms object so future functions work + # scaling atoms to new distorted cell + atoms_copy.set_cell(newcell, scale_atoms=True) + + # if successful structure generated, i.e. atoms are not too close, then break loop + if check_distances(AseAtomsAdaptor.get_structure(atoms_copy), min_distance): + # store scaled cell + distorted_angle_cells.append(AseAtomsAdaptor.get_structure(atoms_copy)) + generated_structures += 1 + break # break the inner loop if successful + else: + raise RuntimeError( + "Maximum attempts (1000) reached without distorting angles successfully." + ) + + return distorted_angle_cells
+ + + +
+[docs] +def std_rattle( + structure: Structure, + n_structures: int = 5, + rattle_std: float = 0.01, + rattle_seed: int = 42, +) -> list[Structure]: + """ + Take in a pymatgen Structure object and generates rattled structures. + + Uses standard ASE rattle. + + Parameters + ---------- + structure : Structure. + The pymatgen structures object. + n_structures: int. + Number of rattled structures to generate. + rattle_std: float. + Rattle amplitude (standard deviation in normal distribution). Default=0.01. + rattle_seed: int. + Seed for setting up NumPy random state from which random numbers are generated. Default= 42. + + Returns + ------- + Response.output. + Rattled structures. + """ + atoms = AseAtomsAdaptor.get_atoms(structure) + rattled_xtals = [] + for i in range(n_structures): + if i == 0: + copy = atoms.copy() + copy.rattle(stdev=rattle_std, seed=rattle_seed) + rattled_xtals.append(AseAtomsAdaptor.get_structure(copy)) + if i > 0: + rattle_seed = rattle_seed + 1 + copy = atoms.copy() + copy.rattle(stdev=rattle_std, seed=rattle_seed) + rattled_xtals.append(AseAtomsAdaptor.get_structure(copy)) + return rattled_xtals
+ + + +
+[docs] +def mc_rattle( + structure: Structure, + n_structures: int = 5, + rattle_std: float = 0.003, + min_distance: float = 1.5, + rattle_seed: int = 42, + rattle_mc_n_iter: int = 10, +) -> list[Structure]: + """ + Take in a pymatgen Structure object and generates rattled structures. + + Randomly draws displacements with a MC trial step that penalises displacements leading to very small interatomic + distances. + Displacements generated will roughly be n_iter**0.5 * rattle_std for small values of n_iter. + See https://hiphive.materialsmodeling.org/moduleref/structures.html?highlight=generate_mc_rattled_structures for + more details. + + Parameters + ---------- + structure : Structure. + The pymatgen structures object. + n_structures: int. + Number of rattled structures to generate. + rattle_std: float. + Rattle amplitude (standard deviation in normal distribution). N.B. this value is not connected to the final + average displacement for the structures. Default= 0.003. + min_distance: float. + Minimum separation of any two atoms in the rattled structures. Used for computing the probability for each + rattle move. Default= 1.5A. + rattle_seed: int. + Seed for setting up NumPy random state from which random numbers are generated. Default= 42. + rattle_mc_n_iter: int. + Number of Monte Carlo iterations. Larger number of iterations will generate larger displacements. Default=10. + + Returns + ------- + Response.output. + Monte-Carlo rattled structures. + """ + atoms = AseAtomsAdaptor.get_atoms(structure) + mc_rattle = generate_mc_rattled_structures( + atoms=atoms, + n_structures=n_structures, + rattle_std=rattle_std, + d_min=min_distance, + seed=rattle_seed, + n_iter=rattle_mc_n_iter, + ) + return [AseAtomsAdaptor.get_structure(xtal) for xtal in mc_rattle]
+ + + +
+[docs] +def filter_outlier_energy( + in_file: str, out_file: str, criteria: float = 0.0005 +) -> None: + """ + Filter data outliers via energy criteria and write them into files. + + Parameters + ---------- + in_file: + Reference file (e.g. DFT). + out_file: + MLIP generated data file. + criteria: + Energy filter threshold. + + """ + # read files + in_atoms = ase.io.read(in_file, ":") + for atoms in in_atoms: + kwargs = { + "energy": atoms.info["REF_energy"], + } + atoms.calc = SinglePointCalculator(atoms=atoms, **kwargs) + out_atoms = ase.io.read(out_file, ":") + + atoms_in = [] + atoms_out = [] + outliers = [] + + for at_in, at_out in zip(in_atoms[:-1], out_atoms): + en_in = at_in.get_potential_energy() / len(at_in.get_chemical_symbols()) + en_out = at_out.get_potential_energy() / len(at_out.get_chemical_symbols()) + en_error = rms_dict(en_in, en_out) + if abs(en_error["rmse"]) < criteria: + atoms_in.append(at_in) + atoms_out.append(at_out) + else: + outliers.append(at_in) + + output_files = { + "filtered_in_energy": atoms_in, + "filtered_out_energy": atoms_out, + "outliers_energy": outliers, + } + + # Iterate over output files and write them + for suffix, atom_list in output_files.items(): + path = Path(in_file).with_name(Path(in_file).name.replace("train", suffix)) + path.parent.mkdir(parents=True, exist_ok=True) # Ensure directory exists + write(path, atom_list, append=True)
+ + + +
+[docs] +def filter_outlier_forces( + in_file: str, out_file: str, symbol="Si", criteria: float = 0.1 +) -> None: + """ + Filter data outliers via force criteria and write them into files. + + Parameters + ---------- + in_file: + Reference file (e.g. DFT). + out_file: + MLIP generated data file. + symbol: + Chemical symbol. + criteria: + Force filter threshold. + + """ + # read files + in_atoms = ase.io.read(in_file, ":") + for atoms in in_atoms[:-1]: + kwargs = { + "forces": atoms.arrays["REF_forces"], + } + atoms.calc = SinglePointCalculator(atoms=atoms, **kwargs) + out_atoms = ase.io.read(out_file, ":") + atoms_in = [] + atoms_out = [] + outliers = [] + # extract data for only one species + for at_in, at_out in zip(in_atoms[:-1], out_atoms): + # get the symbols + sym_all = at_in.get_chemical_symbols() + # add force for each atom + force_error = [] + for j, sym in enumerate(sym_all): + if sym in symbol: + in_force = at_in.get_forces()[j] + out_force = at_out.arrays["force"][j] + rms = rms_dict(in_force, out_force) + force_error.append(rms["rmse"]) + at_in.info["max RMSE"] = max(force_error) if force_error else 0 + at_in.info["avg RMSE"] = ( + sum(force_error) / len(force_error) if force_error else 0 + ) + at_in.info["RMSE"] = force_error + if not any(np.any(value > criteria) for value in force_error): + atoms_in.append(at_in) + atoms_out.append(at_out) + else: + outliers.append(at_in) + + output_files = { + "filtered_in_force": atoms_in, + "filtered_out_force": atoms_out, + "outliers_force": outliers, + } + + # Iterate over output files and write them + for suffix, atom_list in output_files.items(): + path = Path(in_file).with_name(Path(in_file).name.replace("train", suffix)) + path.parent.mkdir(parents=True, exist_ok=True) # Ensure directory exists + write(path, atom_list, append=True)
+ + + +
+[docs] +def energy_plot( + in_file: str | Path, + out_file: str | Path, + ax: plt.Axes, + title: str = "Plot of energy", + label: str = "energy", +) -> float: + """ + Plot the distribution of energy per atom on the output vs the input. + + Adapted and adjusted from libatoms GAP tutorial page + https://libatoms.github.io/GAP/gap_fitting_tutorial.html#make-simple-plots-of-the-energies-and-forces-on-the-EMT-and-GAP-datas + + Parameters + ---------- + in_file: + Reference file (e.g. DFT). + out_file: + MLIP generated data file. + ax: + Panel position in plt.subplots. + title: + Title of the plot. + label: + Legend label + + """ + # read files + in_atoms = ase.io.read(in_file, ":") + out_atoms = ase.io.read(out_file, ":") + atoms_in: list = [] + atoms_out: list = [] + for at_in, at_out in zip(in_atoms, out_atoms): + kwargs = { + "energy": at_in.info["REF_energy"], + } + at_in.calc = SinglePointCalculator(atoms=at_in, **kwargs) + for at, atoms in [(at_in, atoms_in), (at_out, atoms_out)]: + if at.info["config_type"] not in {"isolated_atom", "IsolatedAtom"}: + atoms.append(at) + # list energies + ener_in = [ + at.get_potential_energy() / len(at.get_chemical_symbols()) for at in atoms_in + ] + ener_out = [ + at.get_potential_energy() / len(at.get_chemical_symbols()) for at in atoms_out + ] + # scatter plot of the data + ax.scatter(ener_in, ener_out, label=label) + # get the appropriate limits for the plot + for_limits = np.array(ener_in + ener_out) + elim = (for_limits.min() - 0.01, for_limits.max() + 0.01) + ax.set_xlim(elim) + ax.set_ylim(elim) + # add line of slope 1 for reference + ax.plot(elim, elim, c="k") + # set labels + ax.set_ylabel("energy by GAP / eV") + ax.set_xlabel("energy by DFT / eV") + # set title + ax.set_title(title) + # set legend + ax.legend(loc="upper right") + # add text about RMSE + _rms = rms_dict(ener_in, ener_out) + rmse_text = ( + "RMSE:\n" + + str(np.round(_rms["rmse"], 7)) + + " +- " + + str(np.round(_rms["std"], 9)) + + "eV/atom" + ) + ax.text( + 0.9, + 0.1, + rmse_text, + transform=ax.transAxes, + fontsize="large", + horizontalalignment="right", + verticalalignment="bottom", + ) + + return _rms["rmse"]
+ + + +
+[docs] +def force_plot( + in_file: str | Path, + out_file: str | Path, + ax: plt.Axes, + symbol: str = "Si", + title: str = "Plot of force", + label: str = "force for ", +) -> float: + """ + Plot the distribution of force components per atom on the output vs the input. + + Only plots for the given atom type(s). + + Adapted and adjusted from libatoms GAP tutorial page + https://libatoms.github.io/GAP/gap_fitting_tutorial.html#make-simple-plots-of-the-energies-and-forces-on-the-EMT-and-GAP-datas + + Parameters + ---------- + in_file: + Reference file (e.g. DFT). + out_file: + MLIP generated data file. + ax: + Panel position in plt.subplots. + symbol: + Chemical symbol. + title: + Title of the plot. + label: + Legend label + + """ + in_atoms = ase.io.read(in_file, ":") + for atoms in in_atoms[:-1]: + kwargs = { + "forces": atoms.arrays["REF_forces"], + } + atoms.calc = SinglePointCalculator(atoms=atoms, **kwargs) + out_atoms = ase.io.read(out_file, ":") + # extract data for only one species + in_force, out_force = [], [] + for at_in, at_out in zip(in_atoms[:-1], out_atoms): + # get the symbols + sym_all = at_in.get_chemical_symbols() + # add force for each atom + for j, sym in enumerate(sym_all): + if sym in symbol: + in_force.append(at_in.get_forces()[j]) + # out_force.append(at_out.get_forces()[j]) \ + out_force.append( + at_out.arrays["force"][j] + ) # because QUIP and ASE use different names + # convert to np arrays, much easier to work with + # in_force = np.array(in_force) + # out_force = np.array(out_force) + # scatter plot of the data + ax.scatter(in_force, out_force, label=label + symbol) + # get the appropriate limits for the plot + for_limits = np.array(in_force + out_force) + flim = (for_limits.min() - 1, for_limits.max() + 1) + ax.set_xlim(flim) + ax.set_ylim(flim) + # add line of + ax.plot(flim, flim, c="k") + # set labels + ax.set_ylabel("force by GAP / (eV/Å)") + ax.set_xlabel("force by DFT / (eV/Å)") + # set title + ax.set_title(title) + # set legend + ax.legend(loc="upper right") + # add text about RMSE + _rms = rms_dict(in_force, out_force) + rmse_text = ( + "RMSE:\n" + + str(np.round(_rms["rmse"], 3)) + + " +- " + + str(np.round(_rms["std"], 5)) + + "eV/Å" + ) + ax.text( + 0.9, + 0.1, + rmse_text, + transform=ax.transAxes, + fontsize="large", + horizontalalignment="right", + verticalalignment="bottom", + ) + + return _rms["rmse"]
+ + + +
+[docs] +def plot_energy_forces( + title: str, + energy_limit: float, + force_limit: float, + species_list: list | None = None, + train_name: str = "train.extxyz", + test_name: str = "test.extxyz", +) -> None: + """ + Plot energy and forces of the data. + + Adapted and adjusted from libatoms GAP tutorial page + https://libatoms.github.io/GAP/gap_fitting_tutorial.html#make-simple-plots-of-the-energies-and-forces-on-the-EMT-and-GAP-datas + + Parameters + ---------- + title: str + Title of the plot. + energy_limit: float + Energy limit for data filtering. + force_limit: list + Force limit for data filtering. + species_list: str + List of species. + train_name: str + Name of the training data file. + test_name: str + Name of the test data file. + """ + quip_train_file = train_name.replace("train", "quip_train") + quip_test_file = test_name.replace("test", "quip_test") + path = Path(train_name) + if species_list is None: + species_list = ["Si"] + fig, ax_list = plt.subplots(nrows=3, ncols=2, gridspec_kw={"hspace": 0.3}) + fig.set_size_inches(10, 15) + ax_list = ax_list.flat[:] + rmse = ["Energy and forces and train and test data\n"] + + pretty_species_list = ( + str(species_list).replace("['", "").replace("']", "").replace("'", "") + ) + + energy_rmse_train = energy_plot( + train_name, quip_train_file, ax_list[0], "Energy on training data" + ) + rmse.append(f"Energy train: {energy_rmse_train}") + for species in species_list: + force_rmse_train = force_plot( + train_name, + quip_train_file, + ax_list[1], + species, + f"Force on training data - {pretty_species_list}", + ) + rmse.append(f"Force train {species}: {force_rmse_train}") + energy_rmse_test = energy_plot( + test_name, quip_test_file, ax_list[2], "Energy on test data" + ) + rmse.append(f"Energy test: {energy_rmse_test}") + + filter_outlier_energy(train_name, quip_train_file, energy_limit) + # filter_outlier_energy(test_name, quip_test_file, energy_limit) + for species in species_list: + force_rmse_test = force_plot( + test_name, + quip_test_file, + ax_list[3], + species, + f"Force on test data - {pretty_species_list}", + ) + rmse.append(f"Force test {species}: {force_rmse_test}") + filter_outlier_forces(train_name, quip_train_file, species, force_limit) + # filter_outlier_forces(test_name, quip_test_file, species, force_limit) + + energy_plot( + path.with_name(path.name.replace("train", "filtered_in_energy")), + path.with_name(path.name.replace("train", "filtered_out_energy")), + ax_list[4], + "Energy on filtered data", + "energy-filtered data, energy", + ) + for species in species_list: + force_plot( + path.with_name(path.name.replace("train", "filtered_in_force")), + path.with_name(path.name.replace("train", "filtered_out_force")), + ax_list[5], + species, + f"Force on filtered data - {pretty_species_list}", + "energy-filtered data, force for ", + ) + energy_plot( + path.with_name(path.name.replace("train", "filtered_in_energy")), + path.with_name(path.name.replace("train", "filtered_out_energy")), + ax_list[4], + "Energy on filtered data", + "force-filtered data, energy", + ) + for species in species_list: + force_plot( + path.with_name(path.name.replace("train", "filtered_in_force")), + path.with_name(path.name.replace("train", "filtered_out_force")), + ax_list[5], + species, + f"Force on filtered data - {pretty_species_list}", + "force-filtered data, force for ", + ) + + fig.suptitle(title, fontsize=16) + plt.savefig(train_name.replace("train", "energy_forces").replace(".extxyz", ".png")) + + with open("energy_train_rmse.txt", "a") as file: + for entry in rmse: + file.write(f"{entry}\n")
+ + + +
+[docs] +class ElementCollection: + """ + A class to handle different species operations for a collection of atoms. + + The Species class provides methods to extract unique chemical elements (species), + determine all possible pairs of these species, and retrieve their atomic numbers + in a formatted string. + """ + + def __init__(self, atoms): + self.atoms = atoms + +
+[docs] + def get_species(self) -> list: + """Extract a list of unique species (chemical elements) from the atoms.""" + sepcies_list = [] + + for at in self.atoms: + sym_all = at.get_chemical_symbols() + syms = list(set(sym_all)) + for sym in syms: + if sym in sepcies_list: + continue + sepcies_list.append(sym) + + return sepcies_list
+ + +
+[docs] + def find_element_pairs(self, symb_list=None) -> list: + """ + Generate a list of all possible unique pairs of species. + + It can operate on an optional list of symbols or default to + using the species extracted from the atoms. + + Parameters + ---------- + symb_list: + List of chemical symbols. + + """ + species_list = self.get_species() if symb_list is None else symb_list + + pairs = [] + + for i in range(len(species_list)): + for j in range(i, len(species_list)): + pair = (species_list[i], species_list[j]) + pairs.append(pair) + + return pairs
+ + +
+[docs] + def get_number_of_species(self) -> int: + """Return the number of unique species present among the atoms.""" + return int(len(self.get_species()))
+ + +
+[docs] + def get_species_Z(self) -> str: + """Return a formatted string of atomic numbers of the unique species.""" + atom_numbers = [] + for atom_type in self.get_species(): + atom = Atoms(atom_type, [(0, 0, 0)]) + atom_numbers.append(int(atom.get_atomic_numbers()[0])) + + species_Z = "{" + for i in range(len(atom_numbers) - 1): + species_Z += str(atom_numbers[i]) + " " + species_Z += str(atom_numbers[-1]) + "}" + + return species_Z
+
+ + + +
+[docs] +def parallel_calc_descriptor_vec(atom: Atoms, selected_descriptor: str) -> Atoms: + """ + Calculate the SOAP descriptor vector for a given atom and hypers in parallel. + + Parameters + ---------- + atom : ase.Atoms + The atom for which to calculate the descriptor vector. + selected_descriptor : str + The quip descriptor string to use for the calculation. + + Returns + ------- + ase.Atom + The input atoms, with the descriptor vector added to its info dictionary. + + Notes + ----- + The descriptor vector is added to the atom's info dictionary with the key 'descriptor_vec'. + """ + desc_object = descriptors.Descriptor(selected_descriptor) + atom.info["descriptor_vec"] = desc_object.calc(atom)["data"] + + return atom
+ + + +
+[docs] +def cur_select( + atoms, + selected_descriptor, + kernel_exp, + select_nums, + stochastic=True, + random_seed=None, +) -> list[Atoms] | None: + """ + Perform CUR selection on a set of atoms to get representative SOAP descriptors. + + Parameters + ---------- + atoms: list of ase.Atoms + The atoms for which to perform CUR selection. + selected_descriptor: str + The quip descriptor string to use for the calculation. + kernel_exp: float + The kernel exponent to use in the calculation. + select_nums: int + The number of atoms to select. + stochastic: bool + Whether to perform stochastic CUR selection. + random_seed: int + The seed for the random number generator. + + Returns + ------- + list of ase.Atoms + The selected atoms. + + Notes + ----- + This function calculates the descriptor vector for each atom, + then performs CUR selection on the resulting vectors. + + References + ---------- + * Title: Research data supporting "De novo exploration and self-guided learning of potential-energy surfaces" + * Script: select_by_descriptor.py + * Author: Noam Bernstein, Gábor Csányi and Volker L. Deringer + * Date 11/10/2019 + * Availability: https://www.repository.cam.ac.uk/items/3aff252b-a583-4e7c-afc9-9dc1540cc37e + * License: Attribution 4.0 International (CC BY 4.0) license. + """ + if random_seed is not None: + np.random.seed(random_seed) + + if isinstance(atoms[0], list): + print("flattening") + fatoms = flatten(atoms, recursive=True) + else: + fatoms = atoms + + num_workers = min(len(fatoms), os.cpu_count() or 1) + + with Pool( + processes=num_workers + ) as pool: # TODO: implement argument for number of cores throughout + ats = pool.starmap( + parallel_calc_descriptor_vec, + [(atom, selected_descriptor) for atom in fatoms], + ) + + if isinstance(ats, list) & (len(ats) != 0): + at_descs = np.array([at.info["descriptor_vec"] for at in ats]).T + m = ( + np.matmul((np.squeeze(at_descs)).T, np.squeeze(at_descs)) ** kernel_exp + if kernel_exp > 0.0 + else at_descs + ) + + def descriptor_svd(at_descs, num, do_vectors="vh"): + def mv(v): + return np.dot(at_descs, v) + + def rmv(v): + return np.dot(at_descs.T, v) + + A = LinearOperator(at_descs.shape, matvec=mv, rmatvec=rmv, matmat=mv) + return svds(A, k=num, return_singular_vectors=do_vectors) + + (_, _, vt) = descriptor_svd( + m, min(max(1, int(select_nums / 2)), min(m.shape) - 1) + ) + c_scores = np.sum(vt**2, axis=0) / vt.shape[0] + if stochastic: + selected = sorted( + np.random.choice( + range(len(ats)), size=select_nums, replace=False, p=c_scores + ) + ) + else: + selected = sorted(np.argsort(c_scores)[-select_nums:]) + + selected_atoms = [ats[i] for i in selected] + + for at in selected_atoms: + del at.info["descriptor_vec"] + + return selected_atoms + + return None
+ + + +
+[docs] +def boltzhist_cur_one_shot( + atoms: list[Atoms] | list[list[Atoms]], + descriptor: str, + isolated_atom_energies: dict, + bolt_frac: float = 0.1, + bolt_max_num: int = 3000, + cur_num: int = 100, + kernel_exp: float = 4, + kt: float = 0.3, + energy_label: str = "energy", + pressures: list[float] | list[list[float]] | None = None, + random_seed: int = None, +) -> list | None: + """ + Sample atoms from a list according to boltzmann energy weighting and CUR diversity. + + Parameters + ---------- + atoms: list[ase.Atoms] or list[list[ase.Atoms]] + The atoms from which to select. If this is a list of lists, it is flattened. + descriptor: str + The quippy SOAP descriptor string for CUR. + isolated_atom_energies: dict + Dictionary of isolated energy values for species. Required for 'boltzhist_cur' + selection method. + bolt_frac: float + The fraction to control the flat Boltzmann selection number. + bolt_max_num: int + The maximum number of atoms to select by Boltzmann-weighted flat histogram. + cur_num: int + The number of atoms to select by CUR. + kernel_exp: float + The exponent for the dot-product SOAP kernel. + kt: float + The product of the Boltzmann constant and the temperature, in eV. + energy_label: str + The label for the energy property in the atoms. + pressures: list[float] or list[list[float]] + The pressures at which the atoms have been optimized, in GPa. + random_seed : int + The seed for the random number generator. + + Returns + ------- + list of ase.Atoms + The selected atoms. These are copies of the atoms in the input list. + + Notes + ----- + The algorithm uses a combination of CUR selection and Boltzmann weighting + to select the atoms with diversity and low energy. + + References + ---------- + * Title: Research data supporting "De novo exploration and self-guided learning of potential-energy surfaces" + * Script: select_enthalpy_flat_histogram.py + * Author: Noam Bernstein, Gábor Csányi and Volker L. Deringer + * Date 11/10/2019 + * Availability: https://www.repository.cam.ac.uk/items/3aff252b-a583-4e7c-afc9-9dc1540cc37e + * License: Attribution 4.0 International (CC BY 4.0) license. + """ + if random_seed is not None: + np.random.seed(random_seed) + + if isinstance(atoms[0], list): + print("flattening") + fatoms = flatten(atoms, recursive=True) + else: + fatoms = atoms + + if pressures is None: + logging.info("Pressures not supplied, attempting to use pressure in atoms dict") + + try: + ps = np.array([at.info["RSS_applied_pressure"] for at in fatoms]) + except RuntimeError: + print("No pressures, so can't Boltzmann weight") + + else: + ps = flatten_list(pressures) + + enthalpies = [] + + at_ids = [atom.get_atomic_numbers() for atom in fatoms] + + if energy_label == "energy": + formation_energies = [] + for ct, atom in enumerate(fatoms): + if "energy" in atom.info: + formation_energy = atom.info["energy"] - sum( + [isolated_atom_energies[j] for j in at_ids[ct]] + ) + else: + formation_energy = atom.get_potential_energy() - sum( + [isolated_atom_energies[j] for j in at_ids[ct]] + ) + formation_energies.append(formation_energy) + formation_energies = np.array(formation_energies) + + else: + formation_energies = np.array( + [ + atom.info[energy_label] + - sum([isolated_atom_energies[j] for j in at_ids[ct]]) + for ct, atom in enumerate(fatoms) + ] + ) + + for i, at in enumerate(fatoms): + enthalpy = (formation_energies[i] + at.get_volume() * ps[i] * GPa) / len(at) + enthalpies.append(enthalpy) + + enthalpies = np.array(enthalpies) + min_H = np.min(enthalpies) + config_prob = [] + histo = np.histogram(enthalpies) + for H in enthalpies: + bin_i = np.searchsorted(histo[1][1:], H, side="right") + if bin_i == len(histo[1][1:]): + bin_i = bin_i - 1 + p = 1.0 / histo[0][bin_i] if histo[0][bin_i] > 0.0 else 0.0 + if kt > 0.0: + p *= np.exp(-(H - min_H) / kt) + config_prob.append(p) + + select_num = round(bolt_frac * len(fatoms)) + + select_num = select_num if select_num < bolt_max_num else bolt_max_num + + config_prob = np.array(config_prob) + selected_bolt_ats = [] + for _ in range(select_num): + config_prob /= np.sum(config_prob) + cumul_prob = np.cumsum(config_prob) + rv = np.random.uniform() + config_i = np.searchsorted(cumul_prob, rv) + selected_bolt_ats.append(fatoms[config_i]) + # remove from config_prob by converting to list + config_prob = np.delete(config_prob, config_i) + # remove from other lists + del fatoms[config_i] + enthalpies = np.delete(enthalpies, config_i) + + if cur_num < select_num: + selected_atoms = cur_select( + atoms=selected_bolt_ats, + selected_descriptor=descriptor, + kernel_exp=kernel_exp, + select_nums=cur_num, + stochastic=True, + random_seed=random_seed, + ) + else: + selected_atoms = selected_bolt_ats + + return selected_atoms
+ + + +
+[docs] +def boltzhist_cur_dual_iter( + atoms: list[Atoms] | list[list[Atoms]], + descriptor: str, + isolated_atom_energies: dict, + bolt_frac: float = 0.1, + bolt_max_num: int = 3000, + cur_num: int = 100, + kernel_exp: float = 4, + kt: float = 0.3, + energy_label: str = "energy", + pressures: list[list[float]] | None = None, + random_seed: int = None, +) -> list | None: + """ + Execute sampling with two iterations. + + Each iteration includes a Boltzmann flat histogram in enthalpy + followed by a CUR process. + + Parameters + ---------- + atoms: list[ase.Atoms] or list[list[ase.Atoms]] + The atoms from which to select. If this is a list of lists, it is flattened. + descriptor: str + The quippy SOAP descriptor string for CUR. + isolated_atom_energies: dict + Dictionary of isolated energy values for species. Required for 'boltzhist_cur' + selection method. Default is None. + bolt_frac: float + The fraction to control the flat Boltzmann selection number. + bolt_max_num: int + The maximum number of atoms to select by Boltzmann-weighted flat histogram. + cur_num: int + The number of atoms to select by CUR. + kernel_exp: float + The exponent for the dot-product SOAP kernel. + kt: float + The product of the Boltzmann constant and the temperature, in eV. + energy_label: str + The label for the energy property in the atoms. + pressures: list[float] or list[list[float]] + The pressures at which the atoms have been optimized, in GPa. + random_seed : int + The seed for the random number generator. + + Returns + ------- + list of ase.Atoms + The selected atoms. These are copies of the atoms in the input list. + + Notes + ----- + This function selects the most diverse atoms based on the chosen algorithm. + The algorithm uses a combination of CUR selection and Boltzmann weighting to select the atoms. + """ + atom_minima = [ats[-1] for ats in atoms] + minima_indices = [ats[-1].info["unique_starting_index"] for ats in atoms] + pressure_minima = None if pressures is None else [p[-1] for p in pressures] + + selected_minima = boltzhist_cur_one_shot( + atoms=atom_minima, + descriptor=descriptor, + isolated_atom_energies=isolated_atom_energies, + bolt_frac=bolt_frac, + bolt_max_num=bolt_max_num, + cur_num=cur_num, + kernel_exp=kernel_exp, + kt=kt, + energy_label=energy_label, + pressures=pressure_minima, + random_seed=random_seed, + ) + + if selected_minima is None: + raise ValueError( + "The structures obtained from the first bcur sampling cannot be None." + ) + + selected_minima_indices = [ + at.info["unique_starting_index"] for at in selected_minima + ] + selected__indices = [minima_indices.index(item) for item in selected_minima_indices] + selected_trajs = [atoms[i] for i in selected__indices] + selected_trajs_pressure = ( + None if pressures is None else [pressures[j] for j in selected__indices] + ) + + return boltzhist_cur_one_shot( + atoms=selected_trajs, + descriptor=descriptor, + isolated_atom_energies=isolated_atom_energies, + bolt_frac=bolt_frac, + bolt_max_num=bolt_max_num, + cur_num=cur_num, + kernel_exp=kernel_exp, + kt=kt, + energy_label=energy_label, + pressures=selected_trajs_pressure, + random_seed=random_seed, + )
+ + + +
+[docs] +def convexhull_cur( + atoms: list[Atoms], + descriptor: str, + bolt_frac: float = 0.1, + bolt_max_num: int = 3000, + cur_num: int = 100, + kernel_exp: float = 4, + kt: float = 0.5, + energy_label: str = "REF_energy", + isolated_atom_energies: dict = None, + element_order: list | None = None, + scheme: str = "linear-hull", +) -> list | None: + """ + Sample atoms from a list according to Boltzmann energy weighting relative to convex hull and CUR diversity. + + Parameters + ---------- + atoms: list of ase.Atoms + The atoms for which to perform CUR selection. + bolt_frac: float + The fraction to control the proportion of atoms kept + during the Boltzmann selection step. + bolt_max_num: int + The maximum number of atoms to select by Boltzmann flat + histogram. + cur_num: int + The number of atoms to select by CUR. + kernel_exp: float + The kernel exponent to use in the calculation. + kt: float + The product of the Boltzmann constant and the temperature, + in eV. + energy_label: str + The label for the energy property in the atoms. + descriptor: str, optional + The quip descriptor string to use for the calculation. + isolated_atom_energies: dict, optional + The isolated atom energies for each element in the system. + element_order: list of str, optional + The order of elements for the isolated atom energies. + scheme: str, optional + The scheme to use for the convex hull calculation. + Default is 'linear-hull' (2D E,V hull). + For 2-component systems with varying stoichiometry, + use 'volume-stoichiometry' (3D E,V,mole-fraction hull). + TODO: need to generalise this to ND hulls for mcp systems. + GST good test case. + + Returns + ------- + list of ase.Atoms + The selected atoms. + + Notes + ----- + This function calculates the descriptor vector for each atom, + then performs CUR selection on the resulting vectors. + The selection is based on the convex hull of the vectors. + """ + if isinstance(atoms[0], list): + print("flattening") + fatoms = flatten(atoms, recursive=True) + else: + fatoms = atoms + + if isolated_atom_energies is None: + raise KeyError("isolated_atom_energies must be supplied for convexhull_cur") + + if scheme == "linear-hull": + hull, p = get_convex_hull(fatoms, energy_name=energy_label) + des = np.array( + [ + get_e_distance_to_hull(hull, at, energy_name=energy_label) + for at in fatoms + ] + ) + + elif scheme == "volume-stoichiometry": + points = label_stoichiometry_volume( + fatoms, + isolated_atom_energies=isolated_atom_energies, + energy_name=energy_label, + element_order=element_order, + ) + hull = calculate_hull_3d(points) + + des = np.array( + [ + get_e_distance_to_hull_3d( + hull, + at, + isolated_atom_energies=isolated_atom_energies, + energy_name=energy_label, + element_order=element_order, + ) + for at in fatoms + ] + ) + print("it will be coming soon!") + + else: + raise ValueError( + 'scheme must be either "linear-hull" or "volume-stoichiometry"' + ) + + histo = np.histogram(des) + config_prob = [] + min_ec = np.min(des) + + for ec in des: + bin_i = np.searchsorted(histo[1][1:], ec, side="right") + if bin_i == len(histo[1][1:]): + bin_i = bin_i - 1 + p = 1.0 / histo[0][bin_i] if histo[0][bin_i] > 0.0 else 0.0 + if kt > 0.0: + p *= np.exp(-(ec - min_ec) / kt) + config_prob.append(p) + + select_num = round(bolt_frac * len(fatoms)) + + select_num = select_num if select_num < bolt_max_num else bolt_max_num + + config_prob = np.array(config_prob) + selected_bolt_ats = [] + for _ in range(select_num): + config_prob /= np.sum(config_prob) + cumul_prob = np.cumsum(config_prob) # cumulate prob + rv = np.random.uniform() + config_i = np.searchsorted(cumul_prob, rv) + selected_bolt_ats.append(fatoms[config_i]) + # remove from config_prob by converting to list + config_prob = np.delete(config_prob, config_i) + # remove from other lists + del fatoms[config_i] + des = np.delete(des, config_i) + + # implement CUR + if cur_num < select_num: + selected_atoms = cur_select( + atoms=selected_bolt_ats, + selected_descriptor=descriptor, + kernel_exp=kernel_exp, + select_nums=cur_num, + stochastic=True, + ) + else: + selected_atoms = selected_bolt_ats + + return selected_atoms
+ + + +
+[docs] +def data_distillation( + vasp_ref_dir: str, force_max: float, force_label: str +) -> list[Atom | Atoms]: + """ + For data distillation. + + Parameters + ---------- + vasp_ref_dir: str + VASP reference data directory. + force_max: float + Maximally allowed force. + force_label: str + The label for the force property in the atoms. + + Returns + ------- + atoms_distilled: + List of distilled atoms. + + """ + atoms = ase.io.read(vasp_ref_dir, index=":") + + atoms_distilled = [] + for at in atoms: + forces = np.abs(at.arrays[force_label]) + f_component_max = np.max(forces) + + if f_component_max < force_max: + atoms_distilled.append(at) + + logging.warning( + f"After distillation, there are still {len(atoms_distilled)} data points remaining." + ) + + return atoms_distilled
+ + + +
+[docs] +def stratified_dataset_split(atoms: Atoms, split_ratio: float) -> tuple[ + list[Atom | Atoms] + | list[Atom | Atoms | list[Atom | Atoms] | list[Atom | Atoms | list]], + list[Atom | Atoms | list[Atom | Atoms] | list[Atom | Atoms | list]], +]: + """ + Split the dataset. + + Parameters + ---------- + atoms: Atoms + ASE Atoms object + split_ratio: float + Parameter to divide the training set and the test set. + + Returns + ------- + train_structures, test_structures: + Split-up datasets of train structures and test structures. + + """ + atom_bulk = [] + atom_isolated_and_dimer = [] + for at in atoms: + if ( + at.info["config_type"] != "dimer" + and at.info["config_type"] != "IsolatedAtom" + ): + atom_bulk.append(at) + else: + atom_isolated_and_dimer.append(at) + + if len(atoms) != len(atom_bulk): + atoms = atom_bulk + + average_energies = np.array([atom.info["REF_energy"] / len(atom) for atom in atoms]) + # sort by energy + sorted_indices = np.argsort(average_energies) + atoms = [atoms[i] for i in sorted_indices] + average_energies = average_energies[sorted_indices] + + stratified_average_energies = pd.qcut(average_energies, q=2, labels=False) + split = StratifiedShuffleSplit(n_splits=1, test_size=split_ratio, random_state=42) + + for train_index, test_index in split.split(atoms, stratified_average_energies): + train_structures = [atoms[i] for i in train_index] + test_structures = [atoms[i] for i in test_index] + + if atom_isolated_and_dimer: + train_structures = atom_isolated_and_dimer + train_structures + + return train_structures, test_structures
+ + + +
+[docs] +def create_soap_descriptor( + soap_paras: dict[str, int | float | str], n_species: int, species_Z: str +) -> str: + """ + Generate a SOAP descriptor string based on the given parameters. + + Parameters + ---------- + soap_paras: + A dictionary containing SOAP parameters. + n_species: + The number of species. + species_Z: + A string representing species Z. + """ + return ( + "soap l_max=" + + str(soap_paras["l_max"]) + + " n_max=" + + str(soap_paras["n_max"]) + + " atom_sigma=" + + str(soap_paras["atom_sigma"]) + + " cutoff=" + + str(soap_paras["cutoff"]) + + " n_species=" + + str(n_species) + + " species_Z=" + + species_Z + + " cutoff_transition_width=" + + str(soap_paras["cutoff_transition_width"]) + + " average=" + + str(soap_paras["average"]) + )
+ + + +
+[docs] +def flatten_list(input_list: list | list[list]) -> list: + """Flatten a nested list into a single list if necessary.""" + if ( + isinstance(input_list, list) + and len(input_list) > 0 + and isinstance(input_list[0], list) + ): + return list(chain.from_iterable(input_list)) + + return input_list
+ + + +
+[docs] +def handle_rss_trajectory( + traj_path, remove_traj_files +) -> tuple[list[list], list[list]]: + """ + Handle trajectory and associated information. + + Parameters + ---------- + traj_path: list | None + List of dictionaries containing trajectory information. + Each dictionary should have keys 'traj_path' and 'pressure'. + If None, an empty list will be used. + remove_traj_files: bool + Whether to remove the directories containing trajectory files + after processing them. Default is False. + + Returns + ------- + tuple: + atoms: list + List of ASE Atoms objects read from the trajectory files. + pressures: list + List of pressure values corresponding to the atoms. + """ + atoms = [] + pressures = [] + traj_path = [] if traj_path is None else flatten_list(traj_path) + traj_dirs = [] + + if all(i is None for i in traj_path): + raise ValueError("No valid trajectory path was obtained!") + + for traj in traj_path: + if traj is not None and Path(traj).exists(): + print("Processing trajectory:", traj) + at = ase.io.read(traj, index=":") + atoms.append(at) + pressure = [i.info["RSS_applied_pressure"] for i in at] + pressures.append(pressure) + traj_dirs.append(os.path.dirname(traj)) + + if remove_traj_files and traj_dirs: + traj_dirs = list(set(traj_dirs)) + for dir_path in traj_dirs: + if os.path.exists(dir_path) and os.path.isdir(dir_path): + shutil.rmtree(dir_path) + os.makedirs(dir_path) + + return atoms, pressures
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/autoplex/data/phonons/flows.html b/_modules/autoplex/data/phonons/flows.html new file mode 100644 index 000000000..649deb99a --- /dev/null +++ b/_modules/autoplex/data/phonons/flows.html @@ -0,0 +1,1381 @@ + + + + + + + + + + autoplex.data.phonons.flows — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+
+
+ + + + +
+ +

Source code for autoplex.data.phonons.flows

+"""Flows to create training data for ML potentials."""
+
+from dataclasses import dataclass, field
+
+from atomate2.common.jobs.phonons import run_phonon_displacements
+from atomate2.forcefields.flows.phonons import PhononMaker as FFPhononMaker
+from atomate2.forcefields.jobs import (
+    ForceFieldRelaxMaker,
+    ForceFieldStaticMaker,
+)
+from atomate2.vasp.flows.core import DoubleRelaxMaker
+from atomate2.vasp.flows.phonons import PhononMaker
+from atomate2.vasp.jobs.base import BaseVaspMaker
+from atomate2.vasp.jobs.core import StaticMaker, TightRelaxMaker
+from atomate2.vasp.jobs.phonons import PhononDisplacementMaker
+from atomate2.vasp.sets.base import VaspInputGenerator
+from atomate2.vasp.sets.core import StaticSetGenerator, TightRelaxSetGenerator
+from jobflow import Flow, Maker, Response, job
+from pymatgen.core import Molecule, Site
+from pymatgen.core.structure import Species, Structure
+
+from autoplex.data.common.jobs import generate_randomized_structures
+from autoplex.data.phonons.jobs import reduce_supercell_size_job
+from autoplex.data.phonons.utils import (
+    ml_phonon_maker_preparation,
+    reduce_supercell_size,
+)
+
+__all__ = [
+    "DFTPhononMaker",
+    "IsoAtomMaker",
+    "IsoAtomStaticMaker",
+    "MLPhononMaker",
+    "RandomStructuresDataGenerator",
+    "TightDFTStaticMaker",
+]
+
+
+
+[docs] +@dataclass +class TightDFTStaticMaker(PhononDisplacementMaker): + """Adapted phonon displacement maker for static calculation. + + The input set used is same as PhononDisplacementMaker. + Only difference is Spin polarization is switched off and Gaussian smearing is used + + Parameters + ---------- + name : str + The job name. + input_set_generator : .VaspInputGenerator + Generator used to make the input set. + write_input_set_kwargs : dict + Keyword arguments that will get passed to :obj:`.write_vasp_input_set`. + copy_vasp_kwargs : dict + Keyword arguments that will get passed to :obj:`.copy_vasp_outputs`. + run_vasp_kwargs : dict + Keyword arguments that will get passed to :obj:`.run_vasp`. + task_document_kwargs : dict + Keyword arguments that will get passed to :obj:`.TaskDoc.from_directory`. + stop_children_kwargs : dict + Keyword arguments that will get passed to :obj:`.should_stop_children`. + write_additional_data : dict + Additional data to write to the current directory. Given as a dict of + {filename: data}. Note that if using FireWorks, dictionary keys cannot contain + the "." character which is typically used to denote file extensions. To avoid + this, use the ":" character, which will automatically be converted to ".". E.g. + ``{"my_file:txt": "contents of the file"}``. + """ + + name: str = "dft static" + run_vasp_kwargs: dict = field(default_factory=lambda: {"handlers": ()}) + + input_set_generator: VaspInputGenerator = field( + default_factory=lambda: StaticSetGenerator( + user_incar_settings={ + "ALGO": "Normal", # not switching to Fast because it's not precise enough for the fit + "IBRION": -1, + "ISPIN": 1, + "ISMEAR": 0, + "ISIF": 3, + "ENCUT": 700, + "EDIFF": 1e-7, + "LAECHG": False, + "LREAL": False, + "NSW": 0, + "LCHARG": False, # Do not write the CHGCAR file + "LWAVE": False, # Do not write the WAVECAR file + "LVTOT": False, # Do not write LOCPOT file + "LORBIT": None, # remove Lorbit + "LOPTICS": False, # No PCDAT file + "SIGMA": 0.05, + "ISYM": 0, + "SYMPREC": 1e-9, + "KSPACING": 0.2, + # To be removed + "NPAR": 4, + }, + auto_ispin=False, + ) + )
+ + + +
+[docs] +@dataclass +class DFTPhononMaker(PhononMaker): + """ + Adapted PhononMaker to calculate harmonic phonons with VASP and Phonopy. + + The input set used is same as PhononMaker from atomate2. + Only difference is Spin polarization is switched off and Gaussian smearing is used + + Parameters + ---------- + name : str = "phonon" + Name of the flows produced by this maker. + sym_reduce : bool = True + Whether to reduce the number of deformations using symmetry. + symprec : float = 1e-4 + Symmetry precision to use in the + reduction of symmetry to find the primitive/conventional cell + (use_primitive_standard_structure, use_conventional_standard_structure) + and to handle all symmetry-related tasks in phonopy + displacement: float = 0.01 + Displacement distance for phonons + min_length: float = 20.0 + Minimum length of the supercell that will be built + prefer_90_degrees: bool = True + If set to True, supercell algorithm will first try to find a supercell + with 3 90 degree angles + get_supercell_size_kwargs: dict = {} + Keyword arguments that will be passed to get_supercell_size to determine supercell size + use_symmetrized_structure: str or None = None + Allowed strings: "primitive", "conventional", None + + - "primitive" will enforce to start the phonon computation + from the primitive standard structure + according to Setyawan, W., & Curtarolo, S. (2010). + High-throughput electronic band structure calculations: + Challenges and tools. Computational Materials Science, + 49(2), 299-312. doi:10.1016/j.commatsci.2010.05.010. + This makes it possible to use certain k-path definitions + with this workflow. Otherwise, we must rely on seekpath + - "conventional" will enforce to start the phonon computation + from the conventional standard structure + according to Setyawan, W., & Curtarolo, S. (2010). + High-throughput electronic band structure calculations: + Challenges and tools. Computational Materials Science, + 49(2), 299-312. doi:10.1016/j.commatsci.2010.05.010. + We will however use seekpath and primitive structures + as determined by from phonopy to compute the phonon band structure + bulk_relax_maker : .BaseVaspMaker or None + Maker to perform a tight relaxation on the bulk. + Set to ``None`` to skip the + bulk relaxation + static_energy_maker : .BaseVaspMaker or None + Maker to perform the computation of the DFT energy on the bulk. + Set to ``None`` to skip the + static energy computation + born_maker: .BaseVaspMaker or None + Maker to compute the BORN charges. + phonon_displacement_maker : .BaseVaspMaker or None + Maker used to compute the forces for a supercell. + generate_frequencies_eigenvectors_kwargs : dict + Keyword arguments passed to :obj:`generate_frequencies_eigenvectors`. + create_thermal_displacements: bool + Arg that determines if thermal_displacement_matrices are computed + kpath_scheme: str = "seekpath" + Scheme to generate kpoints. Please be aware that + you can only use seekpath with any kind of cell + Otherwise, please use the standard primitive structure + Available schemes are: + "seekpath", "hinuma", "setyawan_curtarolo", "latimer_munro". + "seekpath" and "hinuma" are the same definition but + seekpath can be used with any kind of unit cell as + it relies on phonopy to handle the relationship + to the primitive cell and not pymatgen + code: str = "vasp" + Determines the DFT code. currently only vasp is implemented. + This keyword might enable the implementation of other codes + in the future + store_force_constants: bool + If True, force constants will be stored + """ + + name: str = "dft phonon" + sym_reduce: bool = True + symprec: float = 1e-4 + displacement: float = 0.01 + min_length: float | None = 20.0 + max_length: float | None = 30.0 + prefer_90_degrees: bool = True + allow_orthorhombic: bool = False + get_supercell_size_kwargs: dict = field( + default_factory=lambda: {"max_atoms": 800, "step_size": 1.0} + ) + use_symmetrized_structure: str | None = None + create_thermal_displacements: bool = False + store_force_constants: bool = False + generate_frequencies_eigenvectors_kwargs: dict = field( + default_factory=lambda: {"tol_imaginary_modes": 1e-1} + ) + bulk_relax_maker: BaseVaspMaker | None = field( + default_factory=lambda: DoubleRelaxMaker.from_relax_maker( + TightRelaxMaker( + run_vasp_kwargs={"handlers": {}}, + input_set_generator=TightRelaxSetGenerator( + user_incar_settings={ + "ALGO": "Normal", + "ISPIN": 1, + "LAECHG": False, + "ISMEAR": 0, + "ENCUT": 700, + "ISYM": 0, + "SIGMA": 0.05, + "LCHARG": False, # Do not write the CHGCAR file + "LWAVE": False, # Do not write the WAVECAR file + "LVTOT": False, # Do not write LOCPOT file + "LORBIT": None, # No output of projected or partial DOS in EIGENVAL, PROCAR and DOSCAR + "LOPTICS": False, # No PCDAT file + # to be removed + "NPAR": 4, + } + ), + ) + ), + ) + static_energy_maker: BaseVaspMaker | None = field( + default_factory=lambda: StaticMaker( + input_set_generator=StaticSetGenerator( + auto_ispin=False, + user_incar_settings={ + "ALGO": "Normal", + "ISPIN": 1, + "LAECHG": False, + "ISMEAR": 0, + "ENCUT": 700, + "SIGMA": 0.05, + "LCHARG": False, # Do not write the CHGCAR file + "LWAVE": False, # Do not write the WAVECAR file + "LVTOT": False, # Do not write LOCPOT file + "LORBIT": None, # No output of projected or partial DOS in EIGENVAL, PROCAR and DOSCAR + "LOPTICS": False, # No PCDAT file + # to be removed + "NPAR": 4, + }, + ) + ) + ) + + phonon_displacement_maker: BaseVaspMaker | None = field( + default_factory=TightDFTStaticMaker + )
+ + + +
+[docs] +@dataclass +class RandomStructuresDataGenerator(Maker): + """ + Maker to generate DFT labelled training data for ML potential fitting based on random atomic displacements. + + This Maker performs the two following steps: + 1. Generates supercells from the provided structure and randomly displaces the atomic positions using ase rattle. + (randomized unit cells can be generated additionally). + 2. Performs the static DFT (VASP) calculations on the randomized cells. + + Parameters + ---------- + name : str + Name of the flows produced by this maker. + displacement_maker: .BaseVaspMaker or None + Maker used for a static calculation for a supercell. + code: str + Determines the dft code. currently only vasp is implemented. + This keyword might enable the implementation of other codes + in the future + n_structures : int. + Total number of distorted structures to be generated. + Must be provided if distorting volume without specifying a range, or if distorting angles. + Default=10. + uc: bool. + If True, will use the unit cells of initial randomly displaced + structures and add phonon static computation jobs to the flow + distort_type : int. + 0- volume distortion, 1- angle distortion, 2- volume and angle distortion. Default=0. + min_distance: float + Minimum separation allowed between any two atoms. + Default= 1.5A. + angle_percentage_scale: float + Angle scaling factor. + Default= 10 will randomly distort angles by +-10% of original value. + angle_max_attempts: int. + Maximum number of attempts to distort structure before aborting. + Default=1000. + w_angle: list[float] + List of angle indices to be changed i.e. 0=alpha, 1=beta, 2=gamma. + Default= [0, 1, 2]. + rattle_type: int. + 0- standard rattling, 1- Monte-Carlo rattling. Default=0. + rattle_std: float. + Rattle amplitude (standard deviation in normal distribution). + Default=0.01. + Note that for MC rattling, displacements generated will roughly be + rattle_mc_n_iter**0.5 * rattle_std for small values of n_iter. + rattle_seed: int. + Seed for setting up NumPy random state from which random numbers are generated. + Default=42. + rattle_mc_n_iter: int. + Number of Monte Carlo iterations. + Larger number of iterations will generate larger displacements. + Default=10. + supercell_settings: dict + Settings for supercells. + """ + + name: str = "RandomStruturesDataGeneratorForML" + displacement_maker: BaseVaspMaker | None = field( + default_factory=TightDFTStaticMaker + ) + bulk_relax_maker: BaseVaspMaker = field( + default_factory=lambda: TightRelaxMaker( + run_vasp_kwargs={"handlers": {}}, + input_set_generator=TightRelaxSetGenerator( + user_incar_settings={ + "ALGO": "Normal", + "ISPIN": 1, + "LAECHG": False, + "ISYM": 0, # to be changed + "ISMEAR": 0, + "SIGMA": 0.05, # to be changed back + "LCHARG": False, # Do not write the CHGCAR file + "LWAVE": False, # Do not write the WAVECAR file + "LVTOT": False, # Do not write LOCPOT file + "LORBIT": None, # No output of projected or partial DOS in EIGENVAL, PROCAR and DOSCAR + "LOPTICS": False, # No PCDAT file + # to be removed + "NPAR": 4, + } + ), + ) + ) + code: str = "vasp" + uc: bool = False + distort_type: int = 0 + n_structures: int = 10 + min_distance: float = 1.5 + angle_percentage_scale: float = 10 + angle_max_attempts: int = 1000 + rattle_type: int = 0 + rattle_std: float = 0.01 + rattle_seed: int = 42 + rattle_mc_n_iter: int = 10 + w_angle: list[float] | None = None + supercell_settings: dict | None = field( + default_factory=lambda: {"min_length": 15, "max_length": 20} + ) + +
+[docs] + def make( + self, + structure: Structure, + mp_id: str, + volume_custom_scale_factors: list[float] | None = None, + volume_scale_factor_range: list[float] | None = None, + ): + """ + Make a flow to generate rattled structures reference DFT data. + + Parameters + ---------- + structure : + The pymatgen structures drawn from the Materials Project. + mp_id: str + Materials Project IDs + volume_scale_factor_range : list[float] + [min, max] of volume scale factors. + e.g. [0.90, 1.10] will distort volume +-10%. + volume_custom_scale_factors : list[float] + Specify explicit scale factors (if range is not specified). + If None, will default to [0.90, 0.95, 0.98, 0.99, 1.01, 1.02, 1.05, 1.10]. + """ + if self.supercell_settings is None: + self.supercell_settings = field( + default_factory=lambda: {"min_length": 15, "max_length": 20} + ) + jobs = [] # initializing empty job list + outputs = [] + + relaxed = self.bulk_relax_maker.make(structure) + jobs.append(relaxed) + structure = relaxed.output.structure + + supercell_matrix = self.supercell_settings.get(mp_id, {}).get( + "supercell_matrix" + ) + if not supercell_matrix: + supercell_matrix_job = reduce_supercell_size_job( + structure=structure, + min_length=self.supercell_settings.get("min_length", 12), + max_length=self.supercell_settings.get("max_length", 20), + fallback_min_length=self.supercell_settings.get( + "fallback_min_length", 10 + ), + max_atoms=self.supercell_settings.get("max_atoms", 500), + min_atoms=self.supercell_settings.get("min_atoms", 50), + step_size=self.supercell_settings.get("step_size", 1.0), + ) + jobs.append(supercell_matrix_job) + supercell_matrix = supercell_matrix_job.output + + random_rattle_sc = generate_randomized_structures( + structure=structure, + supercell_matrix=supercell_matrix, + distort_type=self.distort_type, + n_structures=self.n_structures, + volume_custom_scale_factors=volume_custom_scale_factors, + volume_scale_factor_range=volume_scale_factor_range, + rattle_std=self.rattle_std, + min_distance=self.min_distance, + angle_percentage_scale=self.angle_percentage_scale, + angle_max_attempts=self.angle_max_attempts, + rattle_type=self.rattle_type, + rattle_seed=self.rattle_seed, + rattle_mc_n_iter=self.rattle_mc_n_iter, + w_angle=self.w_angle, + ) + jobs.append(random_rattle_sc) + # perform the phonon displaced calculations for randomized displaced structures. + # The original structure is only needed to keep track of initial structure. + vasp_random_sc_displacement_calcs = run_phonon_displacements( + displacements=random_rattle_sc.output, # pylint: disable=E1101 + structure=structure, + supercell_matrix=None, + phonon_maker=self.displacement_maker, + ) + + jobs.append(vasp_random_sc_displacement_calcs) + outputs.append(vasp_random_sc_displacement_calcs.output["dirs"]) + + if self.uc is True: + random_rattle = generate_randomized_structures( + structure=structure, + supercell_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), + distort_type=self.distort_type, + n_structures=self.n_structures, + volume_custom_scale_factors=volume_custom_scale_factors, + volume_scale_factor_range=volume_scale_factor_range, + rattle_std=self.rattle_std, + min_distance=self.min_distance, + angle_percentage_scale=self.angle_percentage_scale, + angle_max_attempts=self.angle_max_attempts, + rattle_type=self.rattle_type, + rattle_seed=self.rattle_seed, + rattle_mc_n_iter=self.rattle_mc_n_iter, + w_angle=self.w_angle, + ) + jobs.append(random_rattle) + vasp_random_displacement_calcs = run_phonon_displacements( + displacements=random_rattle.output, # pylint: disable=E1101 + structure=structure, + supercell_matrix=None, + phonon_maker=self.displacement_maker, + ) + + jobs.append(vasp_random_displacement_calcs) + outputs.append(vasp_random_displacement_calcs.output["dirs"]) + + # create a flow including all jobs + return Flow(jobs=jobs, output=outputs, name=self.name)
+
+ + + +
+[docs] +@dataclass +class MLPhononMaker(FFPhononMaker): + """ + Maker to calculate harmonic phonons with a force field. + + Calculate the harmonic phonons of a material. Initially, a tight structural + relaxation is performed to obtain a structure without forces on the atoms. + Subsequently, supercells with one displaced atom are generated and accurate + forces are computed for these structures. With the help of phonopy, these + forces are then converted into a dynamical matrix. To correct for polarization + effects, a correction of the dynamical matrix based on BORN charges can + be performed. The BORN charges can be supplied manually. + Finally, phonon densities of states, phonon band structures + and thermodynamic properties are computed. + + Notes + ----- + It is heavily recommended to symmetrize the structure before passing it to + this flow. Otherwise, a different space group might be detected and too + many displacement calculations will be generated. + It is recommended to check the convergence parameters here and + adjust them if necessary. The default might not be strict enough + for your specific case. + + Parameters + ---------- + name : str + Name of the flows produced by this maker. + sym_reduce : bool + Whether to reduce the number of deformations using symmetry. + symprec : float + Symmetry precision to use in the + reduction of symmetry to find the primitive/conventional cell + (use_primitive_standard_structure, use_conventional_standard_structure) + and to handle all symmetry-related tasks in phonopy + displacement: float + Displacement distance for phonons + min_length: float + Minimum length of the supercell that will be built + prefer_90_degrees: bool + If set to True, supercell algorithm will first try to find a supercell + with 3 90 degree angles + get_supercell_size_kwargs: dict + Keyword arguments that will be passed to get_supercell_size to determine supercell size + use_symmetrized_structure: str + Allowed strings: "primitive", "conventional", None + + - "primitive" will enforce to start the phonon computation + from the primitive standard structure + according to Setyawan, W., & Curtarolo, S. (2010). + High-throughput electronic band structure calculations: + Challenges and tools. Computational Materials Science, + 49(2), 299-312. doi:10.1016/j.commatsci.2010.05.010. + This makes it possible to use certain k-path definitions + with this workflow. Otherwise, we must rely on seekpath + - "conventional" will enforce to start the phonon computation + from the conventional standard structure + according to Setyawan, W., & Curtarolo, S. (2010). + High-throughput electronic band structure calculations: + Challenges and tools. Computational Materials Science, + 49(2), 299-312. doi:10.1016/j.commatsci.2010.05.010. + We will, however, use seekpath and primitive structures + as determined by from phonopy to compute the phonon band structure + bulk_relax_maker: .ForceFieldRelaxMaker or None + Maker to perform a tight relaxation on the bulk. + Set to ``None`` to skip the + bulk relaxation + static_energy_maker: .ForceFieldStaticMaker or None + Maker to perform the computation of the DFT energy on the bulk. + Set to ``None`` to skip the + static energy computation + phonon_displacement_maker: .ForceFieldStaticMaker or None + Maker used to compute the forces for a supercell. + generate_frequencies_eigenvectors_kwargs : dict + Keyword arguments passed to :obj:`generate_frequencies_eigenvectors`. + create_thermal_displacements: bool + Arg that determines if thermal_displacement_matrices are computed + kpath_scheme: str + Scheme to generate kpoints. Please be aware that + you can only use seekpath with any kind of cell + Otherwise, please use the standard primitive structure + Available schemes are: + "seekpath", "hinuma", "setyawan_curtarolo", "latimer_munro". + "seekpath" and "hinuma" are the same definition but + seekpath can be used with any kind of unit cell as + it relies on phonopy to handle the relationship + to the primitive cell and not pymatgen + code: str + Determines the DFT code. currently only vasp is implemented. + This keyword might enable the implementation of other codes + in the future + store_force_constants: bool + If True, force constants will be stored + relax_maker_kwargs: dict + Keyword arguments that can be passed to the RelaxMaker. + static_maker_kwargs: dict + Keyword arguments that can be passed to the StaticMaker. + """ + + name: str = "ml phonon" + min_length: float | None = 20.0 + displacement: float = 0.01 + bulk_relax_maker: ForceFieldRelaxMaker | None = field( + default_factory=lambda: ForceFieldRelaxMaker( + relax_cell=True, + relax_kwargs={"interval": 500}, + force_field_name="GAP", + ) + ) + phonon_displacement_maker: ForceFieldStaticMaker | None = field( + default_factory=lambda: ForceFieldStaticMaker( + name="gap phonon static", + force_field_name="GAP", + ) + ) + static_energy_maker: ForceFieldStaticMaker | None = field( + default_factory=lambda: ForceFieldStaticMaker(force_field_name="GAP") + ) + store_force_constants: bool = False + get_supercell_size_kwargs: dict = field( + default_factory=lambda: {"max_atoms": 20000, "step_size": 0.1} + ) + generate_frequencies_eigenvectors_kwargs: dict = field( + default_factory=lambda: {"units": "THz", "tol_imaginary_modes": 1e-1} + ) + relax_maker_kwargs: dict | None = field(default_factory=dict) + static_maker_kwargs: dict | None = field(default_factory=dict) + +
+[docs] + @job + def make_from_ml_model( + self, + structure, + potential_file, + ml_model: str = "GAP", + calculator_kwargs: dict | None = None, + supercell_settings: dict | None = None, + **make_kwargs, + ): + """ + Maker for GAP phonon jobs. + + Parameters + ---------- + structure : .Structure + The pymatgen structure. Please start with a structure + that is nearly fully optimized as the internal optimizers + have very strict settings! + ml_model: str + ML model to be used. Default is GAP. + potential_file : + Complete path to MLIP file(s) (train, test and MLIP files) + calculator_kwargs : + Keyword arguments for the ASE Calculator. + supercell_settings: + Dict with supercell settings. + make_kwargs : + Keyword arguments for the PhononMaker. + + Returns + ------- + PhononMaker jobs. + + """ + if supercell_settings is None: + supercell_settings = field(default_factory=lambda: {"min_length": 15}) + if ml_model == "GAP": + if calculator_kwargs is None: + calculator_kwargs = { + "args_str": "IP GAP", + "param_filename": str(potential_file), + } + + ml_prep = ml_phonon_maker_preparation( + bulk_relax_maker=self.bulk_relax_maker, + phonon_displacement_maker=self.phonon_displacement_maker, + static_energy_maker=self.static_energy_maker, + calculator_kwargs=calculator_kwargs, + relax_maker_kwargs=self.relax_maker_kwargs, + static_maker_kwargs=self.static_maker_kwargs, + ) + + elif ml_model == "J-ACE": + raise UserWarning("No atomate2 ACE.jl PhononMaker implemented.") + + elif ml_model == "NEQUIP": + if calculator_kwargs is None: + calculator_kwargs = { + "model_path": str(potential_file), + "device": "cuda", + } + else: + calculator_kwargs.update({"model_path": str(potential_file)}) + + ml_prep = ml_phonon_maker_preparation( + bulk_relax_maker=ForceFieldRelaxMaker( + relax_cell=True, + relax_kwargs={"interval": 500}, + force_field_name="Nequip", + ), + phonon_displacement_maker=ForceFieldStaticMaker( + name="nequip phonon static", + force_field_name="Nequip", + ), + static_energy_maker=ForceFieldStaticMaker( + force_field_name="Nequip", + ), + calculator_kwargs=calculator_kwargs, + relax_maker_kwargs=self.relax_maker_kwargs, + static_maker_kwargs=self.static_maker_kwargs, + ) + + elif ml_model == "M3GNET": + if calculator_kwargs is None: + calculator_kwargs = {"path": str(potential_file)} + + ml_prep = ml_phonon_maker_preparation( + bulk_relax_maker=ForceFieldRelaxMaker( + relax_cell=True, + relax_kwargs={"interval": 500}, + force_field_name="M3GNet", + ), + phonon_displacement_maker=ForceFieldStaticMaker( + name="m3gnet phonon static", + force_field_name="M3GNet", + ), + static_energy_maker=ForceFieldStaticMaker( + force_field_name="M3GNet", + ), + calculator_kwargs=calculator_kwargs, + relax_maker_kwargs=self.relax_maker_kwargs, + static_maker_kwargs=self.static_maker_kwargs, + ) + + else: # MACE + if calculator_kwargs is None: + calculator_kwargs = {"model": str(potential_file), "device": "cuda"} + elif "model" in calculator_kwargs: + calculator_kwargs.update( + {"default_dtype": "float64"} + ) # Use float64 for geometry optimization. + else: + calculator_kwargs.update( + {"model": str(potential_file), "default_dtype": "float64"} + ) + + ml_prep = ml_phonon_maker_preparation( + bulk_relax_maker=ForceFieldRelaxMaker( + relax_cell=True, + relax_kwargs={"interval": 500}, + force_field_name="MACE", + ), + phonon_displacement_maker=ForceFieldStaticMaker( + name="mace phonon static", + force_field_name="MACE", + ), + static_energy_maker=ForceFieldStaticMaker( + force_field_name="MACE", + ), + calculator_kwargs=calculator_kwargs, + relax_maker_kwargs=self.relax_maker_kwargs, + static_maker_kwargs=self.static_maker_kwargs, + ) + + ( + self.bulk_relax_maker, + self.phonon_displacement_maker, + self.static_energy_maker, + ) = ml_prep + filtered_settings = { + key: value + for key, value in supercell_settings.items() + if key + in [ + "min_length", + "max_length", + "fallback_min_length", + "max_atoms", + "min_atoms", + "step_size", + ] + } + supercell_matrix = reduce_supercell_size( + structure=structure, **filtered_settings + ) + + flow = self.make( + structure=structure, supercell_matrix=supercell_matrix, **make_kwargs + ) + return Response(replace=flow, output=flow.output)
+
+ + + +
+[docs] +@dataclass +class IsoAtomStaticMaker(StaticMaker): + """ + Maker to create Isolated atoms static (VASP) jobs. + + Parameters + ---------- + name : str + The job name. + input_set_generator : .VaspInputGenerator + Generator used to make the input set. + write_input_set_kwargs : dict + Keyword arguments that will get passed to :obj:`.write_vasp_input_set`. + copy_vasp_kwargs : dict + Keyword arguments that will get passed to :obj:`.copy_vasp_outputs`. + run_vasp_kwargs : dict + Keyword arguments that will get passed to :obj:`.run_vasp`. + task_document_kwargs : dict + Keyword arguments that will get passed to :obj:`.TaskDoc.from_directory`. + stop_children_kwargs : dict + Keyword arguments that will get passed to :obj:`.should_stop_children`. + write_additional_data : dict + Additional data to write to the current directory. Given as a dict of + {filename: data}. Note that if using FireWorks, dictionary keys cannot contain + the "." character which is typically used to denote file extensions. To avoid + this, use the ":" character, which will automatically be converted to ".". E.g. + ``{"my_file:txt": "contents of the file"}``. + """ + + name: str = "static" + input_set_generator: VaspInputGenerator = field( + default_factory=lambda: StaticSetGenerator( + user_kpoints_settings={"reciprocal_density": 1}, + user_incar_settings={ + "ALGO": "Normal", + "ISPIN": 1, + "LAECHG": False, + "ISMEAR": 0, + "LCHARG": False, # Do not write the CHGCAR file + "LWAVE": False, # Do not write the WAVECAR file + "LVTOT": False, # Do not write LOCPOT file + "LORBIT": None, # No output of projected or partial DOS in EIGENVAL, PROCAR and DOSCAR + "LOPTICS": False, # No PCDAT file + # to be removed + "NPAR": 4, + }, + ) + )
+ + + +
+[docs] +@dataclass +class IsoAtomMaker(Maker): + """ + Maker to generate DFT data for ML potential fitting from isolated atoms. + + Parameters + ---------- + name : str + Name of the flows produced by this maker. + """ + + name: str = "IsolatedAtomEnergyMaker" + +
+[docs] + def make( + self, + all_species: list[Species], + isolated_atom_maker: IsoAtomStaticMaker = None, + ): + """ + Make a flow to calculate the isolated atom's energy. + + Parameters + ---------- + all_species : list[Species] + List of pymatgen specie object. + isolated_atom_maker: IsoAtomMaker + VASP input set for the isolated atom calculation. + """ + jobs = [] + isoatoms_energy = [] + isoatoms_dirs = [] + if isolated_atom_maker is None: + isolated_atom_static_input_set = StaticSetGenerator( + user_kpoints_settings={"grid_density": 1}, + user_incar_settings={ + "ALGO": "Normal", + "ISPIN": 1, + "LAECHG": False, + "ISMEAR": 0, + "LCHARG": False, # Do not write the CHGCAR file + "LWAVE": False, # Do not write the WAVECAR file + "LVTOT": False, # Do not write LOCPOT file + "LORBIT": None, # No output of projected or partial DOS in EIGENVAL, PROCAR and DOSCAR + "LOPTICS": False, # No PCDAT file + # to be removed + "NPAR": 4, + # TODO: locpot, chgcar, chg can be deactivated! + # TODO: why don't we use the IsoAtomMaker and adapt it? + }, + ) + isolated_atom_maker = StaticMaker( + input_set_generator=isolated_atom_static_input_set, name="stat_iso_atom" + ) + for species in all_species: + site = Site(species=species, coords=[0, 0, 0]) + mol = Molecule.from_sites([site]) + iso_atom = mol.get_boxed_structure(a=20, b=20, c=20) + isolated_atom_maker.name = f"{species}-stat_iso_atom" + isolated_atom_maker.run_vasp_kwargs = {"handlers": ()} + isoatom_calcs = isolated_atom_maker.make(iso_atom) + + jobs.append(isoatom_calcs) + isoatoms_energy.append(isoatom_calcs.output.output.energy_per_atom) + isoatoms_dirs.append(isoatom_calcs.output.dir_name) + + # create a flow including all jobs + return Flow( + jobs=jobs, + output={"energies": isoatoms_energy, "dirs": isoatoms_dirs}, + name=self.name, + )
+
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/autoplex/data/phonons/jobs.html b/_modules/autoplex/data/phonons/jobs.html new file mode 100644 index 000000000..1e0a6ab47 --- /dev/null +++ b/_modules/autoplex/data/phonons/jobs.html @@ -0,0 +1,532 @@ + + + + + + + + + + autoplex.data.phonons.jobs — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+
+
+ + + + +
+ +

Source code for autoplex.data.phonons.jobs

+"""Jobs to create training data for ML potentials."""
+
+from jobflow import job
+from pymatgen.core.structure import Structure
+
+from autoplex.data.phonons.utils import reduce_supercell_size
+
+
+
+[docs] +@job +def reduce_supercell_size_job( + structure: Structure, + min_length: float = 18, + max_length: float = 20, + fallback_min_length: float = 12, + min_atoms: int = 100, + max_atoms: int = 500, + step_size: float = 1, +): + """ + Reduce phonopy supercell size. + + Parameters + ---------- + structure: Structure + The pymatgen Structure object. + min_length: float + Minimum length of the supercell that will be built. + max_length: float + Maximum length of the supercell that will be built. + max_atoms: int + Maximally allowed number of atoms in the supercell. + min_atoms: int + Minimum number of atoms in the supercell that shall be reached. + fallback_min_length: float + Fallback option for minimum length for exceptional cases. + step_size: float + The step_size which is used to increase the supercell. + If allow_orthorhombic and force_90_degrees are both set to True, + the chosen step_size will be automatically multiplied by 5 to + prevent a too long search for the possible supercell. + + Returns + ------- + reduced_supercell_size call. + """ + return reduce_supercell_size( + structure=structure, + min_length=min_length, + max_length=max_length, + fallback_min_length=fallback_min_length, + min_atoms=min_atoms, + max_atoms=max_atoms, + step_size=step_size, + )
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/autoplex/data/phonons/utils.html b/_modules/autoplex/data/phonons/utils.html new file mode 100644 index 000000000..704b94058 --- /dev/null +++ b/_modules/autoplex/data/phonons/utils.html @@ -0,0 +1,782 @@ + + + + + + + + + + autoplex.data.phonons.utils — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+
+
+ + + + +
+ +

Source code for autoplex.data.phonons.utils

+"""Utility functions for data generation jobs."""
+
+import logging
+
+import numpy as np
+from atomate2.forcefields.jobs import (
+    ForceFieldRelaxMaker,
+    ForceFieldStaticMaker,
+)
+from atomate2.vasp.jobs.phonons import PhononDisplacementMaker
+from pymatgen.core import Structure
+from pymatgen.transformations.advanced_transformations import (
+    CubicSupercellTransformation,
+)
+
+# Configure the logger
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+
+
+[docs] +def ml_phonon_maker_preparation( + calculator_kwargs: dict, + relax_maker_kwargs: dict | None, + static_maker_kwargs: dict | None, + bulk_relax_maker: ForceFieldRelaxMaker, + phonon_displacement_maker: ForceFieldStaticMaker, + static_energy_maker: ForceFieldStaticMaker, +) -> tuple[ + ForceFieldRelaxMaker | None, + ForceFieldStaticMaker | None, + ForceFieldStaticMaker | None, +]: + """ + Prepare the MLPhononMaker for the respective MLIP model. + + bulk_relax_maker: .ForceFieldRelaxMaker or None + Maker to perform a tight relaxation on the bulk. + Set to ``None`` to skip the + bulk relaxation + static_energy_maker: .ForceFieldStaticMaker or None + Maker to perform the computation of the DFT energy on the bulk. + Set to ``None`` to skip the + static energy computation + phonon_displacement_maker: .ForceFieldStaticMaker or None + Maker used to compute the forces for a supercell. + relax_maker_kwargs: dict + Keyword arguments that can be passed to the RelaxMaker. + static_maker_kwargs: dict + Keyword arguments that can be passed to the StaticMaker. + """ + if bulk_relax_maker is not None: + bulk_relax_maker = bulk_relax_maker.update_kwargs( + update={"calculator_kwargs": calculator_kwargs} + ) + if relax_maker_kwargs is not None: + bulk_relax_maker = bulk_relax_maker.update_kwargs( + update={**relax_maker_kwargs} + ) + + if phonon_displacement_maker is not None: + phonon_displacement_maker = phonon_displacement_maker.update_kwargs( + update={"calculator_kwargs": calculator_kwargs} + ) + if static_maker_kwargs is not None: + phonon_displacement_maker = phonon_displacement_maker.update_kwargs( + {**static_maker_kwargs} + ) + if static_energy_maker is not None: + static_energy_maker = static_energy_maker.update_kwargs( + update={"calculator_kwargs": calculator_kwargs} + ) + if static_maker_kwargs is not None: + static_energy_maker = static_energy_maker.update_kwargs( + update={**static_maker_kwargs} + ) + + return bulk_relax_maker, phonon_displacement_maker, static_energy_maker
+ + + +
+[docs] +def update_phonon_displacement_maker( + lattice, phonon_displacement_maker +) -> PhononDisplacementMaker: + """ + Update the phonon_displacement_maker. + + Parameters + ---------- + lattice: + (Average) lattice of the structure. + phonon_displacement_maker: + Maker used to compute the forces for a supercell. + + Returns + ------- + Updated phonon_displacement_maker + + """ + if lattice > 10: + density = 350 - 15 * int(round(lattice, 0)) + if lattice > 20: + density = 50 + phonon_displacement_maker.input_set_generator.user_kpoints_settings = { + "reciprocal_density": density + } + return phonon_displacement_maker
+ + + +
+[docs] +def check_supercells( + structure_list: list[Structure], + structure_names: list[str] | None = None, + min_length: float = 18, + max_length: float = 25, + fallback_min_length: float = 10, + min_atoms: int = 100, + max_atoms: int = 500, + tolerance: float = 0.1, +) -> None: + """ + Check the supercell size. + + Prints log output regarding the structures matching the supercell requirements. + + Parameters + ---------- + structure_list: list[Structure] + List of pymatgen Structure object. + structure_names: list[str] + List of structure names. + min_length: float + Minimum length of the supercell that will be built. + max_length: float + Maximum length of the supercell that will be built. + max_atoms: int + Maximally allowed number of atoms in the supercell. + min_atoms: int + Minimum number of atoms in the supercell that shall be reached. + fallback_min_length: float + Fallback option for minimum length for exceptional cases + tolerance: float + Tolerance for min_atoms and max_atoms + + """ + structure_names = ( + [structure.composition.reduced_formula for structure in structure_list] + if structure_names is None + else structure_names + ) + + min_tolerance = 1 - tolerance + max_tolerance = 1 + tolerance + + for name, structure in zip(structure_names, structure_list): + matrix = reduce_supercell_size( + structure, + min_length=min_length, + max_length=max_length, + fallback_min_length=fallback_min_length, + min_atoms=min_atoms, + max_atoms=max_atoms, + ) + supercell = structure.make_supercell(np.array(matrix).transpose()) + a, b, c = supercell.lattice.abc + num_atoms = supercell.num_sites + print(supercell) + # check if supercells are in the requirements with a certain tolerance + if ( + not (min_atoms * min_tolerance <= num_atoms <= max_atoms * max_tolerance) + or ( + not ( + fallback_min_length * min_tolerance + <= a + < max_length * max_tolerance + ) + ) + or ( + not ( + fallback_min_length * min_tolerance + <= b + < max_length * max_tolerance + ) + ) + or ( + not ( + fallback_min_length * min_tolerance + <= c + < max_length * max_tolerance + ) + ) + ): + logger.warning("You should not include structure %s \n", name) + logger.info( + "because the found supercell has the following lattice parameters: %f, %f, %f \n", + a, + b, + c, + ) + logger.info("and it has the following sites: %d \n", num_atoms) + logger.info( + "which usually leads to convergence issues during the DFT steps." + ) + else: + logger.info("%s has passed the supercell check. \n", name)
+ + + +
+[docs] +def reduce_supercell_size( + structure: Structure, + min_length: float = 18, + max_length: float = 22, + fallback_min_length: float = 12, + min_atoms: int = 100, + max_atoms: int = 500, + step_size: float = 1, +) -> list: + """ + Reduce phonopy supercell size. + + Parameters + ---------- + structure: Structure + The pymatgen Structure object. + min_length: float + Minimum length of the supercell that will be built. + max_length: float + Maximum length of the supercell that will be built. + max_atoms: int + Maximally allowed number of atoms in the supercell. + min_atoms: int + Minimum number of atoms in the supercell that shall be reached. + fallback_min_length: float + Fallback option for minimum length for exceptional cases. + step_size: float + The step_size which is used to increase the supercell. + If allow_orthorhombic and force_90_degrees are both set to True, + the chosen step_size will be automatically multiplied by 5 to + prevent a too long search for the possible supercell. + + Returns + ------- + list + The supercell matrix as a list object. + """ + if fallback_min_length >= min_length: + fallback_min_length = min_length - 1 + for minimum in range(int(min_length), int(fallback_min_length), -1): + try: + transformation = CubicSupercellTransformation( + min_length=minimum, + max_length=max_length, + min_atoms=min_atoms, + max_atoms=max_atoms, + step_size=step_size, + allow_orthorhombic=True, + force_90_degrees=True, + ) + new_structure = transformation.apply_transformation(structure=structure) + if min_atoms <= new_structure.num_sites <= max_atoms: + return transformation.transformation_matrix.transpose().tolist() + except AttributeError: + try: + transformation = CubicSupercellTransformation( + min_length=minimum, + max_length=max_length, + min_atoms=min_atoms, + max_atoms=max_atoms, + step_size=step_size, + allow_orthorhombic=True, + force_90_degrees=False, + ) + new_structure = transformation.apply_transformation(structure=structure) + if min_atoms <= new_structure.num_sites <= max_atoms: + return transformation.transformation_matrix.transpose().tolist() + except AttributeError: + try: + transformation = CubicSupercellTransformation( + min_length=minimum, + max_length=max_length, + min_atoms=min_atoms, + max_atoms=max_atoms, + step_size=step_size, + ) + new_structure = transformation.apply_transformation( + structure=structure + ) + if min_atoms <= new_structure.num_sites <= max_atoms: + return transformation.transformation_matrix.transpose().tolist() + except AttributeError: + pass + + a, b, c = structure.lattice.abc + a_factor = np.max((np.floor(max_length / a), 1)) + b_factor = np.max((np.floor(max_length / b), 1)) + c_factor = np.max((np.floor(max_length / c), 1)) + + matrix = np.array([[a_factor, 0, 0], [0, b_factor, 0], [0, 0, c_factor]]) + return matrix.transpose().tolist()
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/autoplex/data/rss/flows.html b/_modules/autoplex/data/rss/flows.html new file mode 100644 index 000000000..a07e9d49f --- /dev/null +++ b/_modules/autoplex/data/rss/flows.html @@ -0,0 +1,589 @@ + + + + + + + + + + autoplex.data.rss.flows — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+
+
+ + + + +
+ +

Source code for autoplex.data.rss.flows

+"""Flows for running RSS."""
+
+from dataclasses import dataclass
+
+from jobflow import Flow, Maker, Response, job
+
+from autoplex.data.common.jobs import (
+    sample_data,
+)
+from autoplex.data.rss.jobs import RandomizedStructure
+
+__all__ = ["BuildMultiRandomizedStructure"]
+
+
+
+[docs] +@dataclass +class BuildMultiRandomizedStructure(Maker): + """ + Maker to create random structures by 'buildcell'. + + Parameters + ---------- + tag: str + Tag of systems. It can also be used for setting up elements and stoichiometry. + For example, 'SiO2' will generate structures with a 2:1 ratio of Si to O. + generated_struct_numbers: list[int] + Expected number of generated randomized unit cells. + buildcell_option: dict + Customized parameters for buildcell. + fragment_file: Atoms | list[Atoms] (optional) + Fragment(s) for random structures, e.g. molecules, to be placed indivudally intact. + atoms.arrays should have a 'fragment_id' key with unique identifiers for each fragment if in same Atoms. + atoms.cell must be defined (e.g. Atoms.cell = np.eye(3)*20). + fragment_numbers: list[str] (optional) + Numbers of each fragment to be included in the random structures. Defaults to 1 for all specified. + remove_tmp_files: bool + Remove all temporary files raised by buildcell to save memory. + initial_selection_enabled: bool + If true, sample structures using CUR. + selected_struct_numbers: list + Number of structures to be sampled. + bcur_params: dict + Parameters for Boltzmann CUR selection. + random_seed: int + A seed to ensure reproducibility of CUR selection. + num_processes: int + Number of processes to use for parallel computation. + name: str + Name of the flows produced by this maker. + + """ + + tag: str + generated_struct_numbers: list[int] + buildcell_options: list[dict] | None = None + fragment_file: str | None = None + fragment_numbers: list[str] | None = None + remove_tmp_files: bool = True + initial_selection_enabled: bool = False + selected_struct_numbers: list[int] | None = None + bcur_params: dict | None = None + random_seed: int | None = None + num_processes: int = 1 + name: str = "do_randomized_structure_generation" + +
+[docs] + @job + def make(self): + """Maker to create random structures by buildcell.""" + job_list = [] + final_structures = [] + for i, struct_number in enumerate(self.generated_struct_numbers): + buildcell_option = None + if self.buildcell_options is not None: + assert len(self.generated_struct_numbers) == len(self.buildcell_options) + buildcell_option = self.buildcell_options[i] + job_struct = RandomizedStructure( + tag=self.tag, + struct_number=struct_number, + remove_tmp_files=self.remove_tmp_files, + buildcell_option=buildcell_option, + fragment_file=self.fragment_file, + fragment_numbers=self.fragment_numbers, + num_processes=self.num_processes, + ).make() + job_struct.name = f"{self.name}_{i}" + + if self.initial_selection_enabled: + assert len(self.generated_struct_numbers) == len( + self.selected_struct_numbers + ) + job_cur = sample_data( + selection_method="cur", + num_of_selection=self.selected_struct_numbers[i], + bcur_params=self.bcur_params, + dir=job_struct.output, + random_seed=self.random_seed, + ) + job_cur.name = f"sampling_{i}" + job_list.append(job_struct) + job_list.append(job_cur) + final_structures.append(job_cur.output) + else: + job_list.append(job_struct) + final_structures.append(job_struct.output) + + return Response( + replace=Flow(job_list), + output=final_structures, + )
+
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/autoplex/data/rss/jobs.html b/_modules/autoplex/data/rss/jobs.html new file mode 100644 index 000000000..af264d3e3 --- /dev/null +++ b/_modules/autoplex/data/rss/jobs.html @@ -0,0 +1,1147 @@ + + + + + + + + + + autoplex.data.rss.jobs — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+
+
+ + + + +
+ +

Source code for autoplex.data.rss.jobs

+"""Jobs for running RSS."""
+
+import os
+import re
+from dataclasses import dataclass
+from multiprocessing import Pool
+from pathlib import Path
+from shutil import which
+from subprocess import run
+
+import ase.io
+import numpy as np
+from ase import Atoms
+from ase.data import atomic_numbers, covalent_radii
+from jobflow import Flow, Maker, Response, job
+from monty.dev import requires
+from pymatgen.core import Element, Structure
+from pymatgen.io.ase import AseAtomsAdaptor
+
+from autoplex.data.common.utils import flatten
+from autoplex.data.rss.utils import minimize_structures, split_structure_into_groups
+
+
+
+[docs] +@dataclass +class RandomizedStructure(Maker): + """ + Maker to create random structures using the 'buildcell' tool. + + Parameters + ---------- + name: str + Name of the flows produced by this maker. + struct_number : int + Expected number of generated randomized unit cells. + tag: str + Tag of systems. It can also be used for setting up elements and stoichiometry. + For example, 'SiO2' will generate structures with a 2:1 ratio of Si to O. + output_file_name: str + Name of the file to store all generated structures. + remove_tmp_files: bool + Remove all temporary files raised by buildcell to save memory. + buildcell_option: dict + Customized parameters for buildcell. + cell_seed_path: str + Path to the custom buildcell control file, which ends with '.cell'. If this file exists, + the buildcell_option argument will no longer take effect. + num_processes: int + Number of processes to use for parallel computation. + fragment: Atoms | list[Atoms] (optional) + Fragment(s) for random structures, e.g. molecules, to be placed indivudally intact. + atoms.arrays should have a 'fragment_id' key with unique identifiers for each fragment if in same Atoms. + atoms.cell must be defined (e.g. Atoms.cell = np.eye(3)*20). + fragment_numbers: list[str] (optional) + Numbers of each fragment to be included in the random structures. Defaults to 1 for all specified. + """ + + name: str = "build_random_cells" + struct_number: int = 20 + tag: str = "Si" + output_file_name: str = "random_structs.extxyz" + remove_tmp_files: bool = True + buildcell_option: dict | None = None + cell_seed_path: str | None = None + num_processes: int = 32 + fragment_file: str | None = None + fragment_numbers: list[str] | None = None + +
+[docs] + @requires( + which("buildcell"), + "RSS flows requires the executable 'buildcell' to be in PATH. " + "Please follow the instructions in the autoplex documentation to install " + "the AIRSS library and add it to PATH. Link to the documentation:" + " https://autoatml.github.io/autoplex/user/index.html#enabling-rss-workflows", + ) + @job + def make(self): + """Maker to create random structures by buildcell.""" + if self.cell_seed_path: + if not os.path.isfile(self.cell_seed_path): + raise FileNotFoundError( + f"No file found at the specified path: {self.cell_seed_path}" + ) + bc_file = self.cell_seed_path + + else: + buildcell_parameters = [ + "SLACK=0.25", + "OVERLAP=0.1", + "COMPACT", + "MINSEP=1.5", + ] + + if self.buildcell_option is not None: + buildcell_parameters = self._update_buildcell_option( + self.buildcell_option, buildcell_parameters + ) + + elements = self._extract_elements(self.tag) # {"Si":1, "O":2} + + if "SPECIES" in self.buildcell_option and self.fragment_file is not None: + raise ValueError( + "Cannot use 'SPECIES' and 'fragment' together in buildcell options.\n" + "Specify your fragment only and use NFORM to control their number." + ) + + if self.buildcell_option is None or ( + "SPECIES" not in self.buildcell_option and self.fragment_file is None + ): + make_species = self._make_species(elements) # Si%NUM=1,O%NUM=2 + buildcell_parameters = self._update_buildcell_option( + {"SPECIES": make_species}, buildcell_parameters + ) + + if ( + self.buildcell_option is None + or ( + "VARVOL" not in self.buildcell_option + and "TARGVOL" not in self.buildcell_option + ) + or "MINSEP" not in self.buildcell_option + ): + r0 = {} + varvol = {} + num_atom_formula = 0 + total_varvol_formula = 0 + + for ele in elements: + r0[ele] = covalent_radii[atomic_numbers[ele]] + + if Element(ele).is_metal: + varvol[ele] = 5.5 * np.power(r0[ele], 3) + else: + varvol[ele] = 14.5 * np.power(r0[ele], 3) + + total_varvol_formula += varvol[ele] * elements[ele] + + num_atom_formula += elements[ele] + + if self.buildcell_option is None or ( + "VARVOL" not in self.buildcell_option + and "TARGVOL" not in self.buildcell_option + ): + mean_var = total_varvol_formula / num_atom_formula * len(elements) + buildcell_parameters = self._update_buildcell_option( + {"TARGVOL": f"{mean_var*0.8}-{mean_var*1.2}"}, + buildcell_parameters, + ) + + if ( + self.buildcell_option is None + or "MINSEP" not in self.buildcell_option + ): + minsep = self._make_minsep(r0) + buildcell_parameters = self._update_buildcell_option( + { + "MINSEP": minsep, + }, + buildcell_parameters, + ) + + if self.fragment_file is not None: + self.fragment = ase.io.read(self.fragment_file, index=":") + + if len(self.fragment) == 1: + self.fragment = self.fragment[0] + + if isinstance(self.fragment, Atoms): + if self.fragment_numbers is None: + fragment_numbers = [1 for _ in self.fragment] + else: + fragment_numbers = self.fragment_numbers + if "fragment_id" not in self.fragment.arrays: + self.fragment.arrays["fragment_id"] = [ + f"{1}-f" for i in self.fragment + ] + + elif isinstance(self.fragment, list): + if self.fragment_numbers is None: + fragment_numbers = [ + 1 for _ in range(sum([len(i) for i in self.fragment])) + ] + else: + fragment_numbers = self.fragment_numbers + write_fragment = self.fragment[0] + for frag in self.fragment[ + 1: + ]: # merge all separate fragments into one Atoms object + write_fragment += frag + + fragment_parameters = [ + "%BLOCK POSITIONS_ABS", + ] + symbols = self.fragment.get_chemical_symbols() + for i, val in enumerate(self.fragment.get_positions(wrap=True)): + if i == 0: + newline = ( + f"{symbols[i]} {val[0]:.8f} {val[1]:.8f} {val[2]:.8f}" + f" # {self.fragment.arrays['fragment_id'][i]}" + f" % NUM={fragment_numbers[i]}" + ) + else: + newline = ( + f"{symbols[i]} {val[0]:.8f} {val[1]:.8f} {val[2]:.8f}" + f" # {self.fragment.arrays['fragment_id'][i]}" + ) + fragment_parameters.append(newline) + fragment_parameters.append("%ENDBLOCK POSITIONS_ABS") + + buildcell_parameters = ( + fragment_parameters + buildcell_parameters + ) # prepend with structural info + + self._cell_seed(buildcell_parameters, self.tag) + bc_file = f"{self.tag}.cell" + + with Pool(processes=self.num_processes) as pool: + args = [ + (i, bc_file, self.tag, self.remove_tmp_files) + for i in range(self.struct_number) + ] + atoms_group = pool.starmap(self._parallel_process, args) + + atoms_group = [ + atom for atom in atoms_group if not np.isnan(atom.get_positions()).any() + ] + + ase.io.write( + self.output_file_name, atoms_group, parallel=False, format="extxyz" + ) + + # structure = [AseAtomsAdaptor().get_structure(at) for at in atoms_group] + return os.path.join(Path.cwd(), self.output_file_name)
+ + # return structure + + def _update_buildcell_option(self, updates, origin) -> list: + """ + Update buildcell parameters based on a dictionary of updates. + + Parameters + ---------- + updates: dict + A dictionary consisting of new values to update buildcell parameters. + origin: list + The default list of buildcell parameters. + """ + updated_keys = set() + + for i, option in enumerate(origin): + option_key = option.split("=")[0] + if option_key in updates: + origin[i] = f"{option_key}={updates[option_key]}" + updated_keys.add(option_key) + + for key, value in updates.items(): + if key not in updated_keys: + origin.append(f"{key}={value}") + + return origin + + def _cell_seed( + self, + buildcell_parameters: list, + tag: str, + ): + """ + Prepare the seed file for buildcell. + + Parameters + ---------- + buildcell_parameters: (list of str) e.g. ['VARVOL=20'] + List of parameters for creating the seed file for buildcell. + tag: str + Tag of systems. + """ + bc_file = f"{tag}.cell" + contents = [] + flag = False # for printing blocks correctly with '#' + for i in buildcell_parameters: + if i.startswith("%") or flag: + flag = not (flag and i.startswith("%")) + contents.append(i + "\n") + else: + contents.append("#" + i + "\n") + + with open(bc_file, "w") as f: + f.writelines(contents) + + def _extract_elements(self, input_str: str) -> dict[str, int]: + """ + Extract elements and their counts from a chemical formula string. + + Parameters + ---------- + input_str: str + A string representing a chemical formula (e.g., "SiO2"). + + Returns + ------- + Dict[str, int] + A dictionary. For example, the input "SiO2" would return {"Si": 1, "O": 2}. + """ + elements: dict[str, int] = {} + pattern = re.compile(r"([A-Z][a-z]*)(\d*)") + matches = pattern.findall(input_str) + + for match in matches: + element, count = match + count = int(count) if count else 1 + if element in elements: + elements[element] += count + else: + elements[element] = count + + return elements + + def _make_species(self, elements: dict[str, int]) -> str: + """ + Create a formatted string from a dictionary of element symbols and their counts. + + Parameters + ---------- + elements: dict + A dictionary of element symbols and their counts, e.g., {"Si": 1, "O": 2}. + + Returns + ------- + str + A formatter string. For example, the input {"Si": 1, "O": 2} would return "Si%NUM=1,O%NUM=2". + """ + output = "" + for element, count in elements.items(): + output += f"{element}%NUM={count}," + return output[:-1] + + def _make_minsep(self, r: dict[str, float]) -> str: + """ + Generate a minsep string based on the radii of the elements. + + Parameters + ---------- + r: dict + A dictionary of element symbols and their atomic radii. + + Returns + ------- + str + A formatted string. For example, the input {"Si": 1.1, "O": 0.66} would + return "1.5 Si-Si=1.76 Si-O=1.408 O-O=1.056". + + TODO: set up robust heuristics for multi-component systems + """ + keys = list(r.keys()) + if len(keys) == 1: + return str(1.6 * r[keys[0]]) + + minsep = "1.5 " + for i in range(len(keys)): + for j in range(i, len(keys)): + el1, el2 = keys[i], keys[j] + r1, r2 = r[el1], r[el2] + result = (r1 + r2) / 2 * 1.6 + + minsep += f"{el1}-{el2}={result} " + + return minsep[:-1] + + def _parallel_process( + self, i: int, bc_file: str, tag: str, remove_tmp_files: bool + ) -> Atoms: + """ + Run the 'buildcell' command in parallel. + + Parameters + ---------- + i: int + Unique index to differentiate temporary files. + bc_file: str + Path to the input 'buildcell' file. + tag: str + Tag used to differentiate temporary files. + remove_tmp_files: bool + If True, remove temporary files after processing. + + """ + tmp_file_name = "tmp." + str(i) + "." + tag + ".cell" + + with ( + open(bc_file) as bc_file_handle, + open(tmp_file_name, "w") as tmp_file_handle, + ): + run( + "buildcell", + stdin=bc_file_handle, + stdout=tmp_file_handle, + shell=True, + check=True, + ) + + atom = ase.io.read(tmp_file_name, parallel=False) + atom.info["unique_starting_index"] = i + + if "castep_labels" in atom.arrays: + del atom.arrays["castep_labels"] + + if "initial_magmoms" in atom.arrays: + del atom.arrays["initial_magmoms"] + + if remove_tmp_files: + os.remove(tmp_file_name) + + return atom
+ + + +
+[docs] +@job +def do_rss_single_node( + mlip_type: str, + mlip_path: str, + iteration_index: str, + structures: list[Structure], + output_file_name: str = "RSS_relax_results", + scalar_pressure_method: str = "exp", + scalar_exp_pressure: float = 100, + scalar_pressure_exponential_width: float = 0.2, + scalar_pressure_low: float = 0, + scalar_pressure_high: float = 50, + max_steps: int = 1000, + force_tol: float = 0.01, + stress_tol: float = 0.01, + hookean_repul: bool = False, + hookean_paras: dict[tuple[int, int], tuple[float, float]] | None = None, + write_traj: bool = True, + num_processes_rss: int = 1, + device: str = "cpu", + isolated_atom_energies: dict[int, float] | None = None, + struct_start_index: int = 0, + config_type: str = "traj", + keep_symmetry: bool = True, +) -> list[str | None]: + """ + Perform sandom structure searching (RSS) on one node using a machine learning interatomic potential (MLIP). + + Parameters + ---------- + mlip_type: str + Choose one specific MLIP type: + 'GAP' | 'J-ACE' | 'NequIP' | 'M3GNet' | 'MACE'. + mlip_path: str + Path to the MLIP model. + iteration_index: str + Index for the current iteration. + structures: list[Structure] + List of structures to be relaxed. + output_file_name: str + Prefix for the trajectory/log file name. The actual output file name + may be composed of this prefix, an index, and file types. + scalar_pressure_method: str + Method for adding external pressures. Default is 'exp'. + scalar_exp_pressure: float + Scalar exponential pressure. Default is 100. + scalar_pressure_exponential_width: float + Width for scalar pressure exponential. Default is 0.2. + scalar_pressure_low: float + Low limit for scalar pressure. Default is 0. + scalar_pressure_high: float + High limit for scalar pressure. Default is 50. + max_steps: int + Maximum number of steps for relaxation. Default is 1000. + force_tol: float + Force residual tolerance for relaxation. Default is 0.01. + stress_tol: float + Stress residual tolerance for relaxation. Default is 0.01. + hookean_repul: bool + If true, apply Hookean repulsion. Default is False. + hookean_paras: dict + Parameters for Hookean repulsion as a dictionary of tuples. Default is None. + write_traj: bool + If true, write trajectory of RSS. Default is True. + num_processes_rss: int + Number of processes used for running RSS. + device: str + Specify device to use "cuda" or "cpu". + isolated_atom_energies: dict + Dictionary of isolated atoms energies. + struct_start_index: int + Specify the starting index within a list + config_type: str + Specify the type of configurations generated from RSS + keep_symmetry: bool + If true, preserve symmetry during relaxation. + + Returns + ------- + list + Output list[str] containing paths for the results of the RSS relaxation. + """ + return minimize_structures( + mlip_type=mlip_type, + mlip_path=mlip_path, + iteration_index=iteration_index, + structures=structures, + output_file_name=output_file_name, + scalar_pressure_method=scalar_pressure_method, + scalar_exp_pressure=scalar_exp_pressure, + scalar_pressure_exponential_width=scalar_pressure_exponential_width, + scalar_pressure_low=scalar_pressure_low, + scalar_pressure_high=scalar_pressure_high, + max_steps=max_steps, + force_tol=force_tol, + stress_tol=stress_tol, + hookean_repul=hookean_repul, + hookean_paras=hookean_paras, + write_traj=write_traj, + num_processes_rss=num_processes_rss, + device=device, + isolated_atom_energies=isolated_atom_energies, + struct_start_index=struct_start_index, + config_type=config_type, + keep_symmetry=keep_symmetry, + )
+ + + +
+[docs] +@job +def do_rss_multi_node( + mlip_type: str, + mlip_path: str, + iteration_index: str, + structure: list[Structure] | list[list[Structure]] | None = None, + structure_paths: str | list[str] | None = None, + output_file_name: str = "RSS_relax_results", + scalar_pressure_method: str = "exp", + scalar_exp_pressure: float = 100, + scalar_pressure_exponential_width: float = 0.2, + scalar_pressure_low: float = 0, + scalar_pressure_high: float = 50, + max_steps: int = 1000, + force_tol: float = 0.01, + stress_tol: float = 0.01, + hookean_repul: bool = False, + hookean_paras: dict[tuple[int, int], tuple[float, float]] | None = None, + write_traj: bool = True, + num_processes_rss: int = 1, + device: str = "cpu", + isolated_atom_energies: dict[int, float] | None = None, + num_groups: int = 1, + config_type: str = "traj", + keep_symmetry: bool = True, +) -> list[list | None]: + """ + Perform sandom structure searching (RSS) on multiple nodes using a machine learning interatomic potential (MLIP). + + Parameters + ---------- + mlip_type: str + Choose one specific MLIP type: + 'GAP' | 'J-ACE' | 'NequIP' | 'M3GNet' | 'MACE'. + mlip_path: str + Path to the MLIP model. + iteration_index: str + Index for the current iteration. + structure: list[Structure] + List of structures to be relaxed. + structure_paths: str | list[str] + Path(s) to structures to be used in the RSS process. + output_file_name: str + Prefix for the trajectory/log file name. The actual output file name + may be composed of this prefix, an index, and file types. + scalar_pressure_method: str + Method for adding external pressures. Default is 'exp'. + scalar_exp_pressure: float + Scalar exponential pressure. Default is 100. + scalar_pressure_exponential_width: float + Width for scalar pressure exponential. Default is 0.2. + scalar_pressure_low: float + Low limit for scalar pressure. Default is 0. + scalar_pressure_high: float + High limit for scalar pressure. Default is 50. + max_steps: int + Maximum number of steps for relaxation. Default is 1000. + force_tol: float + Force residual tolerance for relaxation. Default is 0.01. + stress_tol: float + Stress residual tolerance for relaxation. Default is 0.01. + hookean_repul: bool + If true, apply Hookean repulsion. Default is False. + hookean_paras: dict + Parameters for Hookean repulsion as a dictionary of tuples. Default is None. + write_traj: bool + If true, write trajectory of RSS. Default is True. + num_processes_rss: int + Number of processes used for running RSS. + device: str + Specify device to use "cuda" or "cpu". + isolated_atom_energies: dict + Dictionary of isolated atoms energies. + num_groups: int + Number of structure groups, used for assigning tasks across multiple nodes, + with each node handling one group. + config_type: str + Specify the type of configurations generated from RSS + keep_symmetry: bool + If true, preserve symmetry during relaxation. + + Returns + ------- + list + Output list[str] containing paths for the results of the RSS relaxation. + """ + job_list = [] + + if structure_paths is not None: + if isinstance(structure_paths, list): + atoms = [ase.io.read(dir, index=":") for dir in structure_paths] + atoms = flatten(atoms, recursive=True) + elif isinstance(structure_paths, str): + atoms = ase.io.read(structure_paths, index=":") + structure = [AseAtomsAdaptor().get_structure(at) for at in atoms] + elif structure is not None and isinstance(structure[0], list): + structure = flatten(structure, recursive=False) + + if structure is not None and isinstance(structure, list): + structure_groups = split_structure_into_groups(structure, num_groups) + else: + raise ValueError("Invalid structure format. It must be a list of structures.") + + rss_info = [] + + struct_start_index = 0 + + for i in range(num_groups): + rss = do_rss_single_node( + mlip_type=mlip_type, + mlip_path=mlip_path, + iteration_index=iteration_index, + structures=structure_groups[i], + output_file_name=output_file_name, + scalar_pressure_method=scalar_pressure_method, + scalar_exp_pressure=scalar_exp_pressure, + scalar_pressure_exponential_width=scalar_pressure_exponential_width, + scalar_pressure_low=scalar_pressure_low, + scalar_pressure_high=scalar_pressure_high, + max_steps=max_steps, + force_tol=force_tol, + stress_tol=stress_tol, + hookean_repul=hookean_repul, + hookean_paras=hookean_paras, + write_traj=write_traj, + num_processes_rss=num_processes_rss, + device=device, + isolated_atom_energies=isolated_atom_energies, + struct_start_index=struct_start_index, + config_type=config_type, + keep_symmetry=keep_symmetry, + ) + + struct_start_index += len(structure_groups[i]) + + job_list.append(rss) + rss_info.append(rss.output) + + return Response(replace=Flow(job_list), output=rss_info)
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/autoplex/data/rss/utils.html b/_modules/autoplex/data/rss/utils.html new file mode 100644 index 000000000..18bfe51c3 --- /dev/null +++ b/_modules/autoplex/data/rss/utils.html @@ -0,0 +1,1219 @@ + + + + + + + + + + autoplex.data.rss.utils — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+
+
+ + + + +
+ +

Source code for autoplex.data.rss.utils

+"""Utility functions for rss."""
+
+import ast
+import json
+import os
+from multiprocessing import Pool
+from pathlib import Path
+from typing import Literal
+
+import ase.io
+import matgl
+import numpy as np
+import quippy.potential
+from ase import Atoms
+from ase.constraints import (
+    FixConstraint,
+    FixSymmetry,
+    UnitCellFilter,
+    slice2enlist,
+)
+from ase.data import atomic_numbers, chemical_symbols
+from ase.geometry import find_mic
+from ase.optimize.precon import Exp, PreconLBFGS
+from ase.units import GPa
+from mace.calculators import MACECalculator
+from matgl.ext.ase import M3GNetCalculator
+from nequip.ase import NequIPCalculator
+from pymatgen.core import Structure
+from pymatgen.io.ase import AseAtomsAdaptor
+
+from autoplex.fitting.common.utils import extract_gap_label
+
+
+
+[docs] +class CustomPotential(quippy.potential.Potential): + """A custom potential class that modifies the outputs of potentials.""" + +
+[docs] + def calculate(self, *args, **kwargs): + """Update the atoms object with forces, energy, and virial information.""" + res = super().calculate(*args, **kwargs) + atoms = kwargs["atoms"] if "atoms" in kwargs else args[0] + if "forces" in self.results: + atoms.arrays["forces"] = self.results["forces"].copy() + if "energy" in self.results: + atoms.info["energy"] = self.results["energy"].copy() + if "stress" in self.results: + atoms.info["stress"] = self.results["stress"].copy() + if "virial" in self.extra_results["config"]: + atoms.info["virial"] = self.extra_results["config"]["virial"].copy() + return res
+
+ + + +
+[docs] +def extract_pairstyle( + ace_label: str, ace_json: str, ace_table: str +) -> tuple[dict[str, int], list[str]]: + """ + Extract the pair style and coefficients from ACE potential files for running LAMMPS. + + Parameters + ---------- + ace_label: str + Label for the ACE potential. + ace_json: str + Path to the JSON file of ACE potential. + ace_table: str + Path to the table file containing pairwise coefficients of ACE potential. + """ + with open(ace_json) as file: + data = json.load(file) + + E0 = data["IP"]["components"][2]["E0"] + + elements = list(E0.keys()) + + sorted_elements = sorted(elements, key=lambda x: atomic_numbers[x]) + + with open(ace_table) as file: + lines = file.readlines() + + for line in lines: + if line.strip().startswith("N "): + n_value = line.strip().split()[1] + break + + elements_str = " ".join(sorted_elements) + + cmds = [ + f"pair_style hybrid/overlay pace table spline {n_value}", + f"pair_coeff * * pace {ace_label} {elements_str}", + ] + + atom_types = {} + + for i in range(len(sorted_elements)): + atom_types[sorted_elements[i]] = i + 1 + + for j in range(i, len(sorted_elements)): + pairs = sorted_elements[i] + "_" + sorted_elements[j] + cmds.append(f"pair_coeff {i+1} {j+1} table {ace_table} {pairs}") + + return atom_types, cmds
+ + + +
+[docs] +class HookeanRepulsion(FixConstraint): + """Constrain atoms softly to a minimum separation. + + Intended to avoid early iterations of potentials + causing crashes due to overlapping atoms. + + It is recommended to calibrate the spring constant for your system + dependent on the potential and the atomic species used. It is not guaranteed + that the constraint will be either soft enough (e.g. non-exploding in MD) or + strong enough (to avoid overlaps) for all spring constants and distances. + + References + ---------- + * Title: ASE constraints package at at ase/ase/constraints.py + * Author: Ask Hjorth Larsen + * Date 07/10/2024 + * Code version: 3.23.0 + * Availability: https://gitlab.com/ase/ + """ + + def __init__( + self, + a1: int, + a2: ( + int + | tuple[float, float, float] + | tuple[float, float, float, float] + | Literal["cell"] + ), + k: float, + rt: float | None = None, + ) -> None: + """Apply a Hookean restorative force repel two atoms that are close. + + Parameters + ---------- + a1: int + Atom 1 index + a2: can be one of three options + 1) Atom 2 index + 2) a fixed point in cartesian space to which to tether a1 + 3) a plane given as (A, B, C, D) in A x + B y + C z + D = 0. + 4) 'cell' :: selects the unit cell to constrain + k: float + Hooke's law (spring) constant to apply when distance + exceeds threshold_length. Units are eV Å^-2. + rt: float + Threshold length above which no Hookean force is applied. + This argument is not supplied in case 3. Units of Å. + + Notes + ----- + If a plane is specified, the Hooke's law force is applied if the atom + is on the normal side of the plane. For instance, the plane with + (A, B, C, D) = (0, 0, 1, -7) defines a plane in the xy plane with a z + intercept of +7 and a normal vector pointing in the +z direction. + If the atom has z > 7, then a downward force would be applied of + k * (atom.z - 7). The same plane with the normal vector pointing in + the -z direction would be given by (A, B, C, D) = (0, 0, -1, 7). + + References + ---------- + Andrew A. Peterson, Topics in Catalysis volume 57, pages40-53 (2014) + https://link.springer.com/article/10.1007%2Fs11244-013-0161-8 + """ + if isinstance(a2, int): + self._type = "two atoms" + self.indices = [a1, a2] + elif len(a2) == 3: + self._type = "point" + self.index = a1 + self.origin = np.array(a2) + elif len(a2) == 4: + self._type = "plane" + self.index = a1 + self.plane = a2 + + else: + raise RuntimeError("Unknown type for a2") + self.threshold = rt + self.spring = k + self.used = False + +
+[docs] + def get_removed_dof(self, atoms): + """Get number of removed degrees of freedom due to constraint.""" + return 0
+ + +
+[docs] + def todict(self): + """Convert constraint to dictionary.""" + dct = {"name": "Hookean"} + dct["kwargs"] = {"rt": self.threshold, "k": self.spring} + if self._type == "two atoms": + dct["kwargs"]["a1"] = self.indices[0] + dct["kwargs"]["a2"] = self.indices[1] + elif self._type == "point": + dct["kwargs"]["a1"] = self.index + dct["kwargs"]["a2"] = self.origin + elif self._type == "plane": + dct["kwargs"]["a1"] = self.index + dct["kwargs"]["a2"] = self.plane + else: + raise NotImplementedError(f"Bad type: {self._type}") + return dct
+ + +
+[docs] + def adjust_positions(self, atoms, newpositions): + """Adjust positions to match the constraints. + + Do nothing for this constraint. + """
+ + +
+[docs] + def adjust_momenta(self, atoms, momenta): + """Adjust momenta to match the constraints. + + Do nothing for this constraint. + """
+ + +
+[docs] + def adjust_forces(self, atoms, forces): + """Adjust forces on the atoms to match the constraints.""" + positions = atoms.positions + if self._type == "plane": + A, B, C, D = self.plane + x, y, z = positions[self.index] + d = (A * x + B * y + C * z + D) / np.sqrt(A**2 + B**2 + C**2) + if d < 0: + return + magnitude = self.spring * d + direction = -np.array((A, B, C)) / np.linalg.norm((A, B, C)) + forces[self.index] += direction * magnitude + return + if self._type == "two atoms": + p1, p2 = positions[self.indices] + elif self._type == "point": + p1 = positions[self.index] + p2 = self.origin + + displace, _ = find_mic(p2 - p1, atoms.cell, atoms.pbc) + bondlength = np.linalg.norm(displace) + + if bondlength < self.threshold: + print( + "Hookean adjusting forces, bondlength: ", + bondlength, + " < ", + self.threshold, + ) + self.used = True + magnitude = self.spring * (self.threshold - bondlength) + direction = displace / np.linalg.norm(displace) + if self._type == "two atoms": + forces[self.indices[0]] -= direction * magnitude + forces[self.indices[1]] += direction * magnitude + else: + forces[self.index] += direction * magnitude
+ + +
+[docs] + def adjust_potential_energy(self, atoms): + """Return the difference to the potential energy due to an active constraint. + + (the quantity returned is to be added to the potential energy). + """ + positions = atoms.positions + if self._type == "plane": + A, B, C, D = self.plane + x, y, z = positions[self.index] + d = (A * x + B * y + C * z + D) / np.sqrt(A**2 + B**2 + C**2) + if d > 0: + return 0.5 * self.spring * d**2 + return 0.0 + if self._type == "two atoms": + p1, p2 = positions[self.indices] + elif self._type == "point": + p1 = positions[self.index] + p2 = self.origin + displace, _ = find_mic(p2 - p1, atoms.cell, atoms.pbc) + bondlength = np.linalg.norm(displace) + if bondlength < self.threshold: + return 0.5 * self.spring * (bondlength - self.threshold) ** 2 + return 0.0
+ + +
+[docs] + def get_indices(self): + """Get the indices.""" + if self._type == "two atoms": + return self.indices + if self._type == "point": + return self.index + if self._type == "plane": + return self.index + return None
+ + +
+[docs] + def index_shuffle(self, atoms, ind): + """Change the indices.""" + if self._type == "two atoms": + newa = [-1, -1] # Error condition + for new, old in slice2enlist(ind, len(atoms)): + for i, a in enumerate(self.indices): + if old == a: + newa[i] = new + if newa[0] == -1 or newa[1] == -1: + raise IndexError("Constraint not part of slice") + self.indices = newa + elif (self._type == "point") or (self._type == "plane"): + new_a = -1 # Error condition + for new, old in slice2enlist(ind, len(atoms)): + if old == self.index: + new_a = new + break + if new_a == -1: + raise IndexError("Constraint not part of slice") + self.index = new_a
+ + + def __repr__(self): + """Return a representation of the constraint.""" + if self._type == "two atoms": + return f"Hookean({self.indices[0]}, {self.indices[1]})" + if self._type == "point": + return f"Hookean({self.index}) to cartesian" + return f"Hookean({self.index}) to plane"
+ + + +
+[docs] +def process_rss( + atom: Atoms, + mlip_type: str, + mlip_path: str, + output_file_name: str = "RSS_relax_results", + scalar_pressure_method: str = "exp", + scalar_exp_pressure: float = 100, + scalar_pressure_exponential_width: float = 0.2, + scalar_pressure_low: float = 0, + scalar_pressure_high: float = 50, + max_steps: int = 1000, + force_tol: float = 0.01, + stress_tol: float = 0.01, + hookean_repul: bool = False, + hookean_paras: dict | None = None, + write_traj: bool = True, + device: str = "cpu", + isolated_atom_energies: dict[int, float] | None = None, + config_type: str = "traj", + keep_symmetry: bool = True, +) -> str | None: + """Run RSS on a single thread using MLIPs. + + Parameters + ---------- + atom: Atoms + ASE Atoms object representing the atomic configuration. + mlip_type: str + Choose one specific MLIP type: + 'GAP' | 'J-ACE' | 'NequIP' | 'M3GNet' | 'MACE'. + mlip_path: str + Path to the MLIP model. + output_file_name: str + Prefix for the trajectory/log file name. The actual output file name + may be composed of this prefix, an index, and file types. + scalar_pressure_method: str + Method for adding external pressures. Default is 'exp'. + scalar_exp_pressure: float + Scalar exponential pressure. Default is 100. + scalar_pressure_exponential_width: float + Width for scalar pressure exponential. Default is 0.2. + scalar_pressure_low: float + Low limit for scalar pressure. Default is 0. + scalar_pressure_high: float + High limit for scalar pressure. Default is 50. + max_steps: int + Maximum number of steps for relaxation. Default is 1000. + force_tol: float + Force residual tolerance for relaxation. Default is 0.01. + stress_tol: float + Stress residual tolerance for relaxation. Default is 0.01. + hookean_repul: bool + If true, apply Hookean repulsion. Default is False. + hookean_paras: dict[tuple[int, int], tuple[float, float]] + Parameters for Hookean repulsion as a dictionary of tuples. Default is None. + write_traj: bool + If true, write trajectory of RSS. Default is True. + device: str + Specify the device to use: "cuda" or "cpu". + isolated_atom_energies: dict + Dictionary of isolated atoms energies. + config_type: str + Specify the type of configurations generated from RSS. + keep_symmetry: bool + If true, preserve symmetry during relaxation. + + Returns + ------- + str | None + Output string containing path for the results of the RSS relaxation. + """ + if hookean_paras is not None: + hookean_paras = { + ast.literal_eval(k) if isinstance(k, str) else k: v + for k, v in hookean_paras.items() + } + + if mlip_type == "GAP": + gap_label = os.path.join(mlip_path, "gap_file.xml") + gap_control = "Potential xml_label=" + extract_gap_label(gap_label) + pot = CustomPotential(args_str=gap_control, param_filename=gap_label) + + elif mlip_type == "J-ACE": + from ase.calculators.lammpslib import LAMMPSlib + + try: + from lammps import lammps + + lmp = lammps() + if "ML-PACE" not in lmp.installed_packages: + raise RuntimeError( + "To use RSS flow with J-ACE potential, it needs LAMMPS compiled with" + "lammps-user-pace library. ML-PACE is not found in the current LAMMPS binary installation." + "Please follow the instructions in the autoplex documentation to install" + "LAMMPS with the required packages. Link to the documentation: " + " https://autoatml.github.io/autoplex/user/index.html#lammps-installation" + ) + except Exception as exc: + raise RuntimeError( + "To use RSS flow with J-ACE potential, it needs LAMMPS compiled with" + "python bindings and lammps-user-pace. Please follow the instructions in" + "the autoplex documentation to install LAMMPS with the required packages. " + "Link to the documentation:" + " https://autoatml.github.io/autoplex/user/index.html#lammps-installation" + ) from exc + + ace_label = os.path.join(mlip_path, "acemodel.yace") + ace_json = os.path.join(mlip_path, "acemodel.json") + ace_table = os.path.join(mlip_path, "acemodel_pairpot.table") + + atom_types, cmds = extract_pairstyle(ace_label, ace_json, ace_table) + + pot = LAMMPSlib( + lmpcmds=cmds, atom_types=atom_types, log_file="test.log", keep_alive=True + ) + + elif mlip_type == "NEQUIP": + nequip_label = os.path.join(mlip_path, "deployed_nequip_model.pth") + if isolated_atom_energies: + ele_syms = [ + chemical_symbols[int(e_num)] for e_num in isolated_atom_energies + ] + + else: + raise ValueError("isol_es is empty or not defined!") + pot = NequIPCalculator.from_deployed_model( + model_path=nequip_label, + device=device, + species_to_type_name={s: s for s in ele_syms}, + set_global_options=False, + ) + + elif mlip_type == "M3GNET": + pot_file = matgl.load_model(path=mlip_path) + pot = M3GNetCalculator(potential=pot_file) + + elif mlip_type == "MACE": + mace_label = os.path.join(mlip_path, "checkpoints/MACE_model_run-123.model") + pot = MACECalculator(model_paths=mace_label, device=device) + + unique_starting_index = atom.info["unique_starting_index"] + log_file = output_file_name + "_" + str(unique_starting_index) + ".log" + constraint_list = [] + if hookean_repul and hookean_paras: + atom_num = atom.get_atomic_numbers() + for i in range(len(atom_num)): + for j in range(i + 1, len(atom_num)): + if ( + (atom_num[i], atom_num[j]) in hookean_paras + and hookean_paras[(atom_num[i], atom_num[j])][0] != 0 + and hookean_paras[(atom_num[i], atom_num[j])][1] != 0 + ): + # print(f"Hookean repulsion is used for {atom_num[i]}-{atom_num[j]}!") + constraint_list.append( + HookeanRepulsion( + i, j, *hookean_paras[(atom_num[i], atom_num[j])] + ) + ) + elif ( + (atom_num[j], atom_num[i]) in hookean_paras + and hookean_paras[(atom_num[j], atom_num[i])][0] != 0 + and hookean_paras[(atom_num[j], atom_num[i])][1] != 0 + ): + # print(f"Hookean repulsion is used for {atom_num[j]}-{atom_num[i]}!") + constraint_list.append( + HookeanRepulsion( + i, j, *hookean_paras[(atom_num[j], atom_num[i])] + ) + ) + + if keep_symmetry: + print("Creating FixSymmetry calculator and maintaining initial symmetry!") + constraint_list.append(FixSymmetry(atom, symprec=1.0e-4)) + + if constraint_list: + atom.set_constraint(constraint_list) + + atom.calc = pot + + if scalar_pressure_method == "exp": + scalar_pressure_tmp = scalar_exp_pressure * GPa + if scalar_pressure_exponential_width > 0.0: + scalar_pressure_tmp *= np.random.exponential( + scalar_pressure_exponential_width + ) + elif scalar_pressure_method == "uniform": + scalar_pressure_tmp = ( + np.random.uniform(low=scalar_pressure_low, high=scalar_pressure_high) * GPa + ) + atom.info["RSS_applied_pressure"] = scalar_pressure_tmp / GPa + atom = UnitCellFilter(atom, scalar_pressure=scalar_pressure_tmp) + + try: + optimizer = PreconLBFGS( + atom, precon=Exp(3), use_armijo=True, logfile=log_file, master=True + ) + traj = [] + + def build_traj(): + atom_copy = atom.copy() + atom_copy.info["energy"] = atom.atoms.get_potential_energy() + atom_copy.info["enthalpy"] = atom.get_potential_energy().copy() + traj.append(atom_copy) + + optimizer.attach(build_traj) + optimizer.run(fmax=force_tol, smax=stress_tol, steps=max_steps) + + minim_stat = "converged" if optimizer.converged() else "unconverged" + + for traj_at_i, traj_at in enumerate(traj): + traj_at.info["RSS_minim_iter"] = traj_at_i + traj_at.info["config_type"] = config_type + traj_at.info["minim_stat"] = minim_stat + + if write_traj: + traj_file_name = ( + output_file_name + "_traj_" + str(unique_starting_index) + ".extxyz" + ) + ase.io.write(traj_file_name, traj, parallel=False) + del traj[-1].info["minim_stat"] + traj[-1].info["config_type"] = minim_stat + "_minimum" + + local_minima = traj[-1] + + if local_minima.info["config_type"] == "converged_minimum": + dir_path = Path.cwd() + return os.path.join(dir_path, traj_file_name) + return None + + except RuntimeError: + print("RuntimeError occurred during optimization! Return none!") + return None
+ + + +
+[docs] +def minimize_structures( + mlip_type: str, + mlip_path: str, + iteration_index: str, + structures: list[Structure], + output_file_name: str = "RSS_relax_results", + scalar_pressure_method: str = "exp", + scalar_exp_pressure: float = 100, + scalar_pressure_exponential_width: float = 0.2, + scalar_pressure_low: float = 0, + scalar_pressure_high: float = 50, + max_steps: int = 1000, + force_tol: float = 0.01, + stress_tol: float = 0.01, + hookean_repul: bool = False, + hookean_paras: dict[tuple[int, int], tuple[float, float]] | None = None, + write_traj: bool = True, + num_processes_rss: int = 1, + device: str = "cpu", + isolated_atom_energies: dict[int, float] | None = None, + config_type: str = "traj", + struct_start_index: int = 0, + keep_symmetry: bool = True, +) -> list[str | None]: + """Run RSS in parallel. + + Parameters + ---------- + mlip_type: str + Choose one specific MLIP type: + 'GAP' | 'J-ACE' | 'NequIP' | 'M3GNet' | 'MACE'. + mlip_path: str + Path to the MLIP model. + iteration_index: str + Index for the current iteration. + structures: list[Structure] + List of structures to be relaxed. + output_file_name: str + Prefix for the trajectory/log file name. The actual output file name + may be composed of this prefix, an index, and file types. + scalar_pressure_method: str + Method for adding external pressures. Default is 'exp'. + scalar_exp_pressure: float + Scalar exponential pressure. Default is 100. + scalar_pressure_exponential_width: float + Width for scalar pressure exponential. Default is 0.2. + scalar_pressure_low: float + Low limit for scalar pressure. Default is 0. + scalar_pressure_high: float + High limit for scalar pressure. Default is 50. + max_steps: int + Maximum number of steps for relaxation. Default is 1000. + force_tol: float + Force residual tolerance for relaxation. Default is 0.01. + stress_tol: float + Stress residual tolerance for relaxation. Default is 0.01. + hookean_repul: bool + If true, apply Hookean repulsion. Default is False. + hookean_paras: dict + Parameters for Hookean repulsion as a dictionary of tuples. Default is None. + write_traj: bool + If true, write trajectory of RSS. Default is True. + num_processes_rss: int + Number of processes used for running RSS. + device: str + Specify device to use "cuda" or "cpu". + isolated_atom_energies: dict + Dictionary of isolated atoms energies. + config_type: str + Specify the type of configurations generated from RSS + struct_start_index: int + Specify the starting index within a list + keep_symmetry: bool + If true, preserve symmetry during relaxation. + + Returns + ------- + list + Output list[str] containing paths for the results of the RSS relaxation. + """ + atoms = [AseAtomsAdaptor().get_atoms(structure) for structure in structures] + + if hookean_repul: + print("Hookean repulsion is used!") + + for i, atom in enumerate(atoms): + atom.info["unique_starting_index"] = iteration_index + f"{i+struct_start_index}" + + args = [ + ( + atom, + mlip_type, + mlip_path, + output_file_name, + scalar_pressure_method, + scalar_exp_pressure, + scalar_pressure_exponential_width, + scalar_pressure_low, + scalar_pressure_high, + max_steps, + force_tol, + stress_tol, + hookean_repul, + hookean_paras, + write_traj, + device, + isolated_atom_energies, + config_type, + keep_symmetry, + ) + for atom in atoms + ] + + with Pool(processes=num_processes_rss) as pool: + results = pool.starmap(process_rss, args) + + return list(results)
+ + + +
+[docs] +def split_structure_into_groups(structures: list, num_groups: int) -> list[list]: + """ + Split a list of structures into several groups, with each group being its own list. + + Parameters + ---------- + structures: list + List of structures + num_groups: int + Number of structure groups, used for assigning tasks across multiple nodes, + with each node handling one group. + """ + base_size = len(structures) // num_groups + remainder = len(structures) % num_groups + + structure_groups = [] + start_index = 0 + + for i in range(num_groups): + extra = 1 if i < remainder else 0 + group_size = base_size + extra + + structure_groups.append(structures[start_index : start_index + group_size]) + start_index += group_size + + return structure_groups
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/autoplex/fitting/common/flows.html b/_modules/autoplex/fitting/common/flows.html new file mode 100644 index 000000000..8db8ed3c6 --- /dev/null +++ b/_modules/autoplex/fitting/common/flows.html @@ -0,0 +1,911 @@ + + + + + + + + + + autoplex.fitting.common.flows — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+
+
+ + + + +
+ +

Source code for autoplex.fitting.common.flows

+"""Flows consisting of jobs to fit ML potentials."""
+
+import logging
+import os
+import shutil
+from dataclasses import dataclass
+from pathlib import Path
+
+import ase.io
+from jobflow import Flow, Maker, job
+
+from autoplex.fitting.common.jobs import machine_learning_fit
+from autoplex.fitting.common.regularization import set_custom_sigma
+from autoplex.fitting.common.utils import (
+    get_list_of_vasp_calc_dirs,
+    vaspoutput_2_extended_xyz,
+    write_after_distillation_data_split,
+)
+
+logging.basicConfig(
+    level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
+)
+
+__all__ = [
+    "DataPreprocessing",
+    "MLIPFitMaker",
+]
+
+
+
+[docs] +@dataclass +class MLIPFitMaker(Maker): + """ + Maker to fit ML potentials based on DFT labelled reference data. + + This Maker will filter the provided dataset in a data preprocessing step and then proceed + with the MLIP fit (default is GAP). + + Parameters + ---------- + name : str + Name of the flows produced by this maker. + mlip_type: str + Choose one specific MLIP type to be fitted: + 'GAP' | 'J-ACE' | 'NEQUIP' | 'M3GNET' | 'MACE' + hyperpara_opt: bool + Perform hyperparameter optimization using XPOT + (XPOT: https://pubs.aip.org/aip/jcp/article/159/2/024803/2901815) + ref_energy_name : str + Reference energy name. + ref_force_name : str + Reference force name. + ref_virial_name : str + Reference virial name. + glue_file_path: str + Name of the glue.xml file path. + split_ratio: float + Ratio to divide the dataset into training and test sets. + A value of 0.1 means 90% training data and 10% test data + force_max: float + Maximum allowed force in the dataset. + force_min: float + Minimal force cutoff value for atom-wise regularization. + regularization: bool + For using sigma regularization. + distillation: bool + For using data distillation. + separated: bool + Repeat the fit for each data_type available in the (combined) database. + pre_xyz_files: list[str] or None + Names of the pre-database train xyz file and test xyz file. + pre_database_dir: str or None + The pre-database directory. + path_to_hyperparameters : str or Path. + Path to JSON file containing the MLIP hyperparameters. + atomwise_regularization_parameter: float + Regularization value for the atom-wise force components. + atom_wise_regularization: bool + For including atom-wise regularization. + auto_delta: bool + Automatically determine delta for 2b, 3b and soap terms. + glue_xml: bool + Use the glue.xml core potential instead of fitting 2b terms. + num_processes_fit: int + Number of processes for fitting. + apply_data_preprocessing: bool + Determine whether to preprocess the data. + database_dir: Path | str + Path to the directory containing the database. + use_defaults: bool + If true, uses default fit parameters + """ + + name: str = "MLpotentialFit" + mlip_type: str = "GAP" + hyperpara_opt: bool = False + ref_energy_name: str = "REF_energy" + ref_force_name: str = "REF_forces" + ref_virial_name: str = "REF_virial" + glue_file_path: str = "glue.xml" + split_ratio: float = 0.4 + force_max: float = 40.0 + force_min: float = 0.01 # unit: eV Å-1 + distillation: bool = True + separated: bool = False + pre_xyz_files: list[str] | None = None + pre_database_dir: str | None = None + path_to_hyperparameters: Path | str | None = None + regularization: bool = False # This is only used for GAP. + atomwise_regularization_parameter: float = 0.1 # This is only used for GAP. + atom_wise_regularization: bool = True # This is only used for GAP. + auto_delta: bool = False # This is only used for GAP. + glue_xml: bool = False # This is only used for GAP. + num_processes_fit: int | None = None + apply_data_preprocessing: bool = True + database_dir: Path | str | None = None + use_defaults: bool = True + +
+[docs] + def make( + self, + fit_input: dict | None = None, # This is specific to phonon workflow + species_list: list | None = None, + isolated_atom_energies: dict | None = None, + device: str = "cpu", + **fit_kwargs, + ): + """ + Make a flow for fitting MLIP models. + + Parameters + ---------- + fit_input: dict + Output from the CompletePhononDFTMLDataGenerationFlow process. + species_list: list + List of element names (strings) involved in the training dataset + isolated_atom_energies: dict + Dictionary of isolated atoms energies. + device: str + Device to be used for model fitting, either "cpu" or "cuda". + fit_kwargs: dict + Additional keyword arguments for MLIP fitting. + """ + if self.mlip_type not in ["GAP", "J-ACE", "NEQUIP", "M3GNET", "MACE"]: + raise ValueError( + "Please correct the MLIP name!" + "The current version ONLY supports the following models: GAP, J-ACE, NEQUIP, M3GNET, and MACE." + ) + + if self.apply_data_preprocessing: + jobs = [] + data_prep_job = DataPreprocessing( + split_ratio=self.split_ratio, + regularization=self.regularization, + separated=self.separated, + distillation=self.distillation, + force_max=self.force_max, + pre_xyz_files=self.pre_xyz_files, + pre_database_dir=self.pre_database_dir, + force_min=self.force_min, + atomwise_regularization_parameter=self.atomwise_regularization_parameter, + atom_wise_regularization=self.atom_wise_regularization, + ).make( + fit_input=fit_input, + ) + jobs.append(data_prep_job) + + mlip_fit_job = machine_learning_fit( + database_dir=data_prep_job.output, + isolated_atom_energies=isolated_atom_energies, + num_processes_fit=self.num_processes_fit, + auto_delta=self.auto_delta, + glue_xml=self.glue_xml, + glue_file_path=self.glue_file_path, + mlip_type=self.mlip_type, + hyperpara_opt=self.hyperpara_opt, + ref_energy_name=self.ref_energy_name, + ref_force_name=self.ref_force_name, + ref_virial_name=self.ref_virial_name, + use_defaults=self.use_defaults, + device=device, + species_list=species_list, + **fit_kwargs, + ) + jobs.append(mlip_fit_job) + + return Flow(jobs=jobs, output=mlip_fit_job.output, name=self.name) + + # this will only run if train.extxyz and test.extxyz files are present in the database_dir + # TODO: shouldn't this be the exception rather then the default run?! + # TODO: I assume we always want to use data from before? + if isinstance(self.database_dir, str): + self.database_dir = Path(self.database_dir) + + mlip_fit_job = machine_learning_fit( + database_dir=self.database_dir, + isolated_atom_energies=isolated_atom_energies, + num_processes_fit=self.num_processes_fit, + auto_delta=self.auto_delta, + glue_xml=self.glue_xml, + glue_file_path=self.glue_file_path, + mlip_type=self.mlip_type, + hyperpara_opt=self.hyperpara_opt, + ref_energy_name=self.ref_energy_name, + ref_force_name=self.ref_force_name, + ref_virial_name=self.ref_virial_name, + device=device, + species_list=species_list, + **fit_kwargs, + ) + + return Flow(jobs=mlip_fit_job, output=mlip_fit_job.output, name=self.name)
+
+ + + +
+[docs] +@dataclass +class DataPreprocessing(Maker): + """ + Data preprocessing of the provided dataset. + + Parameters + ---------- + name : str + Name of the flows produced by this maker. + split_ratio: float + Parameter to divide the training set and the test set. + A value of 0.1 means that the ratio of the training set to the test set is 9:1 + regularization: bool + For using sigma regularization. + separated: bool + Repeat the fit for each data_type available in the (combined) database. + distillation: bool + For using data distillation. + force_max: float + Maximally allowed force in the data set. + force_min: float + Minimal force cutoff value for atom-wise regularization. + pre_database_dir: str or None + The pre-database directory. + pre_xyz_files: list[str] or None + Names of the pre-database train xyz file and test xyz file labelled by VASP. + atomwise_regularization_parameter: float + Regularization value for the atom-wise force components. + atom_wise_regularization: bool + If True, includes atom-wise regularization. + + """ + + name: str = "data_preprocessing_for_fitting" + split_ratio: float = 0.5 + regularization: bool = False + separated: bool = False + distillation: bool = False + force_max: float = 40.0 + force_min: float = 0.01 # unit: eV Å-1 + pre_database_dir: str | None = None + pre_xyz_files: list[str] | None = None + atomwise_regularization_parameter: float = 0.1 + atom_wise_regularization: bool = True + +
+[docs] + @job + def make( + self, + fit_input: dict, + ): + """ + Maker for data preprocessing. + + Parameters + ---------- + fit_input: + Mixed list of dictionary and lists of the fit input data. + """ + if self.pre_xyz_files is None: + self.pre_xyz_files = ["train.extxyz", "test.extxyz"] + + list_of_vasp_calc_dirs = get_list_of_vasp_calc_dirs(flow_output=fit_input) + + config_types = [ + key + for key, value in fit_input.items() + for key2, value2 in value.items() + if key2 != "phonon_data" + for _ in value2[0] + ] + + data_types = [ + key2 + for key, value in fit_input.items() + for key2, value2 in value.items() + if key2 != "phonon_data" + for _ in value2[0] + ] + + if self.pre_database_dir and os.path.exists(self.pre_database_dir): + current_working_directory = os.getcwd() + + if len(self.pre_xyz_files) == 1: + for file_name in self.pre_xyz_files: + source_file_path = os.path.join(self.pre_database_dir, file_name) + destination_file_path = os.path.join( + current_working_directory, "vasp_ref.extxyz" + ) + shutil.copy(source_file_path, destination_file_path) + logging.info( + f"File {file_name} has been copied to {destination_file_path}" + ) + if len(self.pre_xyz_files) == 2: + # join to one file and then split again afterwards + # otherwise, split percentage will not be true + destination_file_path = os.path.join( + current_working_directory, "vasp_ref.extxyz" + ) + for file_name in self.pre_xyz_files: + # TODO: if it makes sense to remove isolated atoms from other files as well + atoms_list = ase.io.read( + os.path.join(self.pre_database_dir, file_name), index=":" + ) + new_atoms_list = [ + atoms + for atoms in atoms_list + if atoms.info["config_type"] != "IsolatedAtom" + ] + + ase.io.write(destination_file_path, new_atoms_list, append=True) + + logging.info( + f"File {self.pre_xyz_files[0]} has been copied to {destination_file_path}" + ) + + elif len(self.pre_xyz_files) > 2: + raise ValueError( + "Please provide a train and a test extxyz file (two files in total) for the pre_xyz_files." + ) + + vaspoutput_2_extended_xyz( + path_to_vasp_static_calcs=list_of_vasp_calc_dirs, + config_types=config_types, + data_types=data_types, + f_min=self.force_min, + regularization=self.atomwise_regularization_parameter, + atom_wise_regularization=self.atom_wise_regularization, + ) + + write_after_distillation_data_split( + self.distillation, self.force_max, self.split_ratio + ) + + # Merging database + # TODO: does a merge happen here? + if self.regularization: + base_dir = os.getcwd() + folder_name = os.path.join(base_dir, "without_regularization") + try: + os.makedirs(folder_name, exist_ok=True) + logging.info(f"Created/verified folder: {folder_name}") + except Exception as e: + logging.warning(f"Error creating folder {folder_name}: {e}") + train_path = os.path.join(folder_name, "train.extxyz") + test_path = os.path.join(folder_name, "test.extxyz") + atoms = ase.io.read("train.extxyz", index=":") + ase.io.write(train_path, atoms, format="extxyz") + logging.info(f"Written train file without regularization to: {train_path}") + try: + shutil.copy("test.extxyz", test_path) + logging.info(f"Copied test file to: {test_path}") + except FileNotFoundError: + logging.warning("test.extxyz not found. Skipping copy.") + except Exception as e: + logging.warning(f"Error copying test.extxyz: {e}") + atoms_with_sigma = set_custom_sigma( + atoms, + reg_minmax=[(0.1, 1), (0.001, 0.1), (0.0316, 0.316), (0.0632, 0.632)], + ) + ase.io.write("train.extxyz", atoms_with_sigma, format="extxyz") + if self.separated: + base_dir = os.getcwd() + atoms_train = ase.io.read("train.extxyz", index=":") + atoms_test = ase.io.read("test.extxyz", index=":") + for dt in set(data_types): + data_type = dt.removesuffix("_dir") + if data_type != "iso_atoms": + folder_name = os.path.join(base_dir, data_type) + try: + os.makedirs(folder_name, exist_ok=True) + logging.info(f"Created/verified folder: {folder_name}") + except Exception as e: + logging.warning( + f"Error creating folder {folder_name}: {e}. " + f"\nProceeding without separated dataset" + ) + continue + vasp_ref_path = os.path.join(folder_name, "vasp_ref.extxyz") + train_path = os.path.join(folder_name, "train.extxyz") + test_path = os.path.join(folder_name, "test.extxyz") + + for atoms in atoms_train + atoms_test: + if atoms.info["data_type"] == "iso_atoms": + ase.io.write( + vasp_ref_path, + atoms, + format="extxyz", + append=True, + ) + if atoms.info["data_type"] == data_type: + ase.io.write( + vasp_ref_path, + atoms, + format="extxyz", + append=True, + ) + try: + write_after_distillation_data_split( + distillation=self.distillation, + force_max=self.force_max, + split_ratio=self.split_ratio, + vasp_ref_name=vasp_ref_path, + train_name=train_path, + test_name=test_path, + ) + logging.info(f"Data split written: {train_path}, {test_path}") + except Exception as e: + logging.warning( + f"Error in write_after_distillation_data_split: {e}" + ) + + return Path.cwd()
+
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/autoplex/fitting/common/jobs.html b/_modules/autoplex/fitting/common/jobs.html new file mode 100644 index 000000000..f00490a16 --- /dev/null +++ b/_modules/autoplex/fitting/common/jobs.html @@ -0,0 +1,656 @@ + + + + + + + + + + autoplex.fitting.common.jobs — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+
+
+ + + + +
+ +

Source code for autoplex.fitting.common.jobs

+"""General fitting jobs using several MLIPs available."""
+
+from pathlib import Path
+
+from jobflow import job
+
+from autoplex.fitting.common.utils import (
+    check_convergence,
+    gap_fitting,
+    jace_fitting,
+    m3gnet_fitting,
+    mace_fitting,
+    nequip_fitting,
+)
+
+current_dir = Path(__file__).absolute().parent
+GAP_DEFAULTS_FILE_PATH = current_dir / "mlip-phonon-defaults.json"
+
+
+
+[docs] +@job +def machine_learning_fit( + database_dir: str | Path, + species_list: list, + path_to_hyperparameters: Path | str | None = None, + isolated_atom_energies: dict | None = None, + num_processes_fit: int = 32, + auto_delta: bool = True, + glue_xml: bool = False, + glue_file_path: str = "glue.xml", + mlip_type: str | None = None, + ref_energy_name: str = "REF_energy", + ref_force_name: str = "REF_forces", + ref_virial_name: str = "REF_virial", + use_defaults: bool = True, + device: str = "cuda", + hyperpara_opt: bool = False, + **fit_kwargs, +): + """ + Job for fitting potential(s). + + Parameters + ---------- + database_dir: Str | Path + Path to the directory containing the database. + species_list: list + List of element names (strings) involved in the training dataset + path_to_hyperparameters : str or Path. + Path to JSON file containing the MLIP hyperparameters. + isolated_atom_energies: dict + Dictionary of isolated atoms energies. + num_processes_fit: int + Number of processes for fitting. + auto_delta: bool + Automatically determine delta for 2b, 3b and soap terms. + glue_xml: bool + Use the glue.xml core potential instead of fitting 2b terms. + glue_file_path: str + Name of the glue.xml file path. + mlip_type: str + Choose one specific MLIP type to be fitted: + 'GAP' | 'J-ACE' | 'NEQUIP' | 'M3GNET' | 'MACE' + ref_energy_name: str + Reference energy name. + ref_force_name: str + Reference force name. + ref_virial_name: str + Reference virial name. + use_defaults: bool + If True, use default fitting parameters + device: str + Device to be used for model fitting, either "cpu" or "cuda". + hyperpara_opt: bool + Perform hyperparameter optimization using XPOT + (XPOT: https://pubs.aip.org/aip/jcp/article/159/2/024803/2901815) + fit_kwargs: dict + Additional keyword arguments for MLIP fitting. + """ + if isinstance(database_dir, str): # data_prep_job.output is returned as string + database_dir = Path(database_dir) + + train_files = [ + "train.extxyz", + "without_regularization/train.extxyz", + "phonon/train.extxyz", + "rattled/train.extxyz", + ] + test_files = [ + "test.extxyz", + "without_regularization/test.extxyz", + "phonon/test.extxyz", + "rattled/test.extxyz", + ] + + mlip_paths = [] + + if mlip_type == "GAP": + for train_name, test_name in zip(train_files, test_files): + if (database_dir / train_name).exists() and ( + database_dir / test_name + ).exists(): + train_test_error = gap_fitting( + db_dir=database_dir, + path_to_hyperparameters=path_to_hyperparameters, + species_list=species_list, + num_processes_fit=num_processes_fit, + auto_delta=auto_delta, + glue_xml=glue_xml, + glue_file_path=glue_file_path, + ref_energy_name=ref_energy_name, + ref_force_name=ref_force_name, + ref_virial_name=ref_virial_name, + train_name=train_name, + test_name=test_name, + fit_kwargs=fit_kwargs, + ) + mlip_paths.append(train_test_error["mlip_path"]) + + elif mlip_type == "J-ACE": + train_test_error = jace_fitting( + db_dir=database_dir, + path_to_hyperparameters=path_to_hyperparameters, + isolated_atom_energies=isolated_atom_energies, + ref_energy_name=ref_energy_name, + ref_force_name=ref_force_name, + ref_virial_name=ref_virial_name, + num_processes_fit=num_processes_fit, + fit_kwargs=fit_kwargs, + ) + mlip_paths.append(train_test_error["mlip_path"]) + + elif mlip_type == "NEQUIP": + train_test_error = nequip_fitting( + db_dir=database_dir, + path_to_hyperparameters=path_to_hyperparameters, + isolated_atom_energies=isolated_atom_energies, + ref_energy_name=ref_energy_name, + ref_force_name=ref_force_name, + ref_virial_name=ref_virial_name, + fit_kwargs=fit_kwargs, + device=device, + ) + mlip_paths.append(train_test_error["mlip_path"]) + + elif mlip_type == "M3GNET": + train_test_error = m3gnet_fitting( + db_dir=database_dir, + path_to_hyperparameters=path_to_hyperparameters, + ref_energy_name=ref_energy_name, + ref_force_name=ref_force_name, + ref_virial_name=ref_virial_name, + fit_kwargs=fit_kwargs, + device=device, + ) + mlip_paths.append(train_test_error["mlip_path"]) + + elif mlip_type == "MACE": + train_test_error = mace_fitting( + db_dir=database_dir, + path_to_hyperparameters=path_to_hyperparameters, + ref_energy_name=ref_energy_name, + ref_force_name=ref_force_name, + ref_virial_name=ref_virial_name, + use_defaults=use_defaults, + device=device, + fit_kwargs=fit_kwargs, + ) + mlip_paths.append(train_test_error["mlip_path"]) + + check_conv = check_convergence(train_test_error["test_error"]) + + return { + "mlip_path": mlip_paths, + "train_error": train_test_error["train_error"], + "test_error": train_test_error["test_error"], + "convergence": check_conv, + "database_dir": database_dir, + }
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/autoplex/fitting/common/regularization.html b/_modules/autoplex/fitting/common/regularization.html new file mode 100644 index 000000000..aaac7d0af --- /dev/null +++ b/_modules/autoplex/fitting/common/regularization.html @@ -0,0 +1,1160 @@ + + + + + + + + + + autoplex.fitting.common.regularization — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+
+
+ + + + +
+ +

Source code for autoplex.fitting.common.regularization

+"""Functions for automatic regularization and weighting of training data."""
+
+import ast
+import logging
+import traceback
+from contextlib import suppress
+
+import numpy as np
+from ase import Atoms
+from scipy.spatial import ConvexHull, Delaunay
+
+logging.basicConfig(
+    level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
+)
+
+
+# adapted from MorrowChem's RSS routines.
+
+[docs] +def set_custom_sigma( + atoms: list[Atoms], + reg_minmax: list[tuple], + isolated_atom_energies: dict | None = None, + scheme: str = "linear-hull", + energy_name: str = "REF_energy", + force_name: str = "REF_forces", + virial_name: str = "REF_virial", + element_order: list | None = None, + max_energy: float = 20.0, + config_type_override: dict | None = None, + retain_existing_sigma: bool = False, +) -> list[Atoms]: + """ + Handle automatic regularisation based on distance to convex hull, amongst other things. + + TODO: Need to make sure this works for full multi-stoichiometry systems. + + Parameters + ---------- + atoms: (list of ase.Atoms) + List of atoms objects to set reg. for. Usually fitting database + reg_minmax: (list of tuples) + List of tuples of (min, max) values for energy, force, virial sigmas + scheme: (str) + Method to use for regularization. Options are: linear_hull, volume-stoichiometry + linear_hull: for single-composition system, use 2D convex hull (E, V) + volume-stoichiometry: for multi-composition system, use 3D convex hull of (E, V, mole-fraction) + energy_name: (str) + Name of energy key in atoms.info + force_name: (str) + Name of force key in atoms.arrays + virial_name: (str) + Name of virial key in atoms.info + isolated_atom_energies: (dict) + Dictionary of isolated energies for each atomic number. + Only needed for volume-x scheme e.g. {14: '-163.0', 8:'-75.0'} + for SiO2 + element_order: (list) + List of atomic numbers in order of choice (e.g. [42, 16] for MoS2) + max_energy: (float) + Ignore any structures with energy above hull greater than this value (eV) + config_type_override: (dict) + Give custom regularization for specific configuration types + retain_existing_sigma: bool + Whether to keep the current sigma values for specific configuration types. + If set to True, existing sigma values for specific configurations will remain unchanged. + + e.g. reg_minmax = [(0.1, 1), (0.001, 0.1), (0.0316, 0.316), (0.0632, 0.632)] + [(emin, emax), (semin, semax), (sfmin, sfmax), (ssvmin, svmax)] + + """ + sigs = [[], [], []] # type: ignore + atoms_modi = [] + + if config_type_override is None: + config_type_override = { + "IsolatedAtom": (1e-4, 0.0, 0.0), + "dimer": (0.1, 0.5, 0.0), + } + + for at in atoms: + for config_type, sigs_override in config_type_override.items(): + if at.info["config_type"] == config_type: + at.info["energy_sigma"] = sigs_override[0] + at.info["force_sigma"] = sigs_override[1] + at.info["virial_sigma"] = sigs_override[2] + atoms_modi.append(at) + + if at.info["config_type"] == "IsolatedAtom": + at.calc = None # TODO side-effect alert + del at.info["force_sigma"] + del at.info["virial_sigma"] + continue + + if at.info["config_type"] == "dimer": + with suppress(Exception): + del at.info[virial_name] + + continue + + isolated_atom_energies = isolated_atom_energies or {} + + if scheme == "linear-hull": + logging.info("Regularising with linear hull") + hull, points = get_convex_hull(atoms, energy_name=energy_name) + get_e_distance_func = get_e_distance_to_hull + + elif scheme == "volume-stoichiometry": + logging.info("Regularising with 3D volume-mole fraction hull") + if len(isolated_atom_energies) == 0: + raise ValueError("Need to supply dictionary of isolated energies.") + + isolated_atom_energies = { + ast.literal_eval(k) if isinstance(k, str) else k: v + for k, v in isolated_atom_energies.items() + } + points = label_stoichiometry_volume( + atoms, isolated_atom_energies, energy_name, element_order=element_order + ) # label atoms with volume and mole fraction + hull = calculate_hull_3d(points) # calculate 3D convex hull + get_e_distance_func = get_e_distance_to_hull_3d # type: ignore + + points = {} + for group in sorted( + { # check if set comprehension is running as supposed to + at.info.get("rss_group") + for at in atoms + if not at.info.get("rss_nonperiodic") + } + ): + points[group] = [] + + for at in atoms: + try: + # skip non-periodic configs, volume is meaningless + if at.info.get("rss_nonperiodic"): + continue + points[at.info.get("rss_group")].append(at) + except Exception: + pass + + for group, atoms_group in points.items(): + logging.info(f"group: {group}") + + for val in atoms_group: + + if retain_existing_sigma and "energy_sigma" in val.info: + atoms_modi.append(val) + continue + + de = get_e_distance_func( + hull, + val, + energy_name=energy_name, + isolated_atom_energies=isolated_atom_energies, + ) + + if de > max_energy: + # don't even fit if too high + continue + if val.info["config_type"] not in config_type_override: + if group == "initial": + sigs[0].append(reg_minmax[1][1]) + sigs[1].append(reg_minmax[2][1]) + sigs[2].append(reg_minmax[3][1]) + val.info["energy_sigma"] = reg_minmax[1][1] + val.info["force_sigma"] = reg_minmax[2][1] + val.info["virial_sigma"] = reg_minmax[3][1] + atoms_modi.append(val) + continue + + if de <= reg_minmax[0][0]: + sigs[0].append(reg_minmax[1][0]) + sigs[1].append(reg_minmax[2][0]) + sigs[2].append(reg_minmax[3][0]) + val.info["energy_sigma"] = reg_minmax[1][0] + val.info["force_sigma"] = reg_minmax[2][0] + val.info["virial_sigma"] = reg_minmax[3][0] + atoms_modi.append(val) + + elif de >= reg_minmax[0][1]: + sigs[0].append(reg_minmax[1][1]) + sigs[1].append(reg_minmax[2][1]) + sigs[2].append(reg_minmax[3][1]) + val.info["energy_sigma"] = reg_minmax[1][1] + val.info["force_sigma"] = reg_minmax[2][1] + val.info["virial_sigma"] = reg_minmax[3][1] + atoms_modi.append(val) + + else: + [e, f, v] = piecewise_linear( + de, + [ + ( + 0.1, + [reg_minmax[1][0], reg_minmax[2][0], reg_minmax[3][0]], + ), + ( + 1.0, + [reg_minmax[1][1], reg_minmax[2][1], reg_minmax[3][1]], + ), + ], + ) + sigs[0].append(e) + sigs[1].append(f) + sigs[2].append(v) + val.info["energy_sigma"] = e + val.info["force_sigma"] = f + val.info["virial_sigma"] = v + atoms_modi.append(val) + + labels = ["E", "F", "V"] + data_type = [np.array(sig) for sig in sigs] # [e, f, v] + + for label, data in zip(labels, data_type): + if len(data) == 0: + logging.info( + "No automatic regularisation performed (no structures requested)" + ) + continue + if label == "E": + # Report of the regularisation statistics + logging.info( + f"Automatic regularisation statistics for {len(data)} structures:\n" + ) + logging.info( + "{:>20s}{:>20s}{:>20s}{:>20s}{:>20s}".format( + "", "Mean", "Std", "Nmin", "Nmax" + ) + ) + logging.info( + f"{label:>20s}" + f"{data.mean():>20.4f}" + f"{data.std():>20.4f}" + f"{(data == data.min()).sum():>20d}" + f"{(data == data.max()).sum():>20d}" + ) + + return atoms_modi
+ + + +
+[docs] +def get_convex_hull( + atoms, energy_name="energy", **kwargs +) -> tuple[np.ndarray, np.ndarray]: + """ + Calculate the simple linear (E,V) convex hull. + + Parameters + ---------- + atoms: list + List of atoms objects. + energy_name: str + Name of the energy key in atoms.info (typically a DFT energy). + + Returns + ------- + tuple + A tuple containing two elements: + - lower_half_hull_points: list of points (volume, energy) in the convex hull (lower half only). + - p: list of all points for testing purposes. + + """ + points_list = [] + failed_count = 0 + + for atom in atoms: + if atom.info["config_type"] in ["IsolatedAtom", "dimer"]: + continue + try: + volume_per_atom = atom.get_volume() / len(atom) + energy_per_atom = atom.info[energy_name] / len(atom) + points_list.append((volume_per_atom, energy_per_atom)) + except KeyError: + failed_count += 1 + + if failed_count > 0: + raise ValueError( + f"Convex hull failed to include {failed_count}/{len(atoms)} structures" + ) + + points = np.array(points_list) + points = points.T[:, np.argsort(points.T[0])].T # sort by volume axis + + hull = ConvexHull(points) # generate full convex hull + hull_points = points[hull.vertices] + + min_x_index = np.argmin(hull_points[:, 0]) + max_x_index = np.argmax(hull_points[:, 0]) + + lower_half_hull = [] + i = min_x_index + + while True: + lower_half_hull.append(hull.vertices[i]) + i = (i + 1) % len(hull.vertices) + if i == max_x_index: + lower_half_hull.append(hull.vertices[i]) + break + + lower_half_hull_points = points[lower_half_hull] + + lower_half_hull_points = lower_half_hull_points[ + lower_half_hull_points[:, 1] <= np.max(lower_half_hull_points[:, 1]) + ] + + return lower_half_hull_points, points
+ + + +
+[docs] +def get_e_distance_to_hull( + hull: np.array, atoms, energy_name="energy", **kwargs +) -> float: + """ + Calculate the distance of a structure to the linear convex hull in energy. + + Parameters + ---------- + hull: (np.array) + Points in the convex hull + atoms: (Atoms) + Structure to calculate distance to hull + energy_name: (str) + Name of the energy key in atoms.info (typically a DFT energy) + + """ + volume = atoms.get_volume() / len(atoms) + energy = atoms.info[energy_name] / len(atoms) + tp = np.array([volume, energy]) + hull_ps = hull.points if isinstance(hull, ConvexHull) else hull + + if any( + np.isclose(hull_ps[:], tp).all(1) + ): # if the point is on the hull, return 0.0 + return 0.0 + + nearest = np.searchsorted(hull_ps.T[0], tp, side="right")[ + 0 + ] # find the nearest convex hull point + + return ( + energy + - get_intersect( + tp, # get intersection of the vertical line (energy axis) + tp + np.array([0, 1]), # and the line between the nearest hull points + hull_ps[(nearest - 1) % len(hull_ps.T[0])], + hull_ps[nearest % len(hull_ps.T[0])], + )[1] + )
+ + + +
+[docs] +def get_intersect(a1, a2, b1, b2) -> tuple[float, float] | tuple: + """ + Return the point of intersection of the lines passing through a2,a1 and b2,b1. + + a1: [x, y] + A point on the first line + a2: [x, y] + Another point on the first line + b1: [x, y] + A point on the second line + b2: [x, y] + Another point on the second line + + """ + s = np.vstack([a1, a2, b1, b2]) # s for stacked + h = np.hstack((s, np.ones((4, 1)))) # h for homogeneous + l1 = np.cross(h[0], h[1]) # get first line + l2 = np.cross(h[2], h[3]) # get second line + x, y, z = np.cross(l1, l2) # point of intersection + if z == 0: # lines are parallel + return (float("inf"), float("inf")) + return x / z, y / z
+ + + +
+[docs] +def get_mole_frac(atoms, element_order=None) -> float | int: + """ + Calculate the mole-fraction of a structure. + + Parameters + ---------- + atoms: (Atoms) + Structure to calculate mole-fraction of + element_order: (list) + List of atomic numbers in order of choice (e.g. [42, 16] for MoS2) + + Returns + ------- + (x2, x3...): (array of float) + Reduced mole-fraction of structure - first element n = 1-sum(others) + + """ + element, cts = np.unique(atoms.get_atomic_numbers(), return_counts=True) + + if element_order is None and len(element) < 3: # compatibility with old version + x = cts[1] / sum(cts) if len(element) == 2 else 1 + + else: # new version, requires element_order, recommended for all new calculations + if element_order is None: + element_order = element # use default order + not_in = [i for i in element_order if i not in element] + for i in not_in: + element = np.insert(element, -1, i) + cts = np.insert(cts, -1, 0) + + cts = np.array( + [cts[np.argwhere(element == i).squeeze()] for i in element_order] + ) + element = np.array( + [element[np.argwhere(element == i).squeeze()] for i in element_order] + ) + + x = cts[1:] / sum(cts) + + return x
+ + + +
+[docs] +def label_stoichiometry_volume( + atoms_list: list[Atoms], + isolated_atom_energies: dict, + energy_name: str, + element_order: list | None = None, +) -> np.ndarray: + """ + Calculate the stoichiometry, energy, and volume coordinates for forming the convex hull. + + Parameters + ---------- + atoms_list: (list[Atoms]) + List of atoms objects + isolated_atom_energies: (dict) + Dictionary of isolated atom energies {atomic_number: energy} + energy_name: (str) + Name of energy key in atoms.info (typically a DFT energy) + element_order: (list | None) + List of atomic numbers in order of choice (e.g. [42, 16] for MoS2) + + """ + isolated_atom_energies = { + ast.literal_eval(k) if isinstance(k, str) else k: v + for k, v in isolated_atom_energies.items() + } + points_list = [] + for atom in atoms_list: + try: + volume = atom.get_volume() / len(atom) + # make energy relative to isolated atoms + energy = ( + atom.info[energy_name] + - sum([isolated_atom_energies[j] for j in atom.get_atomic_numbers()]) + ) / len(atom) + mole_frac = get_mole_frac(atom, element_order=element_order) + points_list.append(np.hstack((mole_frac, volume, energy))) + except KeyError: + traceback.print_exc() + points = np.array(points_list) + return points.T[:, np.argsort(points.T[0])].T
+ + + +
+[docs] +def point_in_triangle_2D(p1, p2, p3, pn) -> bool: + """ + Check if a point is inside a triangle in 2D. + + Parameters + ---------- + p1: (tuple) + Coordinates of first point + p2: (tuple) + Coordinates of second point + p3: (tuple) + Coordinates of third point + pn: (tuple) + Coordinates of point to check + + """ + ep = 1e-4 + x1, y1 = p1 + x2, y2 = p2 + x3, y3 = p3 + x, y = pn + + denominator = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3) + a = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / denominator + b = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / denominator + c = 1 - a - b + + return ( + 0 - ep <= a + and a <= 1 + ep + and 0 - ep <= b + and b <= 1 + ep + and 0 - ep <= c + and c <= 1 + ep + )
+ + + +
+[docs] +def point_in_triangle_nd(pn, *preg) -> bool: + """ + Check if a point is inside a region of hyperplanes in N dimensions. + + Make a little convex hull in N-1 D and check this. + + Parameters + ---------- + pn: + Point to check (in ND) + *preg: + List of points defining (in ND) to check against + + """ + hull = Delaunay(preg) + return hull.find_simplex(pn) >= 0
+ + + +
+[docs] +def calculate_hull_3d(points_3d) -> ConvexHull: + """ + Calculate the convex hull in 3D. + + Parameters + ---------- + points_3d: + point in 3D + + Returns + ------- + convex hull in 3D. + + """ + p0 = np.array( + [ + (points_3d[:, i].max() - points_3d[:, i].min()) / 2 + points_3d[:, i].min() + for i in range(2) + ] + + [-1e6] + ) # test point to get the visible facets from below + pn = np.vstack((p0, points_3d)) + + hull = ConvexHull(pn, qhull_options="QG0") + hull.remove_dim = [] + + return hull
+ + + +
+[docs] +def calculate_hull_nd(points_nd) -> ConvexHull: + """ + Calculate the convex hull in ND (N>=3). + + Parameters + ---------- + points_nd: + Point in ND. + + Returns + ------- + Convex hull in ND. + + """ + p0 = np.array( + [ + (points_nd[:, i].max() - points_nd[:, i].min()) / 2 + points_nd[:, i].min() + for i in range(points_nd.shape[1] - 1) + ] + + [-1e6] + ) # test point to get the visible facets from below + pn = np.vstack((p0, points_nd)) + remove_dim = [] + + for i in range(points_nd.shape[1]): + if np.all(points_nd.T[i, 0] == points_nd.T[i, :]): + pn = np.delete(pn, i, axis=1) + print(f"Convex hull lower dimensional - removing dimension {i}") + remove_dim.append(i) + + hull = ConvexHull(pn, qhull_options="QG0") + print("done calculating hull") + hull.remove_dim = remove_dim + + return hull
+ + + +
+[docs] +def get_e_distance_to_hull_3d( + hull, atoms, isolated_atom_energies=None, energy_name="energy", element_order=None +) -> float: + """ + Calculate the energy distance to the convex hull in 3D. + + Parameters + ---------- + hull: + Convex hull. + atoms: (ase.Atoms) + Structure to calculate mole-fraction of + isolated_atom_energies: (dict) + Dictionary of isolated atom energies + energy_name: (str) + Name of energy key in atoms.info (typically a DFT energy) + element_order: (list) + List of atomic numbers in order of choice (e.g. [42, 16] for MoS2) + + """ + isolated_atom_energies = { + ast.literal_eval(k) if isinstance(k, str) else k: v + for k, v in isolated_atom_energies.items() + } + mole_frac = get_mole_frac(atoms, element_order=element_order) + energy = ( + atoms.info[energy_name] + - sum([isolated_atom_energies[j] for j in atoms.get_atomic_numbers()]) + ) / len(atoms) + volume = atoms.get_volume() / len(atoms) + + sp = np.hstack([mole_frac, volume, energy]) + for i in hull.remove_dim: + sp = np.delete(sp, i) + + if len(sp[:-1]) == 1: + # print('doing convexhull analysis in 1D') + return get_e_distance_to_hull(hull, atoms, energy_name=energy_name) + + for _ct, visible_facet in enumerate(hull.simplices[hull.good]): + if point_in_triangle_nd(sp[:-1], *hull.points[visible_facet][:, :-1]): + n_3 = hull.points[visible_facet] + energy = sp[-1] + + norm = np.cross(n_3[2] - n_3[0], n_3[1] - n_3[0]) + plane_norm = norm / np.linalg.norm(norm) # plane normal + plane_constant = np.dot(plane_norm, n_3[0]) # plane constant + + return ( + energy + - (plane_constant - plane_norm[0] * sp[0] - plane_norm[1] * sp[1]) + / plane_norm[2] + ) + + print("Failed to find distance to hull") + return 1e6
+ + + +
+[docs] +def piecewise_linear(x, vals) -> np.ndarray: + """ + Piecewise linear. + + Parameters + ---------- + x: + The x value. + vals: + The values + + """ + i = np.searchsorted([v[0] for v in vals], x) + f0 = (vals[i][0] - x) / (vals[i][0] - vals[i - 1][0]) + return f0 * np.array(vals[i - 1][1]) + (1.0 - f0) * np.array(vals[i][1])
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/autoplex/fitting/common/utils.html b/_modules/autoplex/fitting/common/utils.html new file mode 100644 index 000000000..3964507e5 --- /dev/null +++ b/_modules/autoplex/fitting/common/utils.html @@ -0,0 +1,2591 @@ + + + + + + + + + + autoplex.fitting.common.utils — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+
+
+ + + + +
+ +

Source code for autoplex.fitting.common.utils

+"""Utility functions for fitting jobs."""
+
+import contextlib
+import json
+import logging
+import os
+import re
+import shutil
+import subprocess
+import sys
+import xml.etree.ElementTree as ET
+from collections.abc import Iterable
+from functools import partial
+from pathlib import Path
+
+import ase
+import lightning as pl
+import matplotlib.pyplot as plt
+import numpy as np
+import pandas as pd
+import torch
+from ase.atoms import Atoms
+from ase.constraints import voigt_6_to_full_3x3_stress
+from ase.data import chemical_symbols
+from ase.io import read, write
+from ase.io.extxyz import XYZError
+from ase.neighborlist import NeighborList, natural_cutoffs
+from atomate2.utils.path import strip_hostname
+from dgl.data.utils import split_dataset
+from matgl.apps.pes import Potential
+from matgl.ext.pymatgen import Structure2Graph, get_element_list
+from matgl.graph.data import MGLDataLoader, MGLDataset, collate_fn_pes
+from matgl.models import M3GNet
+from matgl.utils.training import PotentialLightningModule
+from monty.dev import requires
+from nequip.ase import NequIPCalculator
+from numpy import ndarray
+from pymatgen.core import Structure
+from pymatgen.io.ase import AseAtomsAdaptor
+from pytorch_lightning.loggers import CSVLogger
+from scipy.spatial import ConvexHull
+from scipy.special import comb
+
+from autoplex.data.common.utils import (
+    data_distillation,
+    plot_energy_forces,
+    rms_dict,
+    stratified_dataset_split,
+)
+
+current_dir = Path(__file__).absolute().parent
+MLIP_PHONON_DEFAULTS_FILE_PATH = current_dir / "mlip-phonon-defaults.json"
+MLIP_RSS_DEFAULTS_FILE_PATH = current_dir / "mlip-rss-defaults.json"
+logging.basicConfig(
+    level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
+)
+
+
+
+[docs] +def gap_fitting( + db_dir: Path, + species_list: list | None = None, + path_to_hyperparameters: Path | str = MLIP_PHONON_DEFAULTS_FILE_PATH, + num_processes_fit: int = 32, + auto_delta: bool = True, + glue_xml: bool = False, + ref_energy_name: str = "REF_energy", + ref_force_name: str = "REF_forces", + ref_virial_name: str = "REF_virial", + train_name: str = "train.extxyz", + test_name: str = "test.extxyz", + glue_file_path: str = "glue.xml", + fit_kwargs: dict | None = None, # pylint: disable=E3701 +) -> dict: + """ + Perform the GAP (Gaussian approximation potential) model fitting. + + Parameters + ---------- + db_dir: str or path + Path to database directory. + species_list: list + List of element names (strings) + path_to_hyperparameters : str or Path. + Path to JSON file containing the GAP hyperparameters. + num_processes_fit: int + Number of processes used for gap_fit + auto_delta: bool + Automatically determine delta for 2b, 3b and soap terms. + glue_xml: bool + use the glue.xml core potential instead of fitting 2b terms. + ref_energy_name: str + Reference energy name. + ref_force_name : str + Reference force name. + ref_virial_name: str + Reference virial name. + train_name: str + Name of the training set file. + test_name: str + Name of the test set file. + glue_file_path: str + Name of the glue.xml file path. + fit_kwargs: dict + Additional keyword arguments for GAP fitting with keys same as + those in gap-defaults.json. + + Returns + ------- + dict + A dictionary with train_error, test_error, path_to_mlip + + """ + if path_to_hyperparameters is None: + path_to_hyperparameters = MLIP_PHONON_DEFAULTS_FILE_PATH + # keep additional pre- and suffixes + gap_file_xml = train_name.replace("train", "gap_file").replace(".extxyz", ".xml") + quip_train_file = train_name.replace("train", "quip_train") + quip_test_file = test_name.replace("test", "quip_test") + mlip_path: Path = prepare_fit_environment( + db_dir, Path.cwd(), glue_xml, train_name, test_name, glue_file_path + ) + + db_atoms = ase.io.read(os.path.join(db_dir, train_name), index=":") + train_data_path = os.path.join(db_dir, train_name) + + test_data_path = os.path.join(db_dir, test_name) + default_hyperparameters = load_mlip_hyperparameter_defaults( + mlip_fit_parameter_file_path=path_to_hyperparameters + ) + + gap_default_hyperparameters = default_hyperparameters["GAP"] + + gap_default_hyperparameters["general"].update({"gp_file": gap_file_xml}) + gap_default_hyperparameters["general"]["energy_parameter_name"] = ref_energy_name + gap_default_hyperparameters["general"]["force_parameter_name"] = ref_force_name + gap_default_hyperparameters["general"]["virial_parameter_name"] = ref_virial_name + + for parameter in gap_default_hyperparameters: + if fit_kwargs: + for arg in fit_kwargs: + if parameter == arg: + gap_default_hyperparameters[parameter].update(fit_kwargs[arg]) + + include_two_body = gap_default_hyperparameters["general"]["two_body"] + include_three_body = gap_default_hyperparameters["general"]["three_body"] + include_soap = gap_default_hyperparameters["general"]["soap"] + + if include_two_body: + gap_default_hyperparameters["general"].update({"at_file": train_data_path}) + if auto_delta: + delta_2b, num_triplet = calculate_delta(db_atoms, ref_energy_name) + gap_default_hyperparameters["twob"].update({"delta": delta_2b}) + + fit_parameters_list = gap_hyperparameter_constructor( + gap_parameter_dict=gap_default_hyperparameters, + include_two_body=include_two_body, + ) + + run_gap(num_processes_fit, fit_parameters_list) + + run_quip(num_processes_fit, train_data_path, gap_file_xml, quip_train_file) + + if include_three_body: + gap_default_hyperparameters["general"].update({"at_file": train_data_path}) + if auto_delta: + delta_3b = energy_remain(quip_train_file) + delta_3b = delta_3b / num_triplet + gap_default_hyperparameters["threeb"].update({"delta": delta_3b}) + + fit_parameters_list = gap_hyperparameter_constructor( + gap_parameter_dict=gap_default_hyperparameters, + include_two_body=include_two_body, + include_three_body=include_three_body, + ) + + run_gap(num_processes_fit, fit_parameters_list) + run_quip(num_processes_fit, train_data_path, gap_file_xml, quip_train_file) + + if glue_xml: + gap_default_hyperparameters["general"].update({"at_file": train_data_path}) + gap_default_hyperparameters["general"].update({"core_param_file": "glue.xml"}) + gap_default_hyperparameters["general"].update({"core_ip_args": "{IP Glue}"}) + + fit_parameters_list = gap_hyperparameter_constructor( + gap_parameter_dict=gap_default_hyperparameters, + include_two_body=False, + include_three_body=False, + ) + + run_gap(num_processes_fit, fit_parameters_list) + run_quip( + num_processes_fit, + train_data_path, + gap_file_xml, + quip_train_file, + glue_xml, + ) + + if include_soap: + delta_soap = ( + energy_remain(quip_train_file) + if include_two_body or include_three_body + else 1 + ) + gap_default_hyperparameters["general"].update({"at_file": train_data_path}) + if auto_delta: + gap_default_hyperparameters["soap"].update({"delta": delta_soap}) + + fit_parameters_list = gap_hyperparameter_constructor( + gap_parameter_dict=gap_default_hyperparameters, + include_two_body=include_two_body, + include_three_body=include_three_body, + include_soap=include_soap, + ) + + run_gap(num_processes_fit, fit_parameters_list) + run_quip( + num_processes_fit, + train_data_path, + gap_file_xml, + quip_train_file, + glue_xml, + ) + + # Calculate training error + + train_error = energy_remain(quip_train_file) + logging.info(f"Training error of MLIP (eV/at.): {round(train_error, 7)}") + + # Calculate testing error + run_quip(num_processes_fit, test_data_path, gap_file_xml, quip_test_file, glue_xml) + test_error = energy_remain(quip_test_file) + logging.info(f"Testing error of MLIP (eV/at.): {round(test_error, 7)}") + + if not glue_xml and species_list: + try: + plot_energy_forces( + title="Data error metrics", + energy_limit=0.005, + force_limit=0.1, + species_list=species_list, + train_name=train_name, + test_name=test_name, + ) + except (ValueError, XYZError) as e: + logging.warning(f"Skipped fit error metrics plot because of: \n{e}") + + return { + "train_error": train_error, + "test_error": test_error, + "mlip_path": mlip_path, + }
+ + + +
+[docs] +@requires( + ( + subprocess.run( + 'julia -e "using Pkg; println(haskey(Pkg.dependencies(), ' + 'Base.UUID(\\"3b96b61c-0fcc-4693-95ed-1ef9f35fcc53\\")))"', + shell=True, + capture_output=True, + text=True, + check=False, + ).stdout.strip() + ) + == "true", + "J-ACE fitting requires the executable 'julia' and ACEPotentials.jl v0.6.7 library to be in PATH. " + "Please follow the instructions in the autoplex documentation to install the required julia dependencies " + "and add them to PATH. Link to the documentation:" + " https://autoatml.github.io/autoplex/user/index.html#standard-installation", +) +def jace_fitting( + db_dir: str | Path, + path_to_hyperparameters: Path | str = MLIP_RSS_DEFAULTS_FILE_PATH, + isolated_atom_energies: dict | None = None, + ref_energy_name: str = "REF_energy", + ref_force_name: str = "REF_forces", + ref_virial_name: str = "REF_virial", + num_processes_fit: int = 32, + fit_kwargs: dict | None = None, +) -> dict: + """ + Perform the ACE (Atomic Cluster Expansion) potential fitting. + + This function sets up and executes a Julia script to perform ACE fitting using specified parameters + and input data located in the provided directory. It handles the input/output of atomic configurations, + sets up the ACE model, and calculates training and testing errors after fitting. + + Parameters + ---------- + db_dir: str or Path + directory containing the training and testing data files. + path_to_hyperparameters : str or Path. + Path to JSON file containing the J-ACE hyperparameters. + isolated_atom_energies: dict: + mandatory dictionary mapping element numbers to isolated energies. + ref_energy_name : str, optional + Reference energy name. + ref_force_name : str, optional + Reference force name. + ref_virial_name : str, optional + Reference virial name. + num_processes_fit: int + number of processes to use for parallel computation. + fit_kwargs: dict. + optional dictionary with parameters for ace fitting with keys same as + mlip-rss-defaults.json. + + Keyword Arguments + ----------------- + order: int + order of ACE. + totaldegree: int + total degree of the polynomial terms in the ACE model. + cutoff: float + cutoff distance for atomic interactions in the ACE model. + solver: str + solver to be used for fitting the ACE model. Default is "BLR" (Bayesian Linear Regression). + For very large-scale parameter estimation problems, using "LSQR" solver. + + Returns + ------- + dict[str, float] + A dictionary containing train_error, test_error, and the path to the fitted MLIP. + + Raises + ------ + - ValueError: If the `isolated_atom_energies` dictionary is empty or not provided when required. + """ + if path_to_hyperparameters is None: + path_to_hyperparameters = MLIP_RSS_DEFAULTS_FILE_PATH + train_atoms = ase.io.read(os.path.join(db_dir, "train.extxyz"), index=":") + source_file_path = os.path.join(db_dir, "test.extxyz") + shutil.copy(source_file_path, ".") + isolated_atom_energies_update = {} + + if isolated_atom_energies: + for e_num, e_energy in isolated_atom_energies.items(): + isolated_atom_energies_update[chemical_symbols[int(e_num)]] = e_energy + else: + raise ValueError("isolated_atom_energies parameter is empty or not defined!") + + formatted_isolated_atom_energies = ( + "[" + + ", ".join( + [ + f":{key} => {value}" + for key, value in isolated_atom_energies_update.items() + ] + ) + + "]" + ) + formatted_species = ( + "[" + + ", ".join([f":{key}" for key, value in isolated_atom_energies_update.items()]) + + "]" + ) + + train_ace = [ + at for at in train_atoms if "IsolatedAtom" not in at.info["config_type"] + ] + ase.io.write("train_ace.extxyz", train_ace, format="extxyz") + + default_hyperparameters = load_mlip_hyperparameter_defaults( + mlip_fit_parameter_file_path=path_to_hyperparameters + ) + jace_hypers = default_hyperparameters["J-ACE"] + + if fit_kwargs: + for parameter in jace_hypers: + if parameter in fit_kwargs: + if isinstance(fit_kwargs[parameter], type(jace_hypers[parameter])): + jace_hypers[parameter] = fit_kwargs[parameter] + else: + raise TypeError( + f"The type of {parameter} should be {type(jace_hypers[parameter])}!" + ) + + order = jace_hypers["order"] + totaldegree = jace_hypers["totaldegree"] + cutoff = jace_hypers["cutoff"] + solver = jace_hypers["solver"] + + ace_text = f"""using ACEpotentials +using LinearAlgebra: norm, Diagonal +using CSV, DataFrames +using Distributed +addprocs({num_processes_fit - 1}, exeflags="--project=$(Base.active_project())") +@everywhere using ACEpotentials + +data_file = "train_ace.extxyz" +data = read_extxyz(data_file) +test_data_file = "test.extxyz" +test_data = read_extxyz(test_data_file) +data_keys = (energy_key = "{ref_energy_name}", force_key = "{ref_force_name}", virial_key = "{ref_virial_name}") + +model = acemodel(elements={formatted_species}, + order={order}, + totaldegree={totaldegree}, + rcut={cutoff}, + Eref={formatted_isolated_atom_energies}) + +weights = Dict( + "crystal" => Dict("E" => 30.0, "F" => 1.0 , "V" => 1.0 ), + "RSS" => Dict("E" => 3.0, "F" => 0.5 , "V" => 0.1 ), + "amorphous" => Dict("E" => 3.0, "F" => 0.5 , "V" => 0.1 ), + "liquid" => Dict("E" => 10.0, "F" => 0.5 , "V" => 0.25 ), + "RSS_initial" => Dict("E" => 1.0, "F" => 0.5 , "V" => 0.1 ), + "dimer" => Dict("E" => 30.0, "F" => 1.0 , "V" => 1.0 ) + ) + +P = smoothness_prior(model; p = 4) + +solver = ACEfit.{solver}() + +acefit!(model, data; solver=solver, weights=weights, prior = P, data_keys...) + +@info("Training Error Table") +ACEpotentials.linear_errors(data, model; data_keys...) + +@info("Testing Error Table") +ACEpotentials.linear_errors(test_data, model; data_keys...) + +@info("Manual RMSE Test") +potential = model.potential +train_energies = [ JuLIP.get_data(at, "{ref_energy_name}") / length(at) for at in data] +model_energies_train = [energy(potential, at) / length(at) for at in data] +rmse_energy_train = norm(train_energies - model_energies_train) / sqrt(length(data)) +test_energies = [ JuLIP.get_data(at, "{ref_energy_name}") / length(at) for at in test_data] +model_energies_pred = [energy(potential, at) / length(at) for at in test_data] +rmse_energy_test = norm(test_energies - model_energies_pred) / sqrt(length(test_data)) + +df = DataFrame(rmse_energy_train = rmse_energy_train, rmse_energy_test = rmse_energy_test) +CSV.write("rmse_energies.csv", df) + +save_potential("acemodel.json", model) +export2lammps("acemodel.yace", model) + """ + + with open("ace.jl", "w") as file: + file.write(ace_text) + + os.system(f"export OMP_NUM_THREADS={num_processes_fit} && julia ace.jl") + + energy_err = pd.read_csv("rmse_energies.csv") + train_error = energy_err["rmse_energy_train"][0] + test_error = energy_err["rmse_energy_test"][0] + + return { + "train_error": train_error, + "test_error": test_error, + "mlip_path": Path.cwd(), + }
+ + + +
+[docs] +def nequip_fitting( + db_dir: Path, + path_to_hyperparameters: Path | str = MLIP_RSS_DEFAULTS_FILE_PATH, + isolated_atom_energies: dict | None = None, + ref_energy_name: str = "REF_energy", + ref_force_name: str = "REF_forces", + ref_virial_name: str = "REF_virial", + fit_kwargs: dict | None = None, + device: str = "cuda", +) -> dict: + """ + Perform the NequIP potential fitting. + + This function sets up and executes a python script to perform NequIP fitting using specified parameters + and input data located in the provided directory. It handles the input/output of atomic configurations, + sets up the NequIP model, and calculates training and testing errors after fitting. + + Parameters + ---------- + db_dir: Path + directory containing the training and testing data files. + path_to_hyperparameters : str or Path. + Path to JSON file containing the NwquIP hyperparameters. + isolated_atom_energies: dict + mandatory dictionary mapping element numbers to isolated energies. + ref_energy_name : str, optional + Reference energy name. + ref_force_name : str, optional + Reference force name. + ref_virial_name : str, optional + Reference virial name. + device: str + specify device to use cuda or cpu + fit_kwargs: dict. + optional dictionary with parameters for nequip fitting with keys same as + mlip-rss-defaults.json. + + Keyword Arguments + ----------------- + r_max: float + cutoff radius in length units + num_layers: int + number of interaction blocks + l_max: int + maximum irrep order (rotation order) for the network's features + num_features: int + multiplicity of the features + num_basis: int + number of basis functions used in the radial basis + invariant_layers: int + number of radial layers + invariant_neurons: int + number of hidden neurons in radial function + batch_size: int + batch size + learning_rate: float + learning rate + default_dtype: str + type of float to use, e.g. float32 and float64 + + Returns + ------- + dict[str, float] + A dictionary containing train_error, test_error, and the path to the fitted MLIP. + + Raises + ------ + - ValueError: If the `isolated_atom_energies` dictionary is empty or not provided when required. + """ + """ + [TODO] train Nequip on virials + """ + if path_to_hyperparameters is None: + path_to_hyperparameters = MLIP_RSS_DEFAULTS_FILE_PATH + train_data = ase.io.read(os.path.join(db_dir, "train.extxyz"), index=":") + train_nequip = [ + at for at in train_data if "IsolatedAtom" not in at.info["config_type"] + ] + ase.io.write("train_nequip.extxyz", train_nequip, format="extxyz") + + test_data = ase.io.read(os.path.join(db_dir, "test.extxyz"), index=":") + num_of_train = len(train_nequip) + num_of_val = len(test_data) + + isolated_atom_energies_update = "" + ele_syms = [] + if isolated_atom_energies: + for e_num in isolated_atom_energies: + element_symbol = " - " + chemical_symbols[int(e_num)] + "\n" + isolated_atom_energies_update += element_symbol + ele_syms.append(chemical_symbols[int(e_num)]) + else: + raise ValueError("isolated_atom_energies is empty or not defined!") + + default_hyperparameters = load_mlip_hyperparameter_defaults( + mlip_fit_parameter_file_path=path_to_hyperparameters + ) + + nequip_hypers = default_hyperparameters["NEQUIP"] + + if fit_kwargs: + for parameter in nequip_hypers: + if parameter in fit_kwargs: + if isinstance(fit_kwargs[parameter], type(nequip_hypers[parameter])): + nequip_hypers[parameter] = fit_kwargs[parameter] + else: + raise TypeError( + f"The type of {parameter} should be {type(nequip_hypers[parameter])}!" + ) + + r_max = nequip_hypers["r_max"] + num_layers = nequip_hypers["num_layers"] + l_max = nequip_hypers["l_max"] + num_features = nequip_hypers["num_features"] + num_basis = nequip_hypers["num_basis"] + invariant_layers = nequip_hypers["invariant_layers"] + invariant_neurons = nequip_hypers["invariant_neurons"] + batch_size = nequip_hypers["batch_size"] + learning_rate = nequip_hypers["learning_rate"] + max_epochs = nequip_hypers["max_epochs"] + default_dtype = nequip_hypers["default_dtype"] + + nequip_text = f"""root: results +run_name: autoplex +seed: 123 +dataset_seed: 456 +append: true +default_dtype: {default_dtype} + +# network +r_max: {r_max} +num_layers: {num_layers} +l_max: {l_max} +parity: true +num_features: {num_features} +nonlinearity_type: gate + +nonlinearity_scalars: + e: silu + o: tanh + +nonlinearity_gates: + e: silu + o: tanh + +num_basis: {num_basis} +BesselBasis_trainable: true +PolynomialCutoff_p: 6 + +invariant_layers: {invariant_layers} +invariant_neurons: {invariant_neurons} +avg_num_neighbors: auto + +use_sc: true +dataset: ase +validation_dataset: ase +dataset_file_name: ./train_nequip.extxyz +validation_dataset_file_name: {db_dir}/test.extxyz + +ase_args: + format: extxyz +dataset_key_mapping: + {ref_energy_name}: total_energy + {ref_force_name}: forces +validation_dataset_key_mapping: + {ref_energy_name}: total_energy + {ref_force_name}: forces + +chemical_symbols: +{isolated_atom_energies_update} +wandb: False + +verbose: info +log_batch_freq: 10 +log_epoch_freq: 1 +save_checkpoint_freq: -1 +save_ema_checkpoint_freq: -1 + +n_train: {num_of_train} +n_val: {num_of_val} +learning_rate: {learning_rate} +batch_size: {batch_size} +validation_batch_size: 10 +max_epochs: {max_epochs} +shuffle: true +metrics_key: validation_loss +use_ema: true +ema_decay: 0.99 +ema_use_num_updates: true +report_init_validation: true + +early_stopping_patiences: + validation_loss: 50 + +early_stopping_lower_bounds: + LR: 1.0e-5 + +loss_coeffs: + forces: 1 + total_energy: + - 1 + - PerAtomMSELoss + +metrics_components: + - - forces + - mae + - - forces + - rmse + - - forces + - mae + - PerSpecies: True + report_per_component: False + - - forces + - rmse + - PerSpecies: True + report_per_component: False + - - total_energy + - mae + - - total_energy + - mae + - PerAtom: True + +optimizer_name: Adam +optimizer_amsgrad: true + +lr_scheduler_name: ReduceLROnPlateau +lr_scheduler_patience: 100 +lr_scheduler_factor: 0.5 + +per_species_rescale_shifts_trainable: false +per_species_rescale_scales_trainable: false + +per_species_rescale_shifts: dataset_per_atom_total_energy_mean +per_species_rescale_scales: dataset_forces_rms + """ + + with open("nequip.yaml", "w") as file: + file.write(nequip_text) + + run_nequip("nequip-train nequip.yaml", "nequip_train") + run_nequip( + "nequip-deploy build --train-dir results/autoplex ./deployed_nequip_model.pth", + "nequip_deploy", + ) + + calc = NequIPCalculator.from_deployed_model( + model_path="deployed_nequip_model.pth", + device=device, + species_to_type_name={s: s for s in ele_syms}, + set_global_options=False, + ) + + ener_out_train = [] + for at in train_nequip: + at.calc = calc + ener_out_train.append(at.get_potential_energy() / len(at)) + + ener_in_train = [ + at.info["REF_energy"] / len(at.get_chemical_symbols()) for at in train_nequip + ] + + train_error = rms_dict(ener_in_train, ener_out_train)["rmse"] + + ener_out_test = [] + for at in test_data: + at.calc = calc + ener_out_test.append(at.get_potential_energy() / len(at)) + + ener_in_test = [ + at.info["REF_energy"] / len(at.get_chemical_symbols()) for at in test_data + ] + + test_error = rms_dict(ener_in_test, ener_out_test)["rmse"] + + return { + "train_error": train_error, + "test_error": test_error, + "mlip_path": Path.cwd(), + }
+ + + +
+[docs] +def m3gnet_fitting( + db_dir: Path, + path_to_hyperparameters: Path | str = MLIP_RSS_DEFAULTS_FILE_PATH, + device: str = "cuda", + ref_energy_name: str = "REF_energy", + ref_force_name: str = "REF_forces", + ref_virial_name: str = "REF_virial", + fit_kwargs: dict | None = None, +) -> dict: + """ + Perform the M3GNet potential fitting. + + Parameters + ---------- + db_dir: Path + Directory containing the training and testing data files. + path_to_hyperparameters : str or Path. + Path to JSON file containing the M3GNet hyperparameters. + device: str + Device on which the model will be trained, e.g., 'cuda' or 'cpu'. + ref_energy_name : str, optional + Reference energy name. + ref_force_name : str, optional + Reference force name. + ref_virial_name : str, optional + Reference virial name. + fit_kwargs: dict. + optional dictionary with parameters for m3gnet fitting with keys same as + mlip-rss-defaults.json. + + Keyword Arguments + ----------------- + exp_name: str + Name of the experiment, used for saving model checkpoints and logs. + results_dir: str + Directory to store the training results and fitted model. + cutoff: float + Cutoff radius for atomic interactions in length units. + threebody_cutoff: float + Cutoff radius for three-body interactions in length units. + batch_size: int + Number of structures per batch during training. + max_epochs: int + Maximum number of training epochs. + include_stresses: bool + If True, includes stress tensors in the model predictions and training process. + hidden_dim: int + Dimensionality of the hidden layers in the model. + num_units: int + Number of units in each dense layer of the model. + max_l: int + Maximum degree of spherical harmonics. + max_n: int + Maximum radial function degree. + test_equal_to_val: bool + If True, the testing dataset will be the same as the validation dataset. + + Returns + ------- + dict[str, float] + A dictionary containing keys such as 'train_error', 'test_error', and 'path_to_fitted_model', + representing the training error, test error, and the location of the saved model, respectively. + + References + ---------- + * Title: Tutorials of Materials Graph Library (MatGL) + * Author: Tsz Wai Ko, Chi Chen and Shyue Ping Ong + * Version: 1.1.3 + * Date 7/8/2024 + * Availability: https://matgl.ai/tutorials%2FTraining%20a%20M3GNet%20Potential%20with%20PyTorch%20Lightning.html + * License: BSD 3-Clause License + """ + if path_to_hyperparameters is None: + path_to_hyperparameters = MLIP_RSS_DEFAULTS_FILE_PATH + default_hyperparameters = load_mlip_hyperparameter_defaults( + mlip_fit_parameter_file_path=path_to_hyperparameters + ) + + m3gnet_hypers = default_hyperparameters["M3GNET"] + + if fit_kwargs: + for parameter in m3gnet_hypers: + if parameter in fit_kwargs: + if isinstance(fit_kwargs[parameter], type(m3gnet_hypers[parameter])): + m3gnet_hypers[parameter] = fit_kwargs[parameter] + else: + raise TypeError( + f"The type of {parameter} should be {type(m3gnet_hypers[parameter])}!" + ) + + exp_name = m3gnet_hypers["exp_name"] + results_dir = m3gnet_hypers["results_dir"] + cutoff = m3gnet_hypers["cutoff"] + threebody_cutoff = m3gnet_hypers["threebody_cutoff"] + batch_size = m3gnet_hypers["batch_size"] + max_epochs = m3gnet_hypers["max_epochs"] + include_stresses = m3gnet_hypers["include_stresses"] + hidden_dim = m3gnet_hypers["hidden_dim"] + num_units = m3gnet_hypers["num_units"] + max_l = m3gnet_hypers["max_l"] + max_n = m3gnet_hypers["max_n"] + test_equal_to_val = m3gnet_hypers["test_equal_to_val"] + + os.makedirs(os.path.join(results_dir, exp_name), exist_ok=True) + + with open("output.txt", "w") as f: + # Backup original stdout stream. + original_stdout = sys.stdout + + # Set stdout to the file object. + sys.stdout = f + + # Print something (it goes to the file). + print("This line will be written to the file.") + + # Restore original stdout stream. + sys.stdout = original_stdout + + with open("m3gnet.log", "w") as log_file: + original_stdout = sys.stdout + original_stderr = sys.stderr + sys.stdout = log_file + sys.stderr = log_file + + train_data = ase.io.read(os.path.join(db_dir, "train.extxyz"), index=":") + train_m3gnet = [ + at + for at in train_data + if "IsolatedAtom" not in at.info["config_type"] + and "dimer" not in at.info["config_type"] + ] + + # prepare train dataset + ( + train_structs, + train_energies, + train_forces, + train_stresses, + ) = convert_xyz_to_structure( + train_m3gnet, + include_forces=True, + include_stresses=include_stresses, + ref_energy_name=ref_energy_name, + ref_force_name=ref_force_name, + ref_virial_name=ref_virial_name, + ) + + train_labels = { + "energies": train_energies, + "forces": train_forces, + "stresses": train_stresses, + } + train_element_types = get_element_list(train_structs) + + print( + train_element_types + ) # this print has to stay as the stdout is written to the file + train_converter = Structure2Graph( + element_types=train_element_types, cutoff=cutoff + ) + train_datasets = MGLDataset( + threebody_cutoff=threebody_cutoff, + structures=train_structs, + converter=train_converter, + labels=train_labels, + include_line_graph=True, + filename="dgl_graph_train.bin", + filename_lattice="lattice_train.pt", + filename_line_graph="dgl_line_graph_train.bin", + filename_state_attr="state_attr_train.pt", + filename_labels="labels_train.json", + save_dir=os.path.join(results_dir, exp_name), + ) + + if os.path.exists(os.path.join(db_dir, "test.extxyz")): + test_data = ase.io.read(os.path.join(db_dir, "test.extxyz"), index=":") + # prepare test dataset + ( + test_structs, + test_energies, + test_forces, + test_stresses, + ) = convert_xyz_to_structure( + test_data, + include_forces=True, + include_stresses=include_stresses, + ref_energy_name=ref_energy_name, + ref_force_name=ref_force_name, + ref_virial_name=ref_virial_name, + ) + + test_labels = { + "energies": test_energies, + "forces": test_forces, + "stresses": test_stresses, + } + test_element_types = get_element_list(test_structs) + test_converter = Structure2Graph( + element_types=test_element_types, cutoff=cutoff + ) + test_dataset = MGLDataset( + threebody_cutoff=threebody_cutoff, + structures=test_structs, + converter=test_converter, + labels=test_labels, + include_line_graph=True, + filename="dgl_graph_test.bin", + filename_lattice="lattice_test.pt", + filename_line_graph="dgl_line_graph_test.bin", + filename_state_attr="state_attr_test.pt", + filename_labels="labels_test.json", + save_dir=os.path.join(results_dir, exp_name), + ) + + if test_equal_to_val: + train_dataset = train_datasets + val_dataset = test_dataset + else: + if os.path.exists(os.path.join(db_dir, "test.extxyz")): + train_dataset, val_dataset, _ = split_dataset( + train_datasets, + frac_list=[0.9, 0.1, 0], # to guarantee train:valid=9:1 + shuffle=True, + random_state=42, + ) + else: + train_dataset, val_dataset, test_dataset = split_dataset( + train_datasets, + frac_list=[0.8, 0.1, 0.1], # to guarantee train:valid:test=8:1:1 + shuffle=True, + random_state=42, + ) + + my_collate_fn = partial( + collate_fn_pes, include_line_graph=True + ) # Set all include_line_graph to False will disable three-body interactions + train_loader, val_loader, test_loader = MGLDataLoader( + train_data=train_dataset, + val_data=val_dataset, + test_data=test_dataset, + collate_fn=my_collate_fn, + batch_size=batch_size, + num_workers=1, + ) + model = M3GNet( + element_types=train_element_types, + is_intensive=False, + cutoff=cutoff, + threebody_cutoff=threebody_cutoff, + dim_node_embedding=hidden_dim, + dim_edge_embedding=hidden_dim, + units=num_units, + max_l=max_l, + max_n=max_n, + ) + lit_module = PotentialLightningModule(model=model, include_line_graph=True) + logger = CSVLogger(name=exp_name, save_dir=os.path.join(results_dir, "logs")) + # Inference mode = False is required for calculating forces, stress in test mode and prediction mode + if device == "cuda": + if torch.cuda.is_available(): + gpu_id = os.environ.get("CUDA_VISIBLE_DEVICES", "0") + torch.cuda.set_device(torch.device(f"cuda:{gpu_id}")) + trainer = pl.Trainer( + max_epochs=max_epochs, + accelerator="gpu", + logger=logger, + inference_mode=False, + ) + else: + raise ValueError("CUDA is not available.") + else: + trainer = pl.Trainer( + max_epochs=max_epochs, + accelerator="cpu", + logger=logger, + inference_mode=False, + ) + # Again loggers ... + print("Start training...") + print(f"Length of train_loader: {len(train_loader)}") + print(f"Length of val_loader: {len(val_loader)}") + print(f"Length of test_loader: {len(test_loader)}") + trainer.fit( + model=lit_module, train_dataloaders=train_loader, val_dataloaders=val_loader + ) + # test the model, remember to set inference_mode=False in trainer (see above) + print("Train error:") + trainer.test(dataloaders=train_loader) + print("Valid error:") + trainer.test(dataloaders=val_loader) + print("Test error:") + trainer.test(dataloaders=test_loader) + + # save trained model + model_export_path = os.path.join(results_dir, exp_name) + # model.save(model_export_path) + potential = Potential(model=model) + potential.save(model_export_path) + + sys.stdout = original_stdout + sys.stderr = original_stderr + + for fn in ( + "dgl_graph_train.bin", + "lattice_train.pt", + "dgl_line_graph_train.bin", + "state_attr_train.pt", + "labels_train.json", + "dgl_graph_test.bin", + "lattice_test.pt", + "dgl_line_graph_test.bin", + "state_attr_test.pt", + "labels_test.json", + ): + with contextlib.suppress(FileNotFoundError): + os.remove(os.path.join(results_dir, exp_name, fn)) + + sections = { + "Train error:": { + "train_Energy_RMSE": "test_Energy_RMSE", + "train_Force_RMSE": "test_Force_RMSE", + }, + "Valid error:": { + "val_Energy_RMSE": "test_Energy_RMSE", + "val_Force_RMSE": "test_Force_RMSE", + }, + "Test error:": { + "test_Energy_RMSE": "test_Energy_RMSE", + "test_Force_RMSE": "test_Force_RMSE", + }, + } + + extracted_values = {} + with open("m3gnet.log") as file: + content = file.read() + + for section, metrics in sections.items(): + start_index = content.find(section) + if start_index != -1: + next_index = min( + [ + content.find(sec, start_index + 1) + for sec in sections + if content.find(sec, start_index + 1) != -1 + ], + default=len(content), + ) + section_content = content[start_index:next_index] + for key, metric in metrics.items(): + for line in section_content.split("\n"): + if metric in line: + if metric in line.split()[0]: + extracted_values[key] = float(line.split()[1]) + else: + extracted_values[key] = float(line.split()[3]) + + for key, value in extracted_values.items(): + print(f"{key}: {value}") + + """ + !!![Note] The RMSE directly outputted from Torch is not strictly the RMSE of the full datasets; + it is related to the batch size. It only becomes a strict RMSE when the batch size is larger + than the size of the dataset. The output here can be considered as an approximate result. + [TODO] Switch it to the strict RMSE. + """ + mlip_path = Path.cwd() / model_export_path + + return { + "train_error": extracted_values["train_Energy_RMSE"], + "test_error": extracted_values["test_Energy_RMSE"], + "mlip_path": mlip_path, + }
+ + + +
+[docs] +def mace_fitting( + db_dir: Path, + path_to_hyperparameters: Path | str = MLIP_RSS_DEFAULTS_FILE_PATH, + device: str = "cuda", + ref_energy_name: str = "REF_energy", + ref_force_name: str = "REF_forces", + ref_virial_name: str = "REF_virial", + use_defaults=True, + fit_kwargs: dict | None = None, +) -> dict: + """ + Perform the MACE potential fitting. + + This function sets up and executes a python script to perform MACE fitting using specified parameters + and input data located in the provided directory. It handles the input/output of atomic configurations, + sets up the NequIP model, and calculates training and testing errors after fitting. + + Parameters + ---------- + db_dir: Path + directory containing the training and testing data files. + path_to_hyperparameters : str or Path. + Path to JSON file containing the MACE hyperparameters. + device: str + specify device to use cuda or cpu. + ref_energy_name : str, optional + Reference energy name. + ref_force_name : str, optional + Reference force name. + ref_virial_name : str, optional + Reference virial name. + fit_kwargs: dict. + optional dictionary with parameters for mace fitting with keys same as + mlip-rss-defaults.json. + + Keyword Arguments + ----------------- + model: str + type of model to be trained + config_type_weights: str + weights of config types + hidden_irreps: str + control the model size + r_max: float + cutoff radius controls the locality of the model + batch_size: int + batch size (note that batch size cannot be larger than the size of training datasets) + start_swa: str + if the keyword --swa is enabled, the energy weight of the loss is increased + for the last ~20% of the training epochs (from --start_swa epochs) + correlation: int + correlation order corresponds to the order that MACE induces at each layer + loss: str + loss functions + default_dtype: str + type of float to use, e.g. float32 and float64 + + Returns + ------- + dict[str, float] + A dictionary containing train_error, test_error, and the path to the fitted MLIP. + + """ + if path_to_hyperparameters is None: + path_to_hyperparameters = MLIP_RSS_DEFAULTS_FILE_PATH + if ref_virial_name is not None: + atoms = read(f"{db_dir}/train.extxyz", index=":") + mace_virial_format_conversion( + atoms=atoms, ref_virial_name=ref_virial_name, out_file_name="train.extxyz" + ) + + if use_defaults: + default_hyperparameters = load_mlip_hyperparameter_defaults( + mlip_fit_parameter_file_path=path_to_hyperparameters + ) + + mace_hypers = default_hyperparameters["MACE"] + else: + mace_hypers = {} + + # TODO: should we do a type check? not sure + # as it will be a lot of work to keep it updated + mace_hypers.update(fit_kwargs) + + boolean_hypers = [ + "distributed", + "pair_repulsion", + "amsgrad", + "swa", + "stage_two", + "keep_checkpoint", + "save_all_checkpoints", + "restart_latest", + "save_cpu", + "wandb", + "compute_statistics", + "foundation_model_readout", + "ema", + ] + boolean_str_hypers = [ + "compute_avg_num_neighbors", + "compute_stress", + "compute_forces", + "multi_processed_test", + "pin_memory", + "foundation_filter_elements", + "multiheads_finetuning", + "keep_isolated_atoms", + "shuffle", + ] + + hypers = [] + for hyper in mace_hypers: + if hyper in boolean_hypers: + if mace_hypers[hyper] is True: + hypers.append(f"--{hyper}") + elif hyper in boolean_str_hypers: + hypers.append(f"--{hyper}={mace_hypers[hyper]}") + elif hyper in ["train_file", "test_file"]: + logging.info("Train and test files have default names.") + elif hyper in ["energy_key", "virial_key", "forces_key", "device"]: + logging.info("energy_key, virial_key and forces_key have default names.") + else: + hypers.append(f"--{hyper}={mace_hypers[hyper]}") + + hypers.append(f"--train_file={db_dir}/train.extxyz") + hypers.append(f"--valid_file={db_dir}/test.extxyz") + + if ref_energy_name is not None: + hypers.append(f"--energy_key={ref_energy_name}") + if ref_force_name is not None: + hypers.append(f"--forces_key={ref_force_name}") + if ref_virial_name is not None: + hypers.append(f"--virials_key={ref_virial_name}") + if device is not None: + hypers.append(f"--device={device}") + + run_mace(hypers) + + try: + with open("./logs/MACE_model_run-123.log") as file: + log_data = file.read() + except FileNotFoundError: + # to cover finetuning + try: + with open(f"./logs/{fit_kwargs['name']}_run-123.log") as file: + log_data = file.read() + except FileNotFoundError: + try: + with open("./logs/MACE_final_run-3.log") as file: + log_data = file.read() + except FileNotFoundError: + with open(f"./logs/{fit_kwargs['name']}_run-3.log") as file: + log_data = file.read() + + tables = re.split(r"\+-+\+\n", log_data) + # if tables: + last_table = tables[-2] + try: + matches = re.findall( + r"\|\s*(train_default|valid_default)\s*\|\s*([\d\.]+)\s*\|", last_table + ) + + return { + "train_error": float(matches[0][1]), + "test_error": float(matches[1][1]), + "mlip_path": Path.cwd(), + } + except IndexError: + # to ensure backward compatibility to mace 0.3.4 + matches = re.findall(r"\|\s*(train|valid)\s*\|\s*([\d\.]+)\s*\|", last_table) + + return { + "train_error": float(matches[0][1]), + "test_error": float(matches[1][1]), + "mlip_path": Path.cwd(), + }
+ + + +
+[docs] +def check_convergence(test_error: float) -> bool: + """ + Check the convergence of the fit. + + Parameters + ---------- + test_error: + The error of the test data. + + Returns + ------- + The convergence bool. + """ + convergence = False + if test_error < 0.01: + convergence = True + + return convergence
+ + + +
+[docs] +def load_mlip_hyperparameter_defaults(mlip_fit_parameter_file_path: str | Path) -> dict: + """ + Load gap fit default parameters from the json file. + + Parameters + ---------- + mlip_fit_parameter_file_path : str or Path. + Path to MLIP default parameter JSON files. + + Returns + ------- + dict + gap fit default parameters. + """ + with open(mlip_fit_parameter_file_path, encoding="utf-8") as f: + return json.load(f)
+ + + +
+[docs] +def gap_hyperparameter_constructor( + gap_parameter_dict: dict, + include_two_body: bool = False, + include_three_body: bool = False, + include_soap: bool = False, +) -> list: + """ + Construct a list of arguments needed to execute gap potential from the parameters' dict. + + Parameters + ---------- + gap_parameter_dict : dict. + dictionary with gap hyperparameters. + include_two_body : bool. + bool indicating whether to include two-body hyperparameters + include_three_body : bool. + bool indicating whether to include three-body hyperparameters + include_soap : bool. + bool indicating whether to include soap hyperparameters + + Returns + ------- + list + gap fit input parameter string. + """ + dict_wo_term_name = gap_parameter_dict.copy() + if "two_body" in dict_wo_term_name["general"]: + del dict_wo_term_name["general"]["two_body"] + if "three_body" in dict_wo_term_name["general"]: + del dict_wo_term_name["general"]["three_body"] + if "soap" in dict_wo_term_name["general"]: + del dict_wo_term_name["general"]["soap"] + + general = [f"{key}={value}" for key, value in dict_wo_term_name["general"].items()] + + two_body_params = " ".join( + [ + f"{key}={value}" + for key, value in dict_wo_term_name["twob"].items() + if include_two_body is True + ] + ) + + three_body_params = " ".join( + [ + f"{key}={value}" + for key, value in dict_wo_term_name["threeb"].items() + if include_three_body is True + ] + ) + soap_params = " ".join( + [ + f"{key}={value}" + for key, value in dict_wo_term_name["soap"].items() + if include_soap is True + ] + ) + # add separator between the arg types + if include_two_body and include_three_body and include_soap: + three_body_params = " :" + three_body_params + soap_params = " :soap " + soap_params + elif include_two_body and include_three_body and not include_soap: + three_body_params = " :" + three_body_params + elif (include_two_body or include_three_body) and include_soap: + soap_params = " :soap " + soap_params + elif include_soap and not include_three_body and not include_two_body: + soap_params = "soap " + soap_params + + gap_hyperparameters = f"gap={{{two_body_params}{three_body_params}{soap_params}}}" + + return [*general, gap_hyperparameters]
+ + + +
+[docs] +def get_list_of_vasp_calc_dirs(flow_output) -> list[str]: + """ + Return a list of vasp_calc_dirs from PhononDFTMLDataGenerationFlow output. + + Parameters + ---------- + flow_output: dict. + PhononDFTMLDataGenerationFlow output + + Returns + ------- + list. + A list of vasp_calc_dirs + """ + list_of_vasp_calc_dirs: list[str] = [] + for output in flow_output.values(): + for output_type, dirs in output.items(): + if output_type != "phonon_data" and isinstance(dirs, list): + if output_type == "rattled_dir": + flat_dirs = [[item for sublist in dirs for item in sublist]] + list_of_vasp_calc_dirs.extend(*flat_dirs) + else: + list_of_vasp_calc_dirs.extend(*dirs) + + return list_of_vasp_calc_dirs
+ + + +
+[docs] +def vaspoutput_2_extended_xyz( + path_to_vasp_static_calcs: list, + config_types: list[str] | None = None, + data_types: list[str] | None = None, + regularization: float = 0.1, + f_min: float = 0.01, # unit: eV Å-1 + atom_wise_regularization: bool = True, +) -> None: + """ + Parse all VASP output files (vasprun.xml/OUTCAR) and generates a vasp_ref.extxyz. + + Uses ase.io.read to parse the OUTCARs + Adapted from https://lipai.github.io/scripts/ml_scripts/outcar2xyz.html + + Parameters + ---------- + path_to_vasp_static_calcs : list. + List of VASP static calculation directories. + config_types: list[str] or None + list of config_types. + data_types: list[str] or None + track the data type (phonon or random). + regularization: float + regularization value for the atom-wise force components. + f_min: float + minimal force cutoff value for atom-wise regularization. + atom_wise_regularization: bool + for including atom-wise regularization. + """ + counter = 0 + if config_types is None: + config_types = ["bulk"] * len(path_to_vasp_static_calcs) + if data_types is None: + data_types = ["other"] * len(path_to_vasp_static_calcs) + + for path, config_type, data_type in zip( + path_to_vasp_static_calcs, config_types, data_types + ): + # strip hostname if it exists in the path + path_without_hostname = Path(strip_hostname(path)).joinpath("vasprun.xml.gz") + try: + # read the vasp output + file = read(path_without_hostname, index=":") + for i in file: + virial_list = ( + -voigt_6_to_full_3x3_stress(i.get_stress()) * i.get_volume() + ) + i.info["REF_virial"] = " ".join(map(str, virial_list.flatten())) + del i.calc.results["stress"] + i.arrays["REF_forces"] = i.calc.results["forces"] + if atom_wise_regularization and (data_type == "phonon_dir"): + atom_forces = np.array(i.arrays["REF_forces"]) + atom_wise_force = np.array( + [ + force if force > f_min else f_min + for force in np.linalg.norm(atom_forces, axis=1) + ] + ) + i.arrays["force_atom_sigma"] = regularization * atom_wise_force + del i.calc.results["forces"] + i.info["REF_energy"] = i.calc.results["free_energy"] + del i.calc.results["energy"] + del i.calc.results["free_energy"] + i.info["config_type"] = config_type + i.info["data_type"] = data_type.rstrip("_dir") + i.pbc = True + + # TODO: maybe only add isolated atoms energy if it wasn't there? + + write("vasp_ref.extxyz", file, append=True) + + except FileNotFoundError: + counter += 1 + + if counter / len(path_to_vasp_static_calcs) > 0.05: + raise ValueError( + "An insufficient number of data points collected. Workflow stopped." + )
+ + + +
+[docs] +def flatten(atoms_object, recursive=False) -> list[str | bytes | Atoms] | list: + """ + Flatten an iterable fully, but excluding Atoms objects. + + Parameters + ---------- + atoms_object: Atoms object + recursive: bool + set the recursive boolean. + + Returns + ------- + a flattened object, excluding the Atoms objects. + + """ + iteration_list: list[str | bytes | Atoms] | list = [] + + if recursive: + for element in atoms_object: + if isinstance(element, Iterable) and not isinstance( + element, (str, bytes, ase.atoms.Atoms, ase.Atoms) + ): + iteration_list.extend(flatten(element, recursive=True)) + else: + iteration_list.append(element) + return iteration_list + + return [item for sublist in atoms_object for item in sublist]
+ + + +
+[docs] +def gcm3_to_Vm(gcm3, mr, n_atoms=1) -> float: + """ + Convert gcm3 to Vm. + + Parameters + ---------- + gcm3: + Density in grams per cubic centimeter (g/cm³). + mr: + Molar mass in grams per mole (g/mol). + n_atoms: + Number of atoms in the formula unit. Default is 1. + + Returns + ------- + the converted unit. + + """ + return 1 / (n_atoms * (gcm3 / mr) * 6.022e23 / (1e8) ** 3)
+ + + +
+[docs] +def get_atomic_numbers(species: list) -> list[int]: + """ + Get atomic numbers. + + Parameters + ---------- + species: + type of species + + Returns + ------- + atomic_numbers: + list of atomic numbers. + + """ + atom_numbers = [] + for atom_type in species: + atom = Atoms(atom_type, [(0, 0, 0)]) + atom_numbers.append(int(atom.get_atomic_numbers()[0])) + + return atom_numbers
+ + + +
+[docs] +def energy_remain(in_file: str) -> float: + """ + Plot the distribution of energy per atom on the output vs. the input. + + Parameters + ---------- + in_file: + input file + + Returns + ------- + rms["rmse"]: + distribution of energy per atom RMSE of output vs. input. + + """ + # read files + in_atoms = ase.io.read(in_file, ":") + if "config_type" in in_atoms[0].info: + ener_in = [ + at.info["REF_energy"] / len(at.get_chemical_symbols()) + for at in in_atoms + if at.info["config_type"] != "IsolatedAtoms" + ] + ener_out = [ + at.get_potential_energy() / len(at.get_chemical_symbols()) + for at in in_atoms + if at.info["config_type"] != "IsolatedAtoms" + ] + else: + ener_in = [ + at.info["REF_energy"] / len(at.get_chemical_symbols()) for at in in_atoms + ] + ener_out = [ + at.get_potential_energy() / len(at.get_chemical_symbols()) + for at in in_atoms + ] + rms = rms_dict(ener_in, ener_out) + return rms["rmse"]
+ + + +
+[docs] +def extract_gap_label(xml_file_path) -> str: + """ + Extract GAP label. + + Parameters + ---------- + xml_file_path: + path to the GAP fit potential xml file. + + Returns + ------- + the extracted GAP label. + + """ + # Parse the XML file + tree = ET.parse(xml_file_path) + root = tree.getroot() + return root.tag
+ + + +
+[docs] +def plot_convex_hull(all_points: np.ndarray, hull_points: np.ndarray) -> None: + """ + Plot convex hull. + + Parameters + ---------- + all_points : np.ndarray + Array of all points to be plotted. + hull_points : np.ndarray + Array of points used to calculate the convex hull. + """ + hull = ConvexHull(hull_points) + + plt.plot(all_points[:, 0], all_points[:, 1], "o", markersize=3, label="All Points") + + for i, simplex in enumerate(hull.simplices): + if i == 0: + plt.plot( + hull_points[simplex, 0], + hull_points[simplex, 1], + "k-", + label="Convex Hull", + ) + else: + plt.plot(hull_points[simplex, 0], hull_points[simplex, 1], "k-") + + plt.xlabel("Volume") + plt.ylabel("Energy") + plt.title("Convex Hull with All Points") + plt.legend() + plt.savefig("ConvexHull.png")
+ + + +
+[docs] +def calculate_delta(atoms_db: list[Atoms], e_name: str) -> tuple[float, ndarray]: + """ + Calculate the delta parameter and average number of triplets for gap-fitting. + + Parameters + ---------- + atoms_db: list[Atoms] + list of Ase atoms objects + e_name: str + energy_parameter_name as defined in mlip-phonon-defaults.json + + Returns + ------- + tuple[float, float] + A tuple containing: + - delta parameter used for gap-fit, calculated as (es_var / avg_neigh). + - Average number of triplets per atom. + + """ + at_ids = [atom.get_atomic_numbers() for atom in atoms_db] + isolated_atom_energies = { + atom.get_atomic_numbers()[0]: atom.info[e_name] + for atom in atoms_db + if "config_type" in atom.info and "IsolatedAtom" in atom.info["config_type"] + } + + es_visol = np.array( + [ + (atom.info[e_name] - sum([isolated_atom_energies[j] for j in at_ids[ct]])) + / len(atom) + for ct, atom in enumerate(atoms_db) + ] + ) + es_var = np.var(es_visol) + avg_neigh = np.mean([compute_pairs_triplets(atom)[0] for atom in atoms_db]) + num_triplet = np.mean([compute_pairs_triplets(atom)[1] for atom in atoms_db]) + + return es_var / avg_neigh, num_triplet
+ + + +
+[docs] +def compute_pairs_triplets(atoms: Atoms) -> list[float]: + """ + Calculate the number of pairwise and triplet within a cutoff distance for a given list of atoms. + + Parameters + ---------- + atoms : ASE atoms object + + Returns + ------- + list[float, float] + Returns a list of the number of pairs or triplets an atom is involved in. + + """ + cutoffs = natural_cutoffs(atoms) + neighbor_list = NeighborList( + cutoffs=cutoffs, skin=0.15, self_interaction=False, bothways=True + ) + neighbor_list.update(atoms) + counts_list = [ + len(neighbor_list.get_neighbors(index)[0]) for index in range(len(atoms)) + ] + num_pair = sum(counts_list) / len(atoms) + + triplets = [comb(count, 2) for count in counts_list if count > 1] + num_triplet = sum(triplets) / len(atoms) + + return [num_pair, num_triplet]
+ + + +
+[docs] +def run_ace(num_processes_fit: int, script_name: str) -> None: + """ + Julia-ACE script runner. + + Parameters + ---------- + num_processes_fit: int + Number of threads to be used for the run. + script_name: str + Name of the Julia script to run. + + """ + os.environ["JULIA_NUM_THREADS"] = str(num_processes_fit) + + with ( + open("julia-ace_out.log", "w", encoding="utf-8") as file_out, + open("julia-ace_err.log", "w", encoding="utf-8") as file_err, + ): + subprocess.call(["julia", script_name], stdout=file_out, stderr=file_err)
+ + + +
+[docs] +def run_gap(num_processes_fit: int, parameters) -> None: + """ + GAP runner. + + num_processes_fit: int + number of threads to be used for the run. + + Parameters + ---------- + GAP fit parameters. + + """ + os.environ["OMP_NUM_THREADS"] = str(num_processes_fit) + os.environ["OPENBLAS_NUM_THREADS"] = "1" # blas library + os.environ["BLIS_NUM_THREADS"] = "1" # blas library + os.environ["MKL_NUM_THREADS"] = "1" # blas library + os.environ["NETLIB_NUM_THREADS"] = "1" # blas library + + with ( + open("std_gap_out.log", "w", encoding="utf-8") as file_std, + open("std_gap_err.log", "w", encoding="utf-8") as file_err, + ): + subprocess.call(["gap_fit", *parameters], stdout=file_std, stderr=file_err)
+ + + +
+[docs] +def run_quip( + num_processes_fit: int, + data_path: str, + xml_file: str, + filename: str, + glue_xml: bool = False, +) -> None: + """ + QUIP runner. + + num_processes_fit: int + number of threads to be used for the run. + data_path: + Path to the data file. + filename: str + Name of the output file. + + """ + os.environ["OMP_NUM_THREADS"] = str(num_processes_fit) + os.environ["OPENBLAS_NUM_THREADS"] = "1" # blas library + os.environ["BLIS_NUM_THREADS"] = "1" # blas library + os.environ["MKL_NUM_THREADS"] = "1" # blas library + os.environ["NETLIB_NUM_THREADS"] = "1" # blas library + + init_args = "init_args='IP Glue'" if glue_xml else "" + quip = ( + f"quip {init_args} E=T F=T atoms_filename={data_path} param_filename={xml_file}" + ) + command = f"{quip} | grep AT | sed 's/AT//' > {filename}" + with ( + open("std_quip_out.log", "w", encoding="utf-8") as file_std, + open("std_quip_err.log", "w", encoding="utf-8") as file_err, + ): + subprocess.call(command, stdout=file_std, stderr=file_err, shell=True)
+ + + +
+[docs] +def run_nequip(command: str, log_prefix: str) -> None: + """ + Nequip runner. + + Parameters + ---------- + command: str + The command to execute, along with its arguments. + log_prefix: str + Prefix for log file names, used to differentiate between different commands' logs. + + """ + with ( + open(f"{log_prefix}_out.log", "w", encoding="utf-8") as file_out, + open(f"{log_prefix}_err.log", "w", encoding="utf-8") as file_err, + ): + subprocess.call(command.split(), stdout=file_out, stderr=file_err)
+ + + +
+[docs] +def run_mace(hypers: list) -> None: + """ + MACE runner. + + Parameters + ---------- + hypers: list + containing all hyperparameters required for the MACE model training. + + """ + with ( + open("mace_train_out.log", "w", encoding="utf-8") as file_std, + open("mace_train_err.log", "w", encoding="utf-8") as file_err, + ): + subprocess.call(["mace_run_train", *hypers], stdout=file_std, stderr=file_err)
+ + + +
+[docs] +def prepare_fit_environment( + database_dir: Path, + mlip_path: Path, + glue_xml: bool, + train_name: str = "train.extxyz", + test_name: str = "test.extxyz", + glue_name: str = "glue.xml", +) -> Path: + """ + Prepare the environment for the fit. + + Parameters + ---------- + database_dir: Path + Path to database directory. + mlip_path: Path + Path to the MLIP fit run (cwd). + glue_xml: bool + use the glue.xml core potential instead of fitting 2b terms. + train_name: str + name of the training data file. + test_name: str + name of the test data file. + glue_name: str + name of the glue.xml file or path. + + Returns + ------- + the MLIP file path. + """ + os.makedirs( + os.path.join(mlip_path, train_name.replace("train.extxyz", "")), exist_ok=True + ) + shutil.copy( + os.path.join(database_dir, test_name), + os.path.join(mlip_path, test_name), + ) + shutil.copy( + os.path.join(database_dir, train_name), + os.path.join(mlip_path, train_name), + ) + if glue_xml: + shutil.copy( + os.path.join(database_dir, glue_name), + os.path.join(mlip_path, "glue.xml"), + ) + + return Path(os.path.join(mlip_path, train_name.replace("train.extxyz", "")))
+ + + +
+[docs] +def convert_xyz_to_structure( + atoms_list: list, + include_forces: bool = True, + include_stresses: bool = True, + ref_energy_name: str = "REF_energy", + ref_force_name: str = "REF_forces", + ref_virial_name: str = "REF_virial", +) -> tuple[list[Structure], list, list[object], list[object]]: + """ + Convert extxyz to pymatgen Structure format. + + Parameters + ---------- + atoms_list: + list of atoms to be converted. + include_forces: bool + will include forces with the Structure object. + include_stresses: bool + will include stresses with the Structure object. + ref_energy_name : str, optional + Reference energy name. + ref_force_name : str, optional + Reference force name. + ref_virial_name : str, optional + Reference virial name. + + Returns + ------- + tuple(pymatgen Structure object, energies, forces, stresses) + + """ + structures = [] + energies = [] + forces = [] + stresses = [] + for atoms in atoms_list: + structure = AseAtomsAdaptor.get_structure(atoms) + structures.append(structure) + energies.append(atoms.info[ref_energy_name]) + if include_forces: + forces.append(np.array(atoms.arrays[ref_force_name]).tolist()) + else: + forces.append(np.zeros((len(structure), 3)).tolist()) + if include_stresses: + # convert from eV to GPa + virial = atoms.info[ref_virial_name] / atoms.get_volume() # eV/Å^3 + stresses.append(np.array(virial * 160.2176565).tolist()) # eV/Å^3 -> GPa + else: + stresses.append(np.zeros((3, 3)).tolist()) + + logging.info(f"Loaded {len(structures)} structures.") + + return structures, energies, forces, stresses
+ + + +
+[docs] +def write_after_distillation_data_split( + distillation: bool, + force_max: float, + split_ratio: float, + vasp_ref_name: str = "vasp_ref.extxyz", + train_name: str = "train.extxyz", + test_name: str = "test.extxyz", + force_label: str = "REF_forces", +) -> None: + """ + Write train.extxyz and test.extxyz after data distillation and split. + + Reject structures with large force components and split dataset into training and test datasets. + + Parameters + ---------- + distillation: bool + For using data distillation. + force_max: float + Maximally allowed force in the data set. + split_ratio: float + Parameter to divide the training set and the test set. + A value of 0.1 means that the ratio of the training set to the test set is 9:1 + vasp_ref_name: + name of the VASP reference data file. + train_name: + name of the training data file. + test_name: + name of the test data file. + force_label: str + label of the force entries. + """ + # reject structures with large force components + atoms = ( + data_distillation(vasp_ref_name, force_max, force_label) + if distillation + else ase.io.read(vasp_ref_name, index=":") + ) + + # split dataset into training and test datasets + (train_structures, test_structures) = stratified_dataset_split(atoms, split_ratio) + + ase.io.write(train_name, train_structures, format="extxyz", append=True) + ase.io.write(test_name, test_structures, format="extxyz", append=True)
+ + + +
+[docs] +def mace_virial_format_conversion( + atoms: list[Atoms], ref_virial_name: str, out_file_name: str +) -> None: + """ + Convert the format of virial vector (9,) into a format (3x3) recognizable by MACE. + + Parameters + ---------- + atoms: ase.atoms.Atoms + input structures + ref_virial_name: str + virial label + out_file_name: str + name of output file + """ + formatted_atoms = [] + for at in atoms: + if ref_virial_name in at.info: + at.info[ref_virial_name] = at.info[ref_virial_name].reshape(3, 3) + formatted_atoms.append(at) + + write(out_file_name, formatted_atoms, format="extxyz")
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/index.html b/_modules/index.html new file mode 100644 index 000000000..70483b3ca --- /dev/null +++ b/_modules/index.html @@ -0,0 +1,495 @@ + + + + + + + + + + Overview: module code — autoplex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ + + + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_sources/about/changelog.md b/_sources/about/changelog.md new file mode 100644 index 000000000..3139cd42b --- /dev/null +++ b/_sources/about/changelog.md @@ -0,0 +1,2 @@ +```{include} ../../CHANGELOG.md +``` diff --git a/_sources/about/contributing.md b/_sources/about/contributing.md new file mode 100644 index 000000000..3f049161b --- /dev/null +++ b/_sources/about/contributing.md @@ -0,0 +1,6 @@ +--- +orphan: true +--- + +```{include} ../../CONTRIBUTING.md +``` diff --git a/_sources/about/contributors.md b/_sources/about/contributors.md new file mode 100644 index 000000000..0601d0a78 --- /dev/null +++ b/_sources/about/contributors.md @@ -0,0 +1,64 @@ +# Contributors + +`autoplex` is a joint development between two research groups at the Federal Institute for Materials Research and Testing (BAM) Berlin and the University of Oxford. It incorporates scientific and practical know-how developed in both groups and described in our publications (link). + +[gh]: https://cdnjs.cloudflare.com/ajax/libs/octicons/8.5.0/svg/mark-github.svg +[orc]: ../_static/orcid.svg + +## Project lead (Berlin) + +**Prof. Janine George** [![gh]][JaGeo] [![orc]][0000-0001-8907-0336] [[website](https://jageo.github.io/)]\ +BAM Berlin \ +Friedrich Schiller University Jena + +[JaGeo]: https://github.com/JaGeo +[0000-0001-8907-0336]: https://orcid.org/0000-0001-8907-0336 + +## Project lead (Oxford) + +**Prof. Volker Deringer** [![gh]][vlderinger] [![orc]][0000-0001-6873-0278] [[website](http://deringer.chem.ox.ac.uk)]\ +University of Oxford + +[vlderinger]: https://github.com/vlderinger +[0000-0001-6873-0278]: https://orcid.org/0000-0001-6873-0278 + +## Core code developers and maintainers + +**Dr. Christina Ertural** [![gh]][QuantumChemist] [![orc]][0000-0002-7696-5824] \ +BAM Berlin + +[QuantumChemist]: https://github.com/QuantumChemist +[0000-0002-7696-5824]: https://orcid.org/0000-0002-7696-5824 + +**Natascia Fragapane** [![gh]][nfragapane]\ +University of Oxford + +[nfragapane]: https://github.com/nfragapane + +**Prof. Janine George** [![gh]][JaGeo] [![orc]][0000-0001-8907-0336] [[website](https://jageo.github.io/)]\ +BAM Berlin and Friedrich Schiller University Jena + +**Dr Yuanbin Liu** [![gh]][YuanbinLiu] [![orc]][0000-0002-5948-7031] \ +University of Oxford + +[YuanbinLiu]: https://github.com/YuanbinLiu +[0000-0002-5948-7031]: https://orcid.org/0000-0002-5948-7031 + +**Aakash Ashok Naik** [![gh]][naik-aakash] [![orc]][0000-0002-6071-6786] \ +BAM Berlin and Friedrich Schiller University Jena + +[naik-aakash]: https://github.com/naik-aakash +[0000-0002-6071-6786]: https://orcid.org/0000-0002-6071-6786 + +## Contributors + +**Joe Morrow** [![gh]][MorrowChem] [![orc]][0000-0002-3441-8646] (RSS code)\ +University of Oxford + +[MorrowChem]: https://github.com/morrowchem +[0000-0002-3441-8646]: https://orcid.org/0000-0002-3441-8646 + + +We welcome contributions from other researchers! If you would like to contribute, please see the `How to contribute` guidelines. + + diff --git a/_sources/about/license.md b/_sources/about/license.md new file mode 100644 index 000000000..aa55b5090 --- /dev/null +++ b/_sources/about/license.md @@ -0,0 +1,677 @@ +License +======= + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/_sources/dev/contributing.md b/_sources/dev/contributing.md new file mode 100644 index 000000000..e75758cf1 --- /dev/null +++ b/_sources/dev/contributing.md @@ -0,0 +1,2 @@ +```{include} ../../CONTRIBUTING.md +``` \ No newline at end of file diff --git a/_sources/dev/dev_install.md b/_sources/dev/dev_install.md new file mode 100644 index 000000000..b792f0156 --- /dev/null +++ b/_sources/dev/dev_install.md @@ -0,0 +1,63 @@ +# Developer Installation + +Install autoplex from source, by cloning the repository via [github](https://github.com/JaGeo/autoplex.git) + +```bash +git clone https://github.com/autoatml/autoplex.git +cd autoplex +pip install -e .[strict,dev,tests,docs] +``` +This will install autoplex will all dependencies for tests, pre-commit and docs building. +However, note that non-python programs like `buildcell`, `lammps` and `julia` needed for ACE potential fitting +will not be installed with above command. One needs to install these separately. +See the [installation guide](../user/installation/installation.md) for more information. + +Alternatively, one can use the `devcontainer` provided to have your developer environment setup automatically in your IDE. It has been tested to work in [VSCode](https://code.visualstudio.com/docs/devcontainers/containers#_quick-start-open-an-existing-folder-in-a-container) and [PyCharm](https://blog.jetbrains.com/pycharm/2023/06/2023-2-eap-4/). +Only prerequisite is one has [docker](https://docs.docker.com/get-started/get-docker/) installed on the system as it uses the published docker images to create this developer env. +One can also simply use [GitHub Codespaces](https://github.com/features/codespaces) to use the devcontainer. +The codespaces environment will have all the required dependencies installed. + + +## Running unit tests + +Unit tests can be run from the source folder using `pytest`. + +```bash +pytest +``` +This will run all the tests. + +To get a detailed report of test coverage you can use following command +```bash +pytest --cov=autoplex --cov-report term-missing --cov-append +``` + +If you feel test execution takes too long locally, you can speedup the execution using [pytest-xdist](https://pypi.org/project/pytest-xdist/). Install this in library in your environment using + +```bash +pip install pytest-xdist +``` + +Once installed, you can now use multiple processors to run your tests. For example, if you want to use 8 processors to run tests in parallel, run + +```bash +pytest -n 8 +``` + +## Troubleshooting stuck tests + +Incase your test execution get stuck, try adding the prefix `OMP_NUM_THREADS=1` before pytest. Below is an example snippet + +```bash +OMP_NUM_THREADS=1 pytest +``` + +## Building the documentation locally + +The autoplex documentation can be built using the sphinx package. + +The docs can be built to the `_build` directory: + +```bash +sphinx-build -W docs _build +``` diff --git a/_sources/glossary.rst b/_sources/glossary.rst new file mode 100644 index 000000000..fd1888b5f --- /dev/null +++ b/_sources/glossary.rst @@ -0,0 +1,11 @@ +:orphan: + +.. _glossary: + +Glossary +======== + +.. glossary:: + + generic type + A placeholder for a specific type, used in generic programming. diff --git a/_sources/index.md b/_sources/index.md new file mode 100644 index 000000000..1b95b9475 --- /dev/null +++ b/_sources/index.md @@ -0,0 +1,84 @@ +```{toctree} +:caption: User Guide +:hidden: +user/index +user/setup +user/tutorials +``` + +```{toctree} +:caption: Reference +:hidden: +reference/index +``` + +```{toctree} +:caption: Contributing Guide +:hidden: +dev/contributing +dev/dev_install +``` + +```{toctree} +:caption: About +:hidden: +about/changelog +about/contributors +about/license +``` + +![autoplex documentation](_static/autoplex_logo.png) +# autoplex documentation + + +**Date**: {sub-ref}`today` + +**Useful links**: +[Source Repository](https://github.com/JaGeo/autoplex) | +[Issues & Ideas](https://github.com/JaGeo/autoplex/issues) | + +`autoplex` is a software tool for the automated generation and benchmarking of machine learning (ML)-based interatomic potentials. +The aim of autoplex is to provide a fully automated solution for creating high-quality ML potentials. +The software is interfaced to multiple different ML potential fitting frameworks and to the atomate2 and ase environment +for efficient high-throughput computations. +The vision of this project is to allow a wide community of researchers to create accurate and reliable ML potentials for materials simulations. + +::::{grid} 1 1 2 2 +:class-container: text-center +:gutter: 3 + +:::{grid-item-card} +:link: user/setup +:link-type: doc +:class-header: bg-light +**Quick Start** +^^^ +The user guide provides information for getting started with *autoplex*. +::: + +:::{grid-item-card} +:link: user/tutorials +:link-type: doc +:class-header: bg-light +**Tutorials** +^^^ +Tutorials for using autoplex. +::: + +:::{grid-item-card} +:class-header: bg-light +**Automation Setup** +^^^ +Here you can find the setup guides for using `autoplex` with [MongoDB](user/mongodb.md), [FireWorks](user/mongodb.md#fireworks-configuration) and [jobflow-remote](user/jobflowremote.md). +::: + +:::{grid-item-card} +:link: dev/dev_install +:link-type: doc +:class-header: bg-light +**Contributing Guide** +^^^ +Do you want to develop your own workflows or improve existing functionalities? +Check out the Contributing Guide. +::: +:::: diff --git a/_sources/reference/autoplex.auto.phonons.flows.CompleteDFTvsMLBenchmarkWorkflow.rst b/_sources/reference/autoplex.auto.phonons.flows.CompleteDFTvsMLBenchmarkWorkflow.rst new file mode 100644 index 000000000..5052691d2 --- /dev/null +++ b/_sources/reference/autoplex.auto.phonons.flows.CompleteDFTvsMLBenchmarkWorkflow.rst @@ -0,0 +1,8 @@ +CompleteDFTvsMLBenchmarkWorkflow +================================ + +.. currentmodule:: autoplex.auto.phonons.flows + +.. autoclass:: CompleteDFTvsMLBenchmarkWorkflow + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.auto.phonons.flows.CompleteDFTvsMLBenchmarkWorkflowMPSettings.rst b/_sources/reference/autoplex.auto.phonons.flows.CompleteDFTvsMLBenchmarkWorkflowMPSettings.rst new file mode 100644 index 000000000..1fc79a8cc --- /dev/null +++ b/_sources/reference/autoplex.auto.phonons.flows.CompleteDFTvsMLBenchmarkWorkflowMPSettings.rst @@ -0,0 +1,8 @@ +CompleteDFTvsMLBenchmarkWorkflowMPSettings +========================================== + +.. currentmodule:: autoplex.auto.phonons.flows + +.. autoclass:: CompleteDFTvsMLBenchmarkWorkflowMPSettings + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.auto.phonons.flows.DFTSupercellSettingsMaker.rst b/_sources/reference/autoplex.auto.phonons.flows.DFTSupercellSettingsMaker.rst new file mode 100644 index 000000000..93fa52168 --- /dev/null +++ b/_sources/reference/autoplex.auto.phonons.flows.DFTSupercellSettingsMaker.rst @@ -0,0 +1,8 @@ +DFTSupercellSettingsMaker +========================= + +.. currentmodule:: autoplex.auto.phonons.flows + +.. autoclass:: DFTSupercellSettingsMaker + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.auto.phonons.flows.IterativeCompleteDFTvsMLBenchmarkWorkflow.rst b/_sources/reference/autoplex.auto.phonons.flows.IterativeCompleteDFTvsMLBenchmarkWorkflow.rst new file mode 100644 index 000000000..43114da2c --- /dev/null +++ b/_sources/reference/autoplex.auto.phonons.flows.IterativeCompleteDFTvsMLBenchmarkWorkflow.rst @@ -0,0 +1,8 @@ +IterativeCompleteDFTvsMLBenchmarkWorkflow +========================================= + +.. currentmodule:: autoplex.auto.phonons.flows + +.. autoclass:: IterativeCompleteDFTvsMLBenchmarkWorkflow + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.auto.phonons.flows.rst b/_sources/reference/autoplex.auto.phonons.flows.rst new file mode 100644 index 000000000..d6d23526d --- /dev/null +++ b/_sources/reference/autoplex.auto.phonons.flows.rst @@ -0,0 +1,46 @@ +autoplex.auto.phonons.flows +=========================== + +.. automodule:: autoplex.auto.phonons.flows + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + :toctree: + :nosignatures: + + CompleteDFTvsMLBenchmarkWorkflow + CompleteDFTvsMLBenchmarkWorkflowMPSettings + DFTSupercellSettingsMaker + IterativeCompleteDFTvsMLBenchmarkWorkflow + + diff --git a/_sources/reference/autoplex.auto.phonons.jobs.complete_benchmark.rst b/_sources/reference/autoplex.auto.phonons.jobs.complete_benchmark.rst new file mode 100644 index 000000000..49b7b6254 --- /dev/null +++ b/_sources/reference/autoplex.auto.phonons.jobs.complete_benchmark.rst @@ -0,0 +1,6 @@ +complete\_benchmark +=================== + +.. currentmodule:: autoplex.auto.phonons.jobs + +.. autofunction:: complete_benchmark \ No newline at end of file diff --git a/_sources/reference/autoplex.auto.phonons.jobs.dft_phonopy_gen_data.rst b/_sources/reference/autoplex.auto.phonons.jobs.dft_phonopy_gen_data.rst new file mode 100644 index 000000000..a8d71a47a --- /dev/null +++ b/_sources/reference/autoplex.auto.phonons.jobs.dft_phonopy_gen_data.rst @@ -0,0 +1,6 @@ +dft\_phonopy\_gen\_data +======================= + +.. currentmodule:: autoplex.auto.phonons.jobs + +.. autofunction:: dft_phonopy_gen_data \ No newline at end of file diff --git a/_sources/reference/autoplex.auto.phonons.jobs.dft_random_gen_data.rst b/_sources/reference/autoplex.auto.phonons.jobs.dft_random_gen_data.rst new file mode 100644 index 000000000..e6a6a3c9c --- /dev/null +++ b/_sources/reference/autoplex.auto.phonons.jobs.dft_random_gen_data.rst @@ -0,0 +1,6 @@ +dft\_random\_gen\_data +====================== + +.. currentmodule:: autoplex.auto.phonons.jobs + +.. autofunction:: dft_random_gen_data \ No newline at end of file diff --git a/_sources/reference/autoplex.auto.phonons.jobs.do_iterative_rattled_structures.rst b/_sources/reference/autoplex.auto.phonons.jobs.do_iterative_rattled_structures.rst new file mode 100644 index 000000000..c44e8b004 --- /dev/null +++ b/_sources/reference/autoplex.auto.phonons.jobs.do_iterative_rattled_structures.rst @@ -0,0 +1,6 @@ +do\_iterative\_rattled\_structures +================================== + +.. currentmodule:: autoplex.auto.phonons.jobs + +.. autofunction:: do_iterative_rattled_structures \ No newline at end of file diff --git a/_sources/reference/autoplex.auto.phonons.jobs.generate_supercells.rst b/_sources/reference/autoplex.auto.phonons.jobs.generate_supercells.rst new file mode 100644 index 000000000..b8aa71a37 --- /dev/null +++ b/_sources/reference/autoplex.auto.phonons.jobs.generate_supercells.rst @@ -0,0 +1,6 @@ +generate\_supercells +==================== + +.. currentmodule:: autoplex.auto.phonons.jobs + +.. autofunction:: generate_supercells \ No newline at end of file diff --git a/_sources/reference/autoplex.auto.phonons.jobs.get_iso_atom.rst b/_sources/reference/autoplex.auto.phonons.jobs.get_iso_atom.rst new file mode 100644 index 000000000..734f9845a --- /dev/null +++ b/_sources/reference/autoplex.auto.phonons.jobs.get_iso_atom.rst @@ -0,0 +1,6 @@ +get\_iso\_atom +============== + +.. currentmodule:: autoplex.auto.phonons.jobs + +.. autofunction:: get_iso_atom \ No newline at end of file diff --git a/_sources/reference/autoplex.auto.phonons.jobs.get_phonon_output.rst b/_sources/reference/autoplex.auto.phonons.jobs.get_phonon_output.rst new file mode 100644 index 000000000..91ce47ac8 --- /dev/null +++ b/_sources/reference/autoplex.auto.phonons.jobs.get_phonon_output.rst @@ -0,0 +1,6 @@ +get\_phonon\_output +=================== + +.. currentmodule:: autoplex.auto.phonons.jobs + +.. autofunction:: get_phonon_output \ No newline at end of file diff --git a/_sources/reference/autoplex.auto.phonons.jobs.rst b/_sources/reference/autoplex.auto.phonons.jobs.rst new file mode 100644 index 000000000..f410624a9 --- /dev/null +++ b/_sources/reference/autoplex.auto.phonons.jobs.rst @@ -0,0 +1,44 @@ +autoplex.auto.phonons.jobs +========================== + +.. automodule:: autoplex.auto.phonons.jobs + + + + + + .. rubric:: Functions + + .. autosummary:: + :toctree: + :nosignatures: + + complete_benchmark + dft_phonopy_gen_data + dft_random_gen_data + do_iterative_rattled_structures + generate_supercells + get_iso_atom + get_phonon_output + run_supercells + + + + + + + + + + + + + + + + + + + + + diff --git a/_sources/reference/autoplex.auto.phonons.jobs.run_supercells.rst b/_sources/reference/autoplex.auto.phonons.jobs.run_supercells.rst new file mode 100644 index 000000000..bf31c2572 --- /dev/null +++ b/_sources/reference/autoplex.auto.phonons.jobs.run_supercells.rst @@ -0,0 +1,6 @@ +run\_supercells +=============== + +.. currentmodule:: autoplex.auto.phonons.jobs + +.. autofunction:: run_supercells \ No newline at end of file diff --git a/_sources/reference/autoplex.auto.phonons.rst b/_sources/reference/autoplex.auto.phonons.rst new file mode 100644 index 000000000..ec5172344 --- /dev/null +++ b/_sources/reference/autoplex.auto.phonons.rst @@ -0,0 +1,31 @@ +autoplex.auto.phonons +===================== + +.. automodule:: autoplex.auto.phonons + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + flows + jobs diff --git a/_sources/reference/autoplex.auto.rss.flows.RssMaker.rst b/_sources/reference/autoplex.auto.rss.flows.RssMaker.rst new file mode 100644 index 000000000..7bebb5436 --- /dev/null +++ b/_sources/reference/autoplex.auto.rss.flows.RssMaker.rst @@ -0,0 +1,8 @@ +RssMaker +======== + +.. currentmodule:: autoplex.auto.rss.flows + +.. autoclass:: RssMaker + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.auto.rss.flows.rst b/_sources/reference/autoplex.auto.rss.flows.rst new file mode 100644 index 000000000..2ba0e9e7a --- /dev/null +++ b/_sources/reference/autoplex.auto.rss.flows.rst @@ -0,0 +1,33 @@ +autoplex.auto.rss.flows +======================= + +.. automodule:: autoplex.auto.rss.flows + + + + + + + + + + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + :toctree: + :nosignatures: + + RssMaker + + diff --git a/_sources/reference/autoplex.auto.rss.jobs.do_rss_iterations.rst b/_sources/reference/autoplex.auto.rss.jobs.do_rss_iterations.rst new file mode 100644 index 000000000..853ea6403 --- /dev/null +++ b/_sources/reference/autoplex.auto.rss.jobs.do_rss_iterations.rst @@ -0,0 +1,6 @@ +do\_rss\_iterations +=================== + +.. currentmodule:: autoplex.auto.rss.jobs + +.. autofunction:: do_rss_iterations \ No newline at end of file diff --git a/_sources/reference/autoplex.auto.rss.jobs.initial_rss.rst b/_sources/reference/autoplex.auto.rss.jobs.initial_rss.rst new file mode 100644 index 000000000..fdfa332af --- /dev/null +++ b/_sources/reference/autoplex.auto.rss.jobs.initial_rss.rst @@ -0,0 +1,6 @@ +initial\_rss +============ + +.. currentmodule:: autoplex.auto.rss.jobs + +.. autofunction:: initial_rss \ No newline at end of file diff --git a/_sources/reference/autoplex.auto.rss.jobs.rst b/_sources/reference/autoplex.auto.rss.jobs.rst new file mode 100644 index 000000000..b996b2670 --- /dev/null +++ b/_sources/reference/autoplex.auto.rss.jobs.rst @@ -0,0 +1,33 @@ +autoplex.auto.rss.jobs +====================== + +.. automodule:: autoplex.auto.rss.jobs + + + + + + .. rubric:: Functions + + .. autosummary:: + :toctree: + :nosignatures: + + do_rss_iterations + initial_rss + + + + + + + + + + + + + + + + diff --git a/_sources/reference/autoplex.auto.rss.rst b/_sources/reference/autoplex.auto.rss.rst new file mode 100644 index 000000000..aa42d1ce5 --- /dev/null +++ b/_sources/reference/autoplex.auto.rss.rst @@ -0,0 +1,31 @@ +autoplex.auto.rss +================= + +.. automodule:: autoplex.auto.rss + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + flows + jobs diff --git a/_sources/reference/autoplex.auto.rst b/_sources/reference/autoplex.auto.rst new file mode 100644 index 000000000..6e8fc1e64 --- /dev/null +++ b/_sources/reference/autoplex.auto.rst @@ -0,0 +1,31 @@ +autoplex.auto +============= + +.. automodule:: autoplex.auto + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + phonons + rss diff --git a/_sources/reference/autoplex.benchmark.phonons.flows.PhononBenchmarkMaker.rst b/_sources/reference/autoplex.benchmark.phonons.flows.PhononBenchmarkMaker.rst new file mode 100644 index 000000000..2d42f4db6 --- /dev/null +++ b/_sources/reference/autoplex.benchmark.phonons.flows.PhononBenchmarkMaker.rst @@ -0,0 +1,8 @@ +PhononBenchmarkMaker +==================== + +.. currentmodule:: autoplex.benchmark.phonons.flows + +.. autoclass:: PhononBenchmarkMaker + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.benchmark.phonons.flows.rst b/_sources/reference/autoplex.benchmark.phonons.flows.rst new file mode 100644 index 000000000..0079b00e0 --- /dev/null +++ b/_sources/reference/autoplex.benchmark.phonons.flows.rst @@ -0,0 +1,34 @@ +autoplex.benchmark.phonons.flows +================================ + +.. automodule:: autoplex.benchmark.phonons.flows + + + + + + + + + + + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + :toctree: + :nosignatures: + + PhononBenchmarkMaker + + diff --git a/_sources/reference/autoplex.benchmark.phonons.jobs.rst b/_sources/reference/autoplex.benchmark.phonons.jobs.rst new file mode 100644 index 000000000..d8c499ee6 --- /dev/null +++ b/_sources/reference/autoplex.benchmark.phonons.jobs.rst @@ -0,0 +1,30 @@ +autoplex.benchmark.phonons.jobs +=============================== + +.. automodule:: autoplex.benchmark.phonons.jobs + + + + + + .. rubric:: Functions + + .. autosummary:: + :toctree: + :nosignatures: + + write_benchmark_metrics + + + + + + + + + + + + + + diff --git a/_sources/reference/autoplex.benchmark.phonons.jobs.write_benchmark_metrics.rst b/_sources/reference/autoplex.benchmark.phonons.jobs.write_benchmark_metrics.rst new file mode 100644 index 000000000..dca1e22d1 --- /dev/null +++ b/_sources/reference/autoplex.benchmark.phonons.jobs.write_benchmark_metrics.rst @@ -0,0 +1,6 @@ +write\_benchmark\_metrics +========================= + +.. currentmodule:: autoplex.benchmark.phonons.jobs + +.. autofunction:: write_benchmark_metrics \ No newline at end of file diff --git a/_sources/reference/autoplex.benchmark.phonons.rst b/_sources/reference/autoplex.benchmark.phonons.rst new file mode 100644 index 000000000..77b19e9ad --- /dev/null +++ b/_sources/reference/autoplex.benchmark.phonons.rst @@ -0,0 +1,32 @@ +autoplex.benchmark.phonons +========================== + +.. automodule:: autoplex.benchmark.phonons + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + flows + jobs + utils diff --git a/_sources/reference/autoplex.benchmark.phonons.utils.compare_plot.rst b/_sources/reference/autoplex.benchmark.phonons.utils.compare_plot.rst new file mode 100644 index 000000000..b98e38ef8 --- /dev/null +++ b/_sources/reference/autoplex.benchmark.phonons.utils.compare_plot.rst @@ -0,0 +1,6 @@ +compare\_plot +============= + +.. currentmodule:: autoplex.benchmark.phonons.utils + +.. autofunction:: compare_plot \ No newline at end of file diff --git a/_sources/reference/autoplex.benchmark.phonons.utils.compute_bandstructure_benchmark_metrics.rst b/_sources/reference/autoplex.benchmark.phonons.utils.compute_bandstructure_benchmark_metrics.rst new file mode 100644 index 000000000..f81fecff1 --- /dev/null +++ b/_sources/reference/autoplex.benchmark.phonons.utils.compute_bandstructure_benchmark_metrics.rst @@ -0,0 +1,6 @@ +compute\_bandstructure\_benchmark\_metrics +========================================== + +.. currentmodule:: autoplex.benchmark.phonons.utils + +.. autofunction:: compute_bandstructure_benchmark_metrics \ No newline at end of file diff --git a/_sources/reference/autoplex.benchmark.phonons.utils.get_rmse.rst b/_sources/reference/autoplex.benchmark.phonons.utils.get_rmse.rst new file mode 100644 index 000000000..6e7b94879 --- /dev/null +++ b/_sources/reference/autoplex.benchmark.phonons.utils.get_rmse.rst @@ -0,0 +1,6 @@ +get\_rmse +========= + +.. currentmodule:: autoplex.benchmark.phonons.utils + +.. autofunction:: get_rmse \ No newline at end of file diff --git a/_sources/reference/autoplex.benchmark.phonons.utils.rmse_qdep_plot.rst b/_sources/reference/autoplex.benchmark.phonons.utils.rmse_qdep_plot.rst new file mode 100644 index 000000000..efd04b500 --- /dev/null +++ b/_sources/reference/autoplex.benchmark.phonons.utils.rmse_qdep_plot.rst @@ -0,0 +1,6 @@ +rmse\_qdep\_plot +================ + +.. currentmodule:: autoplex.benchmark.phonons.utils + +.. autofunction:: rmse_qdep_plot \ No newline at end of file diff --git a/_sources/reference/autoplex.benchmark.phonons.utils.rst b/_sources/reference/autoplex.benchmark.phonons.utils.rst new file mode 100644 index 000000000..26340511c --- /dev/null +++ b/_sources/reference/autoplex.benchmark.phonons.utils.rst @@ -0,0 +1,36 @@ +autoplex.benchmark.phonons.utils +================================ + +.. automodule:: autoplex.benchmark.phonons.utils + + + + + + .. rubric:: Functions + + .. autosummary:: + :toctree: + :nosignatures: + + compare_plot + compute_bandstructure_benchmark_metrics + get_rmse + rmse_qdep_plot + + + + + + + + + + + + + + + + + diff --git a/_sources/reference/autoplex.benchmark.rss.rst b/_sources/reference/autoplex.benchmark.rss.rst new file mode 100644 index 000000000..1351a14af --- /dev/null +++ b/_sources/reference/autoplex.benchmark.rss.rst @@ -0,0 +1,22 @@ +autoplex.benchmark.rss +====================== + +.. automodule:: autoplex.benchmark.rss + + + + + + + + + + + + + + + + + + diff --git a/_sources/reference/autoplex.benchmark.rst b/_sources/reference/autoplex.benchmark.rst new file mode 100644 index 000000000..77eec779a --- /dev/null +++ b/_sources/reference/autoplex.benchmark.rst @@ -0,0 +1,31 @@ +autoplex.benchmark +================== + +.. automodule:: autoplex.benchmark + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + phonons + rss diff --git a/_sources/reference/autoplex.data.common.flows.DFTStaticLabelling.rst b/_sources/reference/autoplex.data.common.flows.DFTStaticLabelling.rst new file mode 100644 index 000000000..0febfd567 --- /dev/null +++ b/_sources/reference/autoplex.data.common.flows.DFTStaticLabelling.rst @@ -0,0 +1,8 @@ +DFTStaticLabelling +================== + +.. currentmodule:: autoplex.data.common.flows + +.. autoclass:: DFTStaticLabelling + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.flows.GenerateTrainingDataForTesting.rst b/_sources/reference/autoplex.data.common.flows.GenerateTrainingDataForTesting.rst new file mode 100644 index 000000000..bcbd4d0fe --- /dev/null +++ b/_sources/reference/autoplex.data.common.flows.GenerateTrainingDataForTesting.rst @@ -0,0 +1,8 @@ +GenerateTrainingDataForTesting +============================== + +.. currentmodule:: autoplex.data.common.flows + +.. autoclass:: GenerateTrainingDataForTesting + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.flows.rst b/_sources/reference/autoplex.data.common.flows.rst new file mode 100644 index 000000000..15ed7848c --- /dev/null +++ b/_sources/reference/autoplex.data.common.flows.rst @@ -0,0 +1,38 @@ +autoplex.data.common.flows +========================== + +.. automodule:: autoplex.data.common.flows + + + + + + + + + + + + + + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + :toctree: + :nosignatures: + + DFTStaticLabelling + GenerateTrainingDataForTesting + + diff --git a/_sources/reference/autoplex.data.common.jobs.TYPE_CHECKING.rst b/_sources/reference/autoplex.data.common.jobs.TYPE_CHECKING.rst new file mode 100644 index 000000000..a8577d953 --- /dev/null +++ b/_sources/reference/autoplex.data.common.jobs.TYPE_CHECKING.rst @@ -0,0 +1,6 @@ +TYPE\_CHECKING +============== + +.. currentmodule:: autoplex.data.common.jobs + +.. autodata:: TYPE_CHECKING \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.jobs.check_convergence_vasp.rst b/_sources/reference/autoplex.data.common.jobs.check_convergence_vasp.rst new file mode 100644 index 000000000..13f9f3a62 --- /dev/null +++ b/_sources/reference/autoplex.data.common.jobs.check_convergence_vasp.rst @@ -0,0 +1,6 @@ +check\_convergence\_vasp +======================== + +.. currentmodule:: autoplex.data.common.jobs + +.. autofunction:: check_convergence_vasp \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.jobs.collect_dft_data.rst b/_sources/reference/autoplex.data.common.jobs.collect_dft_data.rst new file mode 100644 index 000000000..3af0699c8 --- /dev/null +++ b/_sources/reference/autoplex.data.common.jobs.collect_dft_data.rst @@ -0,0 +1,6 @@ +collect\_dft\_data +================== + +.. currentmodule:: autoplex.data.common.jobs + +.. autofunction:: collect_dft_data \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.jobs.convert_to_extxyz.rst b/_sources/reference/autoplex.data.common.jobs.convert_to_extxyz.rst new file mode 100644 index 000000000..aa06490b3 --- /dev/null +++ b/_sources/reference/autoplex.data.common.jobs.convert_to_extxyz.rst @@ -0,0 +1,6 @@ +convert\_to\_extxyz +=================== + +.. currentmodule:: autoplex.data.common.jobs + +.. autofunction:: convert_to_extxyz \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.jobs.generate_randomized_structures.rst b/_sources/reference/autoplex.data.common.jobs.generate_randomized_structures.rst new file mode 100644 index 000000000..56bd280de --- /dev/null +++ b/_sources/reference/autoplex.data.common.jobs.generate_randomized_structures.rst @@ -0,0 +1,6 @@ +generate\_randomized\_structures +================================ + +.. currentmodule:: autoplex.data.common.jobs + +.. autofunction:: generate_randomized_structures \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.jobs.get_supercell_job.rst b/_sources/reference/autoplex.data.common.jobs.get_supercell_job.rst new file mode 100644 index 000000000..04bcba954 --- /dev/null +++ b/_sources/reference/autoplex.data.common.jobs.get_supercell_job.rst @@ -0,0 +1,6 @@ +get\_supercell\_job +=================== + +.. currentmodule:: autoplex.data.common.jobs + +.. autofunction:: get_supercell_job \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.jobs.plot_force_distribution.rst b/_sources/reference/autoplex.data.common.jobs.plot_force_distribution.rst new file mode 100644 index 000000000..2ef680273 --- /dev/null +++ b/_sources/reference/autoplex.data.common.jobs.plot_force_distribution.rst @@ -0,0 +1,6 @@ +plot\_force\_distribution +========================= + +.. currentmodule:: autoplex.data.common.jobs + +.. autofunction:: plot_force_distribution \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.jobs.preprocess_data.rst b/_sources/reference/autoplex.data.common.jobs.preprocess_data.rst new file mode 100644 index 000000000..cad930e30 --- /dev/null +++ b/_sources/reference/autoplex.data.common.jobs.preprocess_data.rst @@ -0,0 +1,6 @@ +preprocess\_data +================ + +.. currentmodule:: autoplex.data.common.jobs + +.. autofunction:: preprocess_data \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.jobs.rst b/_sources/reference/autoplex.data.common.jobs.rst new file mode 100644 index 000000000..41cabcc27 --- /dev/null +++ b/_sources/reference/autoplex.data.common.jobs.rst @@ -0,0 +1,58 @@ +autoplex.data.common.jobs +========================= + +.. automodule:: autoplex.data.common.jobs + + + + + + .. rubric:: Functions + + .. autosummary:: + :toctree: + :nosignatures: + + check_convergence_vasp + collect_dft_data + convert_to_extxyz + generate_randomized_structures + get_supercell_job + plot_force_distribution + preprocess_data + safe_strip_hostname + sample_data + + + + + + + + + + + + + + + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + :toctree: + :nosignatures: + + TYPE_CHECKING + + diff --git a/_sources/reference/autoplex.data.common.jobs.safe_strip_hostname.rst b/_sources/reference/autoplex.data.common.jobs.safe_strip_hostname.rst new file mode 100644 index 000000000..b6462bf46 --- /dev/null +++ b/_sources/reference/autoplex.data.common.jobs.safe_strip_hostname.rst @@ -0,0 +1,6 @@ +safe\_strip\_hostname +===================== + +.. currentmodule:: autoplex.data.common.jobs + +.. autofunction:: safe_strip_hostname \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.jobs.sample_data.rst b/_sources/reference/autoplex.data.common.jobs.sample_data.rst new file mode 100644 index 000000000..3e8806b5c --- /dev/null +++ b/_sources/reference/autoplex.data.common.jobs.sample_data.rst @@ -0,0 +1,6 @@ +sample\_data +============ + +.. currentmodule:: autoplex.data.common.jobs + +.. autofunction:: sample_data \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.rst b/_sources/reference/autoplex.data.common.rst new file mode 100644 index 000000000..a675b0354 --- /dev/null +++ b/_sources/reference/autoplex.data.common.rst @@ -0,0 +1,32 @@ +autoplex.data.common +==================== + +.. automodule:: autoplex.data.common + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + flows + jobs + utils diff --git a/_sources/reference/autoplex.data.common.utils.ElementCollection.rst b/_sources/reference/autoplex.data.common.utils.ElementCollection.rst new file mode 100644 index 000000000..b70da6e87 --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.ElementCollection.rst @@ -0,0 +1,8 @@ +ElementCollection +================= + +.. currentmodule:: autoplex.data.common.utils + +.. autoclass:: ElementCollection + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.GPa.rst b/_sources/reference/autoplex.data.common.utils.GPa.rst new file mode 100644 index 000000000..b34b8c191 --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.GPa.rst @@ -0,0 +1,6 @@ +GPa +=== + +.. currentmodule:: autoplex.data.common.utils + +.. autodata:: GPa \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.boltzhist_cur_dual_iter.rst b/_sources/reference/autoplex.data.common.utils.boltzhist_cur_dual_iter.rst new file mode 100644 index 000000000..54d4fd7dd --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.boltzhist_cur_dual_iter.rst @@ -0,0 +1,6 @@ +boltzhist\_cur\_dual\_iter +========================== + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: boltzhist_cur_dual_iter \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.boltzhist_cur_one_shot.rst b/_sources/reference/autoplex.data.common.utils.boltzhist_cur_one_shot.rst new file mode 100644 index 000000000..2093fff9a --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.boltzhist_cur_one_shot.rst @@ -0,0 +1,6 @@ +boltzhist\_cur\_one\_shot +========================= + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: boltzhist_cur_one_shot \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.check_distances.rst b/_sources/reference/autoplex.data.common.utils.check_distances.rst new file mode 100644 index 000000000..bd2ad00d8 --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.check_distances.rst @@ -0,0 +1,6 @@ +check\_distances +================ + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: check_distances \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.convexhull_cur.rst b/_sources/reference/autoplex.data.common.utils.convexhull_cur.rst new file mode 100644 index 000000000..ccf311e41 --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.convexhull_cur.rst @@ -0,0 +1,6 @@ +convexhull\_cur +=============== + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: convexhull_cur \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.create_soap_descriptor.rst b/_sources/reference/autoplex.data.common.utils.create_soap_descriptor.rst new file mode 100644 index 000000000..bfbe2096c --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.create_soap_descriptor.rst @@ -0,0 +1,6 @@ +create\_soap\_descriptor +======================== + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: create_soap_descriptor \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.cur_select.rst b/_sources/reference/autoplex.data.common.utils.cur_select.rst new file mode 100644 index 000000000..fee2ee7b1 --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.cur_select.rst @@ -0,0 +1,6 @@ +cur\_select +=========== + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: cur_select \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.data_distillation.rst b/_sources/reference/autoplex.data.common.utils.data_distillation.rst new file mode 100644 index 000000000..ba889c952 --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.data_distillation.rst @@ -0,0 +1,6 @@ +data\_distillation +================== + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: data_distillation \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.energy_plot.rst b/_sources/reference/autoplex.data.common.utils.energy_plot.rst new file mode 100644 index 000000000..391e9c98b --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.energy_plot.rst @@ -0,0 +1,6 @@ +energy\_plot +============ + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: energy_plot \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.filter_outlier_energy.rst b/_sources/reference/autoplex.data.common.utils.filter_outlier_energy.rst new file mode 100644 index 000000000..20606ec13 --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.filter_outlier_energy.rst @@ -0,0 +1,6 @@ +filter\_outlier\_energy +======================= + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: filter_outlier_energy \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.filter_outlier_forces.rst b/_sources/reference/autoplex.data.common.utils.filter_outlier_forces.rst new file mode 100644 index 000000000..3b609ad1f --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.filter_outlier_forces.rst @@ -0,0 +1,6 @@ +filter\_outlier\_forces +======================= + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: filter_outlier_forces \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.flatten.rst b/_sources/reference/autoplex.data.common.utils.flatten.rst new file mode 100644 index 000000000..55ef48a0e --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.flatten.rst @@ -0,0 +1,6 @@ +flatten +======= + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: flatten \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.flatten_list.rst b/_sources/reference/autoplex.data.common.utils.flatten_list.rst new file mode 100644 index 000000000..adfc55c2b --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.flatten_list.rst @@ -0,0 +1,6 @@ +flatten\_list +============= + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: flatten_list \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.force_plot.rst b/_sources/reference/autoplex.data.common.utils.force_plot.rst new file mode 100644 index 000000000..d80ce2449 --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.force_plot.rst @@ -0,0 +1,6 @@ +force\_plot +=========== + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: force_plot \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.handle_rss_trajectory.rst b/_sources/reference/autoplex.data.common.utils.handle_rss_trajectory.rst new file mode 100644 index 000000000..8fbcb2a54 --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.handle_rss_trajectory.rst @@ -0,0 +1,6 @@ +handle\_rss\_trajectory +======================= + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: handle_rss_trajectory \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.mc_rattle.rst b/_sources/reference/autoplex.data.common.utils.mc_rattle.rst new file mode 100644 index 000000000..73670946c --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.mc_rattle.rst @@ -0,0 +1,6 @@ +mc\_rattle +========== + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: mc_rattle \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.parallel_calc_descriptor_vec.rst b/_sources/reference/autoplex.data.common.utils.parallel_calc_descriptor_vec.rst new file mode 100644 index 000000000..fa68294d6 --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.parallel_calc_descriptor_vec.rst @@ -0,0 +1,6 @@ +parallel\_calc\_descriptor\_vec +=============================== + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: parallel_calc_descriptor_vec \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.plot_energy_forces.rst b/_sources/reference/autoplex.data.common.utils.plot_energy_forces.rst new file mode 100644 index 000000000..02c4d1e13 --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.plot_energy_forces.rst @@ -0,0 +1,6 @@ +plot\_energy\_forces +==================== + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: plot_energy_forces \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.random_vary_angle.rst b/_sources/reference/autoplex.data.common.utils.random_vary_angle.rst new file mode 100644 index 000000000..7e1b9f7f4 --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.random_vary_angle.rst @@ -0,0 +1,6 @@ +random\_vary\_angle +=================== + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: random_vary_angle \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.rms_dict.rst b/_sources/reference/autoplex.data.common.utils.rms_dict.rst new file mode 100644 index 000000000..418223052 --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.rms_dict.rst @@ -0,0 +1,6 @@ +rms\_dict +========= + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: rms_dict \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.rst b/_sources/reference/autoplex.data.common.utils.rst new file mode 100644 index 000000000..d49eb1f7a --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.rst @@ -0,0 +1,90 @@ +autoplex.data.common.utils +========================== + +.. automodule:: autoplex.data.common.utils + + + + + + .. rubric:: Functions + + .. autosummary:: + :toctree: + :nosignatures: + + boltzhist_cur_dual_iter + boltzhist_cur_one_shot + check_distances + convexhull_cur + create_soap_descriptor + cur_select + data_distillation + energy_plot + filter_outlier_energy + filter_outlier_forces + flatten + flatten_list + force_plot + handle_rss_trajectory + mc_rattle + parallel_calc_descriptor_vec + plot_energy_forces + random_vary_angle + rms_dict + scale_cell + std_rattle + stratified_dataset_split + to_ase_trajectory + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + :toctree: + :nosignatures: + + ElementCollection + GPa + + diff --git a/_sources/reference/autoplex.data.common.utils.scale_cell.rst b/_sources/reference/autoplex.data.common.utils.scale_cell.rst new file mode 100644 index 000000000..d53185bf9 --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.scale_cell.rst @@ -0,0 +1,6 @@ +scale\_cell +=========== + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: scale_cell \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.std_rattle.rst b/_sources/reference/autoplex.data.common.utils.std_rattle.rst new file mode 100644 index 000000000..d48d2712c --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.std_rattle.rst @@ -0,0 +1,6 @@ +std\_rattle +=========== + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: std_rattle \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.stratified_dataset_split.rst b/_sources/reference/autoplex.data.common.utils.stratified_dataset_split.rst new file mode 100644 index 000000000..e5f27eee8 --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.stratified_dataset_split.rst @@ -0,0 +1,6 @@ +stratified\_dataset\_split +========================== + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: stratified_dataset_split \ No newline at end of file diff --git a/_sources/reference/autoplex.data.common.utils.to_ase_trajectory.rst b/_sources/reference/autoplex.data.common.utils.to_ase_trajectory.rst new file mode 100644 index 000000000..5ec16b329 --- /dev/null +++ b/_sources/reference/autoplex.data.common.utils.to_ase_trajectory.rst @@ -0,0 +1,6 @@ +to\_ase\_trajectory +=================== + +.. currentmodule:: autoplex.data.common.utils + +.. autofunction:: to_ase_trajectory \ No newline at end of file diff --git a/_sources/reference/autoplex.data.phonons.flows.DFTPhononMaker.rst b/_sources/reference/autoplex.data.phonons.flows.DFTPhononMaker.rst new file mode 100644 index 000000000..0fba3c2da --- /dev/null +++ b/_sources/reference/autoplex.data.phonons.flows.DFTPhononMaker.rst @@ -0,0 +1,8 @@ +DFTPhononMaker +============== + +.. currentmodule:: autoplex.data.phonons.flows + +.. autoclass:: DFTPhononMaker + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.data.phonons.flows.IsoAtomMaker.rst b/_sources/reference/autoplex.data.phonons.flows.IsoAtomMaker.rst new file mode 100644 index 000000000..519087061 --- /dev/null +++ b/_sources/reference/autoplex.data.phonons.flows.IsoAtomMaker.rst @@ -0,0 +1,8 @@ +IsoAtomMaker +============ + +.. currentmodule:: autoplex.data.phonons.flows + +.. autoclass:: IsoAtomMaker + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.data.phonons.flows.IsoAtomStaticMaker.rst b/_sources/reference/autoplex.data.phonons.flows.IsoAtomStaticMaker.rst new file mode 100644 index 000000000..52a252f62 --- /dev/null +++ b/_sources/reference/autoplex.data.phonons.flows.IsoAtomStaticMaker.rst @@ -0,0 +1,8 @@ +IsoAtomStaticMaker +================== + +.. currentmodule:: autoplex.data.phonons.flows + +.. autoclass:: IsoAtomStaticMaker + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.data.phonons.flows.MLPhononMaker.rst b/_sources/reference/autoplex.data.phonons.flows.MLPhononMaker.rst new file mode 100644 index 000000000..237ea97d9 --- /dev/null +++ b/_sources/reference/autoplex.data.phonons.flows.MLPhononMaker.rst @@ -0,0 +1,8 @@ +MLPhononMaker +============= + +.. currentmodule:: autoplex.data.phonons.flows + +.. autoclass:: MLPhononMaker + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.data.phonons.flows.RandomStructuresDataGenerator.rst b/_sources/reference/autoplex.data.phonons.flows.RandomStructuresDataGenerator.rst new file mode 100644 index 000000000..ce4d2ea51 --- /dev/null +++ b/_sources/reference/autoplex.data.phonons.flows.RandomStructuresDataGenerator.rst @@ -0,0 +1,8 @@ +RandomStructuresDataGenerator +============================= + +.. currentmodule:: autoplex.data.phonons.flows + +.. autoclass:: RandomStructuresDataGenerator + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.data.phonons.flows.TightDFTStaticMaker.rst b/_sources/reference/autoplex.data.phonons.flows.TightDFTStaticMaker.rst new file mode 100644 index 000000000..e7c691087 --- /dev/null +++ b/_sources/reference/autoplex.data.phonons.flows.TightDFTStaticMaker.rst @@ -0,0 +1,8 @@ +TightDFTStaticMaker +=================== + +.. currentmodule:: autoplex.data.phonons.flows + +.. autoclass:: TightDFTStaticMaker + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.data.phonons.flows.rst b/_sources/reference/autoplex.data.phonons.flows.rst new file mode 100644 index 000000000..0756be88f --- /dev/null +++ b/_sources/reference/autoplex.data.phonons.flows.rst @@ -0,0 +1,54 @@ +autoplex.data.phonons.flows +=========================== + +.. automodule:: autoplex.data.phonons.flows + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + :toctree: + :nosignatures: + + DFTPhononMaker + IsoAtomMaker + IsoAtomStaticMaker + MLPhononMaker + RandomStructuresDataGenerator + TightDFTStaticMaker + + diff --git a/_sources/reference/autoplex.data.phonons.jobs.reduce_supercell_size_job.rst b/_sources/reference/autoplex.data.phonons.jobs.reduce_supercell_size_job.rst new file mode 100644 index 000000000..2e63d303f --- /dev/null +++ b/_sources/reference/autoplex.data.phonons.jobs.reduce_supercell_size_job.rst @@ -0,0 +1,6 @@ +reduce\_supercell\_size\_job +============================ + +.. currentmodule:: autoplex.data.phonons.jobs + +.. autofunction:: reduce_supercell_size_job \ No newline at end of file diff --git a/_sources/reference/autoplex.data.phonons.jobs.rst b/_sources/reference/autoplex.data.phonons.jobs.rst new file mode 100644 index 000000000..b1e837389 --- /dev/null +++ b/_sources/reference/autoplex.data.phonons.jobs.rst @@ -0,0 +1,30 @@ +autoplex.data.phonons.jobs +========================== + +.. automodule:: autoplex.data.phonons.jobs + + + + + + .. rubric:: Functions + + .. autosummary:: + :toctree: + :nosignatures: + + reduce_supercell_size_job + + + + + + + + + + + + + + diff --git a/_sources/reference/autoplex.data.phonons.rst b/_sources/reference/autoplex.data.phonons.rst new file mode 100644 index 000000000..825276f15 --- /dev/null +++ b/_sources/reference/autoplex.data.phonons.rst @@ -0,0 +1,32 @@ +autoplex.data.phonons +===================== + +.. automodule:: autoplex.data.phonons + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + flows + jobs + utils diff --git a/_sources/reference/autoplex.data.phonons.utils.check_supercells.rst b/_sources/reference/autoplex.data.phonons.utils.check_supercells.rst new file mode 100644 index 000000000..1e091c574 --- /dev/null +++ b/_sources/reference/autoplex.data.phonons.utils.check_supercells.rst @@ -0,0 +1,6 @@ +check\_supercells +================= + +.. currentmodule:: autoplex.data.phonons.utils + +.. autofunction:: check_supercells \ No newline at end of file diff --git a/_sources/reference/autoplex.data.phonons.utils.ml_phonon_maker_preparation.rst b/_sources/reference/autoplex.data.phonons.utils.ml_phonon_maker_preparation.rst new file mode 100644 index 000000000..c332ce0f4 --- /dev/null +++ b/_sources/reference/autoplex.data.phonons.utils.ml_phonon_maker_preparation.rst @@ -0,0 +1,6 @@ +ml\_phonon\_maker\_preparation +============================== + +.. currentmodule:: autoplex.data.phonons.utils + +.. autofunction:: ml_phonon_maker_preparation \ No newline at end of file diff --git a/_sources/reference/autoplex.data.phonons.utils.reduce_supercell_size.rst b/_sources/reference/autoplex.data.phonons.utils.reduce_supercell_size.rst new file mode 100644 index 000000000..24ed15357 --- /dev/null +++ b/_sources/reference/autoplex.data.phonons.utils.reduce_supercell_size.rst @@ -0,0 +1,6 @@ +reduce\_supercell\_size +======================= + +.. currentmodule:: autoplex.data.phonons.utils + +.. autofunction:: reduce_supercell_size \ No newline at end of file diff --git a/_sources/reference/autoplex.data.phonons.utils.rst b/_sources/reference/autoplex.data.phonons.utils.rst new file mode 100644 index 000000000..4835270c2 --- /dev/null +++ b/_sources/reference/autoplex.data.phonons.utils.rst @@ -0,0 +1,36 @@ +autoplex.data.phonons.utils +=========================== + +.. automodule:: autoplex.data.phonons.utils + + + + + + .. rubric:: Functions + + .. autosummary:: + :toctree: + :nosignatures: + + check_supercells + ml_phonon_maker_preparation + reduce_supercell_size + update_phonon_displacement_maker + + + + + + + + + + + + + + + + + diff --git a/_sources/reference/autoplex.data.phonons.utils.update_phonon_displacement_maker.rst b/_sources/reference/autoplex.data.phonons.utils.update_phonon_displacement_maker.rst new file mode 100644 index 000000000..cbcc392d7 --- /dev/null +++ b/_sources/reference/autoplex.data.phonons.utils.update_phonon_displacement_maker.rst @@ -0,0 +1,6 @@ +update\_phonon\_displacement\_maker +=================================== + +.. currentmodule:: autoplex.data.phonons.utils + +.. autofunction:: update_phonon_displacement_maker \ No newline at end of file diff --git a/_sources/reference/autoplex.data.rss.flows.BuildMultiRandomizedStructure.rst b/_sources/reference/autoplex.data.rss.flows.BuildMultiRandomizedStructure.rst new file mode 100644 index 000000000..18ab7217e --- /dev/null +++ b/_sources/reference/autoplex.data.rss.flows.BuildMultiRandomizedStructure.rst @@ -0,0 +1,8 @@ +BuildMultiRandomizedStructure +============================= + +.. currentmodule:: autoplex.data.rss.flows + +.. autoclass:: BuildMultiRandomizedStructure + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.data.rss.flows.rst b/_sources/reference/autoplex.data.rss.flows.rst new file mode 100644 index 000000000..da28f3a48 --- /dev/null +++ b/_sources/reference/autoplex.data.rss.flows.rst @@ -0,0 +1,34 @@ +autoplex.data.rss.flows +======================= + +.. automodule:: autoplex.data.rss.flows + + + + + + + + + + + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + :toctree: + :nosignatures: + + BuildMultiRandomizedStructure + + diff --git a/_sources/reference/autoplex.data.rss.jobs.RandomizedStructure.rst b/_sources/reference/autoplex.data.rss.jobs.RandomizedStructure.rst new file mode 100644 index 000000000..253213ec3 --- /dev/null +++ b/_sources/reference/autoplex.data.rss.jobs.RandomizedStructure.rst @@ -0,0 +1,8 @@ +RandomizedStructure +=================== + +.. currentmodule:: autoplex.data.rss.jobs + +.. autoclass:: RandomizedStructure + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.data.rss.jobs.atomic_numbers.rst b/_sources/reference/autoplex.data.rss.jobs.atomic_numbers.rst new file mode 100644 index 000000000..db98d9df3 --- /dev/null +++ b/_sources/reference/autoplex.data.rss.jobs.atomic_numbers.rst @@ -0,0 +1,6 @@ +atomic\_numbers +=============== + +.. currentmodule:: autoplex.data.rss.jobs + +.. autodata:: atomic_numbers \ No newline at end of file diff --git a/_sources/reference/autoplex.data.rss.jobs.covalent_radii.rst b/_sources/reference/autoplex.data.rss.jobs.covalent_radii.rst new file mode 100644 index 000000000..7d72c52db --- /dev/null +++ b/_sources/reference/autoplex.data.rss.jobs.covalent_radii.rst @@ -0,0 +1,6 @@ +covalent\_radii +=============== + +.. currentmodule:: autoplex.data.rss.jobs + +.. autodata:: covalent_radii \ No newline at end of file diff --git a/_sources/reference/autoplex.data.rss.jobs.do_rss_multi_node.rst b/_sources/reference/autoplex.data.rss.jobs.do_rss_multi_node.rst new file mode 100644 index 000000000..a15b9df56 --- /dev/null +++ b/_sources/reference/autoplex.data.rss.jobs.do_rss_multi_node.rst @@ -0,0 +1,6 @@ +do\_rss\_multi\_node +==================== + +.. currentmodule:: autoplex.data.rss.jobs + +.. autofunction:: do_rss_multi_node \ No newline at end of file diff --git a/_sources/reference/autoplex.data.rss.jobs.do_rss_single_node.rst b/_sources/reference/autoplex.data.rss.jobs.do_rss_single_node.rst new file mode 100644 index 000000000..beed113fd --- /dev/null +++ b/_sources/reference/autoplex.data.rss.jobs.do_rss_single_node.rst @@ -0,0 +1,6 @@ +do\_rss\_single\_node +===================== + +.. currentmodule:: autoplex.data.rss.jobs + +.. autofunction:: do_rss_single_node \ No newline at end of file diff --git a/_sources/reference/autoplex.data.rss.jobs.rst b/_sources/reference/autoplex.data.rss.jobs.rst new file mode 100644 index 000000000..8ac69cc03 --- /dev/null +++ b/_sources/reference/autoplex.data.rss.jobs.rst @@ -0,0 +1,52 @@ +autoplex.data.rss.jobs +====================== + +.. automodule:: autoplex.data.rss.jobs + + + + + + .. rubric:: Functions + + .. autosummary:: + :toctree: + :nosignatures: + + do_rss_multi_node + do_rss_single_node + + + + + + + + + + + + + + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + :toctree: + :nosignatures: + + RandomizedStructure + atomic_numbers + covalent_radii + + diff --git a/_sources/reference/autoplex.data.rss.rst b/_sources/reference/autoplex.data.rss.rst new file mode 100644 index 000000000..ee62b1fbe --- /dev/null +++ b/_sources/reference/autoplex.data.rss.rst @@ -0,0 +1,32 @@ +autoplex.data.rss +================= + +.. automodule:: autoplex.data.rss + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + flows + jobs + utils diff --git a/_sources/reference/autoplex.data.rss.utils.CustomPotential.rst b/_sources/reference/autoplex.data.rss.utils.CustomPotential.rst new file mode 100644 index 000000000..75d69fa2d --- /dev/null +++ b/_sources/reference/autoplex.data.rss.utils.CustomPotential.rst @@ -0,0 +1,8 @@ +CustomPotential +=============== + +.. currentmodule:: autoplex.data.rss.utils + +.. autoclass:: CustomPotential + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.data.rss.utils.GPa.rst b/_sources/reference/autoplex.data.rss.utils.GPa.rst new file mode 100644 index 000000000..e59abdd30 --- /dev/null +++ b/_sources/reference/autoplex.data.rss.utils.GPa.rst @@ -0,0 +1,6 @@ +GPa +=== + +.. currentmodule:: autoplex.data.rss.utils + +.. autodata:: GPa \ No newline at end of file diff --git a/_sources/reference/autoplex.data.rss.utils.HookeanRepulsion.rst b/_sources/reference/autoplex.data.rss.utils.HookeanRepulsion.rst new file mode 100644 index 000000000..81dfed110 --- /dev/null +++ b/_sources/reference/autoplex.data.rss.utils.HookeanRepulsion.rst @@ -0,0 +1,8 @@ +HookeanRepulsion +================ + +.. currentmodule:: autoplex.data.rss.utils + +.. autoclass:: HookeanRepulsion + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.data.rss.utils.atomic_numbers.rst b/_sources/reference/autoplex.data.rss.utils.atomic_numbers.rst new file mode 100644 index 000000000..b88ab35d6 --- /dev/null +++ b/_sources/reference/autoplex.data.rss.utils.atomic_numbers.rst @@ -0,0 +1,6 @@ +atomic\_numbers +=============== + +.. currentmodule:: autoplex.data.rss.utils + +.. autodata:: atomic_numbers \ No newline at end of file diff --git a/_sources/reference/autoplex.data.rss.utils.chemical_symbols.rst b/_sources/reference/autoplex.data.rss.utils.chemical_symbols.rst new file mode 100644 index 000000000..45043c4ff --- /dev/null +++ b/_sources/reference/autoplex.data.rss.utils.chemical_symbols.rst @@ -0,0 +1,6 @@ +chemical\_symbols +================= + +.. currentmodule:: autoplex.data.rss.utils + +.. autodata:: chemical_symbols \ No newline at end of file diff --git a/_sources/reference/autoplex.data.rss.utils.extract_pairstyle.rst b/_sources/reference/autoplex.data.rss.utils.extract_pairstyle.rst new file mode 100644 index 000000000..a21e461cb --- /dev/null +++ b/_sources/reference/autoplex.data.rss.utils.extract_pairstyle.rst @@ -0,0 +1,6 @@ +extract\_pairstyle +================== + +.. currentmodule:: autoplex.data.rss.utils + +.. autofunction:: extract_pairstyle \ No newline at end of file diff --git a/_sources/reference/autoplex.data.rss.utils.minimize_structures.rst b/_sources/reference/autoplex.data.rss.utils.minimize_structures.rst new file mode 100644 index 000000000..c296ab1ab --- /dev/null +++ b/_sources/reference/autoplex.data.rss.utils.minimize_structures.rst @@ -0,0 +1,6 @@ +minimize\_structures +==================== + +.. currentmodule:: autoplex.data.rss.utils + +.. autofunction:: minimize_structures \ No newline at end of file diff --git a/_sources/reference/autoplex.data.rss.utils.process_rss.rst b/_sources/reference/autoplex.data.rss.utils.process_rss.rst new file mode 100644 index 000000000..573ce608a --- /dev/null +++ b/_sources/reference/autoplex.data.rss.utils.process_rss.rst @@ -0,0 +1,6 @@ +process\_rss +============ + +.. currentmodule:: autoplex.data.rss.utils + +.. autofunction:: process_rss \ No newline at end of file diff --git a/_sources/reference/autoplex.data.rss.utils.rst b/_sources/reference/autoplex.data.rss.utils.rst new file mode 100644 index 000000000..aab1dad59 --- /dev/null +++ b/_sources/reference/autoplex.data.rss.utils.rst @@ -0,0 +1,64 @@ +autoplex.data.rss.utils +======================= + +.. automodule:: autoplex.data.rss.utils + + + + + + .. rubric:: Functions + + .. autosummary:: + :toctree: + :nosignatures: + + extract_pairstyle + minimize_structures + process_rss + split_structure_into_groups + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + :toctree: + :nosignatures: + + CustomPotential + GPa + HookeanRepulsion + atomic_numbers + chemical_symbols + + diff --git a/_sources/reference/autoplex.data.rss.utils.split_structure_into_groups.rst b/_sources/reference/autoplex.data.rss.utils.split_structure_into_groups.rst new file mode 100644 index 000000000..36fd3f2e5 --- /dev/null +++ b/_sources/reference/autoplex.data.rss.utils.split_structure_into_groups.rst @@ -0,0 +1,6 @@ +split\_structure\_into\_groups +============================== + +.. currentmodule:: autoplex.data.rss.utils + +.. autofunction:: split_structure_into_groups \ No newline at end of file diff --git a/_sources/reference/autoplex.data.rst b/_sources/reference/autoplex.data.rst new file mode 100644 index 000000000..0205862d1 --- /dev/null +++ b/_sources/reference/autoplex.data.rst @@ -0,0 +1,32 @@ +autoplex.data +============= + +.. automodule:: autoplex.data + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + common + phonons + rss diff --git a/_sources/reference/autoplex.fitting.common.flows.DataPreprocessing.rst b/_sources/reference/autoplex.fitting.common.flows.DataPreprocessing.rst new file mode 100644 index 000000000..51176c34b --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.flows.DataPreprocessing.rst @@ -0,0 +1,8 @@ +DataPreprocessing +================= + +.. currentmodule:: autoplex.fitting.common.flows + +.. autoclass:: DataPreprocessing + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.flows.MLIPFitMaker.rst b/_sources/reference/autoplex.fitting.common.flows.MLIPFitMaker.rst new file mode 100644 index 000000000..c9831cef4 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.flows.MLIPFitMaker.rst @@ -0,0 +1,8 @@ +MLIPFitMaker +============ + +.. currentmodule:: autoplex.fitting.common.flows + +.. autoclass:: MLIPFitMaker + :show-inheritance: + :members: \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.flows.rst b/_sources/reference/autoplex.fitting.common.flows.rst new file mode 100644 index 000000000..b4dca74fb --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.flows.rst @@ -0,0 +1,38 @@ +autoplex.fitting.common.flows +============================= + +.. automodule:: autoplex.fitting.common.flows + + + + + + + + + + + + + + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + :toctree: + :nosignatures: + + DataPreprocessing + MLIPFitMaker + + diff --git a/_sources/reference/autoplex.fitting.common.jobs.machine_learning_fit.rst b/_sources/reference/autoplex.fitting.common.jobs.machine_learning_fit.rst new file mode 100644 index 000000000..5be8e2990 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.jobs.machine_learning_fit.rst @@ -0,0 +1,6 @@ +machine\_learning\_fit +====================== + +.. currentmodule:: autoplex.fitting.common.jobs + +.. autofunction:: machine_learning_fit \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.jobs.rst b/_sources/reference/autoplex.fitting.common.jobs.rst new file mode 100644 index 000000000..d6ec51cce --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.jobs.rst @@ -0,0 +1,30 @@ +autoplex.fitting.common.jobs +============================ + +.. automodule:: autoplex.fitting.common.jobs + + + + + + .. rubric:: Functions + + .. autosummary:: + :toctree: + :nosignatures: + + machine_learning_fit + + + + + + + + + + + + + + diff --git a/_sources/reference/autoplex.fitting.common.regularization.calculate_hull_3d.rst b/_sources/reference/autoplex.fitting.common.regularization.calculate_hull_3d.rst new file mode 100644 index 000000000..e7e4fa4c4 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.regularization.calculate_hull_3d.rst @@ -0,0 +1,6 @@ +calculate\_hull\_3d +=================== + +.. currentmodule:: autoplex.fitting.common.regularization + +.. autofunction:: calculate_hull_3d \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.regularization.calculate_hull_nd.rst b/_sources/reference/autoplex.fitting.common.regularization.calculate_hull_nd.rst new file mode 100644 index 000000000..fcf758ed1 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.regularization.calculate_hull_nd.rst @@ -0,0 +1,6 @@ +calculate\_hull\_nd +=================== + +.. currentmodule:: autoplex.fitting.common.regularization + +.. autofunction:: calculate_hull_nd \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.regularization.get_convex_hull.rst b/_sources/reference/autoplex.fitting.common.regularization.get_convex_hull.rst new file mode 100644 index 000000000..c4b21b74b --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.regularization.get_convex_hull.rst @@ -0,0 +1,6 @@ +get\_convex\_hull +================= + +.. currentmodule:: autoplex.fitting.common.regularization + +.. autofunction:: get_convex_hull \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.regularization.get_e_distance_to_hull.rst b/_sources/reference/autoplex.fitting.common.regularization.get_e_distance_to_hull.rst new file mode 100644 index 000000000..55234df7e --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.regularization.get_e_distance_to_hull.rst @@ -0,0 +1,6 @@ +get\_e\_distance\_to\_hull +========================== + +.. currentmodule:: autoplex.fitting.common.regularization + +.. autofunction:: get_e_distance_to_hull \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.regularization.get_e_distance_to_hull_3d.rst b/_sources/reference/autoplex.fitting.common.regularization.get_e_distance_to_hull_3d.rst new file mode 100644 index 000000000..a763915a3 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.regularization.get_e_distance_to_hull_3d.rst @@ -0,0 +1,6 @@ +get\_e\_distance\_to\_hull\_3d +============================== + +.. currentmodule:: autoplex.fitting.common.regularization + +.. autofunction:: get_e_distance_to_hull_3d \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.regularization.get_intersect.rst b/_sources/reference/autoplex.fitting.common.regularization.get_intersect.rst new file mode 100644 index 000000000..6c92aa167 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.regularization.get_intersect.rst @@ -0,0 +1,6 @@ +get\_intersect +============== + +.. currentmodule:: autoplex.fitting.common.regularization + +.. autofunction:: get_intersect \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.regularization.get_mole_frac.rst b/_sources/reference/autoplex.fitting.common.regularization.get_mole_frac.rst new file mode 100644 index 000000000..58cd0b0de --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.regularization.get_mole_frac.rst @@ -0,0 +1,6 @@ +get\_mole\_frac +=============== + +.. currentmodule:: autoplex.fitting.common.regularization + +.. autofunction:: get_mole_frac \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.regularization.label_stoichiometry_volume.rst b/_sources/reference/autoplex.fitting.common.regularization.label_stoichiometry_volume.rst new file mode 100644 index 000000000..382096673 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.regularization.label_stoichiometry_volume.rst @@ -0,0 +1,6 @@ +label\_stoichiometry\_volume +============================ + +.. currentmodule:: autoplex.fitting.common.regularization + +.. autofunction:: label_stoichiometry_volume \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.regularization.piecewise_linear.rst b/_sources/reference/autoplex.fitting.common.regularization.piecewise_linear.rst new file mode 100644 index 000000000..1877055fd --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.regularization.piecewise_linear.rst @@ -0,0 +1,6 @@ +piecewise\_linear +================= + +.. currentmodule:: autoplex.fitting.common.regularization + +.. autofunction:: piecewise_linear \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.regularization.point_in_triangle_2D.rst b/_sources/reference/autoplex.fitting.common.regularization.point_in_triangle_2D.rst new file mode 100644 index 000000000..3bb8ab6fb --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.regularization.point_in_triangle_2D.rst @@ -0,0 +1,6 @@ +point\_in\_triangle\_2D +======================= + +.. currentmodule:: autoplex.fitting.common.regularization + +.. autofunction:: point_in_triangle_2D \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.regularization.point_in_triangle_nd.rst b/_sources/reference/autoplex.fitting.common.regularization.point_in_triangle_nd.rst new file mode 100644 index 000000000..6bec063c5 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.regularization.point_in_triangle_nd.rst @@ -0,0 +1,6 @@ +point\_in\_triangle\_nd +======================= + +.. currentmodule:: autoplex.fitting.common.regularization + +.. autofunction:: point_in_triangle_nd \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.regularization.rst b/_sources/reference/autoplex.fitting.common.regularization.rst new file mode 100644 index 000000000..71373eb40 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.regularization.rst @@ -0,0 +1,52 @@ +autoplex.fitting.common.regularization +====================================== + +.. automodule:: autoplex.fitting.common.regularization + + + + + + .. rubric:: Functions + + .. autosummary:: + :toctree: + :nosignatures: + + calculate_hull_3d + calculate_hull_nd + get_convex_hull + get_e_distance_to_hull + get_e_distance_to_hull_3d + get_intersect + get_mole_frac + label_stoichiometry_volume + piecewise_linear + point_in_triangle_2D + point_in_triangle_nd + set_custom_sigma + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_sources/reference/autoplex.fitting.common.regularization.set_custom_sigma.rst b/_sources/reference/autoplex.fitting.common.regularization.set_custom_sigma.rst new file mode 100644 index 000000000..b37c3b185 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.regularization.set_custom_sigma.rst @@ -0,0 +1,6 @@ +set\_custom\_sigma +================== + +.. currentmodule:: autoplex.fitting.common.regularization + +.. autofunction:: set_custom_sigma \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.rst b/_sources/reference/autoplex.fitting.common.rst new file mode 100644 index 000000000..51bdad03e --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.rst @@ -0,0 +1,33 @@ +autoplex.fitting.common +======================= + +.. automodule:: autoplex.fitting.common + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + flows + jobs + regularization + utils diff --git a/_sources/reference/autoplex.fitting.common.utils.calculate_delta.rst b/_sources/reference/autoplex.fitting.common.utils.calculate_delta.rst new file mode 100644 index 000000000..a18292866 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.calculate_delta.rst @@ -0,0 +1,6 @@ +calculate\_delta +================ + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: calculate_delta \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.check_convergence.rst b/_sources/reference/autoplex.fitting.common.utils.check_convergence.rst new file mode 100644 index 000000000..872bdeeb5 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.check_convergence.rst @@ -0,0 +1,6 @@ +check\_convergence +================== + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: check_convergence \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.chemical_symbols.rst b/_sources/reference/autoplex.fitting.common.utils.chemical_symbols.rst new file mode 100644 index 000000000..ffb860ca6 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.chemical_symbols.rst @@ -0,0 +1,6 @@ +chemical\_symbols +================= + +.. currentmodule:: autoplex.fitting.common.utils + +.. autodata:: chemical_symbols \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.compute_pairs_triplets.rst b/_sources/reference/autoplex.fitting.common.utils.compute_pairs_triplets.rst new file mode 100644 index 000000000..328b4b4b0 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.compute_pairs_triplets.rst @@ -0,0 +1,6 @@ +compute\_pairs\_triplets +======================== + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: compute_pairs_triplets \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.convert_xyz_to_structure.rst b/_sources/reference/autoplex.fitting.common.utils.convert_xyz_to_structure.rst new file mode 100644 index 000000000..cf75f089e --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.convert_xyz_to_structure.rst @@ -0,0 +1,6 @@ +convert\_xyz\_to\_structure +=========================== + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: convert_xyz_to_structure \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.energy_remain.rst b/_sources/reference/autoplex.fitting.common.utils.energy_remain.rst new file mode 100644 index 000000000..1ca7f7c68 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.energy_remain.rst @@ -0,0 +1,6 @@ +energy\_remain +============== + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: energy_remain \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.extract_gap_label.rst b/_sources/reference/autoplex.fitting.common.utils.extract_gap_label.rst new file mode 100644 index 000000000..0333ae24f --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.extract_gap_label.rst @@ -0,0 +1,6 @@ +extract\_gap\_label +=================== + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: extract_gap_label \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.flatten.rst b/_sources/reference/autoplex.fitting.common.utils.flatten.rst new file mode 100644 index 000000000..8b1352f54 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.flatten.rst @@ -0,0 +1,6 @@ +flatten +======= + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: flatten \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.gap_fitting.rst b/_sources/reference/autoplex.fitting.common.utils.gap_fitting.rst new file mode 100644 index 000000000..eb86b976b --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.gap_fitting.rst @@ -0,0 +1,6 @@ +gap\_fitting +============ + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: gap_fitting \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.gap_hyperparameter_constructor.rst b/_sources/reference/autoplex.fitting.common.utils.gap_hyperparameter_constructor.rst new file mode 100644 index 000000000..63607ec14 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.gap_hyperparameter_constructor.rst @@ -0,0 +1,6 @@ +gap\_hyperparameter\_constructor +================================ + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: gap_hyperparameter_constructor \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.gcm3_to_Vm.rst b/_sources/reference/autoplex.fitting.common.utils.gcm3_to_Vm.rst new file mode 100644 index 000000000..66d8e93b5 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.gcm3_to_Vm.rst @@ -0,0 +1,6 @@ +gcm3\_to\_Vm +============ + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: gcm3_to_Vm \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.get_atomic_numbers.rst b/_sources/reference/autoplex.fitting.common.utils.get_atomic_numbers.rst new file mode 100644 index 000000000..0766c24af --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.get_atomic_numbers.rst @@ -0,0 +1,6 @@ +get\_atomic\_numbers +==================== + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: get_atomic_numbers \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.get_list_of_vasp_calc_dirs.rst b/_sources/reference/autoplex.fitting.common.utils.get_list_of_vasp_calc_dirs.rst new file mode 100644 index 000000000..5405c7263 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.get_list_of_vasp_calc_dirs.rst @@ -0,0 +1,6 @@ +get\_list\_of\_vasp\_calc\_dirs +=============================== + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: get_list_of_vasp_calc_dirs \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.jace_fitting.rst b/_sources/reference/autoplex.fitting.common.utils.jace_fitting.rst new file mode 100644 index 000000000..f0d8fc336 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.jace_fitting.rst @@ -0,0 +1,6 @@ +jace\_fitting +============= + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: jace_fitting \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.load_mlip_hyperparameter_defaults.rst b/_sources/reference/autoplex.fitting.common.utils.load_mlip_hyperparameter_defaults.rst new file mode 100644 index 000000000..b80b841e1 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.load_mlip_hyperparameter_defaults.rst @@ -0,0 +1,6 @@ +load\_mlip\_hyperparameter\_defaults +==================================== + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: load_mlip_hyperparameter_defaults \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.m3gnet_fitting.rst b/_sources/reference/autoplex.fitting.common.utils.m3gnet_fitting.rst new file mode 100644 index 000000000..b9eb87e7a --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.m3gnet_fitting.rst @@ -0,0 +1,6 @@ +m3gnet\_fitting +=============== + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: m3gnet_fitting \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.mace_fitting.rst b/_sources/reference/autoplex.fitting.common.utils.mace_fitting.rst new file mode 100644 index 000000000..64a11225e --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.mace_fitting.rst @@ -0,0 +1,6 @@ +mace\_fitting +============= + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: mace_fitting \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.mace_virial_format_conversion.rst b/_sources/reference/autoplex.fitting.common.utils.mace_virial_format_conversion.rst new file mode 100644 index 000000000..43f59329c --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.mace_virial_format_conversion.rst @@ -0,0 +1,6 @@ +mace\_virial\_format\_conversion +================================ + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: mace_virial_format_conversion \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.nequip_fitting.rst b/_sources/reference/autoplex.fitting.common.utils.nequip_fitting.rst new file mode 100644 index 000000000..4c916d8d3 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.nequip_fitting.rst @@ -0,0 +1,6 @@ +nequip\_fitting +=============== + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: nequip_fitting \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.plot_convex_hull.rst b/_sources/reference/autoplex.fitting.common.utils.plot_convex_hull.rst new file mode 100644 index 000000000..9dcb6f9de --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.plot_convex_hull.rst @@ -0,0 +1,6 @@ +plot\_convex\_hull +================== + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: plot_convex_hull \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.prepare_fit_environment.rst b/_sources/reference/autoplex.fitting.common.utils.prepare_fit_environment.rst new file mode 100644 index 000000000..7c3719ead --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.prepare_fit_environment.rst @@ -0,0 +1,6 @@ +prepare\_fit\_environment +========================= + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: prepare_fit_environment \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.rst b/_sources/reference/autoplex.fitting.common.utils.rst new file mode 100644 index 000000000..33ff8c2b4 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.rst @@ -0,0 +1,94 @@ +autoplex.fitting.common.utils +============================= + +.. automodule:: autoplex.fitting.common.utils + + + + + + .. rubric:: Functions + + .. autosummary:: + :toctree: + :nosignatures: + + calculate_delta + check_convergence + compute_pairs_triplets + convert_xyz_to_structure + energy_remain + extract_gap_label + flatten + gap_fitting + gap_hyperparameter_constructor + gcm3_to_Vm + get_atomic_numbers + get_list_of_vasp_calc_dirs + jace_fitting + load_mlip_hyperparameter_defaults + m3gnet_fitting + mace_fitting + mace_virial_format_conversion + nequip_fitting + plot_convex_hull + prepare_fit_environment + run_ace + run_gap + run_mace + run_nequip + run_quip + vaspoutput_2_extended_xyz + write_after_distillation_data_split + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + :toctree: + :nosignatures: + + chemical_symbols + + diff --git a/_sources/reference/autoplex.fitting.common.utils.run_ace.rst b/_sources/reference/autoplex.fitting.common.utils.run_ace.rst new file mode 100644 index 000000000..170e3bd6d --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.run_ace.rst @@ -0,0 +1,6 @@ +run\_ace +======== + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: run_ace \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.run_gap.rst b/_sources/reference/autoplex.fitting.common.utils.run_gap.rst new file mode 100644 index 000000000..75cfe6d70 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.run_gap.rst @@ -0,0 +1,6 @@ +run\_gap +======== + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: run_gap \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.run_mace.rst b/_sources/reference/autoplex.fitting.common.utils.run_mace.rst new file mode 100644 index 000000000..907c488f1 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.run_mace.rst @@ -0,0 +1,6 @@ +run\_mace +========= + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: run_mace \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.run_nequip.rst b/_sources/reference/autoplex.fitting.common.utils.run_nequip.rst new file mode 100644 index 000000000..73e530be4 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.run_nequip.rst @@ -0,0 +1,6 @@ +run\_nequip +=========== + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: run_nequip \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.run_quip.rst b/_sources/reference/autoplex.fitting.common.utils.run_quip.rst new file mode 100644 index 000000000..597113acc --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.run_quip.rst @@ -0,0 +1,6 @@ +run\_quip +========= + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: run_quip \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.vaspoutput_2_extended_xyz.rst b/_sources/reference/autoplex.fitting.common.utils.vaspoutput_2_extended_xyz.rst new file mode 100644 index 000000000..f4116f532 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.vaspoutput_2_extended_xyz.rst @@ -0,0 +1,6 @@ +vaspoutput\_2\_extended\_xyz +============================ + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: vaspoutput_2_extended_xyz \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.common.utils.write_after_distillation_data_split.rst b/_sources/reference/autoplex.fitting.common.utils.write_after_distillation_data_split.rst new file mode 100644 index 000000000..2fff95b71 --- /dev/null +++ b/_sources/reference/autoplex.fitting.common.utils.write_after_distillation_data_split.rst @@ -0,0 +1,6 @@ +write\_after\_distillation\_data\_split +======================================= + +.. currentmodule:: autoplex.fitting.common.utils + +.. autofunction:: write_after_distillation_data_split \ No newline at end of file diff --git a/_sources/reference/autoplex.fitting.phonons.rst b/_sources/reference/autoplex.fitting.phonons.rst new file mode 100644 index 000000000..b2d1b6b7d --- /dev/null +++ b/_sources/reference/autoplex.fitting.phonons.rst @@ -0,0 +1,22 @@ +autoplex.fitting.phonons +======================== + +.. automodule:: autoplex.fitting.phonons + + + + + + + + + + + + + + + + + + diff --git a/_sources/reference/autoplex.fitting.rss.rst b/_sources/reference/autoplex.fitting.rss.rst new file mode 100644 index 000000000..0c5d01842 --- /dev/null +++ b/_sources/reference/autoplex.fitting.rss.rst @@ -0,0 +1,22 @@ +autoplex.fitting.rss +==================== + +.. automodule:: autoplex.fitting.rss + + + + + + + + + + + + + + + + + + diff --git a/_sources/reference/autoplex.fitting.rst b/_sources/reference/autoplex.fitting.rst new file mode 100644 index 000000000..8b70c178d --- /dev/null +++ b/_sources/reference/autoplex.fitting.rst @@ -0,0 +1,32 @@ +autoplex.fitting +================ + +.. automodule:: autoplex.fitting + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + common + phonons + rss diff --git a/_sources/reference/index.rst b/_sources/reference/index.rst new file mode 100644 index 000000000..a4e2055e8 --- /dev/null +++ b/_sources/reference/index.rst @@ -0,0 +1,18 @@ +.. _api: + +API reference +------------- + +This section gives an overview of the API for autoplex. + +.. currentmodule:: autoplex + +.. autosummary:: + :recursive: + :toctree: + :nosignatures: + + auto + benchmark + data + fitting diff --git a/_sources/user/index.md b/_sources/user/index.md new file mode 100644 index 000000000..2f6bec61d --- /dev/null +++ b/_sources/user/index.md @@ -0,0 +1,7 @@ +Overview +================ +```{include} ../../README.md +--- +start-line: 3 +--- +``` diff --git a/_sources/user/installation/installation.md b/_sources/user/installation/installation.md new file mode 100644 index 000000000..b787f6312 --- /dev/null +++ b/_sources/user/installation/installation.md @@ -0,0 +1,173 @@ +(installation)= + +# Installation guide + +## Before you start using `autoplex` + +We expect the general user of `autoplex` to be familiar with the [Materials Project](https://github.com/materialsproject) framework software tools and related +packages for (high-throughput) workflow submission and management. +This involves the following software packages: +- [pymatgen](https://github.com/materialsproject/pymatgen) for input and output handling of computational materials science software, +- [atomate2](https://github.com/materialsproject/atomate2) for providing a library of pre-defined computational materials science workflows, +- [jobflow](https://github.com/materialsproject/jobflow) for processes, job and workflow handling, +- [jobflow-remote](https://github.com/Matgenix/jobflow-remote) or [FireWorks](https://github.com/materialsproject/fireworks) for workflow and database (MongoDB) management, +- [MongoDB](https://www.mongodb.com/) as the database (we recommend installing the MongoDB community edition). + + +We are also referring the user to the [installation guide of atomate2](https://materialsproject.github.io/atomate2/user/install.html) in order to setup the mandatory prerequisites to +be able to use `autoplex`. + +After setting up `atomate2`, make sure to add `VASP_INCAR_UPDATES: {"NPAR": number}` in your ~/atomate2/config/atomate2.yaml file. +Set a number that is a divisor of the number of tasks you use for the VASP calculations. + + +## Installation Documentation and Guides of the Dependencies + +The first step you need to do is to set up a [MongoDB](https://www.mongodb.com/) database. Help and tips regarding the MongoDB installation +can be found [here](https://materialsproject.github.io/fireworks/installation.html#install-mongodb). +We recommend installing the [MongoDB community edition](https://www.mongodb.com/docs/manual/administration/install-community/). +MongoDB also provides lots of [installation guides](https://www.mongodb.com/docs/manual/administration/install-on-linux/#std-label-install-mdb-community-edition-linux) +and [tutorials](https://www.mongodb.com/docs/manual/administration/self-managed-configuration-and-maintenance/) +to setup and manage your database. For a Kick-start with MongoDB, we also provide a [MongoDB tutorial](../mongodb.md). +Also consider asking your IT administration for help. + +The next step you need do is to install a workflow manager. There are currently two options: [jobflow-remote](https://github.com/Matgenix/jobflow-remote) or [FireWorks](https://github.com/materialsproject/fireworks). +There are also documentation and tutorials available for [FireWorks](https://materialsproject.github.io/fireworks/) and [jobflow-remote](https://matgenix.github.io/jobflow-remote/). +We recommend using `jobflow-remote` and provide a more comprehensive `jobflow-remote` tutorial [here](../jobflowremote.md). + +Please take your time and check out all the documentation and tutorials! + +When you have completed all these preparation steps, it's time to install `autoplex`! + +You can install `autoplex` simply by: + +``` +pip install autoplex[strict] +``` +This will install all the Python packages and dependencies needed for MLIP fits. + +Additionally, to fit and validate `ACEpotentials`, one also needs to install Julia, as `autoplex` relies on [ACEpotentials](https://acesuit.github.io/ACEpotentials.jl/dev/gettingstarted/installation/), which supports fitting of linear ACE. Currently, no Python package exists for the same. +Please run the following commands to enable the `ACEpotentials` fitting options and further functionality. + +Install Julia v1.9.2 + +```bash +curl -fsSL https://install.julialang.org | sh -s -- default-channel 1.9.2 +``` + +Once installed in the terminal, run the following commands to get Julia ACEpotentials dependencies. + +```bash +julia -e 'using Pkg; Pkg.Registry.add("General"); Pkg.Registry.add(Pkg.Registry.RegistrySpec(url="https://github.com/ACEsuit/ACEregistry")); Pkg.add(Pkg.PackageSpec(;name="ACEpotentials", version="0.6.7")); Pkg.add("DataFrames"); Pkg.add("CSV")' +``` + +### Enabling RSS workflows + +Additionally, `buildcell` as a part of `AIRSS` needs to be installed if one wants to use the RSS functionality: + +```bash +curl -O https://www.mtg.msm.cam.ac.uk/files/airss-0.9.3.tgz; tar -xf airss-0.9.3.tgz; rm airss-0.9.3.tgz; cd airss; make ; make install ; make neat; cd .. +``` + +### LAMMPS installation + +You only need to install LAMMPS, if you want to use J-ACE as your MLIP. +Recipe for compiling lammps-ace including the download of the `libpace.tar.gz` file: + +``` +git clone -b release https://github.com/lammps/lammps +cd lammps +mkdir build +cd build +wget -O libpace.tar.gz https://github.com/wcwitt/lammps-user-pace/archive/main.tar.gz + +cmake -C ../cmake/presets/clang.cmake -D BUILD_SHARED_LIBS=on -D BUILD_MPI=yes \ +-DMLIAP_ENABLE_PYTHON=yes -D PKG_PYTHON=on -D PKG_KOKKOS=yes -D Kokkos_ARCH_ZEN3=yes \ +-D PKG_PHONON=yes -D PKG_MOLECULE=yes -D PKG_MANYBODY=yes \ +-D Kokkos_ENABLE_OPENMP=yes -D BUILD_OMP=yes -D LAMMPS_EXCEPTIONS=yes \ +-D PKG_ML-PACE=yes -D PACELIB_MD5=$(md5sum libpace.tar.gz | awk '{print $1}') \ +-D CMAKE_INSTALL_PREFIX=$LAMMPS_INSTALL -D CMAKE_EXE_LINKER_FLAGS:STRING="-lgfortran" \ +../cmake + +make -j 16 +make install-python +``` + +$LAMMPS_INSTALL is the conda environment for installing the lammps-python interface. +Use `BUILD_MPI=yes` to enable MPI for parallelization. + +After the installation is completed, enter the following commands in the Python environment. +If you get the same output, it means the installation was successful. + +``` +from lammps import lammps; lmp = lammps() +LAMMPS (27 Jun 2024) +OMP_NUM_THREADS environment is not set. Defaulting to 1 thread. (src/comm.cpp:98) + using 1 OpenMP thread(s) per MPI task +Total wall time: 0:02:22 +``` +It is very important to have it compiled with Python (`-D PKG_PYTHON=on`) and +LIB PACE flags (`-D PACELIB_MD5=$(md5sum libpace.tar.gz | awk '{print $1}')`). + +As `autoplex` heavily relies on `atomate2`, it is strongly recommended to also make yourself familiar with the [atomate2 documentation](https://materialsproject.github.io/atomate2/). + + +For a more advanced installation, you can also follow the [developer installation guide](../../dev/dev_install.md). + +## Workflow management + +You can manage your `autoplex` workflow using [`FireWorks`](https://materialsproject.github.io/fireworks/) or [`jobflow-remote`](https://matgenix.github.io/jobflow-remote/). +Please follow the installation and setup instructions on the respective guide website. +Both packages rely on the [MongoDB](https://www.mongodb.com/) database manager for data storage. + +We recommend using `jobflow-remote` as it is more flexible to use, especially on clusters where users cannot store their +own MongoDB. You can find a more comprehensive `jobflow-remote` tutorial [here](../jobflowremote.md). + +Submission using `FireWorks`: +```python +from fireworks import LaunchPad +from jobflow.managers.fireworks import flow_to_workflow + +... + +autoplex_flow = ... + +wf = flow_to_workflow(autoplex_flow) + +# submit the workflow to the FireWorks launchpad +lpad = LaunchPad.auto_load() +lpad.add_wf(wf) +``` + +Submission using `jobflow-remote`: +```python +from jobflow_remote import submit_flow, set_run_config + +... + +autoplex_flow = ... + +# setting different job setups in the submission script directly: +resources = {"nodes": N, "partition": "name", "time": "01:00:00", "ntasks": ntasks, "qverbatim": "#SBATCH --get-user-env", + "mail_user": "your_email@adress", "mail_type": "ALL"} + # put your slurm submission keywords as needed + # you can add "qverbatim": "#SBATCH --get-user-env" in case your conda env is not activated automatically + +resources_phon = {"nodes": N, "partition": "name", "time": "05:00:00", "ntasks": ntasks, "qverbatim": "#SBATCH --get-user-env", + "mail_user": "your_email@adress", "mail_type": "ALL"} + +resources_ratt = {"nodes": N, "partition": "micro", "time": "03:00:00", "ntasks": ntasks, "qverbatim": "#SBATCH --get-user-env", + "mail_user": "your_email@adress", "mail_type": "ALL"} + +resources_mlip = {"nodes": N, "partition": "name", "time": "02:00:00", "ntasks": ntasks, "qverbatim": "#SBATCH --get-user-env", + "mail_user": "your_email@adress", "mail_type": "ALL"} + +autoplex_flow = set_run_config(autoplex_flow, name_filter="dft phonon static", resources=resources_phon) + +autoplex_flow = set_run_config(autoplex_flow, name_filter="dft rattle static", resources=resources_ratt) + +autoplex_flow = set_run_config(autoplex_flow, name_filter="machine_learning_fit", resources=resources_mlip) + +# submit the workflow to jobflow-remote +print(submit_flow(autoplex_flow, worker="autoplex_worker", resources=resources, project="autoplex")) +``` diff --git a/_sources/user/jobflowremote.md b/_sources/user/jobflowremote.md new file mode 100644 index 000000000..e71362147 --- /dev/null +++ b/_sources/user/jobflowremote.md @@ -0,0 +1,242 @@ +--- +orphan: true +--- + +# Jobflow-remote setup + +This will result in a setup for automation where +1. We will add/submit job to DB on your local machine. +2. Jobs will be executed on your remote custer. + +# Installation + +## on your local machine +1. Create a new env > `conda create -n autoplex python=3.10`. (You can choose any other env name.) +2. Activate your env > `conda activate autoplex` +3. Install jobflow-remote by `pip install git+https://github.com/Matgenix/jobflow-remote@v0.1.4`. +4. You can check for the latest release of [jobflow-remote](https://github.com/Matgenix/jobflow-remote/releases). +5. Install autoplex > In your local autoplex directory: `pip install -e .[strict]`. +6. Run `jf project generate --full YOUR_PROJECT_NAME`. +Choose a sensible project name. +This will generate an empty project config file in your home directory. +You can find this file inside `~/.jfremote` +(This is optional, a config file is provided here: [test_project.yaml](test_project.yaml), +you can simply copy this file to the `~/.jfremote` directory. +You will need to create `~/.jfremote` directory in your home if it's not created automatically. +Creat the log, tmp and daemon subfolders if they are not created automatically.) + + +## on your remote cluster +7. Repeat step 1,2,3,4 and 5 on your remote cluster. +8. Now setup atomate2 config as usual. +Just `atomate2/config/atomate2.yaml`. (We do not need to set up jobflow.yaml in atomate2/config) + +Below is an example `atomate2.yaml` config file +```yaml +VASP_CMD: your hpc vasp_std cmd +VASP_GAMMA_CMD: your hpc vasp_gam cmd +LOBSTER_CMD: your hpc lobster cmd +``` + +9. Add environment variable to your ~/.bashrc `export ATOMATE2_CONFIG_FILE="/path/to/atomate2/config/atomate2.yaml"` + +## Changes to be done in the config file - on your local machine +1. Set paths to base, tmp, log, daemon dir. Best would be, simply creating empty dirs in your `~/.jfremote` directory. +Use the paths as provided in sample config file for reference. +2. Under the `workers` section of the yaml, change worker name from `example_worker` to your liking, set `work_dir` +(directory where calcs will be run), set `pre_run` command (use to activate the environment before job execution), +set `user` (this your username on your remote cluster) +3. In `queue` section, just change details as per your MongoDB (admin username password, host, port, name) + + +# Check if your setup works correctly + +> Note: If you have any password protected key in your `~/.ssh` directory worker might fail to start. To overcome this, temporarily move your passphrase procted keys from `~/.ssh` directory to some other directory before starting the runner. + +1. `jf project check -w example_worker` +(If everything is setup correctly, you will get asked for password and OTP (one-time password) for MFA (multifactor +authentication) login and will exit with a green tick in few secs.) +2. `jf project check --errors` this will check all even your MongoDB connection is proper or not. +If anything fails, please check the config file. + + +# Getting started + +1. Run `jf admin reset` (Do not worry, this will reset your DB, necessary to do only once. +You can skip this if you want to keep the data in your DB.) +2. `jf runner start -s -i` + +You can type `jf runner start -h` for help and more information: +```bash +(conda_env) user@local_host:~$ jf runner start -h +The selected project is test_pproject from config file /home/user/.jfremote/test_project.yaml + + Usage: jf runner start [OPTIONS] + + Start the Runner as a daemon + +╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ --transfer -t INTEGER The number of processes dedicated to completing jobs [default: 1] │ +│ --complete -com INTEGER The number of processes dedicated to completing jobs [default: 1] │ +│ --single -s <--- Use a single process for the runner │ +│ --log-level -log [error|warn|info|debug] Set the log level of the runner [default: info] │ +│ --connect-interactive -i <--- Wait for the daemon to start and manually log in the connection for interactive remote host. Requires --single. │ +│ --help -h Show this message and exit. │ +╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +``` +We need to use the options `-s -i` for the interactive mode to use the MFA login with OTP. +The two options are highlighted by `<---` in the above example. + +You will be prompted with a question "Do you want to open the connection for the host of the XXX worker?" +Answer "y". And then you should be prompted for password and OTP. +After that you can quit the interactive mode with ctrl+c. The runner should now be working fine until the connection drops. + +During the starting of the runner, you will probably see a few error/warnings. +First, a warning that the password may be echoed. Ignore it, it should not. + +3. `jf runner status` (this should return status of runner as `running`, if everything is set up correctly) + + +# Example job scripts to test (Add/Submit jobs to DB from your local machine) + +## Simple Python job + +```python +from jobflow_remote.utils.examples import add +from jobflow_remote import submit_flow +from jobflow import Flow + +job1 = add(1, 2) +job2 = add(job1.output, 2) + +flow = Flow([job1, job2]) + +resources = {"nodes": N, "partition": "name", "time": "01:00:00", "ntasks": ntasks, "qverbatim": "#SBATCH --get-user-env", + "mail_user": "your_email@adress", "mail_type": "ALL"} + +print(submit_flow(flow, worker="example_worker", resources=resources, project="test_project")) +# Do not forget to change worker and project name to what you se tup in the jobflow remote config file. +``` +> NOTE: We are using [Simple Linux Utility for Resource Management (SLURM)](https://matgenix.github.io/qtoolkit/api/qtoolkit.io.slurm.html) specific keywords in our examples. +> For [Portable Batch System (PBS)](https://matgenix.github.io/qtoolkit/api/qtoolkit.io.pbs.html) specific commands, see [here](https://matgenix.github.io/qtoolkit/api/qtoolkit.io.pbs.html). + +## VASP relax job using atomate2 workflow + +```python +from jobflow_remote.utils.examples import add +from jobflow_remote import submit_flow +from jobflow import Flow +from mp_api.client import MPRester +from atomate2.vasp.flows.core import DoubleRelaxMaker +from atomate2.vasp.powerups import update_user_incar_settings + + +mpid = "mp-22862" +mr = MPRester(api_key='YOUR_MP_API_KEY') +struct = mr.get_structure_by_material_id(mpid) + +# we use the same structure (mp-22862) here and instantiate the workflow +relax_job = DoubleRelaxMaker().make(structure=struct) + +relax_job = update_user_incar_settings(relax_job, {"NPAR": 4}) + +# You can also pass exe_config for the worker using exe_config in submit flow. Below is an example +# exec_config={"pre_run": "source activate autoplex \n module load slurm_setup \n module load vasp/6.1.2"} + +resources = {"nodes": N, "partition": "name", "time": "01:00:00", "ntasks": ntasks, "qverbatim": "#SBATCH --get-user-env", + "mail_user": "your_email@adress", "mail_type": "ALL"} + +print(submit_flow(relax_job, worker="example_worker", resources=resources, project="test_project")) +``` +It is crucial to set `"qverbatim": "#SBATCH --get-user-env"` to make sure the same environment is used on your remote cluster. + +# Setting different workers for different job types + +This is very much similar to how we do in atomate2, jobflow-remote provides a specific utility for this. +```python +from jobflow_remote import set_run_config +``` +An example use case can be found [here](https://matgenix.github.io/jobflow-remote/user/tuning.html#jobconfig) + +# Querying completed jobs from DB using jobflow-remote Python API + +```python +from jobflow_remote import get_jobstore + +js = get_jobstore(project_name='YOUR_PROJECT_NAME') +js.connect() +result = js.query(criteria={"name": "generate_frequencies_eigenvectors"},load=True) +# example query for completed phonon workflow runs +# the query methods are the same as in atomate2 basically, +for i in result: + print(i['output']["phonon_bandstructure"]) + # get phonon banstructure pymatgen object +``` + +# Updating failed jobs time limit or execution config +```python +from jobflow_remote.jobs.jobcontroller import JobController + +jc = JobController.from_project_name(project_name='YOUR_PROJECT_NAME') # initialize a job controller + +job_docs = jc.get_jobs_doc(db_ids='214') # query job docs based on different criteria +# (Check documentation to see all available options https://github.com/Matgenix/jobflow-remote/blob/967e7c512f230105b1a82c2227fb101d8d4acb3d/src/jobflow_remote/jobs/jobcontroller.py#L467) + +# get your existing resources +resources = job_docs[0].resources + +# update time limit in the retrieved dict (you can update any other keys like partition/ nodes etc as well) +resources["time"] = '8:00:00' + +jc.rerun_job(db_id=job_docs[0].db_id, force=True) # important for jobs that are in failed state to reset them first +jc.set_job_run_properties(db_ids=[job_docs[0].db_id], resources=resources) # this will update the DB entry +``` + +> IMPORTANT: When you restart VASP calculations, make sure to move the old VASP files somewhere else, +> because jobflow-remote will restart your calculation in the same directory and that leads to some clash of old and new files. + +# Update pre-exsiting job input parameters in the DB + +```python +# Note that this way is bit involved and you need to find exact structure of your nested DB entry based on type of maker used + +# Following is an example for failed vasp job where NPAR and ALGO tags in DB entry are updated +from jobflow_remote.jobs.jobcontroller import JobController + +jc = JobController.from_project_name(project_name='YOUR_PROJECT_NAME') + +job_collection = jc.db.jobs # get jobs collection from mongoDB + +for i in job_collection.find({'db_id': '214'}): + job_dict = i['_id'] # get object id in mongodb (this is used to as filter) + incar_settings = i['job']['function']['@bound']['input_set_generator']['user_incar_settings'] # get existing user incar settings + +incar_settings.update({'NPAR': 2, 'ALGO': 'FAST'}) # now update incar settings here as per requirement +job_collection.update_one({'_id': job_dict}, {'$set': {'job.function.@bound.input_set_generator.user_incar_settings' : incar_settings}}) + +print(jc.get_jobs_doc(db_ids='214')[0].job.maker.input_set_generator.user_incar_settings) # check if entries are updated +``` +> IMPORTANT: When you restart VASP calculations, make sure to move the old VASP files somewhere else, +> because jobflow-remote will restart your calculation in the same directory and that leads to some clash of old and new files. + +# Some useful commands + +1. `jf job list` (list jobs in the DB) +2. `jf flow list` (list of flows in the DB) +3. `jf job info jobid` (provides some info of job like workdir, error info if it failed) +4. `jf flow delete -did db_id` (deletes flow from DB) +5. `jf job list -s STATE`, e.g. `jf job list -s FAILED` +6. `jf job rerun -s STATE`, e.g. `jf job rerun -s FAILED` +7. `jf job retry -s STATE`, e.g. `jf job retry -s REMOTE_ERROR` +8. `jf flow info flow_id` (shows the jobs of a certain flow) +9. `jf flow -h` or `jf job -h` for checking other options + +In case your remote cluster connection is broken or lost, it is crucial to restart the +jobflow-remote runner [(first stop/kill, then start)](#getting-started). Job states that are `REMOTE_ERROR`, `FAILED` or +inconsistent because of this, can be fixed be force-rerunning the respective jobs by `jf job rerun -f -s STATE`. + +# Some useful links + +1. Check slurm.py for finding different available options you can set for resources dict [here](https://github.com/Matgenix/qtoolkit/tree/develop/src/qtoolkit/io) +2. More details on project config and settings can be found [here](https://matgenix.github.io/jobflow-remote/user/projectconf.html) +3. Details on different setup options [here](https://matgenix.github.io/jobflow-remote/user/install.html) diff --git a/_sources/user/mongodb.md b/_sources/user/mongodb.md new file mode 100644 index 000000000..e13223fa9 --- /dev/null +++ b/_sources/user/mongodb.md @@ -0,0 +1,270 @@ +--- +orphan: true +--- + +# MongoDB setup tutorial + +`autoplex` and `atomate2` use the MongoDB database framework to store the data output from the calculations. +The data is stored in a JSON-like format which makes it easier to access through Python for further post-processing. + +## MongoDB setup + +MongoDB is best run on the back-end. For this, please ask your IT department to install [MongoDB](https://www.mongodb.com/) for you! +There might be cases, where this is not feasible and then you need to run a mongodb on the FRONT-end yourself. +This guide will walk you through the steps that you to do for running a mongodb in front-end. + + * Get mongodb from here: https://www.mongodb.com/try/download/community + * You can e.g. download "Platform: SUSE 15" and "Package: tgz" and put it on your remote cluster. Check the platform-dependency of your cluster!!! + * Extract the files from the downloaded archive and add the executables within to your PATH environment variable i.e open the `~/.bashrc` file and add following line to it. + ```bash + export PATH="/path/to/the/mongodb-directory/bin:$PATH" + ``` + * Now, you can start the mongodb database with the following command: + ```bash + mongod --bind_ip_all --dbpath /this_is_the_path_to_your_db --quiet --port 27017 + ``` + * In case you run into errors to start the mongodb on login node, it is mainly due to `mongodb-27017.sock` file permissions. This file is created by mongodb inside /tmp directory. Use the following additional tag in such scenarios (--unixSocketPrefix allows you the change the directory of this file created) + ```bash + mongod --bind_ip_all --dbpath /this_is_the_path_to_your_db --quiet --port 27017 --unixSocketPrefix /new/directory/for/temp/file + ``` + * You can shut it down with: + ```bash + mongod --shutdown --bind_ip_all --dbpath /this_is_the_path_to_your_db + ``` + * Alternatively, you can define a `mongod.conf` file and use that file to start your mongodb database instance. This file looks somewhat like this. You can read more about the parameters within this file here : https://docs.mongodb.com/manual/administration/configuration/#std-label-base-config + ```yaml + processManagement: + fork: false + pidFilePath: /dss/dsshome1/00/username/path/to/store/your/pidfile/mongod.pid + net: + bindIp: 0.0.0.0 + port: 27017 + storage: + dbPath: /dss/dsshome1/00/username/path/to/store/your/db/mongo + systemLog: + destination: file + path: /dss/dsshome1/00/username/path/to/mongod.log + logAppend: true + storage: + journal: + enabled: true + + ``` + + * You can start the mongodb instance using the `mongod.conf` as follows: + + ```bash + mongod -f path/to/mongod.conf --quiet --unixSocketPrefix path/to/store/.sock/file + ``` + * *Note* down the remote cluster login node where you started your mongodb instance. + * Open another terminal, login to your remote cluster again and ssh to same login node on which your mongodb instance is running. + +Note that the next steps of creating databases also have to be done by your IT admin for running MongoDB in backend: + + * For this, open the mongo shell there + ```bash + mongosh + ``` +> ℹ️ Note that the [`mongo` shell](https://www.mongodb.com/docs/manual/reference/mongo/) has been deprecated in MongoDB v5.0. The replacement is `mongosh`! + + * You need to add a db called "database_name" (you can choose any name) and also add an admin and a readonly user to this database + * switch to ```admin``` database and create a user + ```bash + use admin + db.createUser( + { + user: "myUserAdmin", + pwd: "password", + roles: [ + { role: "userAdminAnyDatabase", db: "admin" }, + { role: "readWriteAnyDatabase", db: "admin" } + ] + } + ) + ``` + + * Add a database for our automation + ```bash + use database_name + ``` + + * Test if you are really in this database: + ```bash + db + ``` + * Create a read user for this database: + ```bash + db.createUser( + { + user: "read", + pwd: "password", // or cleartext password + roles: [ { role: "read", db: "database_name" } ] + } + ) + ``` + * Add again the admin user to this database as well, use the following command + ```bash + db.createUser( + { + user: "myUserAdmin", + pwd: "password", + roles: [ + { role: "userAdminAnyDatabase", db: "admin" }, + { role: "readWriteAnyDatabase", db: "admin" } + ] + } + ) + ``` + * Close the mongo shell + + ## Alternative MongoDB installation through Homebrew + +To install MongoDB through Homebrew, run the following commands in the terminal: +```bash +brew tap mongodb/brew +brew update +brew install mongodb-community +``` +Then you can start the server with: +```bash +brew services start mongodb-community +``` +Check if the server is running with `brew services list` and it should display something like +`mongodb-community started uthpala ~/Library/LaunchAgents/homebrew.mxcl.mongodb-community`. + + + ## atomate2 configuration + +* Create a directory scaffold for atomate2 use `mkdir -p atomate2/{config,logs}` +* Create a new conda environment called atomate2 with Python 3.11 using `conda create -n atomate2 python==3.11` +* Activate your environment : `conda activate atomate2` +* Install atomate2 using : `pip install atomate2` +* Create `atomate2.yaml` file with following content, use `vi ~/atomate2/config/atomate2.yaml` + ```yaml + VASP_CMD: mpirun -np ${SLURM_NTASKS} /path/to/vasp_std > vasp.out + VASP_GAMMA_CMD: mpirun -np ${SLURM_NTASKS} /path/to/vasp_gam > vasp.out + LOBSTER_CMD: /path/to/your/lobster/binary + ``` + +* Create `jobflow.yaml` file with following content, use `vi ~/atomate2/config/jobflow.yaml` + ```yaml + JOB_STORE: + docs_store: + type: MongoStore + database: autoplex + host: local host name + port: 27017 + username: xxx_admin + password: xxx + collection_name: outputs + additional_stores: + data: + type: GridFSStore + database: autoplex + host: local host name + port: 27017 + username: xxx_admin + password: xxxx + collection_name: outputs_blobs + + ``` +* You have to export the location of your files in your ~/.bashrc or ~/.bash_profile + ```bash + export ATOMATE2_CONFIG_FILE="/home/username/atomate2/config/atomate2.yaml" + export JOBFLOW_CONFIG_FILE="/home/username/atomate2/config/jobflow.yaml" + ``` + + +## FireWorks configuration + +We will give you a short introduction how to setup [FireWorks](https://github.com/materialsproject/fireworks) to be used with the MongoDB. For [jobflow-remote](https://github.com/Matgenix/jobflow-remote), please have a look [here](jobflowremote.md). + +FireWorks can be installed via `pip install fireworks`. + +Then follow the next steps: + + * You need to add "my_launchpad.yaml" to your current folder to add information for testing. You need to find out on which login-server you are. ```echo $(hostname)``` and then adapt the following script. + ```yaml + host: login_node_name + port: 27017 + name: database_name + username: admin + password: adminpassword + ssl_ca_file: null + logdir: null + strm_lvl: INFO + user_indices: [] + wf_user_indices: [] + ``` + + * Once this is done, you can try to use "lpad reset" + * Then, you also need a ```my_fworker.yaml``` simlar to this: + ```yaml + name: worker + category: '' + query: '{}' + env: + db_file: /path/to/config/db.json + vasp_cmd: srun -N 3 --ntasks=144 /path/to/vasp_std + lobster_cmd: /path/to/lobster-5.1.0 + scratch_dir: null + auto_npar: True + + ``` + + * And, a ```db.json``` file similar to this: + ```json + { + "host": "login_node_name", + "port": 27017, + "database": "database_name", + "collection": "vasp", + "admin_user": "adminname", + "admin_password": "adminpassword", + "readonly_user": "read", + "readonly_password": "readpassword", + "aliases": {} + } + + ``` + + * Then, you can use the following job script or something similar to run VASP jobs: + ```bash + #!/bin/bash + #SBATCH -J vaspjob + #Output and error + #SBATCH -o ./%x.%j.out + #SBATCH -e ./%x.%j.err + #Initial working directory + #SBATCH -D ./ + #Notification and type + #SBATCH --mail-type=END + #SBATCH --mail-user=your.email@email + # Wall clock limit: + #SBATCH --time=00:30:00 + #SBATCH --no-requeue + #Setup of execution environment + #SBATCH --export=NONE + #SBATCH --get-user-env + #SBATCH --nodes=3 + #SBATCH --ntasks=144 + #SBATCH --account=account_name + #SBATCH --partition=partition_name + #SBATCH --ear=off + + source /where_is_your_python_environment/bin/activate + + module load slurm_setup + module load vasp/6.1.2 + + cd /where_do_you_want_to_start_your_calcs/ + + + rlaunch -c /where_do_all_yamls_lie_folder multi 1 + + ``` + Please adjust all the scripts according to your needs, setup and cluster requirement! + +## Visualize MongoDB-Database + +To visualize your database, you can use portforwarding, e.g., ssh remote_cluster -L 8888:localhost:5000. Then open a browser on your local machine to view "http://127.0.0.1:8888/". diff --git a/_sources/user/phonon/flows/benchmark/benchmark.md b/_sources/user/phonon/flows/benchmark/benchmark.md new file mode 100644 index 000000000..2c8cde013 --- /dev/null +++ b/_sources/user/phonon/flows/benchmark/benchmark.md @@ -0,0 +1,154 @@ +(benchmark)= + +# Benchmark of ML-based phonon structure + +This tutorial will help you understand all the `autoplex` benchmark specifications. + +## General settings + +For the benchmark, you do not have to worry about a lot of settings. The crucial part here is the number of benchmark structures you are interested in. +All benchmark harmonic phonon runs will always be generated with a displacement of 0.01 even though the fitting procedure can also include different displacements. + +```python +from mp_api.client import MPRester +from autoplex.auto.phonons.flows import CompleteDFTvsMLBenchmarkWorkflow +from atomate2.common.schemas.phonons import PhononBSDOSDoc +from monty.serialization import loadfn + +mpr = MPRester(api_key='YOUR_MP_API_KEY') +structure_list = [] +benchmark_structure_list = [] +mpids = ["mp-22905"] +mpbenchmark = ["mp-22905"] +dft_data = loadfn("/path/to/DFT/ref/data/PhononBSDOSDoc_mp_22905.json") +dft_reference: PhononBSDOSDoc = dft_data["output"] +for mpid in mpids: + structure = mpr.get_structure_by_material_id(mpid) + structure_list.append(structure) +for mpbm in mpbenchmark: + bm_structure = mpr.get_structure_by_material_id(mpbm) + benchmark_structure_list.append(bm_structure) + +complete_flow = CompleteDFTvsMLBenchmarkWorkflow( + apply_data_preprocessing=True, +).make( + structure_list=structure_list, mp_ids=mpids, + benchmark_structures=benchmark_structure_list, benchmark_mp_ids=mpbenchmark, + dft_references=[dft_reference]) +``` +In case you have pre-existing DFT calculations, you can pass them as a list via the `dft_references` parameters. Make sure, it is the same order as in the benchmark MP-IDs and structures. +It is important to provide the pre-existing DFT data in form of a `PhononBSDOSDoc` task document object (from `atomate2`). Without any DFT reference calculations given, `autoplex` will automatically execute the VASP calculations. A mix of pre-existing and missing DFT references is not supported. + +## Error metrics +`autoplex` automatically provides you with a phonon bandstructure comparison plot, a q-point wise RMSE plot and an overall RMSE value (`results_XY.txt` file). For examples see [here](../flows.md#output-and-results). + +## Run a benchmark with a pre-existing DFT calculation and GAP potential + +If you want to run or repeat the benchmark with your pre-existing DFT calculation and your pre-existing GAP potential, you can use the following Python script as a template. +It is important to provide the pre-existing DFT data in form of a `PhononBSDOSDoc` task document object (from `atomate2`). + +```python +#!/usr/bin/env python + +import os +from atomate2.common.schemas.phonons import PhononBSDOSDoc +from atomate2.forcefields.jobs import ForceFieldRelaxMaker, ForceFieldStaticMaker +from mp_api.client import MPRester +from autoplex.benchmark.phonons.flows import PhononBenchmarkMaker +from atomate2.forcefields.flows.phonons import PhononMaker +from autoplex.benchmark.phonons.jobs import write_benchmark_metrics +from monty.serialization import loadfn +from jobflow import SETTINGS +from jobflow import run_locally + +os.environ["OMP_NUM_THREADS"] = "48" +os.environ["OPENBLAS_NUM_THREADS"] = "1" + +store = SETTINGS.JOB_STORE +# connect to the job store +store.connect() + +mpr = MPRester(api_key = 'YOUR_API_KEY') +mpid = "mp-22905" +structure = mpr.get_structure_by_material_id(mpid) +dft_data = loadfn("/path/to/DFT/ref/data/PhononBSDOSDoc_mp_22905.json") +dft_reference: PhononBSDOSDoc = dft_data["output"] +potential_filename = "/path/to/GAP/file/gap_file.xml" + +phojob = PhononMaker( + bulk_relax_maker=ForceFieldRelaxMaker(calculator_kwargs={"args_str": "IP GAP", "param_filename": potential_filename}, + relax_cell=True, relax_kwargs={"interval": 500, "fmax": 0.00001}, steps=10000, + force_field_name="GAP", +), + phonon_displacement_maker=ForceFieldStaticMaker(calculator_kwargs={"args_str": "IP GAP", "param_filename": potential_filename}, + force_field_name="GAP", +), + static_energy_maker=ForceFieldStaticMaker(calculator_kwargs={"args_str": "IP GAP", "param_filename": potential_filename}), + store_force_constants=False, min_length=18, + generate_frequencies_eigenvectors_kwargs={"units": "THz"}, force_field_name="GAP", +).make(structure=structure) + +bm = PhononBenchmarkMaker(name="Benchmark").make( + structure=structure, benchmark_mp_id = "mp-22905", + displacement=0.01, atomwise_regularization_parameter=0.1, + soap_dict={'n_sparse': 6000, 'delta': 0.5}, suffix="", # exemplary values + ml_phonon_task_doc = phojob.output, dft_phonon_task_doc = dft_reference) + +comp_bm = write_benchmark_metrics( + benchmark_structures=[structure], + metrics=[[bm.output]], + ) + +run_locally([phojob, bm, comp_bm], create_folders=True, store=store) +``` +If you use another [`ForceFieldRelaxMaker` and `ForceFieldStaticMaker`](https://github.com/materialsproject/atomate2/blob/main/src/atomate2/forcefields/jobs.py), you can switch from GAP to one of the other +[MLIP potentials](../fitting/fitting.md#fitting-phonon-accurate-potentials). + +You can extract a JSON file containing your pre-existing VASP DFT run from your MongoDB with the following script: +```python +from jobflow import SETTINGS +from monty.json import jsanitize +from monty.serialization import dumpfn + +store = SETTINGS.JOB_STORE +store.connect() + +result = store.query( {'name': 'generate_frequencies_eigenvectors'}, load=True) +phononbsdosdoc = store.query({'uuid': 'put the MongoDB UUID here'}, load=True) +for i in phononbsdosdoc: + del i["_id"] + monty_encoded_json_doc = jsanitize(i, allow_bson=True, strict=True, enum_values=True) + + dumpfn(monty_encoded_json_doc, 'PhononBSDOSDoc_mp_22905.json') +``` + +And check if it contains the correct output with: +```python +from monty.serialization import loadfn + +data = loadfn('PhononBSDOSDoc_mp_22905.json') +data['output'].structure +``` + +Your output for `structure` should look like: +```bash +Structure Summary +Lattice + abc : 5.061019144638489 5.061019144638489 5.061019144638489 + angles : 90.0 90.0 90.0 + volume : 129.63251308285152 + A : 5.061019144638489 -0.0 3e-16 + B : 8e-16 5.061019144638489 3e-16 + C : 0.0 -0.0 5.061019144638489 + pbc : True True True +PeriodicSite: Li (0.0, 0.0, 0.0) [0.0, 0.0, 0.0] +PeriodicSite: Li (4e-16, 2.531, 2.531) [0.0, 0.5, 0.5] +PeriodicSite: Li (2.531, 0.0, 2.531) [0.5, 0.0, 0.5] +PeriodicSite: Li (2.531, 2.531, 3e-16) [0.5, 0.5, 0.0] +PeriodicSite: Cl (2.531, 0.0, 1.5e-16) [0.5, 0.0, 0.0] +PeriodicSite: Cl (2.531, 2.531, 2.531) [0.5, 0.5, 0.5] +PeriodicSite: Cl (0.0, 0.0, 2.531) [0.0, 0.0, 0.5] +PeriodicSite: Cl (4e-16, 2.531, 1.5e-16) [0.0, 0.5, 0.0] +``` + + diff --git a/_sources/user/phonon/flows/fitting/fitting.md b/_sources/user/phonon/flows/fitting/fitting.md new file mode 100644 index 000000000..130990124 --- /dev/null +++ b/_sources/user/phonon/flows/fitting/fitting.md @@ -0,0 +1,397 @@ +(fitting)= + +# Fitting phonon-accurate potentials + +This tutorial will show you how to control the MLIP fit settings with the `autoplex` workflow. +The choice of the correct fit setup and hyperparameter settings has a significant influence on the final result. +Please note that the fitting might need nodes with very large memory requirements (1 TB) in some cases. + +## General settings + +There are two categories of fit settings that you can change. The first type concerns the general fit setup, +that will affect the fit regardless of the chosen MLIP method, and e.g. changes database specific settings +(like the split-up into training and test data). The other type of settings influences the MLIP specific setup +like e.g. the choice of hyperparameters. + +In case of the general settings, you can pass the MLIP model you want to use with the `ml_models` parameter list. +You can set the maximum force threshold `force_max` for filtering the data ("distillation") in the MLIP fit preprocess step. +In principle, the distillation step can be turned off by passing `distillation=False`, +but it is strongly advised to filter out too high force data points. +The hyperparameters and further parameters can be passed in the `make` call (see below) using `fit_kwargs_list`. +```python +complete_flow = CompleteDFTvsMLBenchmarkWorkflow( + ml_models=["GAP", "MACE"], ..., + apply_data_preprocessing=True, + f_max=40.0, split_ratio=0.4, + num_processes_fit=48, + benchmark_kwargs={"relax_maker_kwargs": {"relax_cell": False, "relax_kwargs": ...}, "calculator_kwargs": {"device": "cpu"}} +).make(..., + fit_kwargs_list=[ + {"general": {"two_body": True, "three_body": False, "soap": False}}, # GAP parameters + {"model": "MACE", "device": "cuda"} # MACE parameters + # fit_kwargs_list has to have the same order as in ml_models + ], + ... # put the other hyperparameter commands here as shown below +) +``` + +The MLIP model specific settings and hyperparameters setup varies from model to model and is demonstrated in the next +sections. Also, [`atomate2`-based MLPhononMaker](https://materialsproject.github.io/atomate2/reference/atomate2.forcefields.jobs.html#module-atomate2.forcefields.jobs) settings can be changed via `benchmark_kwargs` as shown in the code snippet. +> ℹ️ Note that `autoplex` provides the most comprehensive features for **GAP**, and more features for the other models will +follow in future versions. + +## GAP + +There are several overall settings for the GAP fit that will change the mode in which `autoplex` runs. +When `hyper_para_loop` is set to `True`, `autoplex` wil automatically iterate through a set of several hyperparameters +(`atomwise_regularization_list`, `soap_delta_list` and `n_sparse_list`) and repeat the GAP fit for each combination. +More information on the atom-wise regularization parameter can be found in [J. Chem. Phys. 153, 044104 (2020)](https://pubs.aip.org/aip/jcp/article/153/4/044104/1056348/Combining-phonon-accuracy-with-high) +and a comprehensive list GAP hyperparameters can be found in the [QUIP/GAP user guide](https://libatoms.github.io/GAP/gap_fit.html#command-line-example). +The other keywords to change `autoplex`'s mode are `glue_xml` (use glue.xml core potential instead of 2b/3b terms), +`regularization` (use a sigma regularization) and `separated` (repeat the GAP fit for the combined database and each +separated subset). +The parameter `atom_wise_regularization` can turn the atom-wise regularization on and off, +`atomwise_regularization_parameter` is the value that shall be set and `force_min` is the lower bound cutoff of forces +taken into account for the atom-wise regularization or otherwise be replaced by the f_min value. +`auto_delta` let's you decide if you want to pass a fixed delta value for the 2b, 3b and SOAP terms or let `autoplex` +automatically determine a suitable delta value based on the database's energies. +```python +complete_flow = CompleteDFTvsMLBenchmarkWorkflow( + ml_models=["GAP"], ..., + apply_data_preprocessing=True, + atom_wise_regularization=True, + atomwise_regularization_parameter=0.1, + force_min=0.01, + auto_delta=False, + hyper_para_loop=True, + atomwise_regularization_list=[0.01, 0.1], + soap_delta_list=[0.5, 1.0, 1.5], + n_sparse_list=[1000, 3000, 6000, 9000] +).make(..., + structure_list=[structure], + mp_ids=["mpid"], + benchmark_mp_ids=["mpid"], + benchmark_structures=[structure], + glue_xml=False, + regularization=False, + separated=False, + fit_kwargs_list=[{ + "general": {"default_sigma": "{0.001 0.05 0.05 0.0}", {"two_body": True, "three_body": False,"soap": False},...}, + "twob": {"cutoff": 5.0,...}, + "threeb": {"cutoff": 3.25,...}, + "soap": {"delta": 1.0, "l_max": 12, "n_max": 10,...}, + }] +) +``` +`autoplex` provides a JSON dict file containing default GAP fit settings in +`autoplex/fitting/common/mlip-phonon-defaults.json`, +that can be overwritten using the fit keyword arguments as demonstrated in the code snippet. + +`autoplex` follows a certain convention for naming files and labelling the data +(see `autoplex/fitting/common/mlip-phonon-defaults.json`). +```json + "general": { + "at_file": "train.extxyz", + "energy_parameter_name": "REF_energy", + "force_parameter_name": "REF_forces", + "virial_parameter_name": "REF_virial", + "gp_file": "gap_file.xml" + }, +``` +You can either adapt to the `autoplex` conventions or change by passing your preferred names and label to the fit keyword arguments. + + + +## ACE + +For fitting and validating ACE potentials, one needs to install **julia** as `autoplex` relies on +[ACEpotentials.jl](https://acesuit.github.io/ACEpotentials.jl/dev/gettingstarted/installation/) which support fitting of linear ACE. Currently no python package exists for the same. + +```python +complete_flow = CompleteDFTvsMLBenchmarkWorkflow( + ml_models=["J-ACE"], ..., apply_data_preprocessing=True, +).make(..., + structure_list=[structure], + mp_ids=["mpid"], + benchmark_mp_ids=["mpid"], + benchmark_structures=[structure], + fit_kwargs_list=[{ + "order": 3, + "totaldegree": 6, + "cutoff": 2.0, + "solver": "BLR" + }], + ...) +``` +The ACE fit hyperparameters can be passed in the `make` call with its distinct commands. +Because there is no respective MLPhononMaker in `atomate2` for J-ACE, the functionalities for `autoplex` are limited to +the data generation and ML fit in this case. + +## Nequip + +The Nequip fit procedure can be controlled by fit hyperparameters in the `make` call. + +```python +complete_flow = CompleteDFTvsMLBenchmarkWorkflow( + ml_models=["Nequip"], ..., apply_data_preprocessing=True, +).make(..., + structure_list=[structure], + mp_ids=["mpid"], + benchmark_mp_ids=["mpid"], + benchmark_structures=[structure], + fit_kwargs_list=[{ + "r_max": 4.0, + "num_layers": 4, + "l_max": 2, + "num_features": 32, + "num_basis": 8, + "invariant_layers": 2, + "invariant_neurons": 64, + "batch_size": 5, + "learning_rate": 0.005, + "max_epochs": 10000, + "default_dtype": "float32", + "device": "cuda" + }], + ... +) +``` + +## M3GNet + +In a similar way, the M3GNet fit hyperparameters can be passed using `make` as well. + +```python +complete_flow = CompleteDFTvsMLBenchmarkWorkflow( + ml_models=["M3GNet"], ..., apply_data_preprocessing=True, +).make(..., + structure_list=[structure], + mp_ids=["mpid"], + benchmark_mp_ids=["mpid"], + benchmark_structures=[structure], + fit_kwargs_list=[{ + "cutoff": 5.0, + "threebody_cutoff": 4.0, + "batch_size": 10, + "max_epochs": 1000, + "include_stresses": True, + "hidden_dim": 128, + "num_units": 128, + "max_l": 4, + "max_n": 4, + "device": "cuda", + "test_equal_to_val": True + }], + ..., + ) +``` + +## MACE + +Here again, you can pass the MACE fit hyperparameters to `make`. + +```python +complete_flow = CompleteDFTvsMLBenchmarkWorkflow( + ml_models=["MACE"], ..., apply_data_preprocessing=True, +).make(..., + structure_list=[structure], + mp_ids=["mpid"], + benchmark_mp_ids=["mpid"], + benchmark_structures=[structure], + fit_kwargs_list=[{ + "model": "MACE", + "config_type_weights": '{"Default": 1.0}', + "hidden_irreps": "128x0e + 128x1o", + "r_max": 5.0, + "batch_size": 10, + "max_num_epochs": 1500, + "start_swa": 1200, + "ema_decay": 0.99, + "correlation": 3, + "loss": "huber", + "default_dtype": "float32", + "device": "cuda" + }], + ... +) +``` +### Finetuning MACE-MP-0 + +It is also possible to finetune MACE-MP-0. To do so, you need to install MACE-torch 0.3.7. +Currently, this can only be done by cloning the git-repo and installing it from there: +[https://github.com/ACEsuit/mace/](https://github.com/ACEsuit/mace/). We currently install the main branch from there +automatically within autoplex. + +It is now important that you switch off the default settings for the fitting procedure (use_defaults_fitting=False). +Please be careful with performing very low-data finetuning. Currently, we use a stratified split for splitting the +data into train and test data, i.e. there will be at least one data point from the dataset including single displaced +cells and one rattled structure. + +The following workflow `CompleteDFTvsMLBenchmarkWorkflowMPSettings` uses Materials Project default settings slightly adapted to phonon runs (more accurate convergence, ALGO=Normal). +It can also be used without finetuning option. To finetune optimally, please adapt the MACE fitting parameters yourself. + +```python +complete_workflow_mace = CompleteDFTvsMLBenchmarkWorkflowMPSettings( + ml_models=["MACE"], + volume_custom_scale_factors=[0.95,1.00,1.05], rattle_type=0, distort_type=0, + apply_data_preprocessing=True, use_defaults_fitting=False, + ... + ).make( + structure_list=[structure], + mp_ids=["mpid"], + benchmark_mp_ids=["mpid"], + benchmark_structures=[structure], + fit_kwargs_list=[{ + "model": "MACE", + "name": "MACE_final", + "foundation_model": "large", + "multiheads_finetuning": False, + "r_max": 6, + "loss": "huber", + "energy_weight": 1000.0, + "forces_weight": 1000.0, + "stress_weight": 1.0, + "compute_stress": True, + "E0s": "average", + "scaling": "rms_forces_scaling", + "batch_size": 1, + "max_num_epochs": 200, + "ema": True, + "ema_decay": 0.99, + "amsgrad": True, + "default_dtype": "float64", + "restart_latest": True, + "lr": 0.0001, + "patience": 20, + "device": "cpu", + "save_cpu": True, + "seed": 3 + }], + ) +``` + +If you do not have internet access on the cluster, please make sure that you have downloaded and deposited the +model that you want to finetune on the cluster beforehand. Instead of `foundation_model="large"`, you can then simply +set `foundation_model="full_path_on_the_cluster"` + +## Example script for `autoplex` workflow using GAP to fit and benchmark a Si database + +The following code snippet will demonstrate, how you can submit an `autoplex` workflow for an automated SOAP-only GAP fit +and DFT benchmark for a Si allotrope database. The GAP fit parameters are taken from [J. Chem. Phys. 153, 044104 (2020)](https://pubs.aip.org/aip/jcp/article/153/4/044104/1056348/Combining-phonon-accuracy-with-high). +In this example we will also use `hyper_para_loop=True` to loop through a set of given GAP fit convergence parameter +and hyperparameters set as provided by the lists `atomwise_regularization_list`, `soap_delta_list` and `n_sparse_list`. +In this example script, we are using `jobflow_remote` to submit the jobs to a remote cluster. + +```python +from jobflow_remote import submit_flow +from autoplex.auto.phonons.flows import CompleteDFTvsMLBenchmarkWorkflow +from mp_api.client import MPRester + +mpr = MPRester(api_key='YOUR_MP_API_KEY') +struc_list = [] +benchmark_structure_list = [] +mpids = ["mp-149"] # add all the Si structure mpids you are interested in +mpbenchmark = ["mp-149"] # add all the Si structure mpids you are interested in +for mpid in mpids: + struc = mpr.get_structure_by_material_id(mpid) + struc_list.append(struc) +for mpbm in mpbenchmark: + bm_struc = mpr.get_structure_by_material_id(mpbm) + benchmark_structure_list.append(bm_struc) + +autoplex_flow = CompleteDFTvsMLBenchmarkWorkflow( + n_structures=50, symprec=0.1, + volume_scale_factor_range=[0.95, 1.05], rattle_type=0, distort_type=0, + hyper_para_loop=True, atomwise_regularization_list=[0.1, 0.01], + apply_data_preprocessing=True, + soap_delta_list=[0.5], n_sparse_list=[7000, 8000, 9000], + split_ratio=0.33, regularization=False, + separated=True, num_processes_fit=48,).make( + structure_list=struc_list, mp_ids=mpids, benchmark_structures=benchmark_structure_list, + benchmark_mp_ids=mpbenchmark, + fit_kwargs_list=[{"soap": {"delta": 1.0, "l_max": 12, "n_max": 10, + "atom_sigma": 0.5, "zeta": 4, "cutoff": 5.0, + "cutoff_transition_width": 1.0, + "central_weight": 1.0, "n_sparse": 9000, "f0": 0.0, + "covariance_type": "dot_product", + "sparse_method": "cur_points"}, + "general": {"two_body": False, "three_body": False, "soap": True, + "default_sigma": "{0.001 0.05 0.05 0.0}", "sparse_jitter": 1.0e-8, }}}], +) + +autoplex_flow.name = "autoplex_wf" + +resources = {...} + +print(submit_flow(autoplex_flow, worker="autoplex_worker", resources=resources, project="autoplex")) +``` + + +## Running a MLIP fit only + +The following script shows an example of how you can run a sole GAP fit with `autoplex` using `run_locally` from +`jobflow` for the job management. + +```python +#!/usr/bin/env python + +from jobflow import SETTINGS +from autoplex.fitting.common.flows import MLIPFitMaker +import os +from jobflow import run_locally + +os.environ["OMP_NUM_THREADS"] = "48" +os.environ["OPENBLAS_NUM_THREADS"] = "1" + +os.chdir("/path/to/destination/directory") +store = SETTINGS.JOB_STORE +# connect to the job store +store.connect() + + +fit_input_dict = { + "mp-id": { # put a mp-id or another kind of data marker here + "rattled_dir": [[ + ( + "/path/to/randomized/supercell/structure/calculation" + ), + ( + "/path/to/randomized/supercell/structure/calculation" + ), + ]], + "phonon_dir": [[ + ( + "/path/to/phonopy/supercell/structure/calculation" + ), + ]], + "phonon_data": [], + }, + "IsolatedAtom": {"iso_atoms_dir": [[ + ( + "/path/to/isolated/atom/calculation" + ), + ]] + } + } + + +mlip_fit = MLIPFitMaker( + mlip_type="GAP", ..., + pre_xyz_files=["vasp_ref.extxyz"], + pre_database_dir="/path/to/pre_database", + auto_delta = True, + glue_xml = False, +).make( + species_list=["Li", "Cl"], + fit_input=fit_input_dict, + **{...} + ) + +run_locally(mlip_fit, create_folders=True, store=store) +``` +Additional fit settings can again be passed using `fit_kwargs` or `**{...}`. + +> ℹ️ Note that in the current setup of `autoplex`, you need to pass a `fit_input_dict` to the `MLIPFitMaker` +> containing at least one entry for "rattled_dir", "phonon_dir" and "isolated_atom" **VASP** calculations, +> otherwise the code will not finish successfully. + diff --git a/_sources/user/phonon/flows/flows.md b/_sources/user/phonon/flows/flows.md new file mode 100644 index 000000000..eea8ad672 --- /dev/null +++ b/_sources/user/phonon/flows/flows.md @@ -0,0 +1,260 @@ +(flows)= + +# Out-of-the-box workflow + +## Phonon-accurate machine-learned potentials workflow + +This tutorial will demonstrate how to use `autoplex` with its default setup and settings. + +> ℹ️ The default setting might not be sufficient or not suitable in any other way for your calculations. Carefully check your results with the default setup and adjust the settings when needed. + +## General workflow + +The complete workflow of `autoplex` involves the data generation +(including the execution of VASP calculations), +the fitting of the machine-learned interatomic potential (MLIP) and the benchmark to the DFT results. + +We also have an iterative version of this workflow that reruns the complete workflow until a certain quality of the phonons is reached. It is described below. + +### Before running the workflow + +As a first step, you should check if our algorithm can generate supercells that are small enough to be treated with DFT +(not too many atoms) and large enough to result into reliable phonon properties. +To do so, you can use the following function: + +```python +from mp_api.client import MPRester +from autoplex.data.phonons.utils import check_supercells + + +# first you can download structures from the Materials Project +mpr = MPRester(api_key='YOUR_MP_API_KEY') +structure_list = [] + +mpids = ["mp-149","mp-165","mp-168","mp-16220","mp-571520","mp-971661","mp-971662","mp-999200", "mp-1072544", "mp-1079297", "mp-1095269", "mp-1200830", "mp-1203790"] +for mpid in mpids: + structure = mpr.get_structure_by_material_id(mpid) + structure_list.append(structure) + + +# then you can use the check_supercells function +check_supercells(structure_list, mpids, min_length=18, max_length=25, fallback_min_length=10, min_atoms=100, max_atoms=500, tolerance=0.1) +``` + +`check_supercells` will list all structures that should likely be excluded. +However, please carefully check yourself as your local memory requirements might be different. +Remove all structures which you cannot treat computationally +(e.g., structures with lattice parameters larger than 25 Å or more than 500 atoms). + +Using the `MPRester` is a convenient way to draw structures from the Materials Project database using their MP-ID. + + +### Test DFT run times and memory requirements + +To get a rough estimate of DFT requirements for the supercells that you have chosen, you can use the `DFTSupercellSettingsMaker` +to test the DFT run times for an undisplaced supercell of a similar size to the ones we will use in the overall workflow. + +```python +from jobflow.core.flow import Flow +from mp_api.client import MPRester +from autoplex.auto.phonons.flows import DFTSupercellSettingsMaker + +mpr = MPRester(api_key='YOUR_MP_API_KEY') +structure_list = [] +benchmark_structure_list = [] +mpids = ["mp-22905"] +# you can put as many mpids as needed e.g. mpids = ["mp-22905", "mp-1185319"] for all LiCl entries in the Materials Project +mpbenchmark = ["mp-22905"] +for mpid in mpids: + structure = mpr.get_structure_by_material_id(mpid) + structure_list.append(structure) +for mpbm in mpbenchmark: + bm_structure = mpr.get_structure_by_material_id(mpbm) + benchmark_structure_list.append(bm_structure) + +dft_supercell_check_flow = DFTSupercellSettingsMaker().make( + structure_list=structure_list, mp_ids=mpids) + +dft_supercell_check_flow.name = "tutorial" +autoplex_flow = dft_supercell_check_flow +``` + +This will allow you to check whether memory requirements on your supercomputers are enough and if you might need to switch to smaller systems. + + +## Now start the workflow +We will use [jobflow](https://github.com/materialsproject/jobflow) to control the execution of our jobs in form of flows and jobs. +The only module we need to import from `autoplex` is the `CompleteDFTvsMLBenchmarkWorkflow`. + + +Next, we are going to construct the workflow based on the rocksalt-type LiCl ([*mp-22905*](https://next-gen.materialsproject.org/materials/mp-22905?material_ids=mp-22905)). +Remember to replace `YOUR_MP_API_KEY` with your personal [Materials Project API key](https://next-gen.materialsproject.org/api#api-key). + +```python +from mp_api.client import MPRester +from autoplex.auto.phonons.flows import CompleteDFTvsMLBenchmarkWorkflow + +mpr = MPRester(api_key='YOUR_MP_API_KEY') +structure_list = [] +benchmark_structure_list = [] +mpids = ["mp-22905"] +# you can put as many mpids as needed e.g. mpids = ["mp-22905", "mp-1185319"] for all LiCl entries in the Materials Project +mpbenchmark = ["mp-22905"] +for mpid in mpids: + structure = mpr.get_structure_by_material_id(mpid) + structure_list.append(structure) +for mpbm in mpbenchmark: + bm_structure = mpr.get_structure_by_material_id(mpbm) + benchmark_structure_list.append(bm_structure) + +complete_flow = CompleteDFTvsMLBenchmarkWorkflow( + apply_data_preprocessing=True, +).make( + structure_list=structure_list, mp_ids=mpids, + benchmark_structures=benchmark_structure_list, benchmark_mp_ids=mpbenchmark) + +complete_flow.name = "tutorial" +autoplex_flow = complete_flow +``` +The only default information we need to provide is which structures we want to calculate and use for the MLIP fitting +and which structures we want to benchmark to. +The `autoplex` workflow will then perform automated VASP and `phonopy` calculations, MLIP fits, and benchmarks. +Of course, you can change and adjust the settings to your own needs, e.g. by setting a smaller supercell for the +`phonopy` calculations using `CompleteDFTvsMLBenchmarkWorkflow(min_length=15).make(...)`. +You can find more details on the subsequent tutorial pages. +With additional flows or jobs in the `[complete_flow]` list, +you can combine the `autoplex` flow with other flows and jobs. +As the `mp_id` parameter is a string, you can also use any other *unique* structure object identifier instead. + +The following workflow diagram will give you an overview of the flows and jobs in the default autoplex workflow: +```{mermaid} +flowchart TD + becfe032-c5ca-4398-9691-0f16baacb237(external) -->|'rattled_dir'| 42331e94-129c-45ef-9116-770369f6eab1(data_preprocessing_for_fitting) + becfe032-c5ca-4398-9691-0f16baacb237(external) -->|'rattled_dir'| 56cb031a-0cd7-4aa5-b857-c2c4d17e86c4(complete_benchmark_mp-22905) + a0286b49-988f-4628-8da6-270caade44bc(external) -->|'phonon_dir', 'phonon_data'| 42331e94-129c-45ef-9116-770369f6eab1(data_preprocessing_for_fitting) + a0286b49-988f-4628-8da6-270caade44bc(external) -->|'phonon_dir', 'phonon_data'| 56cb031a-0cd7-4aa5-b857-c2c4d17e86c4(complete_benchmark_mp-22905) + 3b147b9e-05ff-4823-9dbc-7f8855fa99b5(external) -->|'dirs'| 42331e94-129c-45ef-9116-770369f6eab1(data_preprocessing_for_fitting) + 3b147b9e-05ff-4823-9dbc-7f8855fa99b5(external) -->|'species', 'energies'| e0655654-19da-4c19-9e4e-d2a4014084db(machine_learning_fit) + 3b147b9e-05ff-4823-9dbc-7f8855fa99b5(external) -->|'dirs'| 56cb031a-0cd7-4aa5-b857-c2c4d17e86c4(complete_benchmark_mp-22905) + 42331e94-129c-45ef-9116-770369f6eab1(data_preprocessing_for_fitting) -->|output| e0655654-19da-4c19-9e4e-d2a4014084db(machine_learning_fit) + e0655654-19da-4c19-9e4e-d2a4014084db(machine_learning_fit) -->|'mlip_path'| 56cb031a-0cd7-4aa5-b857-c2c4d17e86c4(complete_benchmark_mp-22905) + 56cb031a-0cd7-4aa5-b857-c2c4d17e86c4(complete_benchmark_mp-22905) -->|output| 56e6d111-8da3-4ae6-a69c-33632121a3d7(write_benchmark_metrics) + becfe032-c5ca-4398-9691-0f16baacb237(rattled supercells_mp-22905) + a0286b49-988f-4628-8da6-270caade44bc(single-atom displaced supercells_mp-22905) + 3b147b9e-05ff-4823-9dbc-7f8855fa99b5(get_iso_atom) + subgraph 5f9b5d90-8fe0-418b-a90a-6c39371b8e04 [MLpotentialFit] + 42331e94-129c-45ef-9116-770369f6eab1(data_preprocessing_for_fitting) + e0655654-19da-4c19-9e4e-d2a4014084db(machine_learning_fit) + end + 56cb031a-0cd7-4aa5-b857-c2c4d17e86c4(complete_benchmark_mp-22905) + 56e6d111-8da3-4ae6-a69c-33632121a3d7(write_benchmark_metrics) +``` +The workflow starts with three flows that are supposed to generate data for our database: +* The first flow is preparing the VASP calculation for the isolated atoms (`get_iso_atom`). +* A second flow is preparing the `phonopy` calculations to collect the VASP data from the single-atom displaced supercells (`single-atom displaced supercells_mp-22905`). +* The third flow is constructing rattled supercells by rattling the atoms, i.e. displacing all atoms' positions (in the default setup), preparing the VASP calculations and collecting the data for the MLIP fit (`rattled supercells_mp-22905`). + +After a few data preprocessing steps (`data_preprocessing_for_fitting`) to filter out data with too strong force values, +the MLIP fit (`machine_learning_fit`) is run and the resulting potential is used for the benchmark against DFT data +(`complete_benchmark_mp-22905`). + +Finally, the result metrics are collected in form of output plots and files (`write_benchmark_metrics`). +The lines connecting two flows or jobs are showing what type of data is passed on to the next step, like "data", "dirs" or "output". +"Output" is a generic name for the several job outputs, e.g. the output of `rattled supercells_mp-22905` contains where +`data_preprocessing_for_fitting` can find the files it needs for the MLIP fit. +"Data" contains the [phonon calculation task documents](https://materialsproject.github.io/atomate2/reference/atomate2.common.schemas.phonons.PhononBSDOSDoc.html#atomate2.common.schemas.phonons.PhononBSDOSDoc) and "dirs" contains the path to the directory where the jobs +were executed. +"Energies" and "species" are isolated atoms' energies and a list of species used, and "mlip_path" is the path to the MLIP fit files. + +The workflow diagram was automatically generated using [Mermaid](https://mermaid.live/) and the job connection data collected by `jobflow` +for a simple job default setup with only one MP-ID. +You can add the following lines to your `autoplex` submission script to generate the specific Mermaid diagram for your +own workflow setup: +```python +from jobflow.utils.graph import to_mermaid + +... + +autoplex_flow = ... + +graph_source = to_mermaid(autoplex_flow, show_flow_boxes=True) +print(graph_source) # show mermaid graph +``` +Then you can paste the printed text to the [Mermaid Live Online FlowChart & Diagrams Editor](https://mermaid.live/). + +The `autoplex` workflow is easy to customize and every aspect of the workflow (data generation, MLIP fit, benchmark) is +in the control of the user as demonstrated in the subsequent tutorial pages. + + +## Output and results + +The default `autoplex` phonon workflow provides you with diagnostic and benchmark output plots and results. +Please note that the current shown results are the autoplex unit test examples and have not been produced by the tutorial settings. + +After the MLIP fit is finished, `autoplex` outputs the training and the testing error of the current potential that is fitted. + +```bash +Training error of MLIP (eV/at.): 0.0049634 +Testing error of MLIP (eV/at.): 0.0023569 +``` + +"MLIP vs. DFT" plots for the energy and force values will be automatically saved which provides you with information +about the quality of your fit. +![autoplex diagnostic](../../../_static/energy_forces.png) +The plot is divided into three sections. First, the energies and forces for the training data, and then for the test data is plotted. `autoplex` also automatically filters the data according to a certain energy threshold (eV) `energy_limit=0.005` as well as a certain force threshold (ev/Å) `force_limit=0.1` to catch outliers resulting from inconsistencies in the data. +Finally, the energy and force filtered data is plotted in the third section. This can help you to figure out if there is +a problem with your data in case the MLIP fit quality does not turn out as expected. + +At the end of each workflow run, `autoplex` also provides you with the benchmark plots for the phonon bandstructure +comparison between the ML-based (here GAP) and the DFT-based result. +![autoplex default](../../../_static/LiCl_band_comparison.png) + + +as well as the q-point wise phonon RMSE plot. +![autoplex default](../../../_static/LiCl_rmse_phonons.png) +This will give you feedback of the overall quality of the generated ML potential. + +`autoplex` also prints the file `results_LiCl.txt` (here for the example of LiCl) with a summary of the essential +results of your workflow setup. +```text +Potential Structure MPID Displacement (Å) RMSE (THz) imagmodes(pot) imagmodes(dft) Database type (Hyper-)Parameters +GAP LiCl mp-22905 0.01 0.57608 False False full atom-wise f=0.1: n_sparse = 6000, SOAP delta = 0.5 +``` + +## Iterative version of the default workflow + +To systematically converge the quality of the potentials, we have built an iterative version of the default workflow `CompleteDFTvsMLBenchmarkWorkflow`. It will run the `CompleteDFTvsMLBenchmarkWorkflow` until the worst RMSE value of the benchmark structures falls under a certain value or a maximum number of repetitions is reached. + +We allow users in the first generation to use a slightly different workflow than in the subsequent generations. This can help to initially obtain enough structures for an MLIP fit and only slightly increase the number of structures in the next generations. Please don't forget to deactivate the phonon data generation after the first iteration. + +```python +from mp_api.client import MPRester +from autoplex.auto.phonons.flows import CompleteDFTvsMLBenchmarkWorkflow, IterativeCompleteDFTvsMLBenchmarkWorkflow + +mpr = MPRester(api_key='YOUR_MP_API_KEY') +structure_list = [] +benchmark_structure_list = [] +mpids = ["mp-22905"] +# you can put as many mpids as needed e.g. mpids = ["mp-22905", "mp-1185319"] for all LiCl entries in the Materials Project +mpbenchmark = ["mp-22905"] +for mpid in mpids: + structure = mpr.get_structure_by_material_id(mpid) + structure_list.append(structure) +for mpbm in mpbenchmark: + bm_structure = mpr.get_structure_by_material_id(mpbm) + benchmark_structure_list.append(bm_structure) + +complete_flow=IterativeCompleteDFTvsMLBenchmarkWorkflow(rms_max=0.2, max_iterations=4, + complete_dft_vs_ml_benchmark_workflow_0=CompleteDFTvsMLBenchmarkWorkflow( + apply_data_preprocessing=True, add_dft_phonon_struct=True, +), + complete_dft_vs_ml_benchmark_workflow_1=CompleteDFTvsMLBenchmarkWorkflow( + apply_data_preprocessing=True, add_dft_phonon_struct=False, +) + ).make( + structure_list=structure_list, mp_ids=mpids, + benchmark_structures=benchmark_structure_list, benchmark_mp_ids=mpbenchmark) + +complete_flow.name = "tutorial" +autoplex_flow = complete_flow +``` diff --git a/_sources/user/phonon/flows/generation/data.md b/_sources/user/phonon/flows/generation/data.md new file mode 100644 index 000000000..5b3d39ce6 --- /dev/null +++ b/_sources/user/phonon/flows/generation/data.md @@ -0,0 +1,221 @@ +(generation)= + +# Generating reference data + +This tutorial will explain how the reference data from DFT-based phonon structures for the MLIP fit can be generated. +The idea behind the VASP reference data generation stems from [J. Chem. Phys. 153, 044104 (2020)](https://pubs.aip.org/aip/jcp/article/153/4/044104/1056348/Combining-phonon-accuracy-with-high) +and demonstrates that a robust database of **crystalline structures** for MLIP reproducing accurate phonon structures can +be build by generating the single-atom displaced supercells using `phonopy` and combining them with a set of rattled supercell +structures, generated from the same unit cell models. + +## Single-atom displaced supercell structures + +The single-atom displaced supercell structures used in `autoplex` are generated by [phonopy](https://phonopy.github.io/phonopy/vasp.html) VASP-related routines, +that are collected in the `dft_phonopy_gen_data` flow +(see diagram in the [general tutorial](../flows.md#general-workflow). +The displacement default is 0.1 Å and can be adjusted. A `phonopy` calculation with a displacement of 0.1 Å is +automatically included in the workflow for calculating the DFT benchmark reference +(if no reference is provided by the user). The `min_length` parameter controls the supercells size and is set to 20 +per default as this value is good for ensuring that the periodic boundary conditions and the energy convergence criteria +for phonon calculations are met. More settings can be found in the [API reference](#autoplex.auto.phonons.flows.CompleteDFTvsMLBenchmarkWorkflow). + +There is the possibility to run the complete `autoplex` workflow using only `phonopy` generated supercells: +```python +complete_flow = CompleteDFTvsMLBenchmarkWorkflow(add_dft_random_struct=False, min_length=20, + displacements=displacement_list).make( + structure_list=structure_list, mp_ids=mpids, + benchmark_structures=benchmark_structure_list, benchmark_mp_ids=mpbenchmark) +``` +By doing so, the generation of the randomized structures has to be tuned off by setting the `add_dft_random_struct` bool to `False`. +You can also decide if you want to only use those single-atom displaced supercells only for the MLIP fit, or if the data +shall be added to an existing database. + +Adding data to an existing database is achieved by: +```python +complete_flow = CompleteDFTvsMLBenchmarkWorkflow( + add_dft_random_struct=False, + pre_xyz_files=["vasp_ref.extxyz"], pre_database_dir=path/to/database +).make( + structure_list=structure_list, mp_ids=mpids, + benchmark_structures=benchmark_structure_list, benchmark_mp_ids=mpbenchmark,) +``` +Where `pre_xyz_files` can also take a train and test database as argument, e.g. as +`pre_xyz_files=["pre_xyz_train.extxyz", "pre_xyz_test.extxyz"]`. + +`autoplex` is equipped with a [DFTPhononMaker](#autoplex.data.phonons.flows.DFTPhononMaker) class that inherits from the `atomate2` [PhononMaker](https://materialsproject.github.io/atomate2/reference/atomate2.vasp.flows.phonons.PhononMaker.html#atomate2.vasp.flows.phonons.PhononMaker) with +specific VASP input adjustments to guarantee high quality fit data. It can be used to run individual and customized `phonopy` workflows to generate MLIP fit data. + +## Rattled supercell structures + +There are several ways available in `autoplex` to rattle supercell structures, +that are collected in the `dft_random_gen_data` flow +(see diagram in the [general tutorial](../flows.md#general-workflow). +The size of the supercell is determined by the `supercell_matrix`, +and there is the option of volume distortion, angle distortion or a combination of both provided by `distort_type`. +The displacement of all atomic positions ("rattling") is controlled by the parameter `rattle_type`, +which uses the the `ase` [rattle](https://wiki.fysik.dtu.dk/ase/ase/atoms.html#ase.Atoms.rattle) function +(using a normal distribution of a certain standard deviation to draw the displacement value) +by default and can be changed to Monte-Carlo rattling. +```python +complete_flow = CompleteDFTvsMLBenchmarkWorkflow(rattle_type=0, # 0 = standard ase.Atoms.rattle(stddev) + distort_type=0, # only volume distortion + supercell_matrix=[[3, 0, 0], [0, 3, 0]], + volume_scale_factor_range=[0.95, 1.05], + n_structures=20).make( + structure_list=structure_list, mp_ids=mpids, + benchmark_structures=benchmark_structure_list, benchmark_mp_ids=mpbenchmark) +``` +The combination of parameters `volume_scale_factor_range` and `n_structures` will produce 21 +(20 volume distorted + the undistorted supercell) supercells with a volume range of 95 to 105% of the original supercell. +Alternatively, the parameter `volume_custom_scale_factors` can be used to set specific scale factors. +> ℹ️ It is important to note that by using `volume_custom_scale_factors` the parameter `n_structures` is ignored +> and **only one** rattled supercell for each given factor is generated. If more supercells with the same volume scale +> are needed, this can be achieved by e.g. +> ```python +> scale_factors = [0.90, 0.95, 1.00, 1.05, 1.10] +> +> complete_flow = CompleteDFTvsMLBenchmarkWorkflow( +> volume_custom_scale_factors=[val for val in scale_factors for _ in range(5)]).make(...) +> # will repeat each scale factor five times +>``` +> Explicitly specifying `volume_custom_scale_factors` is useful if you don’t want evenly spaced intervals between +> scale factors as e.g., you want to sample around the minimum more closely. + +More details and settings are given in the [API reference](#autoplex.auto.phonons.flows.CompleteDFTvsMLBenchmarkWorkflow). + +Similar to the single-atom displaced supercells, you can run the complete `autoplex` workflow using only randomized +structures by setting `add_dft_phonon_struct` to `False`. +```python +complete_flow = CompleteDFTvsMLBenchmarkWorkflow(add_dft_phonon_struct=False).make( + structure_list=structure_list, mp_ids=mpids, preprocessing_data=True, + benchmark_structures=benchmark_structure_list, benchmark_mp_ids=mpbenchmark) +``` +It can also be used to extend an already existing database in the same way as demonstrated above. + +As a counterpart to the `DFTPhononMaker` for generating data, `autoplex` includes a [RandomStructuresDataGenerator](#autoplex.data.phonons.flows.RandomStructuresDataGenerator) +that can be used to construct customized randomized structures workflows. +`autoplex` provides a variety of [utility](#autoplex.data.common.utils) subroutines to further customize a workflow. + +## Adjust supercell settings + +You can adjust the supercell settings by passing a dictionary containing your specific supercell settings for each +MP-ID to `CompleteDFTvsMLBenchmarkWorkflow`, e.g. like: +```python +mp_id = "mp-22905" + +supercell_settings = { + mp_id: { + "supercell_matrix": [[0, 2, 0], [0, 0, 2], [2, 0, 0]] + }, + "min_length": 11, + "max_length": 25, + "fallback_min_length": 9, + "min_atoms": 100, + "max_atoms": 200, + "step_size": 1, +} + +complete_flow = CompleteDFTvsMLBenchmarkWorkflow( + ..., + supercell_settings=supercell_settings, + ...).make(...) +``` +To keep the calculations consistent, this will adjust the settings of single-atom displaced and rattled supercells. + +## VASP settings + +This part will show you how you can adjust the different Makers for the VASP calculations in the workflow. + +For the single-atom displaced as well as the rattled structures the `autoplex` [TightDFTStaticMaker](#autoplex.data.phonons.flows.TightDFTStaticMaker) is +used to set up the VASP calculation input and settings. PBEsol is the default GGA functional. For the VASP calculation +of the isolated atoms' energies, `autoplex` also provides its own [IsoAtomStaticMaker](#autoplex.data.phonons.flows.IsoAtomStaticMaker), +which settings you can further adjust. +For the VASP geometry relaxation and static calculations of the unit cells as prerequisite calculations for generating +the single-atom displaced as well as the rattled supercells, +we rely on the [atomate2](https://materialsproject.github.io/atomate2/user/codes/vasp.html#list-of-vasp-workflows) +Makers `StaticMaker`, `TightRelaxMaker` in combination with the `StaticSetGenerator` VASP input set generator for this example. + +```python +from autoplex.auto.phonons.flows import CompleteDFTvsMLBenchmarkWorkflow +from autoplex.data.phonons.flows import IsoAtomStaticMaker, TightDFTStaticMaker +from atomate2.vasp.jobs.core import StaticMaker, TightRelaxMaker +from atomate2.vasp.sets.core import StaticSetGenerator + +example_input_set = StaticSetGenerator( # you can also define multiple input sets + user_kpoints_settings={"grid_density": 1}, + user_incar_settings={ + "ALGO": "Normal", + "IBRION": -1, + "ISPIN": 1, + "ISMEAR": 0, + ..., # set all INCAR tags you need + "SIGMA": 0.05, + "GGA": "PE", # switches to PBE + ...}, +) +static_isolated_atom_maker = IsoAtomStaticMaker( + name="isolated_atom_maker", + input_set_generator=example_input_set, +) +displacement_maker = TightDFTStaticMaker( + name="displacement_maker", + input_set_generator=example_input_set, +) +rattled_bulk_relax_maker = TightRelaxMaker( + name="bulk_rattled_maker", + input_set_generator=example_input_set, +) +phonon_bulk_relax_maker = TightRelaxMaker( + name="bulk_phonon_maker", + input_set_generator=example_input_set, +) +phonon_static_energy_maker = StaticMaker( + name="phonon_static_energy_maker", + input_set_generator=example_input_set, +) + +complete_flow = CompleteDFTvsMLBenchmarkWorkflow( + displacement_maker=displacement_maker, # one displacement maker for rattled and single-atom displaced supercells to keep VASP settings consistent + phonon_bulk_relax_maker=phonon_bulk_relax_maker, + phonon_static_energy_maker=phonon_static_energy_maker, + rattled_bulk_relax_maker=rattled_bulk_relax_maker, + isolated_atom_maker=static_isolated_atom_maker,).make(...) +``` + +Note, that for consistency of job handling, `autoplex` internally will override the jobs names to the `autoplex` defaults: +``` +INFO Started executing jobs locally +INFO Starting job - reduce_supercell_size_job_mp-22905 +INFO Finished job - reduce_supercell_size_job_mp-22905 +INFO Starting job - rattled supercells_mp-22905 +INFO Finished job - rattled supercells_mp-22905 +INFO Starting job - tight relax_mp-22905 +INFO Finished job - tight relax_mp-22905 +INFO Starting job - generate_randomized_structures_mp-22905 +INFO Finished job - generate_randomized_structures_mp-22905 +INFO Starting job - run_phonon_displacements_mp-22905 +INFO Finished job - run_phonon_displacements_mp-22905 +INFO Starting job - dft rattle static 1/12_mp-22905 +INFO Finished job - dft rattle static 1/12_mp-22905 +INFO Starting job - dft rattle static 2/12_mp-22905 +INFO Finished job - dft rattle static 2/12_mp-22905 +... +INFO Starting job - single-atom displaced supercells_mp-22905 +INFO Finished job - single-atom displaced supercells_mp-22905 +INFO Starting job - tight relax_mp-22905 +INFO Finished job - tight relax_mp-22905 +INFO Starting job - static_mp-22905 +INFO Finished job - static_mp-22905 +INFO Starting job - generate_phonon_displacements_mp-22905 +INFO Finished job - generate_phonon_displacements_mp-22905 +INFO Starting job - run_phonon_displacements_mp-22905 +INFO Finished job - run_phonon_displacements_mp-22905 +INFO Starting job - dft phonon static 1/2_mp-22905 +INFO Finished job - dft phonon static 1/2_mp-22905 +INFO Starting job - dft phonon static 2/2_mp-22905 +INFO Finished job - dft phonon static 2/2_mp-22905 +... +INFO Starting job - write_benchmark_metrics +INFO Finished job - write_benchmark_metrics +INFO Finished executing jobs locally +``` \ No newline at end of file diff --git a/_sources/user/phonon/index.md b/_sources/user/phonon/index.md new file mode 100644 index 000000000..c88005fba --- /dev/null +++ b/_sources/user/phonon/index.md @@ -0,0 +1,13 @@ +(phonon)= + +# Phonon workflow + +The section gives the instructions for the phonon-related workflow. + +```{toctree} +:maxdepth: 3 +flows/flows +flows/generation/data +flows/fitting/fitting +flows/benchmark/benchmark +``` \ No newline at end of file diff --git a/_sources/user/rss.md b/_sources/user/rss.md new file mode 100644 index 000000000..253a866c1 --- /dev/null +++ b/_sources/user/rss.md @@ -0,0 +1,3 @@ +--- +orphan: true +--- \ No newline at end of file diff --git a/_sources/user/rss/flow/example/example.md b/_sources/user/rss/flow/example/example.md new file mode 100644 index 000000000..aba1c5dcf --- /dev/null +++ b/_sources/user/rss/flow/example/example.md @@ -0,0 +1,177 @@ +(example)= + +# Examples + +This section demonstrates various YAML configurations beyond Si and explores use cases for the RSS workflow. + +## SiO2 + +This section provides guidance on exploring silica models at different functional levels. + +```yaml +# General Parameters +tag: 'SiO2' +train_from_scratch: true +resume_from_previous_state: + test_error: + pre_database_dir: + mlip_path: + isolated_atom_energies: + +# Buildcell Parameters +generated_struct_numbers: + - 8000 + - 2000 +buildcell_options: + - SPECIES: "Si%NUM=1,O%NUM=2" + NFORM: '{2,4,6,8}' + SYMMOPS: '1-4' + MINSEP: '1.5 Si-Si=2.7-3.0 Si-O=1.3-1.6 O-O=2.28-2.58' + - SPECIES: "Si%NUM=1,O%NUM=2" + NFORM: '{3,5,7}' + SYMMOPS: '1-4' + MINSEP: '1.5 Si-Si=2.7-3.0 Si-O=1.3-1.6 O-O=2.28-2.58' +fragment_file: null +fragment_numbers: null +num_processes_buildcell: 128 + +# Sampling Parameters +num_of_initial_selected_structs: + - 80 + - 20 +num_of_rss_selected_structs: 100 +initial_selection_enabled: true +rss_selection_method: 'bcur2i' +bcur_params: + soap_paras: + l_max: 12 + n_max: 12 + atom_sigma: 0.0875 + cutoff: 10.5 + cutoff_transition_width: 1.0 + zeta: 4.0 + average: true + species: true + frac_of_bcur: 0.8 + bolt_max_num: 3000 +random_seed: null + +# DFT Labelling Parameters +include_isolated_atom: true +isolatedatom_box: + - 20.0 + - 20.0 + - 20.0 +e0_spin: false +include_dimer: false +dimer_box: + - 20.0 + - 20.0 + - 20.0 +dimer_range: + - 1.0 + - 5.0 +dimer_num: 41 +custom_incar: + KPAR: 8 + NCORE: 16 + LSCALAPACK: ".FALSE." + LPLANE: ".FALSE." + ISMEAR: 0 + SIGMA: 0.1 + PREC: "Accurate" + ADDGRID: ".TRUE." + EDIFF: 1E-7 + NELM: 250 + LWAVE: ".FALSE." + LCHARG: ".FALSE." + ALGO: "normal" + AMIX: null + LREAL: ".FALSE." + ISYM: 0 + ENCUT: 900.0 + KSPACING: 0.23 + GGA: null + AMIX_MAG: null + BMIX: null + BMIX_MAG: null + ISTART: null + LMIXTAU: null + NBANDS: null + NELMDL: null + METAGGA: "SCAN" + LASPH: ".TRUE." +custom_potcar: +vasp_ref_file: 'vasp_ref.extxyz' + +# Data Preprocessing Parameters +config_types: + - 'initial' + - 'traj_early' + - 'traj' +rss_group: + - 'traj' +test_ratio: 0.0 +regularization: true +scheme: 'linear-hull' +reg_minmax: + - [0.1, 1] + - [0.001, 0.1] + - [0.0316, 0.316] + - [0.0632, 0.632] +distillation: false +force_max: null +force_label: null +pre_database_dir: null + +# MLIP Parameters +mlip_type: 'GAP' +ref_energy_name: 'REF_energy' +ref_force_name: 'REF_forces' +ref_virial_name: 'REF_virial' +auto_delta: true +num_processes_fit: 32 +device_for_fitting: 'cpu' +twob: + cutoff: 5.0 + n_sparse: 15 + theta_uniform: 1.0 + delta: 1.0 +threeb: + cutoff: 3.25 +soap: + l_max: 6 + n_max: 12 + atom_sigma: 0.5 + n_sparse: 3000 + cutoff: 5.0 + delta: 0.2 +general: + three_body: false + +# RSS Exploration Parameters +scalar_pressure_method: 'exp' +scalar_exp_pressure: 100 +scalar_pressure_exponential_width: 0.2 +scalar_pressure_low: 0 +scalar_pressure_high: 25 +max_steps: 300 +force_tol: 0.01 +stress_tol: 0.01 +stop_criterion: 0.001 +max_iteration_number: 10 +num_groups: 16 +initial_kt: 0.3 +current_iter_index: 1 +hookean_repul: true +hookean_paras: + '(1, 1)': [1000, 0.6] + '(8, 1)': [1000, 0.4] + '(8, 8)': [1000, 1.0] +keep_symmetry: false +write_traj: true +num_processes_rss: 128 +device_for_rss: 'cpu' +``` + +To switch from SCAN to PBE, simply remove the `METAGGA` and `LASPH` entries from the `custom_incar` settings. All other parameters remain unchanged. diff --git a/_sources/user/rss/flow/input/input.md b/_sources/user/rss/flow/input/input.md new file mode 100644 index 000000000..91e78ebe0 --- /dev/null +++ b/_sources/user/rss/flow/input/input.md @@ -0,0 +1,232 @@ +(input)= + +# Input YAML configuration file + +The YAML configuration file contains all the inputs you need to define and customize your RSS workflow. This section provides a detailed explanation of how to set up the file, using silicon (`Si`) as an example. To make it easier to understand, we will break down the YAML file into several sections based on the key components of the RSS workflow and explain each part in detail. + +## General parameters + +The section defines the general settings for the RSS workflow, including the system's formula, whether to start fresh, or resume from a previous state. + +```yaml +# General Parameters +tag: 'Si' +train_from_scratch: false +resume_from_previous_state: + test_error: + pre_database_dir: + mlip_path: + isolated_atom_energies: +``` + +The `tag` identifies the elements in the system, such as `Si` in this example, and can also be passed to the parameters for random structure generation. Setting `train_from_scratch=false` indicates that the RSS workflow will start from scratch. To resume a workflow, refer to the [Resuming workflow from point of interruption](../quick_start/start.md) section. + +## Buildcell parameters + +The section defines the settings for generating initial random structures. These parameters control the diversity, symmetry, size, and number of the generated structures. We utilize the `buildcell` software from [AIRSS](https://airss-docs.github.io/technical-reference/buildcell-manual) for random structure generation, ensuring flexibility and efficiency in exploring configurational space. + +```yaml +# Buildcell Parameters +generated_struct_numbers: + - 8000 + - 2000 +buildcell_options: + - NFORM: '1' + SYMMOPS: '1-4' + SLACK: 0.25 + OVERLAP: 0.1 + NATOM: '{6,8,10,12,14,16,18,20,22,24}' + - NFORM: '1' + SYMMOPS: '1-4' + SLACK: 0.25 + OVERLAP: 0.1 + NATOM: '{7,9,11,13,15,17,19,21,23}' +fragment_file: null +fragment_numbers: null +num_processes_buildcell: 128 +``` + +The `buildcell_options` parameter is one of the most critical settings, +as it determines the scope of the RSS search and directly influences the diversity of the generated structures. +We provide multiple sets of `buildcell` input parameters. +Using the configuration file above, a total of 8000 structures will be generated, each containing an even number of atoms. +Additionally, 2000 structures will be created with an odd number of atoms. The symmetry operations will vary from 1 to 4. +You can supply a single set or multiple sets as needed. +This flexibility ensures that the initial structures have sufficient diversity. +In principle, any parameter supported by [buildcell](https://airss-docs.github.io/technical-reference/buildcell-manual) can be used in the `buildcell_options` section. + +The `fragment_file` and `fragment_numbers` parameters are used during random structure generation to define specific fragments as the smallest building blocks. For example, you can define an H2O molecule as a fragment and use it as the basic unit for generating random structures. This allows for more customized and realistic initial configurations when working with molecular or other complex systems. The `num_processes_buildcell` parameter specifies the number of CPU cores to be used in parallel during random structure generation. Note that this parameter is limited to a single node. + +> **Note**: The `generated_struct_numbers` and `buildcell_options` parameters must have the same length. Each entry in `buildcell_options` corresponds to the number of structures specified at the same position in `generated_struct_numbers`. + +## Sampling parameters + +The section controls the selection methods of structures during the RSS workflow. These parameters determine how initial and iteratively generated structures are selected. + +```yaml +# Sampling Parameters +num_of_initial_selected_structs: + - 80 + - 20 +num_of_rss_selected_structs: 100 +initial_selection_enabled: true +rss_selection_method: 'bcur2i' +bcur_params: + soap_paras: + l_max: 12 + n_max: 12 + atom_sigma: 0.0875 + cutoff: 10.5 + cutoff_transition_width: 1.0 + zeta: 4.0 + average: true + species: true + frac_of_bcur: 0.8 + bolt_max_num: 3000 +random_seed: null +``` + +If the RSS workflow starts exploration from scratch, it consists of two stages. In the first stage, there is no pre-existing potential, so structures are directly selected from the initial random structures for fitting. The `num_of_initial_selected_structs` parameter defines how many structures are selected from the initial random structures. In the example provided, the workflow selects 80 structures from 8000 even-numbered cells and 20 structures from 2000 odd-numbered cells for fitting the initial potential. In the second stage, after obtaining the initial potential, the workflow transitions to ML-driven RSS iterations. During this stage, the `num_of_rss_selected_structs` parameter specifies the number of structures sampled in each RSS iteration. + +The `initial_selection_enabled` parameter enables initial structure selection when set to `true`. In this case, the sampling method will be CUR. The `rss_selection_method` defines the strategy for selecting structures during the RSS iteration. In this example, the method `bcur2i` is used, which combines Boltzmann-weighted energy histograms and CUR sampling to select low-energy and diverse structures. The `bcur_params` section provides detailed settings for the `bcur2i` method. The `soap_paras` define the SOAP descriptors used for structure representation. + +## DFT labelling parameters + +The section allows you to set up VASP static calculations for accurately labeling training structures, including bulk, isolated atoms, and dimers. + +```yaml +# DFT Labelling Parameters +include_isolated_atom: true +isolatedatom_box: + - 20.0 + - 20.0 + - 20.0 +e0_spin: false +include_dimer: true +dimer_box: + - 20.0 + - 20.0 + - 20.0 +dimer_range: + - 1.0 + - 5.0 +dimer_num: 21 +custom_incar: + KPAR: 8 + NCORE: 16 + LSCALAPACK: ".FALSE." + LPLANE: ".FALSE." + ISMEAR: 0 + SIGMA: 0.05 + PREC: "Accurate" + ADDGRID: ".TRUE." + EDIFF: 1E-7 + NELM: 250 + LWAVE: ".FALSE." + LCHARG: ".FALSE." + ALGO: "normal" + AMIX: 0.1 + LREAL: ".FALSE." + ISYM: 0 + ENCUT: 400.0 + KSPACING: 0.25 + GGA: 'PS' +custom_potcar: +vasp_ref_file: 'vasp_ref.extxyz' +``` + +If `include_isolated_atom` and `include_dimer` are set to `true`, the program will automatically identify all elements present in the structures generated by `buildcell` and set up cells for isolated atoms and dimers accordingly based on the recognized elements. The `custom_incar` parameter allows you to define any VASP settings. You can adjust these settings by adding or removing keys as needed. + +## Data preprocessing parameters + +The section defines how the training data is prepared before fitting potentials. This includes filtering, regularization, combining external datasets, and splitting the data into training and testing sets. + +```yaml +# Data Preprocessing Parameters +config_types: + - 'initial' + - 'traj_early' + - 'traj' +rss_group: + - 'traj' +test_ratio: 0.1 +regularization: true +scheme: 'linear-hull' +retain_existing_sigma: true +reg_minmax: + - [0.1, 1] + - [0.001, 0.1] + - [0.0316, 0.316] + - [0.0632, 0.632] +distillation: false +force_max: null +force_label: null +pre_database_dir: null +``` + +Regularization is currently only applicable to GAP potentials and is adjusted using the `scheme` parameter. Common schemes include `'linear-hull'` and `'volume-stoichiometry'`. For systems with fixed stoichiometry, `'linear-hull'` is recommended. For systems with varying stoichiometries, `'volume-stoichiometry'` is more appropriate. + +## MLIP Parameters + +The section defines the settings for training machine learning potentials. Currently supported architectures include GAP, ACE(Julia), NequIP, M3GNet, and MACE. You can specify the desired model using the `mlip_type` argument and tune hyperparameters flexibly by adding key-value pairs. Default and adjustable hyperparameters are available in `autoplex/autoplex/fitting/common/mlip-rss-defaults.json`. + +```yaml +# MLIP Parameters +mlip_type: 'GAP' +ref_energy_name: 'REF_energy' +ref_force_name: 'REF_forces' +ref_virial_name: 'REF_virial' +auto_delta: true +num_processes_fit: 32 +device_for_fitting: 'cpu' +twob: + cutoff: 10.0 + n_sparse: 30 + theta_uniform: 1.0 +threeb: + cutoff: 3.25 +soap: + l_max: 8 + n_max: 8 + atom_sigma: 0.75 + n_sparse: 2000 + cutoff: 5.0 +general: + three_body: true +``` + +## RSS Exploration Parameters + +The section sets up the RSS iterative process, including parameters for structure optimization and convergence criteria. + +```yaml +# RSS Exploration Parameters +scalar_pressure_method: 'exp' +scalar_exp_pressure: 1 +scalar_pressure_exponential_width: 0.2 +scalar_pressure_low: 0 +scalar_pressure_high: 25 +max_steps: 200 +force_tol: 0.01 +stress_tol: 0.01 +stop_criterion: 0.001 +max_iteration_number: 25 +num_groups: 6 +initial_kt: 0.3 +current_iter_index: 1 +hookean_repul: true +hookean_paras: + '(14, 14)': [1000, 1.0] +keep_symmetry: true +write_traj: true +num_processes_rss: 128 +device_for_rss: 'cpu' +``` + +The RSS workflow supports searching for structures under high pressure, controlled by the `scalar_pressure_method` parameter. Two methods are available: `exp`, where pressure is sampled based on an exponential distribution with control parameters `scalar_exp_pressure` and `scalar_pressure_exponential_width`, and `uniform`, where pressure is sampled uniformly within a range defined by `scalar_pressure_low` and `scalar_pressure_high`. + +To terminate the iterative process, two stopping criteria are provided: `stop_criterion` and `max_iteration_number`. The iterations stop when the prediction error falls below the value of `stop_criterion`. Or the iterations stop when the number of iterations exceeds the limit defined by `max_iteration_number`. The workflow will stop when either of the above criteria is satisfied. + +We strongly recommend enabling `hookean_repul`, as it applies a strong repulsive force when the distance between two atoms falls below a certain threshold. This ensures that the generated structures are physically reasonable. + +The GAP-RSS model of Si was iterated 25 times on a server cluster with 7 nodes, each equipped with 128 cores, taking approximately 1 day to complete. The resulting potential was found to accurately describe different crystalline polymorphs as well as disordered phases. diff --git a/_sources/user/rss/flow/introduction/intro.md b/_sources/user/rss/flow/introduction/intro.md new file mode 100644 index 000000000..46ef17d85 --- /dev/null +++ b/_sources/user/rss/flow/introduction/intro.md @@ -0,0 +1,21 @@ +(introduction)= + +# Brief overview + +The random structure searching (RSS) approach was initially proposed for predicting crystal structures by generating randomized, sensible structures and optimising them via first-principles calculations ([Phys. Rev. Lett. 97, 045504 (2006)](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.97.045504) and [J. Phys.: Condens. Matter 23, 053201 (2011)](https://iopscience.iop.org/article/10.1088/0953-8984/23/5/053201)). Recently, RSS was expanded into a methodology for exploring and learning potential-energy surfaces from scratch ([Phys. Rev. Lett. 120, 156001 (2018)](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.120.156001) and [npj Comput. Mater. 5, 99 (2019)](https://www.nature.com/articles/s41524-019-0236-6)). Enhanced with physics-inspired sampling methods, such as Boltzmann-probability biased histograms and CUR, this approach ensures both the significance (low-energy) and diversity of the structures being searched. + +## Features + +Generating datasets for potential fitting through classical or AIMD simulations is common practice. However, such methods heavily rely on the initial input structures, often limiting the exploration to a narrow phase space. This is because they struggle to overcome the energy barriers associated with phase transitions. As a result, a single (AI)MD trajectory typically works only for specific systems, such as single phases with fixed stoichiometry. In contrast, RSS enables access to a much larger configurational space due to the high diversity of initial random structures. Overall, RSS has the following key features: + +- **Broad configurational space exploration** + RSS ensures structure diversity by exploring a broad configurational space. This makes it suitable for generating general-purpose potentials or pre-trained models. + +- **Low cost** + RSS eliminates dependence on AIMD, relying instead on ML-driven iterative optimization. As a result, it only requires single-point DFT calculations, largely reducing the computational cost. + +- **Energy-based sampling** + By employing energy-weighted sampling, RSS can generate physically reasonable training sets. + +- **Versatile applicability** + RSS can effortlessly handle varying elements, stoichiometries, and densities, making it highly adaptable to different material systems. To date, RSS has been demonstrated to produce accurate potentials for elemental, binary, and ternary material systems. diff --git a/_sources/user/rss/flow/quick_start/start.md b/_sources/user/rss/flow/quick_start/start.md new file mode 100644 index 000000000..bc38c3fd7 --- /dev/null +++ b/_sources/user/rss/flow/quick_start/start.md @@ -0,0 +1,109 @@ +(rss-quickstart)= + +# Quick start + +The `RssMaker` class in `autoplex` is the core interface for creating ML-RSS potential models from scratch. It accepts customizable parameters that control key aspects of the RSS process, including: + +- **Randomized structure generation** + Generate diverse initial structures for broader configurational space exploration. + +- **Sampling strategies** + Customize methods for selecting configurations based on energy and structure diversity. + +- **DFT labeling** + Perform single-point DFT calculations to provide accurate energy and force labels for training. + +- **Data preprocessing** + Include steps like data regularization and filtering to improve model performance. + +- **Potential fitting** + Perform machine learning potential fitting with flexible hyperparameter tuning. + +- **Iterative loop** + Continuously refine the potential model through iterative RSS cycles. + +Parameters can be specified either through a YAML configuration file or as direct arguments in the `make` method. + +## Running the workflow with a YAML configuration file + +> **Recommendation**: This is currently our recommended approach for setting up and managing RSS workflows. + +The RSS workflow can be initiated using a custom YAML configuration file. A comprehensive list of parameters, including default settings and modifiable options, is available in `autoplex/auto/rss/rss_default_configuration.yaml`. When creating a new YAML file, any specified keys will override the corresponding default values. To start a new workflow, pass the path to your YAML file as the `config_file` argument in the `make` method. If you are using remote submission via jobflow-remote, please be aware that the configuration file has to be placed on the remote cluster and the file path has to reflect this as well (i.e., it is a path on the remote cluster). + +```python +from autoplex.auto.rss.flows import RssMaker +from fireworks import LaunchPad +from jobflow import Flow +from jobflow.managers.fireworks import flow_to_workflow + +rss_job = RssMaker(name="your workflow name").make(config_file='path/to/your/name.yaml') +wf = flow_to_workflow(rss_job) +lpad = LaunchPad.auto_load() +lpad.add_wf(wf) +``` + +The above code is based on [`FireWorks`](https://materialsproject.github.io/fireworks/) for job submission and management. You could also use [`jobflow-remote`](https://matgenix.github.io/jobflow-remote/), in which case the code snippet would change as follows. + + +```python +from autoplex.auto.rss.flows import RssMaker +from jobflow_remote import submit_flow + +rss_job = RssMaker(name="your workflow name").make(config_file='path/to/your/name.yaml') +resources = {"nodes": N, "partition": "name", "qos": "name", "time": "8:00:00", "mail_user": "your_email", "mail_type": "ALL", "account": "your account"} +print(submit_flow(rss_job, worker="your worker", resources=resources, project="your project name")) +``` + +For details on setting up `FireWorks`, see [FireWorks setup](../../../mongodb.md#fireworks-configuration) and for `jobflow-remote`, see [jobflow-remote setup](../../../jobflowremote.md). + +## Running the workflow with direct parameter specification + +As an alternative to using a YAML configuration file, the RSS workflow can be initiated by directly specifying parameters in the `make` method. This approach is ideal for cases where only a few parameters need to be customized. You can override the default settings by passing them as keyword arguments, offering a more flexible and lightweight way to set up the workflow. + +```python +rss_job = RssMaker(name="your workflow name").make(tag='Si', + buildcell_options=[{'NFORM': '{1,3,5}'}, + {'NFORM': '{2,4,6}'}], + hookean_repul=True, + hookean_paras={'(14, 14)': (100, 1.2)}) +``` + +If you choose to use the direct parameter specification method, at a minimum, you must provide the following arguments: + +- `tag`: defines the system's elements and stoichiometry (only for compounds). +- `buildcell_options`: controls the parameters for generating the initial randomized structures. +- `hookean_repul`: enables a strong repulsive force to avoid physically unrealistic structures. +- `hookean_paras`: specifies the Hookean repulsion parameters. + +> **Recommendation**: We strongly recommend enabling `hookean_repul`, as it applies a strong repulsive force when the distance between two atoms falls below a certain threshold. This ensures that the generated structures are physically reasonable. + +> **Note**: If both a YAML file and direct parameter specifications are provided, any overlapping parameters will be overridden by the directly specified values. + +## Building RSS models with various ML potentials + +Currently, `RssMaker` supports GAP (Gaussian Approximation Potential), ACE (Atomic Cluster Expansion), and three graph-based network models including NequIP, M3GNet, and MACE. You can specify the desired model using the `mlip_type` argument and adjust relevant hyperparameters within the `make` method. Default and adjustable hyperparameters are available in `autoplex/fitting/common/mlip-rss-defaults.json`. + +```python +rss_job = RssMaker(name="your workflow name").make(tag='SiO2', + ... # Other parameters here + mlip_type='MACE', + hidden_irreps="128x0e + 128x1o", + r_max=5.0) +``` + +> **Note**: We primarily recommend the GAP-RSS model for now, as GAP has demonstrated great stability with small datasets. Other models have not been thoroughly explored yet. However, we encourage users to experiment with and test other individual models or combinations for potentially interesting results. + +## Resuming workflow from point of interruption + +To resume an interrupted RSS workflow, use the `resume_from_previous_state` argument, which accepts a dictionary containing the necessary state information. Additionally, ensure that `train_from_scratch` is set to `False` to enable resuming from the previous state. This way, you are allowed to continue the workflow from any previously saved state. + +```python +rss_job = RssMaker(name="your workflow name").make(tag='SiO2', + ... # Other parameters here + train_from_scratch=False, + resume_from_previous_state={'test_error': 0.24, + 'pre_database_dir': 'path/to/pre-existing/database', + 'mlip_path': 'path/to/previous/MLIP-model', + 'isolated_atom_energies': {8: -0.16613333, 14: -0.16438578}, + }) +``` diff --git a/_sources/user/rss/index.md b/_sources/user/rss/index.md new file mode 100644 index 000000000..255b307c3 --- /dev/null +++ b/_sources/user/rss/index.md @@ -0,0 +1,13 @@ +(rss)= + +# RSS workflow + +This section provides tutorials for the random structure searching (RSS) workflow. + +```{toctree} +:maxdepth: 3 +flow/introduction/intro +flow/quick_start/start +flow/input/input +flow/example/example +``` \ No newline at end of file diff --git a/_sources/user/setup.md b/_sources/user/setup.md new file mode 100644 index 000000000..0796354b4 --- /dev/null +++ b/_sources/user/setup.md @@ -0,0 +1,141 @@ +Quick-start +================ + +This guide assumes that you have all the [Materials Project](https://github.com/materialsproject) framework software tools as well as a working [MongoDB](https://www.mongodb.com/) +database setup and have experience using [atomate2](https://github.com/materialsproject/atomate2). + +You can install `autoplex` simply by: + +``` +pip install autoplex[strict] +``` +This will install all the Python packages and dependencies needed for MLIP fits. + +Additionally, to fit and validate `ACEpotentials`, one also needs to install Julia, as `autoplex` relies on [ACEpotentials](https://acesuit.github.io/ACEpotentials.jl/dev/gettingstarted/installation/), which supports fitting of linear ACE. Currently, no Python package exists for the same. +Please run the following commands to enable the `ACEpotentials` fitting options and further functionality. + +Install Julia v1.9.2 + +```bash +curl -fsSL https://install.julialang.org | sh -s -- default-channel 1.9.2 +``` + +Once installed in the terminal, run the following commands to get Julia ACEpotentials dependencies. + +```bash +julia -e 'using Pkg; Pkg.Registry.add("General"); Pkg.Registry.add(Pkg.Registry.RegistrySpec(url="https://github.com/ACEsuit/ACEregistry")); Pkg.add(Pkg.PackageSpec(;name="ACEpotentials", version="0.6.7")); Pkg.add("DataFrames"); Pkg.add("CSV")' +``` + +## Enabling RSS workflows + +Additionally, `buildcell` as a part of `AIRSS` needs to be installed if one wants to use the RSS functionality: + +```bash +curl -O https://www.mtg.msm.cam.ac.uk/files/airss-0.9.3.tgz; tar -xf airss-0.9.3.tgz; rm airss-0.9.3.tgz; cd airss; make ; make install ; make neat; cd .. +``` + +## LAMMPS installation + +You only need to install LAMMPS, if you want to use J-ACE as your MLIP. +Recipe for compiling lammps-ace including the download of the `libpace.tar.gz` file: + +``` +git clone -b stable_29Aug2024_update1 https://github.com/lammps/lammps.git +cd lammps +mkdir build +cd build +wget -O libpace.tar.gz https://github.com/wcwitt/lammps-user-pace/archive/main.tar.gz + +cmake -C ../cmake/presets/clang.cmake -D BUILD_SHARED_LIBS=on -D BUILD_MPI=yes \ +-DMLIAP_ENABLE_PYTHON=yes -D PKG_PYTHON=on -D PKG_KOKKOS=yes -D Kokkos_ARCH_ZEN3=yes \ +-D PKG_PHONON=yes -D PKG_MOLECULE=yes -D PKG_MANYBODY=yes \ +-D Kokkos_ENABLE_OPENMP=yes -D BUILD_OMP=yes -D LAMMPS_EXCEPTIONS=yes \ +-D PKG_ML-PACE=yes -D PACELIB_MD5=$(md5sum libpace.tar.gz | awk '{print $1}') \ +-D CMAKE_INSTALL_PREFIX=$LAMMPS_INSTALL -D CMAKE_EXE_LINKER_FLAGS:STRING="-lgfortran" \ +../cmake + +make -j 16 +make install-python +``` + +$LAMMPS_INSTALL is the conda environment for installing the lammps-python interface. +Use `BUILD_MPI=yes` to enable MPI for parallelization. + +After the installation is completed, enter the following commands in the Python environment. +If you get the same output, it means the installation was successful. + +```python +from lammps import lammps; lmp = lammps() +``` +```bash +LAMMPS (29 Aug 2024 - Update 1) +OMP_NUM_THREADS environment is not set. Defaulting to 1 thread. (src/comm.cpp:98) + using 1 OpenMP thread(s) per MPI task +>>> +``` + +It is very important to have it compiled with Python (`-D PKG_PYTHON=on`) and +LIB PACE flags (`-D PACELIB_MD5=$(md5sum libpace.tar.gz | awk '{print $1}')`). + + +A more comprehensive `autoplex` installation guide can be found [here](installation/installation.md) and for a even more advanced +installation, you can also follow the [developer installation guide](../dev/dev_install.md). + + +## Workflow management + +You can manage your `autoplex` workflow using [`FireWorks`](https://materialsproject.github.io/fireworks/) or [`jobflow-remote`](https://matgenix.github.io/jobflow-remote/). +Please follow the installation and setup instructions on the respective guide website. +Both packages rely on the [MongoDB](https://www.mongodb.com/) database manager for data storage. + +We recommend using `jobflow-remote` as it is more flexible to use, especially on clusters where users cannot store their +own MongoDB. You can find a more comprehensive `jobflow-remote` tutorial [here](jobflowremote.md). + +Submission using `FireWorks`: +```python +from fireworks import LaunchPad +from jobflow.managers.fireworks import flow_to_workflow + +... + +autoplex_flow = ... + +wf = flow_to_workflow(autoplex_flow) + +# submit the workflow to the FireWorks launchpad +lpad = LaunchPad.auto_load() +lpad.add_wf(wf) +``` + +Submission using `jobflow-remote`: +```python +from jobflow_remote import submit_flow, set_run_config + +... + +autoplex_flow = ... + +# setting different job setups in the submission script directly: +resources = {"nodes": N, "partition": "name", "time": "01:00:00", "ntasks": ntasks, "qverbatim": "#SBATCH --get-user-env", + "mail_user": "your_email@adress", "mail_type": "ALL"} + # put your slurm submission keywords as needed + # you can add "qverbatim": "#SBATCH --get-user-env" in case your conda env is not activated automatically + +resources_phon = {"nodes": N, "partition": "name", "time": "05:00:00", "ntasks": ntasks, "qverbatim": "#SBATCH --get-user-env", + "mail_user": "your_email@adress", "mail_type": "ALL"} + +resources_ratt = {"nodes": N, "partition": "micro", "time": "03:00:00", "ntasks": ntasks, "qverbatim": "#SBATCH --get-user-env", + "mail_user": "your_email@adress", "mail_type": "ALL"} + +resources_mlip = {"nodes": N, "partition": "name", "time": "02:00:00", "ntasks": ntasks, "qverbatim": "#SBATCH --get-user-env", + "mail_user": "your_email@adress", "mail_type": "ALL"} + +autoplex_flow = set_run_config(autoplex_flow, name_filter="dft phonon static", resources=resources_phon) + +autoplex_flow = set_run_config(autoplex_flow, name_filter="dft rattle static", resources=resources_ratt) + +autoplex_flow = set_run_config(autoplex_flow, name_filter="machine_learning_fit", resources=resources_mlip) + +# submit the workflow to jobflow-remote +print(submit_flow(autoplex_flow, worker="autoplex_worker", resources=resources, project="autoplex")) +``` diff --git a/_sources/user/tutorials.md b/_sources/user/tutorials.md new file mode 100644 index 000000000..ba30ec2e4 --- /dev/null +++ b/_sources/user/tutorials.md @@ -0,0 +1,60 @@ +Tutorials +========== + +*`autoplex` tutorials written by [autoplex](https://github.com/autoatml/autoplex) developers.* + +The user is advised to have a general familiarity with the following software packages and tools: + * VASP + * the machine-learned interatomic potential (MLIP) framework that is aimed to be used + * the [Materials Project](https://next-gen.materialsproject.org/) framework + * atomate2 + * ase + * basic Python knowledge + +The tutorials are aimed to demonstrate the usage of `autoplex` to generate MLIPs and benchmark it to DFT results. +By the end of these tutorials, you should be able to: + +* use `autoplex` out of the box with default settings +* customize the workflow settings +* use any of the submodule workflows to build your own workflows + +## Tutorial table of content + +```{toctree} +:maxdepth: 2 +installation/installation +``` + +```{toctree} +:maxdepth: 3 +rss/index +``` + +```{toctree} +:maxdepth: 3 +phonon/index +``` + +## Contributors and Their Contributions + +- **[Christina Ertural](mailto:christina.ertural@bam.de)** + - Initial setup documentation + - Phonon flows: + - Flows documentation + - Data generation guide + - Fitting documentation + - Benchmarking documentation + - Contributed to installation guide (with Aakash Naik) + - Contributed to jobflow-remote and MongoDB setup (with Aakash Naik) + +- **[Aakash Naik](mailto:aakash.naik@bam.de)** + - Contributed to installation guide (with Christina Ertural) + - Jobflow-remote and MongoDB setup documentation (with Christina Ertural) + +- **[Yuanbin Liu](mailto:yuanbin.liu@chem.ox.ac.uk)** + - RSS documentation + +- **[Janine George](mailto:janine.george@bam.de)** + - Phonon flows documentation (with Christina Ertural) + + diff --git a/_sphinx_design_static/design-tabs.js b/_sphinx_design_static/design-tabs.js new file mode 100644 index 000000000..b25bd6a4f --- /dev/null +++ b/_sphinx_design_static/design-tabs.js @@ -0,0 +1,101 @@ +// @ts-check + +// Extra JS capability for selected tabs to be synced +// The selection is stored in local storage so that it persists across page loads. + +/** + * @type {Record} + */ +let sd_id_to_elements = {}; +const storageKeyPrefix = "sphinx-design-tab-id-"; + +/** + * Create a key for a tab element. + * @param {HTMLElement} el - The tab element. + * @returns {[string, string, string] | null} - The key. + * + */ +function create_key(el) { + let syncId = el.getAttribute("data-sync-id"); + let syncGroup = el.getAttribute("data-sync-group"); + if (!syncId || !syncGroup) return null; + return [syncGroup, syncId, syncGroup + "--" + syncId]; +} + +/** + * Initialize the tab selection. + * + */ +function ready() { + // Find all tabs with sync data + + /** @type {string[]} */ + let groups = []; + + document.querySelectorAll(".sd-tab-label").forEach((label) => { + if (label instanceof HTMLElement) { + let data = create_key(label); + if (data) { + let [group, id, key] = data; + + // add click event listener + // @ts-ignore + label.onclick = onSDLabelClick; + + // store map of key to elements + if (!sd_id_to_elements[key]) { + sd_id_to_elements[key] = []; + } + sd_id_to_elements[key].push(label); + + if (groups.indexOf(group) === -1) { + groups.push(group); + // Check if a specific tab has been selected via URL parameter + const tabParam = new URLSearchParams(window.location.search).get( + group + ); + if (tabParam) { + console.log( + "sphinx-design: Selecting tab id for group '" + + group + + "' from URL parameter: " + + tabParam + ); + window.sessionStorage.setItem(storageKeyPrefix + group, tabParam); + } + } + + // Check is a specific tab has been selected previously + let previousId = window.sessionStorage.getItem( + storageKeyPrefix + group + ); + if (previousId === id) { + // console.log( + // "sphinx-design: Selecting tab from session storage: " + id + // ); + // @ts-ignore + label.previousElementSibling.checked = true; + } + } + } + }); +} + +/** + * Activate other tabs with the same sync id. + * + * @this {HTMLElement} - The element that was clicked. + */ +function onSDLabelClick() { + let data = create_key(this); + if (!data) return; + let [group, id, key] = data; + for (const label of sd_id_to_elements[key]) { + if (label === this) continue; + // @ts-ignore + label.previousElementSibling.checked = true; + } + window.sessionStorage.setItem(storageKeyPrefix + group, id); +} + +document.addEventListener("DOMContentLoaded", ready, false); diff --git a/_sphinx_design_static/sphinx-design.min.css b/_sphinx_design_static/sphinx-design.min.css new file mode 100644 index 000000000..860c36da0 --- /dev/null +++ b/_sphinx_design_static/sphinx-design.min.css @@ -0,0 +1 @@ +.sd-bg-primary{background-color:var(--sd-color-primary) !important}.sd-bg-text-primary{color:var(--sd-color-primary-text) !important}button.sd-bg-primary:focus,button.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}a.sd-bg-primary:focus,a.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}.sd-bg-secondary{background-color:var(--sd-color-secondary) !important}.sd-bg-text-secondary{color:var(--sd-color-secondary-text) !important}button.sd-bg-secondary:focus,button.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}a.sd-bg-secondary:focus,a.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}.sd-bg-success{background-color:var(--sd-color-success) !important}.sd-bg-text-success{color:var(--sd-color-success-text) !important}button.sd-bg-success:focus,button.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}a.sd-bg-success:focus,a.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}.sd-bg-info{background-color:var(--sd-color-info) !important}.sd-bg-text-info{color:var(--sd-color-info-text) !important}button.sd-bg-info:focus,button.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}a.sd-bg-info:focus,a.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}.sd-bg-warning{background-color:var(--sd-color-warning) !important}.sd-bg-text-warning{color:var(--sd-color-warning-text) !important}button.sd-bg-warning:focus,button.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}a.sd-bg-warning:focus,a.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}.sd-bg-danger{background-color:var(--sd-color-danger) !important}.sd-bg-text-danger{color:var(--sd-color-danger-text) !important}button.sd-bg-danger:focus,button.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}a.sd-bg-danger:focus,a.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}.sd-bg-light{background-color:var(--sd-color-light) !important}.sd-bg-text-light{color:var(--sd-color-light-text) !important}button.sd-bg-light:focus,button.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}a.sd-bg-light:focus,a.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}.sd-bg-muted{background-color:var(--sd-color-muted) !important}.sd-bg-text-muted{color:var(--sd-color-muted-text) !important}button.sd-bg-muted:focus,button.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}a.sd-bg-muted:focus,a.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}.sd-bg-dark{background-color:var(--sd-color-dark) !important}.sd-bg-text-dark{color:var(--sd-color-dark-text) !important}button.sd-bg-dark:focus,button.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}a.sd-bg-dark:focus,a.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}.sd-bg-black{background-color:var(--sd-color-black) !important}.sd-bg-text-black{color:var(--sd-color-black-text) !important}button.sd-bg-black:focus,button.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}a.sd-bg-black:focus,a.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}.sd-bg-white{background-color:var(--sd-color-white) !important}.sd-bg-text-white{color:var(--sd-color-white-text) !important}button.sd-bg-white:focus,button.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}a.sd-bg-white:focus,a.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}.sd-text-primary,.sd-text-primary>p{color:var(--sd-color-primary) !important}a.sd-text-primary:focus,a.sd-text-primary:hover{color:var(--sd-color-primary-highlight) !important}.sd-text-secondary,.sd-text-secondary>p{color:var(--sd-color-secondary) !important}a.sd-text-secondary:focus,a.sd-text-secondary:hover{color:var(--sd-color-secondary-highlight) !important}.sd-text-success,.sd-text-success>p{color:var(--sd-color-success) !important}a.sd-text-success:focus,a.sd-text-success:hover{color:var(--sd-color-success-highlight) !important}.sd-text-info,.sd-text-info>p{color:var(--sd-color-info) !important}a.sd-text-info:focus,a.sd-text-info:hover{color:var(--sd-color-info-highlight) !important}.sd-text-warning,.sd-text-warning>p{color:var(--sd-color-warning) !important}a.sd-text-warning:focus,a.sd-text-warning:hover{color:var(--sd-color-warning-highlight) !important}.sd-text-danger,.sd-text-danger>p{color:var(--sd-color-danger) !important}a.sd-text-danger:focus,a.sd-text-danger:hover{color:var(--sd-color-danger-highlight) !important}.sd-text-light,.sd-text-light>p{color:var(--sd-color-light) !important}a.sd-text-light:focus,a.sd-text-light:hover{color:var(--sd-color-light-highlight) !important}.sd-text-muted,.sd-text-muted>p{color:var(--sd-color-muted) !important}a.sd-text-muted:focus,a.sd-text-muted:hover{color:var(--sd-color-muted-highlight) !important}.sd-text-dark,.sd-text-dark>p{color:var(--sd-color-dark) !important}a.sd-text-dark:focus,a.sd-text-dark:hover{color:var(--sd-color-dark-highlight) !important}.sd-text-black,.sd-text-black>p{color:var(--sd-color-black) !important}a.sd-text-black:focus,a.sd-text-black:hover{color:var(--sd-color-black-highlight) !important}.sd-text-white,.sd-text-white>p{color:var(--sd-color-white) !important}a.sd-text-white:focus,a.sd-text-white:hover{color:var(--sd-color-white-highlight) !important}.sd-outline-primary{border-color:var(--sd-color-primary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-primary:focus,a.sd-outline-primary:hover{border-color:var(--sd-color-primary-highlight) !important}.sd-outline-secondary{border-color:var(--sd-color-secondary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-secondary:focus,a.sd-outline-secondary:hover{border-color:var(--sd-color-secondary-highlight) !important}.sd-outline-success{border-color:var(--sd-color-success) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-success:focus,a.sd-outline-success:hover{border-color:var(--sd-color-success-highlight) !important}.sd-outline-info{border-color:var(--sd-color-info) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-info:focus,a.sd-outline-info:hover{border-color:var(--sd-color-info-highlight) !important}.sd-outline-warning{border-color:var(--sd-color-warning) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-warning:focus,a.sd-outline-warning:hover{border-color:var(--sd-color-warning-highlight) !important}.sd-outline-danger{border-color:var(--sd-color-danger) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-danger:focus,a.sd-outline-danger:hover{border-color:var(--sd-color-danger-highlight) !important}.sd-outline-light{border-color:var(--sd-color-light) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-light:focus,a.sd-outline-light:hover{border-color:var(--sd-color-light-highlight) !important}.sd-outline-muted{border-color:var(--sd-color-muted) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-muted:focus,a.sd-outline-muted:hover{border-color:var(--sd-color-muted-highlight) !important}.sd-outline-dark{border-color:var(--sd-color-dark) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-dark:focus,a.sd-outline-dark:hover{border-color:var(--sd-color-dark-highlight) !important}.sd-outline-black{border-color:var(--sd-color-black) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-black:focus,a.sd-outline-black:hover{border-color:var(--sd-color-black-highlight) !important}.sd-outline-white{border-color:var(--sd-color-white) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-white:focus,a.sd-outline-white:hover{border-color:var(--sd-color-white-highlight) !important}.sd-bg-transparent{background-color:transparent !important}.sd-outline-transparent{border-color:transparent !important}.sd-text-transparent{color:transparent !important}.sd-p-0{padding:0 !important}.sd-pt-0,.sd-py-0{padding-top:0 !important}.sd-pr-0,.sd-px-0{padding-right:0 !important}.sd-pb-0,.sd-py-0{padding-bottom:0 !important}.sd-pl-0,.sd-px-0{padding-left:0 !important}.sd-p-1{padding:.25rem !important}.sd-pt-1,.sd-py-1{padding-top:.25rem !important}.sd-pr-1,.sd-px-1{padding-right:.25rem !important}.sd-pb-1,.sd-py-1{padding-bottom:.25rem !important}.sd-pl-1,.sd-px-1{padding-left:.25rem !important}.sd-p-2{padding:.5rem !important}.sd-pt-2,.sd-py-2{padding-top:.5rem !important}.sd-pr-2,.sd-px-2{padding-right:.5rem !important}.sd-pb-2,.sd-py-2{padding-bottom:.5rem !important}.sd-pl-2,.sd-px-2{padding-left:.5rem !important}.sd-p-3{padding:1rem !important}.sd-pt-3,.sd-py-3{padding-top:1rem !important}.sd-pr-3,.sd-px-3{padding-right:1rem !important}.sd-pb-3,.sd-py-3{padding-bottom:1rem !important}.sd-pl-3,.sd-px-3{padding-left:1rem !important}.sd-p-4{padding:1.5rem !important}.sd-pt-4,.sd-py-4{padding-top:1.5rem !important}.sd-pr-4,.sd-px-4{padding-right:1.5rem !important}.sd-pb-4,.sd-py-4{padding-bottom:1.5rem !important}.sd-pl-4,.sd-px-4{padding-left:1.5rem !important}.sd-p-5{padding:3rem !important}.sd-pt-5,.sd-py-5{padding-top:3rem !important}.sd-pr-5,.sd-px-5{padding-right:3rem !important}.sd-pb-5,.sd-py-5{padding-bottom:3rem !important}.sd-pl-5,.sd-px-5{padding-left:3rem !important}.sd-m-auto{margin:auto !important}.sd-mt-auto,.sd-my-auto{margin-top:auto !important}.sd-mr-auto,.sd-mx-auto{margin-right:auto !important}.sd-mb-auto,.sd-my-auto{margin-bottom:auto !important}.sd-ml-auto,.sd-mx-auto{margin-left:auto !important}.sd-m-0{margin:0 !important}.sd-mt-0,.sd-my-0{margin-top:0 !important}.sd-mr-0,.sd-mx-0{margin-right:0 !important}.sd-mb-0,.sd-my-0{margin-bottom:0 !important}.sd-ml-0,.sd-mx-0{margin-left:0 !important}.sd-m-1{margin:.25rem !important}.sd-mt-1,.sd-my-1{margin-top:.25rem !important}.sd-mr-1,.sd-mx-1{margin-right:.25rem !important}.sd-mb-1,.sd-my-1{margin-bottom:.25rem !important}.sd-ml-1,.sd-mx-1{margin-left:.25rem !important}.sd-m-2{margin:.5rem !important}.sd-mt-2,.sd-my-2{margin-top:.5rem !important}.sd-mr-2,.sd-mx-2{margin-right:.5rem !important}.sd-mb-2,.sd-my-2{margin-bottom:.5rem !important}.sd-ml-2,.sd-mx-2{margin-left:.5rem !important}.sd-m-3{margin:1rem !important}.sd-mt-3,.sd-my-3{margin-top:1rem !important}.sd-mr-3,.sd-mx-3{margin-right:1rem !important}.sd-mb-3,.sd-my-3{margin-bottom:1rem !important}.sd-ml-3,.sd-mx-3{margin-left:1rem !important}.sd-m-4{margin:1.5rem !important}.sd-mt-4,.sd-my-4{margin-top:1.5rem !important}.sd-mr-4,.sd-mx-4{margin-right:1.5rem !important}.sd-mb-4,.sd-my-4{margin-bottom:1.5rem !important}.sd-ml-4,.sd-mx-4{margin-left:1.5rem !important}.sd-m-5{margin:3rem !important}.sd-mt-5,.sd-my-5{margin-top:3rem !important}.sd-mr-5,.sd-mx-5{margin-right:3rem !important}.sd-mb-5,.sd-my-5{margin-bottom:3rem !important}.sd-ml-5,.sd-mx-5{margin-left:3rem !important}.sd-w-25{width:25% !important}.sd-w-50{width:50% !important}.sd-w-75{width:75% !important}.sd-w-100{width:100% !important}.sd-w-auto{width:auto !important}.sd-h-25{height:25% !important}.sd-h-50{height:50% !important}.sd-h-75{height:75% !important}.sd-h-100{height:100% !important}.sd-h-auto{height:auto !important}.sd-d-none{display:none !important}.sd-d-inline{display:inline !important}.sd-d-inline-block{display:inline-block !important}.sd-d-block{display:block !important}.sd-d-grid{display:grid !important}.sd-d-flex-row{display:-ms-flexbox !important;display:flex !important;flex-direction:row !important}.sd-d-flex-column{display:-ms-flexbox !important;display:flex !important;flex-direction:column !important}.sd-d-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}@media(min-width: 576px){.sd-d-sm-none{display:none !important}.sd-d-sm-inline{display:inline !important}.sd-d-sm-inline-block{display:inline-block !important}.sd-d-sm-block{display:block !important}.sd-d-sm-grid{display:grid !important}.sd-d-sm-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-sm-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 768px){.sd-d-md-none{display:none !important}.sd-d-md-inline{display:inline !important}.sd-d-md-inline-block{display:inline-block !important}.sd-d-md-block{display:block !important}.sd-d-md-grid{display:grid !important}.sd-d-md-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-md-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 992px){.sd-d-lg-none{display:none !important}.sd-d-lg-inline{display:inline !important}.sd-d-lg-inline-block{display:inline-block !important}.sd-d-lg-block{display:block !important}.sd-d-lg-grid{display:grid !important}.sd-d-lg-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-lg-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 1200px){.sd-d-xl-none{display:none !important}.sd-d-xl-inline{display:inline !important}.sd-d-xl-inline-block{display:inline-block !important}.sd-d-xl-block{display:block !important}.sd-d-xl-grid{display:grid !important}.sd-d-xl-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-xl-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}.sd-align-major-start{justify-content:flex-start !important}.sd-align-major-end{justify-content:flex-end !important}.sd-align-major-center{justify-content:center !important}.sd-align-major-justify{justify-content:space-between !important}.sd-align-major-spaced{justify-content:space-evenly !important}.sd-align-minor-start{align-items:flex-start !important}.sd-align-minor-end{align-items:flex-end !important}.sd-align-minor-center{align-items:center !important}.sd-align-minor-stretch{align-items:stretch !important}.sd-text-justify{text-align:justify !important}.sd-text-left{text-align:left !important}.sd-text-right{text-align:right !important}.sd-text-center{text-align:center !important}.sd-font-weight-light{font-weight:300 !important}.sd-font-weight-lighter{font-weight:lighter !important}.sd-font-weight-normal{font-weight:400 !important}.sd-font-weight-bold{font-weight:700 !important}.sd-font-weight-bolder{font-weight:bolder !important}.sd-font-italic{font-style:italic !important}.sd-text-decoration-none{text-decoration:none !important}.sd-text-lowercase{text-transform:lowercase !important}.sd-text-uppercase{text-transform:uppercase !important}.sd-text-capitalize{text-transform:capitalize !important}.sd-text-wrap{white-space:normal !important}.sd-text-nowrap{white-space:nowrap !important}.sd-text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sd-fs-1,.sd-fs-1>p{font-size:calc(1.375rem + 1.5vw) !important;line-height:unset !important}.sd-fs-2,.sd-fs-2>p{font-size:calc(1.325rem + 0.9vw) !important;line-height:unset !important}.sd-fs-3,.sd-fs-3>p{font-size:calc(1.3rem + 0.6vw) !important;line-height:unset !important}.sd-fs-4,.sd-fs-4>p{font-size:calc(1.275rem + 0.3vw) !important;line-height:unset !important}.sd-fs-5,.sd-fs-5>p{font-size:1.25rem !important;line-height:unset !important}.sd-fs-6,.sd-fs-6>p{font-size:1rem !important;line-height:unset !important}.sd-border-0{border:0 solid !important}.sd-border-top-0{border-top:0 solid !important}.sd-border-bottom-0{border-bottom:0 solid !important}.sd-border-right-0{border-right:0 solid !important}.sd-border-left-0{border-left:0 solid !important}.sd-border-1{border:1px solid !important}.sd-border-top-1{border-top:1px solid !important}.sd-border-bottom-1{border-bottom:1px solid !important}.sd-border-right-1{border-right:1px solid !important}.sd-border-left-1{border-left:1px solid !important}.sd-border-2{border:2px solid !important}.sd-border-top-2{border-top:2px solid !important}.sd-border-bottom-2{border-bottom:2px solid !important}.sd-border-right-2{border-right:2px solid !important}.sd-border-left-2{border-left:2px solid !important}.sd-border-3{border:3px solid !important}.sd-border-top-3{border-top:3px solid !important}.sd-border-bottom-3{border-bottom:3px solid !important}.sd-border-right-3{border-right:3px solid !important}.sd-border-left-3{border-left:3px solid !important}.sd-border-4{border:4px solid !important}.sd-border-top-4{border-top:4px solid !important}.sd-border-bottom-4{border-bottom:4px solid !important}.sd-border-right-4{border-right:4px solid !important}.sd-border-left-4{border-left:4px solid !important}.sd-border-5{border:5px solid !important}.sd-border-top-5{border-top:5px solid !important}.sd-border-bottom-5{border-bottom:5px solid !important}.sd-border-right-5{border-right:5px solid !important}.sd-border-left-5{border-left:5px solid !important}.sd-rounded-0{border-radius:0 !important}.sd-rounded-1{border-radius:.2rem !important}.sd-rounded-2{border-radius:.3rem !important}.sd-rounded-3{border-radius:.5rem !important}.sd-rounded-pill{border-radius:50rem !important}.sd-rounded-circle{border-radius:50% !important}.shadow-none{box-shadow:none !important}.sd-shadow-sm{box-shadow:0 .125rem .25rem var(--sd-color-shadow) !important}.sd-shadow-md{box-shadow:0 .5rem 1rem var(--sd-color-shadow) !important}.sd-shadow-lg{box-shadow:0 1rem 3rem var(--sd-color-shadow) !important}@keyframes sd-slide-from-left{0%{transform:translateX(-100%)}100%{transform:translateX(0)}}@keyframes sd-slide-from-right{0%{transform:translateX(200%)}100%{transform:translateX(0)}}@keyframes sd-grow100{0%{transform:scale(0);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50{0%{transform:scale(0.5);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50-rot20{0%{transform:scale(0.5) rotateZ(-20deg);opacity:.5}75%{transform:scale(1) rotateZ(5deg);opacity:1}95%{transform:scale(1) rotateZ(-1deg);opacity:1}100%{transform:scale(1) rotateZ(0);opacity:1}}.sd-animate-slide-from-left{animation:1s ease-out 0s 1 normal none running sd-slide-from-left}.sd-animate-slide-from-right{animation:1s ease-out 0s 1 normal none running sd-slide-from-right}.sd-animate-grow100{animation:1s ease-out 0s 1 normal none running sd-grow100}.sd-animate-grow50{animation:1s ease-out 0s 1 normal none running sd-grow50}.sd-animate-grow50-rot20{animation:1s ease-out 0s 1 normal none running sd-grow50-rot20}.sd-badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.sd-badge:empty{display:none}a.sd-badge{text-decoration:none}.sd-btn .sd-badge{position:relative;top:-1px}.sd-btn{background-color:transparent;border:1px solid transparent;border-radius:.25rem;cursor:pointer;display:inline-block;font-weight:400;font-size:1rem;line-height:1.5;padding:.375rem .75rem;text-align:center;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;vertical-align:middle;user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none}.sd-btn:hover{text-decoration:none}@media(prefers-reduced-motion: reduce){.sd-btn{transition:none}}.sd-btn-primary,.sd-btn-outline-primary:hover,.sd-btn-outline-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-primary:hover,.sd-btn-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary-highlight) !important;border-color:var(--sd-color-primary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-primary{color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary,.sd-btn-outline-secondary:hover,.sd-btn-outline-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary:hover,.sd-btn-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary-highlight) !important;border-color:var(--sd-color-secondary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-secondary{color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success,.sd-btn-outline-success:hover,.sd-btn-outline-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success:hover,.sd-btn-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success-highlight) !important;border-color:var(--sd-color-success-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-success{color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info,.sd-btn-outline-info:hover,.sd-btn-outline-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info:hover,.sd-btn-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info-highlight) !important;border-color:var(--sd-color-info-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-info{color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning,.sd-btn-outline-warning:hover,.sd-btn-outline-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning:hover,.sd-btn-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning-highlight) !important;border-color:var(--sd-color-warning-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-warning{color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger,.sd-btn-outline-danger:hover,.sd-btn-outline-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger:hover,.sd-btn-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger-highlight) !important;border-color:var(--sd-color-danger-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-danger{color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light,.sd-btn-outline-light:hover,.sd-btn-outline-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light:hover,.sd-btn-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light-highlight) !important;border-color:var(--sd-color-light-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-light{color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted,.sd-btn-outline-muted:hover,.sd-btn-outline-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted:hover,.sd-btn-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted-highlight) !important;border-color:var(--sd-color-muted-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-muted{color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark,.sd-btn-outline-dark:hover,.sd-btn-outline-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark:hover,.sd-btn-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark-highlight) !important;border-color:var(--sd-color-dark-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-dark{color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black,.sd-btn-outline-black:hover,.sd-btn-outline-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black:hover,.sd-btn-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black-highlight) !important;border-color:var(--sd-color-black-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-black{color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white,.sd-btn-outline-white:hover,.sd-btn-outline-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white:hover,.sd-btn-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white-highlight) !important;border-color:var(--sd-color-white-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-white{color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.sd-hide-link-text{font-size:0}.sd-octicon,.sd-material-icon{display:inline-block;fill:currentColor;vertical-align:middle}.sd-avatar-xs{border-radius:50%;object-fit:cover;object-position:center;width:1rem;height:1rem}.sd-avatar-sm{border-radius:50%;object-fit:cover;object-position:center;width:3rem;height:3rem}.sd-avatar-md{border-radius:50%;object-fit:cover;object-position:center;width:5rem;height:5rem}.sd-avatar-lg{border-radius:50%;object-fit:cover;object-position:center;width:7rem;height:7rem}.sd-avatar-xl{border-radius:50%;object-fit:cover;object-position:center;width:10rem;height:10rem}.sd-avatar-inherit{border-radius:50%;object-fit:cover;object-position:center;width:inherit;height:inherit}.sd-avatar-initial{border-radius:50%;object-fit:cover;object-position:center;width:initial;height:initial}.sd-card{background-clip:border-box;background-color:var(--sd-color-card-background);border:1px solid var(--sd-color-card-border);border-radius:.25rem;color:var(--sd-color-card-text);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;position:relative;word-wrap:break-word}.sd-card>hr{margin-left:0;margin-right:0}.sd-card-hover:hover{border-color:var(--sd-color-card-border-hover);transform:scale(1.01)}.sd-card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem 1rem}.sd-card-title{margin-bottom:.5rem}.sd-card-subtitle{margin-top:-0.25rem;margin-bottom:0}.sd-card-text:last-child{margin-bottom:0}.sd-card-link:hover{text-decoration:none}.sd-card-link+.card-link{margin-left:1rem}.sd-card-header{padding:.5rem 1rem;margin-bottom:0;background-color:var(--sd-color-card-header);border-bottom:1px solid var(--sd-color-card-border)}.sd-card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.sd-card-footer{padding:.5rem 1rem;background-color:var(--sd-color-card-footer);border-top:1px solid var(--sd-color-card-border)}.sd-card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.sd-card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.sd-card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.sd-card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom,.sd-card-img-top{width:100%}.sd-card-img,.sd-card-img-top{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom{border-bottom-left-radius:calc(0.25rem - 1px);border-bottom-right-radius:calc(0.25rem - 1px)}.sd-cards-carousel{width:100%;display:flex;flex-wrap:nowrap;-ms-flex-direction:row;flex-direction:row;overflow-x:hidden;scroll-snap-type:x mandatory}.sd-cards-carousel.sd-show-scrollbar{overflow-x:auto}.sd-cards-carousel:hover,.sd-cards-carousel:focus{overflow-x:auto}.sd-cards-carousel>.sd-card{flex-shrink:0;scroll-snap-align:start}.sd-cards-carousel>.sd-card:not(:last-child){margin-right:3px}.sd-card-cols-1>.sd-card{width:90%}.sd-card-cols-2>.sd-card{width:45%}.sd-card-cols-3>.sd-card{width:30%}.sd-card-cols-4>.sd-card{width:22.5%}.sd-card-cols-5>.sd-card{width:18%}.sd-card-cols-6>.sd-card{width:15%}.sd-card-cols-7>.sd-card{width:12.8571428571%}.sd-card-cols-8>.sd-card{width:11.25%}.sd-card-cols-9>.sd-card{width:10%}.sd-card-cols-10>.sd-card{width:9%}.sd-card-cols-11>.sd-card{width:8.1818181818%}.sd-card-cols-12>.sd-card{width:7.5%}.sd-container,.sd-container-fluid,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container-xl{margin-left:auto;margin-right:auto;padding-left:var(--sd-gutter-x, 0.75rem);padding-right:var(--sd-gutter-x, 0.75rem);width:100%}@media(min-width: 576px){.sd-container-sm,.sd-container{max-width:540px}}@media(min-width: 768px){.sd-container-md,.sd-container-sm,.sd-container{max-width:720px}}@media(min-width: 992px){.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:960px}}@media(min-width: 1200px){.sd-container-xl,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:1140px}}.sd-row{--sd-gutter-x: 1.5rem;--sd-gutter-y: 0;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-top:calc(var(--sd-gutter-y) * -1);margin-right:calc(var(--sd-gutter-x) * -0.5);margin-left:calc(var(--sd-gutter-x) * -0.5)}.sd-row>*{box-sizing:border-box;flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--sd-gutter-x) * 0.5);padding-left:calc(var(--sd-gutter-x) * 0.5);margin-top:var(--sd-gutter-y)}.sd-col{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-auto>*{flex:0 0 auto;width:auto}.sd-row-cols-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}@media(min-width: 576px){.sd-col-sm{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-sm-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-sm-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-sm-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-sm-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-sm-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-sm-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-sm-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-sm-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-sm-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-sm-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-sm-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-sm-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-sm-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 768px){.sd-col-md{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-md-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-md-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-md-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-md-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-md-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-md-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-md-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-md-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-md-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-md-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-md-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-md-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-md-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 992px){.sd-col-lg{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-lg-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-lg-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-lg-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-lg-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-lg-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-lg-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-lg-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-lg-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-lg-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-lg-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-lg-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-lg-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-lg-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 1200px){.sd-col-xl{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-xl-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-xl-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-xl-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-xl-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-xl-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-xl-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-xl-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-xl-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-xl-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-xl-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-xl-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-xl-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-xl-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}.sd-col-auto{flex:0 0 auto;-ms-flex:0 0 auto;width:auto}.sd-col-1{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}.sd-col-2{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-col-3{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-col-4{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-col-5{flex:0 0 auto;-ms-flex:0 0 auto;width:41.6666666667%}.sd-col-6{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-col-7{flex:0 0 auto;-ms-flex:0 0 auto;width:58.3333333333%}.sd-col-8{flex:0 0 auto;-ms-flex:0 0 auto;width:66.6666666667%}.sd-col-9{flex:0 0 auto;-ms-flex:0 0 auto;width:75%}.sd-col-10{flex:0 0 auto;-ms-flex:0 0 auto;width:83.3333333333%}.sd-col-11{flex:0 0 auto;-ms-flex:0 0 auto;width:91.6666666667%}.sd-col-12{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-g-0,.sd-gy-0{--sd-gutter-y: 0}.sd-g-0,.sd-gx-0{--sd-gutter-x: 0}.sd-g-1,.sd-gy-1{--sd-gutter-y: 0.25rem}.sd-g-1,.sd-gx-1{--sd-gutter-x: 0.25rem}.sd-g-2,.sd-gy-2{--sd-gutter-y: 0.5rem}.sd-g-2,.sd-gx-2{--sd-gutter-x: 0.5rem}.sd-g-3,.sd-gy-3{--sd-gutter-y: 1rem}.sd-g-3,.sd-gx-3{--sd-gutter-x: 1rem}.sd-g-4,.sd-gy-4{--sd-gutter-y: 1.5rem}.sd-g-4,.sd-gx-4{--sd-gutter-x: 1.5rem}.sd-g-5,.sd-gy-5{--sd-gutter-y: 3rem}.sd-g-5,.sd-gx-5{--sd-gutter-x: 3rem}@media(min-width: 576px){.sd-col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-sm-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-sm-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-sm-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-sm-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-sm-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-sm-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-sm-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-sm-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-sm-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-sm-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-sm-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-sm-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-sm-0,.sd-gy-sm-0{--sd-gutter-y: 0}.sd-g-sm-0,.sd-gx-sm-0{--sd-gutter-x: 0}.sd-g-sm-1,.sd-gy-sm-1{--sd-gutter-y: 0.25rem}.sd-g-sm-1,.sd-gx-sm-1{--sd-gutter-x: 0.25rem}.sd-g-sm-2,.sd-gy-sm-2{--sd-gutter-y: 0.5rem}.sd-g-sm-2,.sd-gx-sm-2{--sd-gutter-x: 0.5rem}.sd-g-sm-3,.sd-gy-sm-3{--sd-gutter-y: 1rem}.sd-g-sm-3,.sd-gx-sm-3{--sd-gutter-x: 1rem}.sd-g-sm-4,.sd-gy-sm-4{--sd-gutter-y: 1.5rem}.sd-g-sm-4,.sd-gx-sm-4{--sd-gutter-x: 1.5rem}.sd-g-sm-5,.sd-gy-sm-5{--sd-gutter-y: 3rem}.sd-g-sm-5,.sd-gx-sm-5{--sd-gutter-x: 3rem}}@media(min-width: 768px){.sd-col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-md-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-md-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-md-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-md-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-md-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-md-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-md-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-md-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-md-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-md-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-md-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-md-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-md-0,.sd-gy-md-0{--sd-gutter-y: 0}.sd-g-md-0,.sd-gx-md-0{--sd-gutter-x: 0}.sd-g-md-1,.sd-gy-md-1{--sd-gutter-y: 0.25rem}.sd-g-md-1,.sd-gx-md-1{--sd-gutter-x: 0.25rem}.sd-g-md-2,.sd-gy-md-2{--sd-gutter-y: 0.5rem}.sd-g-md-2,.sd-gx-md-2{--sd-gutter-x: 0.5rem}.sd-g-md-3,.sd-gy-md-3{--sd-gutter-y: 1rem}.sd-g-md-3,.sd-gx-md-3{--sd-gutter-x: 1rem}.sd-g-md-4,.sd-gy-md-4{--sd-gutter-y: 1.5rem}.sd-g-md-4,.sd-gx-md-4{--sd-gutter-x: 1.5rem}.sd-g-md-5,.sd-gy-md-5{--sd-gutter-y: 3rem}.sd-g-md-5,.sd-gx-md-5{--sd-gutter-x: 3rem}}@media(min-width: 992px){.sd-col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-lg-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-lg-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-lg-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-lg-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-lg-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-lg-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-lg-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-lg-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-lg-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-lg-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-lg-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-lg-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-lg-0,.sd-gy-lg-0{--sd-gutter-y: 0}.sd-g-lg-0,.sd-gx-lg-0{--sd-gutter-x: 0}.sd-g-lg-1,.sd-gy-lg-1{--sd-gutter-y: 0.25rem}.sd-g-lg-1,.sd-gx-lg-1{--sd-gutter-x: 0.25rem}.sd-g-lg-2,.sd-gy-lg-2{--sd-gutter-y: 0.5rem}.sd-g-lg-2,.sd-gx-lg-2{--sd-gutter-x: 0.5rem}.sd-g-lg-3,.sd-gy-lg-3{--sd-gutter-y: 1rem}.sd-g-lg-3,.sd-gx-lg-3{--sd-gutter-x: 1rem}.sd-g-lg-4,.sd-gy-lg-4{--sd-gutter-y: 1.5rem}.sd-g-lg-4,.sd-gx-lg-4{--sd-gutter-x: 1.5rem}.sd-g-lg-5,.sd-gy-lg-5{--sd-gutter-y: 3rem}.sd-g-lg-5,.sd-gx-lg-5{--sd-gutter-x: 3rem}}@media(min-width: 1200px){.sd-col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-xl-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-xl-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-xl-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-xl-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-xl-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-xl-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-xl-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-xl-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-xl-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-xl-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-xl-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-xl-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-xl-0,.sd-gy-xl-0{--sd-gutter-y: 0}.sd-g-xl-0,.sd-gx-xl-0{--sd-gutter-x: 0}.sd-g-xl-1,.sd-gy-xl-1{--sd-gutter-y: 0.25rem}.sd-g-xl-1,.sd-gx-xl-1{--sd-gutter-x: 0.25rem}.sd-g-xl-2,.sd-gy-xl-2{--sd-gutter-y: 0.5rem}.sd-g-xl-2,.sd-gx-xl-2{--sd-gutter-x: 0.5rem}.sd-g-xl-3,.sd-gy-xl-3{--sd-gutter-y: 1rem}.sd-g-xl-3,.sd-gx-xl-3{--sd-gutter-x: 1rem}.sd-g-xl-4,.sd-gy-xl-4{--sd-gutter-y: 1.5rem}.sd-g-xl-4,.sd-gx-xl-4{--sd-gutter-x: 1.5rem}.sd-g-xl-5,.sd-gy-xl-5{--sd-gutter-y: 3rem}.sd-g-xl-5,.sd-gx-xl-5{--sd-gutter-x: 3rem}}.sd-flex-row-reverse{flex-direction:row-reverse !important}details.sd-dropdown{position:relative;font-size:var(--sd-fontsize-dropdown)}details.sd-dropdown:hover{cursor:pointer}details.sd-dropdown .sd-summary-content{cursor:default}details.sd-dropdown summary.sd-summary-title{padding:.5em .6em .5em 1em;font-size:var(--sd-fontsize-dropdown-title);font-weight:var(--sd-fontweight-dropdown-title);user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;list-style:none;display:inline-flex;justify-content:space-between}details.sd-dropdown summary.sd-summary-title::-webkit-details-marker{display:none}details.sd-dropdown summary.sd-summary-title:focus{outline:none}details.sd-dropdown summary.sd-summary-title .sd-summary-icon{margin-right:.6em;display:inline-flex;align-items:center}details.sd-dropdown summary.sd-summary-title .sd-summary-icon svg{opacity:.8}details.sd-dropdown summary.sd-summary-title .sd-summary-text{flex-grow:1;line-height:1.5;padding-right:.5rem}details.sd-dropdown summary.sd-summary-title .sd-summary-state-marker{pointer-events:none;display:inline-flex;align-items:center}details.sd-dropdown summary.sd-summary-title .sd-summary-state-marker svg{opacity:.6}details.sd-dropdown summary.sd-summary-title:hover .sd-summary-state-marker svg{opacity:1;transform:scale(1.1)}details.sd-dropdown[open] summary .sd-octicon.no-title{visibility:hidden}details.sd-dropdown .sd-summary-chevron-right{transition:.25s}details.sd-dropdown[open]>.sd-summary-title .sd-summary-chevron-right{transform:rotate(90deg)}details.sd-dropdown[open]>.sd-summary-title .sd-summary-chevron-down{transform:rotate(180deg)}details.sd-dropdown:not([open]).sd-card{border:none}details.sd-dropdown:not([open])>.sd-card-header{border:1px solid var(--sd-color-card-border);border-radius:.25rem}details.sd-dropdown.sd-fade-in[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out;animation:sd-fade-in .5s ease-in-out}details.sd-dropdown.sd-fade-in-slide-down[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out}.sd-col>.sd-dropdown{width:100%}.sd-summary-content>.sd-tab-set:first-child{margin-top:0}@keyframes sd-fade-in{0%{opacity:0}100%{opacity:1}}@keyframes sd-slide-down{0%{transform:translate(0, -10px)}100%{transform:translate(0, 0)}}.sd-tab-set{border-radius:.125rem;display:flex;flex-wrap:wrap;margin:1em 0;position:relative}.sd-tab-set>input{opacity:0;position:absolute}.sd-tab-set>input:checked+label{border-color:var(--sd-color-tabs-underline-active);color:var(--sd-color-tabs-label-active)}.sd-tab-set>input:checked+label+.sd-tab-content{display:block}.sd-tab-set>input:not(:checked)+label:hover{color:var(--sd-color-tabs-label-hover);border-color:var(--sd-color-tabs-underline-hover)}.sd-tab-set>input:focus+label{outline-style:auto}.sd-tab-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.sd-tab-set>label{border-bottom:.125rem solid transparent;margin-bottom:0;color:var(--sd-color-tabs-label-inactive);border-color:var(--sd-color-tabs-underline-inactive);cursor:pointer;font-size:var(--sd-fontsize-tabs-label);font-weight:700;padding:1em 1.25em .5em;transition:color 250ms;width:auto;z-index:1}html .sd-tab-set>label:hover{color:var(--sd-color-tabs-label-active)}.sd-col>.sd-tab-set{width:100%}.sd-tab-content{box-shadow:0 -0.0625rem var(--sd-color-tabs-overline),0 .0625rem var(--sd-color-tabs-underline);display:none;order:99;padding-bottom:.75rem;padding-top:.75rem;width:100%}.sd-tab-content>:first-child{margin-top:0 !important}.sd-tab-content>:last-child{margin-bottom:0 !important}.sd-tab-content>.sd-tab-set{margin:0}.sd-sphinx-override,.sd-sphinx-override *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.sd-sphinx-override p{margin-top:0}:root{--sd-color-primary: #0071bc;--sd-color-secondary: #6c757d;--sd-color-success: #28a745;--sd-color-info: #17a2b8;--sd-color-warning: #f0b37e;--sd-color-danger: #dc3545;--sd-color-light: #f8f9fa;--sd-color-muted: #6c757d;--sd-color-dark: #212529;--sd-color-black: black;--sd-color-white: white;--sd-color-primary-highlight: #0060a0;--sd-color-secondary-highlight: #5c636a;--sd-color-success-highlight: #228e3b;--sd-color-info-highlight: #148a9c;--sd-color-warning-highlight: #cc986b;--sd-color-danger-highlight: #bb2d3b;--sd-color-light-highlight: #d3d4d5;--sd-color-muted-highlight: #5c636a;--sd-color-dark-highlight: #1c1f23;--sd-color-black-highlight: black;--sd-color-white-highlight: #d9d9d9;--sd-color-primary-bg: rgba(0, 113, 188, 0.2);--sd-color-secondary-bg: rgba(108, 117, 125, 0.2);--sd-color-success-bg: rgba(40, 167, 69, 0.2);--sd-color-info-bg: rgba(23, 162, 184, 0.2);--sd-color-warning-bg: rgba(240, 179, 126, 0.2);--sd-color-danger-bg: rgba(220, 53, 69, 0.2);--sd-color-light-bg: rgba(248, 249, 250, 0.2);--sd-color-muted-bg: rgba(108, 117, 125, 0.2);--sd-color-dark-bg: rgba(33, 37, 41, 0.2);--sd-color-black-bg: rgba(0, 0, 0, 0.2);--sd-color-white-bg: rgba(255, 255, 255, 0.2);--sd-color-primary-text: #fff;--sd-color-secondary-text: #fff;--sd-color-success-text: #fff;--sd-color-info-text: #fff;--sd-color-warning-text: #212529;--sd-color-danger-text: #fff;--sd-color-light-text: #212529;--sd-color-muted-text: #fff;--sd-color-dark-text: #fff;--sd-color-black-text: #fff;--sd-color-white-text: #212529;--sd-color-shadow: rgba(0, 0, 0, 0.15);--sd-color-card-border: rgba(0, 0, 0, 0.125);--sd-color-card-border-hover: hsla(231, 99%, 66%, 1);--sd-color-card-background: transparent;--sd-color-card-text: inherit;--sd-color-card-header: transparent;--sd-color-card-footer: transparent;--sd-color-tabs-label-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-hover: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-inactive: hsl(0, 0%, 66%);--sd-color-tabs-underline-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-underline-hover: rgba(178, 206, 245, 0.62);--sd-color-tabs-underline-inactive: transparent;--sd-color-tabs-overline: rgb(222, 222, 222);--sd-color-tabs-underline: rgb(222, 222, 222);--sd-fontsize-tabs-label: 1rem;--sd-fontsize-dropdown: inherit;--sd-fontsize-dropdown-title: 1rem;--sd-fontweight-dropdown-title: 700} diff --git a/_static/LiCl_band_comparison.pdf b/_static/LiCl_band_comparison.pdf new file mode 100644 index 000000000..0dc4d2599 Binary files /dev/null and b/_static/LiCl_band_comparison.pdf differ diff --git a/_static/LiCl_band_comparison.png b/_static/LiCl_band_comparison.png new file mode 100644 index 000000000..12bb1e413 Binary files /dev/null and b/_static/LiCl_band_comparison.png differ diff --git a/_static/LiCl_rmse_phonons.pdf b/_static/LiCl_rmse_phonons.pdf new file mode 100644 index 000000000..bea6d2492 Binary files /dev/null and b/_static/LiCl_rmse_phonons.pdf differ diff --git a/_static/LiCl_rmse_phonons.png b/_static/LiCl_rmse_phonons.png new file mode 100644 index 000000000..018708cfd Binary files /dev/null and b/_static/LiCl_rmse_phonons.png differ diff --git a/_static/autodoc_pydantic.css b/_static/autodoc_pydantic.css new file mode 100644 index 000000000..994a3e548 --- /dev/null +++ b/_static/autodoc_pydantic.css @@ -0,0 +1,11 @@ +.autodoc_pydantic_validator_arrow { + padding-left: 8px; + } + +.autodoc_pydantic_collapsable_json { + cursor: pointer; + } + +.autodoc_pydantic_collapsable_erd { + cursor: pointer; + } \ No newline at end of file diff --git a/_static/autoplex_default_wf.png b/_static/autoplex_default_wf.png new file mode 100644 index 000000000..e7bddd3a4 Binary files /dev/null and b/_static/autoplex_default_wf.png differ diff --git a/_static/autoplex_favicon.png b/_static/autoplex_favicon.png new file mode 100644 index 000000000..ba99c9aca Binary files /dev/null and b/_static/autoplex_favicon.png differ diff --git a/_static/autoplex_logo.png b/_static/autoplex_logo.png new file mode 100644 index 000000000..5266eefa9 Binary files /dev/null and b/_static/autoplex_logo.png differ diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 000000000..7ebbd6d07 --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,914 @@ +/* + * Sphinx stylesheet -- basic theme. + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin-top: 10px; +} + +ul.search li { + padding: 5px 0; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/check-solid.svg b/_static/check-solid.svg new file mode 100644 index 000000000..92fad4b5c --- /dev/null +++ b/_static/check-solid.svg @@ -0,0 +1,4 @@ + + + + diff --git a/_static/clipboard.min.js b/_static/clipboard.min.js new file mode 100644 index 000000000..54b3c4638 --- /dev/null +++ b/_static/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.8 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return o}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),c=n.n(e);function a(t){try{return document.execCommand(t)}catch(t){return}}var f=function(t){t=c()(t);return a("cut"),t};var l=function(t){var e,n,o,r=1 + + + + diff --git a/_static/copybutton.css b/_static/copybutton.css new file mode 100644 index 000000000..f1916ec7d --- /dev/null +++ b/_static/copybutton.css @@ -0,0 +1,94 @@ +/* Copy buttons */ +button.copybtn { + position: absolute; + display: flex; + top: .3em; + right: .3em; + width: 1.7em; + height: 1.7em; + opacity: 0; + transition: opacity 0.3s, border .3s, background-color .3s; + user-select: none; + padding: 0; + border: none; + outline: none; + border-radius: 0.4em; + /* The colors that GitHub uses */ + border: #1b1f2426 1px solid; + background-color: #f6f8fa; + color: #57606a; +} + +button.copybtn.success { + border-color: #22863a; + color: #22863a; +} + +button.copybtn svg { + stroke: currentColor; + width: 1.5em; + height: 1.5em; + padding: 0.1em; +} + +div.highlight { + position: relative; +} + +/* Show the copybutton */ +.highlight:hover button.copybtn, button.copybtn.success { + opacity: 1; +} + +.highlight button.copybtn:hover { + background-color: rgb(235, 235, 235); +} + +.highlight button.copybtn:active { + background-color: rgb(187, 187, 187); +} + +/** + * A minimal CSS-only tooltip copied from: + * https://codepen.io/mildrenben/pen/rVBrpK + * + * To use, write HTML like the following: + * + *

Short

+ */ + .o-tooltip--left { + position: relative; + } + + .o-tooltip--left:after { + opacity: 0; + visibility: hidden; + position: absolute; + content: attr(data-tooltip); + padding: .2em; + font-size: .8em; + left: -.2em; + background: grey; + color: white; + white-space: nowrap; + z-index: 2; + border-radius: 2px; + transform: translateX(-102%) translateY(0); + transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); +} + +.o-tooltip--left:hover:after { + display: block; + opacity: 1; + visibility: visible; + transform: translateX(-100%) translateY(0); + transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); + transition-delay: .5s; +} + +/* By default the copy button shouldn't show up when printing a page */ +@media print { + button.copybtn { + display: none; + } +} diff --git a/_static/copybutton.js b/_static/copybutton.js new file mode 100644 index 000000000..2ea7ff3e2 --- /dev/null +++ b/_static/copybutton.js @@ -0,0 +1,248 @@ +// Localization support +const messages = { + 'en': { + 'copy': 'Copy', + 'copy_to_clipboard': 'Copy to clipboard', + 'copy_success': 'Copied!', + 'copy_failure': 'Failed to copy', + }, + 'es' : { + 'copy': 'Copiar', + 'copy_to_clipboard': 'Copiar al portapapeles', + 'copy_success': '¡Copiado!', + 'copy_failure': 'Error al copiar', + }, + 'de' : { + 'copy': 'Kopieren', + 'copy_to_clipboard': 'In die Zwischenablage kopieren', + 'copy_success': 'Kopiert!', + 'copy_failure': 'Fehler beim Kopieren', + }, + 'fr' : { + 'copy': 'Copier', + 'copy_to_clipboard': 'Copier dans le presse-papier', + 'copy_success': 'Copié !', + 'copy_failure': 'Échec de la copie', + }, + 'ru': { + 'copy': 'Скопировать', + 'copy_to_clipboard': 'Скопировать в буфер', + 'copy_success': 'Скопировано!', + 'copy_failure': 'Не удалось скопировать', + }, + 'zh-CN': { + 'copy': '复制', + 'copy_to_clipboard': '复制到剪贴板', + 'copy_success': '复制成功!', + 'copy_failure': '复制失败', + }, + 'it' : { + 'copy': 'Copiare', + 'copy_to_clipboard': 'Copiato negli appunti', + 'copy_success': 'Copiato!', + 'copy_failure': 'Errore durante la copia', + } +} + +let locale = 'en' +if( document.documentElement.lang !== undefined + && messages[document.documentElement.lang] !== undefined ) { + locale = document.documentElement.lang +} + +let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT; +if (doc_url_root == '#') { + doc_url_root = ''; +} + +/** + * SVG files for our copy buttons + */ +let iconCheck = ` + ${messages[locale]['copy_success']} + + +` + +// If the user specified their own SVG use that, otherwise use the default +let iconCopy = ``; +if (!iconCopy) { + iconCopy = ` + ${messages[locale]['copy_to_clipboard']} + + + +` +} + +/** + * Set up copy/paste for code blocks + */ + +const runWhenDOMLoaded = cb => { + if (document.readyState != 'loading') { + cb() + } else if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', cb) + } else { + document.attachEvent('onreadystatechange', function() { + if (document.readyState == 'complete') cb() + }) + } +} + +const codeCellId = index => `codecell${index}` + +// Clears selected text since ClipboardJS will select the text when copying +const clearSelection = () => { + if (window.getSelection) { + window.getSelection().removeAllRanges() + } else if (document.selection) { + document.selection.empty() + } +} + +// Changes tooltip text for a moment, then changes it back +// We want the timeout of our `success` class to be a bit shorter than the +// tooltip and icon change, so that we can hide the icon before changing back. +var timeoutIcon = 2000; +var timeoutSuccessClass = 1500; + +const temporarilyChangeTooltip = (el, oldText, newText) => { + el.setAttribute('data-tooltip', newText) + el.classList.add('success') + // Remove success a little bit sooner than we change the tooltip + // So that we can use CSS to hide the copybutton first + setTimeout(() => el.classList.remove('success'), timeoutSuccessClass) + setTimeout(() => el.setAttribute('data-tooltip', oldText), timeoutIcon) +} + +// Changes the copy button icon for two seconds, then changes it back +const temporarilyChangeIcon = (el) => { + el.innerHTML = iconCheck; + setTimeout(() => {el.innerHTML = iconCopy}, timeoutIcon) +} + +const addCopyButtonToCodeCells = () => { + // If ClipboardJS hasn't loaded, wait a bit and try again. This + // happens because we load ClipboardJS asynchronously. + if (window.ClipboardJS === undefined) { + setTimeout(addCopyButtonToCodeCells, 250) + return + } + + // Add copybuttons to all of our code cells + const COPYBUTTON_SELECTOR = 'div.highlight pre'; + const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR) + codeCells.forEach((codeCell, index) => { + const id = codeCellId(index) + codeCell.setAttribute('id', id) + + const clipboardButton = id => + `` + codeCell.insertAdjacentHTML('afterend', clipboardButton(id)) + }) + +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +/** + * Removes excluded text from a Node. + * + * @param {Node} target Node to filter. + * @param {string} exclude CSS selector of nodes to exclude. + * @returns {DOMString} Text from `target` with text removed. + */ +function filterText(target, exclude) { + const clone = target.cloneNode(true); // clone as to not modify the live DOM + if (exclude) { + // remove excluded nodes + clone.querySelectorAll(exclude).forEach(node => node.remove()); + } + return clone.innerText; +} + +// Callback when a copy button is clicked. Will be passed the node that was clicked +// should then grab the text and replace pieces of text that shouldn't be used in output +function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { + var regexp; + var match; + + // Do we check for line continuation characters and "HERE-documents"? + var useLineCont = !!lineContinuationChar + var useHereDoc = !!hereDocDelim + + // create regexp to capture prompt and remaining line + if (isRegexp) { + regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') + } else { + regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') + } + + const outputLines = []; + var promptFound = false; + var gotLineCont = false; + var gotHereDoc = false; + const lineGotPrompt = []; + for (const line of textContent.split('\n')) { + match = line.match(regexp) + if (match || gotLineCont || gotHereDoc) { + promptFound = regexp.test(line) + lineGotPrompt.push(promptFound) + if (removePrompts && promptFound) { + outputLines.push(match[2]) + } else { + outputLines.push(line) + } + gotLineCont = line.endsWith(lineContinuationChar) & useLineCont + if (line.includes(hereDocDelim) & useHereDoc) + gotHereDoc = !gotHereDoc + } else if (!onlyCopyPromptLines) { + outputLines.push(line) + } else if (copyEmptyLines && line.trim() === '') { + outputLines.push(line) + } + } + + // If no lines with the prompt were found then just use original lines + if (lineGotPrompt.some(v => v === true)) { + textContent = outputLines.join('\n'); + } + + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent +} + + +var copyTargetText = (trigger) => { + var target = document.querySelector(trigger.attributes['data-clipboard-target'].value); + + // get filtered text + let exclude = '.linenos'; + + let text = filterText(target, exclude); + return formatCopyText(text, '', false, true, true, true, '', '') +} + + // Initialize with a callback so we can modify the text before copy + const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText}) + + // Update UI with error/success messages + clipboard.on('success', event => { + clearSelection() + temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success']) + temporarilyChangeIcon(event.trigger) + }) + + clipboard.on('error', event => { + temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure']) + }) +} + +runWhenDOMLoaded(addCopyButtonToCodeCells) \ No newline at end of file diff --git a/_static/copybutton_funcs.js b/_static/copybutton_funcs.js new file mode 100644 index 000000000..dbe1aaad7 --- /dev/null +++ b/_static/copybutton_funcs.js @@ -0,0 +1,73 @@ +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +/** + * Removes excluded text from a Node. + * + * @param {Node} target Node to filter. + * @param {string} exclude CSS selector of nodes to exclude. + * @returns {DOMString} Text from `target` with text removed. + */ +export function filterText(target, exclude) { + const clone = target.cloneNode(true); // clone as to not modify the live DOM + if (exclude) { + // remove excluded nodes + clone.querySelectorAll(exclude).forEach(node => node.remove()); + } + return clone.innerText; +} + +// Callback when a copy button is clicked. Will be passed the node that was clicked +// should then grab the text and replace pieces of text that shouldn't be used in output +export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { + var regexp; + var match; + + // Do we check for line continuation characters and "HERE-documents"? + var useLineCont = !!lineContinuationChar + var useHereDoc = !!hereDocDelim + + // create regexp to capture prompt and remaining line + if (isRegexp) { + regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') + } else { + regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') + } + + const outputLines = []; + var promptFound = false; + var gotLineCont = false; + var gotHereDoc = false; + const lineGotPrompt = []; + for (const line of textContent.split('\n')) { + match = line.match(regexp) + if (match || gotLineCont || gotHereDoc) { + promptFound = regexp.test(line) + lineGotPrompt.push(promptFound) + if (removePrompts && promptFound) { + outputLines.push(match[2]) + } else { + outputLines.push(line) + } + gotLineCont = line.endsWith(lineContinuationChar) & useLineCont + if (line.includes(hereDocDelim) & useHereDoc) + gotHereDoc = !gotHereDoc + } else if (!onlyCopyPromptLines) { + outputLines.push(line) + } else if (copyEmptyLines && line.trim() === '') { + outputLines.push(line) + } + } + + // If no lines with the prompt were found then just use original lines + if (lineGotPrompt.some(v => v === true)) { + textContent = outputLines.join('\n'); + } + + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent +} diff --git a/_static/custom.css b/_static/custom.css new file mode 100644 index 000000000..8bb1ab956 --- /dev/null +++ b/_static/custom.css @@ -0,0 +1,127 @@ +/******************************************************************************* +* light theme +* +* all the variables used for light theme coloring +*/ +html[data-theme="light"] { + /***************************************************************************** + * main colors + */ + --pst-color-primary: rgba(46, 49, 145); + --pst-color-secondary: rgb(200, 30, 109); + --pst-color-success: rgb(40, 167, 69); + --pst-color-info: var(--pst-color-primary); + --pst-color-warning: var(--pst-color-secondary); + --pst-color-danger: rgb(220, 53, 69); + --pst-color-text-base: rgb(51, 51, 51); + --pst-color-text-muted: rgb(77, 77, 77); + --pst-color-border: rgb(201, 201, 201); + --pst-color-shadow: rgb(216, 216, 216); + + /***************************************************************************** + * depth colors + * + * background: the more in depth color + * on-background: the object directly set on the background, use of shadows in light theme + * surface: object set on the background (without shadows) + * on_surface: object set on surface object (without shadows) + */ + --pst-color-background: rgb(255, 255, 255); + --pst-color-on-background: rgb(242, 239, 233); + --pst-color-surface: rgb(242, 239, 233); + --pst-color-on-surface: rgb(242, 239, 233); + + /***************************************************************************** + * extentions + */ + + --pst-color-panel-background: var(--pst-color-background); + + /***************************************************************************** + * layout + */ + + // links + --pst-color-link: var(--pst-color-primary); + --pst-color-link-hover: var(--pst-color-secondary); + + // inline code + --pst-color-inline-code: rgb(232, 62, 140); + + // targeted content + --pst-color-target: rgb(251, 229, 78); + + // hide any content that should not be displayed in the light theme + .only-dark { + display: none !important; + } +} + +/******************************************************************************* +* dark theme +* +* all the variables used for dark theme coloring +*/ +html[data-theme="dark"] { + /***************************************************************************** + * main colors + */ + --pst-color-primary: rgb(82, 143, 228); + --pst-color-secondary: rgb(200, 30, 109); + --pst-color-success: rgb(72, 135, 87); + --pst-color-info: var(--pst-color-primary); + --pst-color-warning: var(--pst-color-secondary); + --pst-color-danger: rgb(203, 70, 83); + --pst-color-text-base: rgb(201, 209, 217); + --pst-color-text-muted: rgb(192, 192, 192); + --pst-color-border: rgb(192, 192, 192); + --pst-color-shadow: var(--pst-color-background); + + /***************************************************************************** + * depth colors + * + * background: the more in depth color + * on-background: the object directly set on the background, use of a light grey in dark theme + * surface: object set on the background (without shadows) + * on_surface: object set on surface object (without shadows) + */ + --pst-color-background: rgb(18, 18, 18); + --pst-color-on-background: rgb(30, 30, 30); + --pst-color-surface: rgb(41, 41, 41); + --pst-color-on-surface: rgb(55, 55, 55); + + /***************************************************************************** + * extentions + */ + + --pst-color-panel-background: var(--pst-color-background-up); + + /***************************************************************************** + * layout + */ + + // links + --pst-color-link: var(--pst-color-primary); + --pst-color-link-hover: var(--pst-color-secondary); + + // inline code + --pst-color-inline-code: rgb(221, 158, 194); + + // targeted content + --pst-color-target: rgb(71, 39, 0); + + // hide any content that should not be displayed in the dark theme + .only-light { + display: none !important; + } + + // specific brightness applied on images + img { + filter: brightness(0.8) contrast(1.2); + } +} + + +.bg-light { + background-color: rgba(82, 143, 228, 0.52) !important +} diff --git a/_static/design-tabs.js b/_static/design-tabs.js new file mode 100644 index 000000000..b25bd6a4f --- /dev/null +++ b/_static/design-tabs.js @@ -0,0 +1,101 @@ +// @ts-check + +// Extra JS capability for selected tabs to be synced +// The selection is stored in local storage so that it persists across page loads. + +/** + * @type {Record} + */ +let sd_id_to_elements = {}; +const storageKeyPrefix = "sphinx-design-tab-id-"; + +/** + * Create a key for a tab element. + * @param {HTMLElement} el - The tab element. + * @returns {[string, string, string] | null} - The key. + * + */ +function create_key(el) { + let syncId = el.getAttribute("data-sync-id"); + let syncGroup = el.getAttribute("data-sync-group"); + if (!syncId || !syncGroup) return null; + return [syncGroup, syncId, syncGroup + "--" + syncId]; +} + +/** + * Initialize the tab selection. + * + */ +function ready() { + // Find all tabs with sync data + + /** @type {string[]} */ + let groups = []; + + document.querySelectorAll(".sd-tab-label").forEach((label) => { + if (label instanceof HTMLElement) { + let data = create_key(label); + if (data) { + let [group, id, key] = data; + + // add click event listener + // @ts-ignore + label.onclick = onSDLabelClick; + + // store map of key to elements + if (!sd_id_to_elements[key]) { + sd_id_to_elements[key] = []; + } + sd_id_to_elements[key].push(label); + + if (groups.indexOf(group) === -1) { + groups.push(group); + // Check if a specific tab has been selected via URL parameter + const tabParam = new URLSearchParams(window.location.search).get( + group + ); + if (tabParam) { + console.log( + "sphinx-design: Selecting tab id for group '" + + group + + "' from URL parameter: " + + tabParam + ); + window.sessionStorage.setItem(storageKeyPrefix + group, tabParam); + } + } + + // Check is a specific tab has been selected previously + let previousId = window.sessionStorage.getItem( + storageKeyPrefix + group + ); + if (previousId === id) { + // console.log( + // "sphinx-design: Selecting tab from session storage: " + id + // ); + // @ts-ignore + label.previousElementSibling.checked = true; + } + } + } + }); +} + +/** + * Activate other tabs with the same sync id. + * + * @this {HTMLElement} - The element that was clicked. + */ +function onSDLabelClick() { + let data = create_key(this); + if (!data) return; + let [group, id, key] = data; + for (const label of sd_id_to_elements[key]) { + if (label === this) continue; + // @ts-ignore + label.previousElementSibling.checked = true; + } + window.sessionStorage.setItem(storageKeyPrefix + group, id); +} + +document.addEventListener("DOMContentLoaded", ready, false); diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 000000000..0398ebb9f --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,149 @@ +/* + * Base JavaScript utilities for all Sphinx HTML documentation. + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 000000000..f62a2ee88 --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '0.1.0', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/energy_forces.png b/_static/energy_forces.png new file mode 100644 index 000000000..8866491ba Binary files /dev/null and b/_static/energy_forces.png differ diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 000000000..a858a410e Binary files /dev/null and b/_static/file.png differ diff --git a/_static/fonts/lato-bold-italic.woff b/_static/fonts/lato-bold-italic.woff new file mode 100644 index 000000000..88ad05b9f Binary files /dev/null and b/_static/fonts/lato-bold-italic.woff differ diff --git a/_static/fonts/lato-bold-italic.woff2 b/_static/fonts/lato-bold-italic.woff2 new file mode 100644 index 000000000..c4e3d804b Binary files /dev/null and b/_static/fonts/lato-bold-italic.woff2 differ diff --git a/_static/fonts/lato-bold.woff b/_static/fonts/lato-bold.woff new file mode 100644 index 000000000..c6dff51f0 Binary files /dev/null and b/_static/fonts/lato-bold.woff differ diff --git a/_static/fonts/lato-bold.woff2 b/_static/fonts/lato-bold.woff2 new file mode 100644 index 000000000..bb195043c Binary files /dev/null and b/_static/fonts/lato-bold.woff2 differ diff --git a/_static/fonts/lato-normal-italic.woff b/_static/fonts/lato-normal-italic.woff new file mode 100644 index 000000000..76114bc03 Binary files /dev/null and b/_static/fonts/lato-normal-italic.woff differ diff --git a/_static/fonts/lato-normal-italic.woff2 b/_static/fonts/lato-normal-italic.woff2 new file mode 100644 index 000000000..3404f37e2 Binary files /dev/null and b/_static/fonts/lato-normal-italic.woff2 differ diff --git a/_static/fonts/lato-normal.woff b/_static/fonts/lato-normal.woff new file mode 100644 index 000000000..ae1307ff5 Binary files /dev/null and b/_static/fonts/lato-normal.woff differ diff --git a/_static/fonts/lato-normal.woff2 b/_static/fonts/lato-normal.woff2 new file mode 100644 index 000000000..3bf984332 Binary files /dev/null and b/_static/fonts/lato-normal.woff2 differ diff --git a/_static/images/logo_binder.svg b/_static/images/logo_binder.svg new file mode 100644 index 000000000..45fecf751 --- /dev/null +++ b/_static/images/logo_binder.svg @@ -0,0 +1,19 @@ + + + + +logo + + + + + + + + diff --git a/_static/images/logo_colab.png b/_static/images/logo_colab.png new file mode 100644 index 000000000..b7560ec21 Binary files /dev/null and b/_static/images/logo_colab.png differ diff --git a/_static/images/logo_deepnote.svg b/_static/images/logo_deepnote.svg new file mode 100644 index 000000000..fa77ebfc2 --- /dev/null +++ b/_static/images/logo_deepnote.svg @@ -0,0 +1 @@ + diff --git a/_static/images/logo_jupyterhub.svg b/_static/images/logo_jupyterhub.svg new file mode 100644 index 000000000..60cfe9f22 --- /dev/null +++ b/_static/images/logo_jupyterhub.svg @@ -0,0 +1 @@ +logo_jupyterhubHub diff --git a/_static/index_api.svg b/_static/index_api.svg new file mode 100644 index 000000000..70bf0d350 --- /dev/null +++ b/_static/index_api.svg @@ -0,0 +1,97 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/_static/index_contribute.svg b/_static/index_contribute.svg new file mode 100644 index 000000000..e86c3e9fd --- /dev/null +++ b/_static/index_contribute.svg @@ -0,0 +1,76 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/_static/index_support.svg b/_static/index_support.svg new file mode 100644 index 000000000..539eb7c65 --- /dev/null +++ b/_static/index_support.svg @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/_static/index_user_guide.svg b/_static/index_user_guide.svg new file mode 100644 index 000000000..a567103af --- /dev/null +++ b/_static/index_user_guide.svg @@ -0,0 +1,67 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/_static/language_data.js b/_static/language_data.js new file mode 100644 index 000000000..c7fe6c6fa --- /dev/null +++ b/_static/language_data.js @@ -0,0 +1,192 @@ +/* + * This script contains the language-specific data used by searchtools.js, + * namely the list of stopwords, stemmer, scorer and splitter. + */ + +var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; + + +/* Non-minified version is copied as a separate JS file, if available */ + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/_static/locales/ar/LC_MESSAGES/booktheme.mo b/_static/locales/ar/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..15541a6a3 Binary files /dev/null and b/_static/locales/ar/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/ar/LC_MESSAGES/booktheme.po b/_static/locales/ar/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..1237f3432 --- /dev/null +++ b/_static/locales/ar/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ar\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "تنزيل ملف المصدر" + +msgid "suggest edit" +msgstr "أقترح تحرير" + +msgid "Toggle navigation" +msgstr "تبديل التنقل" + +msgid "open issue" +msgstr "قضية مفتوحة" + +msgid "Download notebook file" +msgstr "تنزيل ملف دفتر الملاحظات" + +msgid "repository" +msgstr "مخزن" + +msgid "Theme by the" +msgstr "موضوع بواسطة" + +msgid "Print to PDF" +msgstr "طباعة إلى PDF" + +msgid "Download this page" +msgstr "قم بتنزيل هذه الصفحة" + +msgid "Copyright" +msgstr "حقوق النشر" + +msgid "Last updated on" +msgstr "آخر تحديث في" + +msgid "Launch" +msgstr "إطلاق" + +msgid "Open an issue" +msgstr "افتح قضية" + +msgid "Fullscreen mode" +msgstr "وضع ملء الشاشة" + +msgid "Sphinx Book Theme" +msgstr "موضوع كتاب أبو الهول" + +msgid "Contents" +msgstr "محتويات" + +msgid "Edit this page" +msgstr "قم بتحرير هذه الصفحة" + +msgid "next page" +msgstr "الصفحة التالية" + +msgid "Source repository" +msgstr "مستودع المصدر" + +msgid "By" +msgstr "بواسطة" + +msgid "By the" +msgstr "بواسطة" + +msgid "previous page" +msgstr "الصفحة السابقة" diff --git a/_static/locales/bg/LC_MESSAGES/booktheme.mo b/_static/locales/bg/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..da9512003 Binary files /dev/null and b/_static/locales/bg/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/bg/LC_MESSAGES/booktheme.po b/_static/locales/bg/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..45a6ed0a4 --- /dev/null +++ b/_static/locales/bg/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: bg\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Изтеглете изходния файл" + +msgid "suggest edit" +msgstr "предложи редактиране" + +msgid "Toggle navigation" +msgstr "Превключване на навигацията" + +msgid "open issue" +msgstr "отворен брой" + +msgid "Download notebook file" +msgstr "Изтеглете файла на бележника" + +msgid "repository" +msgstr "хранилище" + +msgid "Theme by the" +msgstr "Тема от" + +msgid "Print to PDF" +msgstr "Печат в PDF" + +msgid "Download this page" +msgstr "Изтеглете тази страница" + +msgid "Copyright" +msgstr "Авторско право" + +msgid "Last updated on" +msgstr "Последна актуализация на" + +msgid "Launch" +msgstr "Стартиране" + +msgid "Open an issue" +msgstr "Отворете проблем" + +msgid "Fullscreen mode" +msgstr "Режим на цял екран" + +msgid "Sphinx Book Theme" +msgstr "Тема на книгата Sphinx" + +msgid "Contents" +msgstr "Съдържание" + +msgid "Edit this page" +msgstr "Редактирайте тази страница" + +msgid "next page" +msgstr "Следваща страница" + +msgid "Source repository" +msgstr "Хранилище на източника" + +msgid "By" +msgstr "От" + +msgid "By the" +msgstr "По" + +msgid "previous page" +msgstr "предишна страница" diff --git a/_static/locales/bn/LC_MESSAGES/booktheme.mo b/_static/locales/bn/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..6b96639b7 Binary files /dev/null and b/_static/locales/bn/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/bn/LC_MESSAGES/booktheme.po b/_static/locales/bn/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..122a369e5 --- /dev/null +++ b/_static/locales/bn/LC_MESSAGES/booktheme.po @@ -0,0 +1,63 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: bn\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "উত্স ফাইল ডাউনলোড করুন" + +msgid "Toggle navigation" +msgstr "নেভিগেশন টগল করুন" + +msgid "open issue" +msgstr "খোলা সমস্যা" + +msgid "Download notebook file" +msgstr "নোটবুক ফাইল ডাউনলোড করুন" + +msgid "Theme by the" +msgstr "থিম দ্বারা" + +msgid "Print to PDF" +msgstr "পিডিএফ প্রিন্ট করুন" + +msgid "Download this page" +msgstr "এই পৃষ্ঠাটি ডাউনলোড করুন" + +msgid "Copyright" +msgstr "কপিরাইট" + +msgid "Last updated on" +msgstr "সর্বশেষ আপডেট" + +msgid "Launch" +msgstr "শুরু করা" + +msgid "Open an issue" +msgstr "একটি সমস্যা খুলুন" + +msgid "Sphinx Book Theme" +msgstr "স্পিনিক্স বুক থিম" + +msgid "Edit this page" +msgstr "এই পৃষ্ঠাটি সম্পাদনা করুন" + +msgid "next page" +msgstr "পরবর্তী পৃষ্ঠা" + +msgid "Source repository" +msgstr "উত্স সংগ্রহস্থল" + +msgid "By" +msgstr "দ্বারা" + +msgid "By the" +msgstr "দ্বারা" + +msgid "previous page" +msgstr "আগের পৃষ্ঠা" diff --git a/_static/locales/ca/LC_MESSAGES/booktheme.mo b/_static/locales/ca/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..a4dd30e9b Binary files /dev/null and b/_static/locales/ca/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/ca/LC_MESSAGES/booktheme.po b/_static/locales/ca/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..c757deb8e --- /dev/null +++ b/_static/locales/ca/LC_MESSAGES/booktheme.po @@ -0,0 +1,66 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ca\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Baixeu el fitxer font" + +msgid "suggest edit" +msgstr "suggerir edició" + +msgid "Toggle navigation" +msgstr "Commuta la navegació" + +msgid "open issue" +msgstr "número obert" + +msgid "Download notebook file" +msgstr "Descarregar fitxer de quadern" + +msgid "Theme by the" +msgstr "Tema del" + +msgid "Print to PDF" +msgstr "Imprimeix a PDF" + +msgid "Download this page" +msgstr "Descarregueu aquesta pàgina" + +msgid "Copyright" +msgstr "Copyright" + +msgid "Last updated on" +msgstr "Darrera actualització el" + +msgid "Launch" +msgstr "Llançament" + +msgid "Open an issue" +msgstr "Obriu un número" + +msgid "Sphinx Book Theme" +msgstr "Tema del llibre Esfinx" + +msgid "Edit this page" +msgstr "Editeu aquesta pàgina" + +msgid "next page" +msgstr "pàgina següent" + +msgid "Source repository" +msgstr "Dipòsit de fonts" + +msgid "By" +msgstr "Per" + +msgid "By the" +msgstr "Per la" + +msgid "previous page" +msgstr "Pàgina anterior" diff --git a/_static/locales/cs/LC_MESSAGES/booktheme.mo b/_static/locales/cs/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..c39e01a6a Binary files /dev/null and b/_static/locales/cs/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/cs/LC_MESSAGES/booktheme.po b/_static/locales/cs/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..3571c23b4 --- /dev/null +++ b/_static/locales/cs/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: cs\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Stáhněte si zdrojový soubor" + +msgid "suggest edit" +msgstr "navrhnout úpravy" + +msgid "Toggle navigation" +msgstr "Přepnout navigaci" + +msgid "open issue" +msgstr "otevřené číslo" + +msgid "Download notebook file" +msgstr "Stáhnout soubor poznámkového bloku" + +msgid "repository" +msgstr "úložiště" + +msgid "Theme by the" +msgstr "Téma od" + +msgid "Print to PDF" +msgstr "Tisk do PDF" + +msgid "Download this page" +msgstr "Stáhněte si tuto stránku" + +msgid "Copyright" +msgstr "autorská práva" + +msgid "Last updated on" +msgstr "Naposledy aktualizováno" + +msgid "Launch" +msgstr "Zahájení" + +msgid "Open an issue" +msgstr "Otevřete problém" + +msgid "Fullscreen mode" +msgstr "Režim celé obrazovky" + +msgid "Sphinx Book Theme" +msgstr "Téma knihy Sfinga" + +msgid "Contents" +msgstr "Obsah" + +msgid "Edit this page" +msgstr "Upravit tuto stránku" + +msgid "next page" +msgstr "další strana" + +msgid "Source repository" +msgstr "Zdrojové úložiště" + +msgid "By" +msgstr "Podle" + +msgid "By the" +msgstr "Podle" + +msgid "previous page" +msgstr "předchozí stránka" diff --git a/_static/locales/da/LC_MESSAGES/booktheme.mo b/_static/locales/da/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..f43157d70 Binary files /dev/null and b/_static/locales/da/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/da/LC_MESSAGES/booktheme.po b/_static/locales/da/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..c39223fde --- /dev/null +++ b/_static/locales/da/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: da\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Download kildefil" + +msgid "suggest edit" +msgstr "foreslå redigering" + +msgid "Toggle navigation" +msgstr "Skift navigation" + +msgid "open issue" +msgstr "åbent nummer" + +msgid "Download notebook file" +msgstr "Download notesbog-fil" + +msgid "repository" +msgstr "lager" + +msgid "Theme by the" +msgstr "Tema af" + +msgid "Print to PDF" +msgstr "Udskriv til PDF" + +msgid "Download this page" +msgstr "Download denne side" + +msgid "Copyright" +msgstr "ophavsret" + +msgid "Last updated on" +msgstr "Sidst opdateret den" + +msgid "Launch" +msgstr "Start" + +msgid "Open an issue" +msgstr "Åbn et problem" + +msgid "Fullscreen mode" +msgstr "Fuldskærmstilstand" + +msgid "Sphinx Book Theme" +msgstr "Sphinx bogtema" + +msgid "Contents" +msgstr "Indhold" + +msgid "Edit this page" +msgstr "Rediger denne side" + +msgid "next page" +msgstr "Næste side" + +msgid "Source repository" +msgstr "Kildelager" + +msgid "By" +msgstr "Ved" + +msgid "By the" +msgstr "Ved" + +msgid "previous page" +msgstr "forrige side" diff --git a/_static/locales/de/LC_MESSAGES/booktheme.mo b/_static/locales/de/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..648b565c7 Binary files /dev/null and b/_static/locales/de/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/de/LC_MESSAGES/booktheme.po b/_static/locales/de/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..e22b505f2 --- /dev/null +++ b/_static/locales/de/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: de\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Quelldatei herunterladen" + +msgid "suggest edit" +msgstr "vorschlagen zu bearbeiten" + +msgid "Toggle navigation" +msgstr "Navigation umschalten" + +msgid "open issue" +msgstr "offenes Thema" + +msgid "Download notebook file" +msgstr "Notebook-Datei herunterladen" + +msgid "repository" +msgstr "Repository" + +msgid "Theme by the" +msgstr "Thema von der" + +msgid "Print to PDF" +msgstr "In PDF drucken" + +msgid "Download this page" +msgstr "Laden Sie diese Seite herunter" + +msgid "Copyright" +msgstr "Urheberrechte ©" + +msgid "Last updated on" +msgstr "Zuletzt aktualisiert am" + +msgid "Launch" +msgstr "Starten" + +msgid "Open an issue" +msgstr "Öffnen Sie ein Problem" + +msgid "Fullscreen mode" +msgstr "Vollbildmodus" + +msgid "Sphinx Book Theme" +msgstr "Sphinx-Buch-Thema" + +msgid "Contents" +msgstr "Inhalt" + +msgid "Edit this page" +msgstr "Bearbeite diese Seite" + +msgid "next page" +msgstr "Nächste Seite" + +msgid "Source repository" +msgstr "Quell-Repository" + +msgid "By" +msgstr "Durch" + +msgid "By the" +msgstr "Bis zum" + +msgid "previous page" +msgstr "vorherige Seite" diff --git a/_static/locales/el/LC_MESSAGES/booktheme.mo b/_static/locales/el/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..fca6e9355 Binary files /dev/null and b/_static/locales/el/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/el/LC_MESSAGES/booktheme.po b/_static/locales/el/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..588f2efd8 --- /dev/null +++ b/_static/locales/el/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: el\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Λήψη αρχείου προέλευσης" + +msgid "suggest edit" +msgstr "προτείνω επεξεργασία" + +msgid "Toggle navigation" +msgstr "Εναλλαγή πλοήγησης" + +msgid "open issue" +msgstr "ανοιχτό ζήτημα" + +msgid "Download notebook file" +msgstr "Λήψη αρχείου σημειωματάριου" + +msgid "repository" +msgstr "αποθήκη" + +msgid "Theme by the" +msgstr "Θέμα από το" + +msgid "Print to PDF" +msgstr "Εκτύπωση σε PDF" + +msgid "Download this page" +msgstr "Λήψη αυτής της σελίδας" + +msgid "Copyright" +msgstr "Πνευματική ιδιοκτησία" + +msgid "Last updated on" +msgstr "Τελευταία ενημέρωση στις" + +msgid "Launch" +msgstr "Εκτόξευση" + +msgid "Open an issue" +msgstr "Ανοίξτε ένα ζήτημα" + +msgid "Fullscreen mode" +msgstr "ΛΕΙΤΟΥΡΓΙΑ ΠΛΗΡΟΥΣ ΟΘΟΝΗΣ" + +msgid "Sphinx Book Theme" +msgstr "Θέμα βιβλίου Sphinx" + +msgid "Contents" +msgstr "Περιεχόμενα" + +msgid "Edit this page" +msgstr "Επεξεργαστείτε αυτήν τη σελίδα" + +msgid "next page" +msgstr "επόμενη σελίδα" + +msgid "Source repository" +msgstr "Αποθήκη πηγής" + +msgid "By" +msgstr "Με" + +msgid "By the" +msgstr "Από το" + +msgid "previous page" +msgstr "προηγούμενη σελίδα" diff --git a/_static/locales/eo/LC_MESSAGES/booktheme.mo b/_static/locales/eo/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..d1072bbec Binary files /dev/null and b/_static/locales/eo/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/eo/LC_MESSAGES/booktheme.po b/_static/locales/eo/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..4614fe0aa --- /dev/null +++ b/_static/locales/eo/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: eo\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Elŝutu fontodosieron" + +msgid "suggest edit" +msgstr "sugesti redaktadon" + +msgid "Toggle navigation" +msgstr "Ŝalti navigadon" + +msgid "open issue" +msgstr "malferma numero" + +msgid "Download notebook file" +msgstr "Elŝutu kajeran dosieron" + +msgid "repository" +msgstr "deponejo" + +msgid "Theme by the" +msgstr "Temo de la" + +msgid "Print to PDF" +msgstr "Presi al PDF" + +msgid "Download this page" +msgstr "Elŝutu ĉi tiun paĝon" + +msgid "Copyright" +msgstr "Kopirajto" + +msgid "Last updated on" +msgstr "Laste ĝisdatigita la" + +msgid "Launch" +msgstr "Lanĉo" + +msgid "Open an issue" +msgstr "Malfermu numeron" + +msgid "Fullscreen mode" +msgstr "Plenekrana reĝimo" + +msgid "Sphinx Book Theme" +msgstr "Sfinksa Libro-Temo" + +msgid "Contents" +msgstr "Enhavo" + +msgid "Edit this page" +msgstr "Redaktu ĉi tiun paĝon" + +msgid "next page" +msgstr "sekva paĝo" + +msgid "Source repository" +msgstr "Fonto-deponejo" + +msgid "By" +msgstr "De" + +msgid "By the" +msgstr "Per la" + +msgid "previous page" +msgstr "antaŭa paĝo" diff --git a/_static/locales/es/LC_MESSAGES/booktheme.mo b/_static/locales/es/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..ba2ee4dc2 Binary files /dev/null and b/_static/locales/es/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/es/LC_MESSAGES/booktheme.po b/_static/locales/es/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..b4fccf194 --- /dev/null +++ b/_static/locales/es/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Descargar archivo fuente" + +msgid "suggest edit" +msgstr "sugerir editar" + +msgid "Toggle navigation" +msgstr "Navegación de palanca" + +msgid "open issue" +msgstr "Tema abierto" + +msgid "Download notebook file" +msgstr "Descargar archivo de cuaderno" + +msgid "repository" +msgstr "repositorio" + +msgid "Theme by the" +msgstr "Tema por el" + +msgid "Print to PDF" +msgstr "Imprimir en PDF" + +msgid "Download this page" +msgstr "Descarga esta pagina" + +msgid "Copyright" +msgstr "Derechos de autor" + +msgid "Last updated on" +msgstr "Ultima actualización en" + +msgid "Launch" +msgstr "Lanzamiento" + +msgid "Open an issue" +msgstr "Abrir un problema" + +msgid "Fullscreen mode" +msgstr "Modo de pantalla completa" + +msgid "Sphinx Book Theme" +msgstr "Tema del libro de la esfinge" + +msgid "Contents" +msgstr "Contenido" + +msgid "Edit this page" +msgstr "Edita esta página" + +msgid "next page" +msgstr "siguiente página" + +msgid "Source repository" +msgstr "Repositorio de origen" + +msgid "By" +msgstr "Por" + +msgid "By the" +msgstr "Por el" + +msgid "previous page" +msgstr "pagina anterior" diff --git a/_static/locales/et/LC_MESSAGES/booktheme.mo b/_static/locales/et/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..983b82391 Binary files /dev/null and b/_static/locales/et/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/et/LC_MESSAGES/booktheme.po b/_static/locales/et/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..b748b37f0 --- /dev/null +++ b/_static/locales/et/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: et\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Laadige alla lähtefail" + +msgid "suggest edit" +msgstr "soovita muuta" + +msgid "Toggle navigation" +msgstr "Lülita navigeerimine sisse" + +msgid "open issue" +msgstr "avatud küsimus" + +msgid "Download notebook file" +msgstr "Laadige sülearvuti fail alla" + +msgid "repository" +msgstr "hoidla" + +msgid "Theme by the" +msgstr "Teema" + +msgid "Print to PDF" +msgstr "Prindi PDF-i" + +msgid "Download this page" +msgstr "Laadige see leht alla" + +msgid "Copyright" +msgstr "Autoriõigus" + +msgid "Last updated on" +msgstr "Viimati uuendatud" + +msgid "Launch" +msgstr "Käivitage" + +msgid "Open an issue" +msgstr "Avage probleem" + +msgid "Fullscreen mode" +msgstr "Täisekraanirežiim" + +msgid "Sphinx Book Theme" +msgstr "Sfinksiraamatu teema" + +msgid "Contents" +msgstr "Sisu" + +msgid "Edit this page" +msgstr "Muutke seda lehte" + +msgid "next page" +msgstr "järgmine leht" + +msgid "Source repository" +msgstr "Allikahoidla" + +msgid "By" +msgstr "Kõrval" + +msgid "By the" +msgstr "Autor" + +msgid "previous page" +msgstr "eelmine leht" diff --git a/_static/locales/fi/LC_MESSAGES/booktheme.mo b/_static/locales/fi/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..d8ac05459 Binary files /dev/null and b/_static/locales/fi/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/fi/LC_MESSAGES/booktheme.po b/_static/locales/fi/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..f58cf58d3 --- /dev/null +++ b/_static/locales/fi/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: fi\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Lataa lähdetiedosto" + +msgid "suggest edit" +msgstr "ehdottaa muokkausta" + +msgid "Toggle navigation" +msgstr "Vaihda navigointia" + +msgid "open issue" +msgstr "avoin ongelma" + +msgid "Download notebook file" +msgstr "Lataa muistikirjatiedosto" + +msgid "repository" +msgstr "arkisto" + +msgid "Theme by the" +msgstr "Teeman tekijä" + +msgid "Print to PDF" +msgstr "Tulosta PDF-tiedostoon" + +msgid "Download this page" +msgstr "Lataa tämä sivu" + +msgid "Copyright" +msgstr "Tekijänoikeus" + +msgid "Last updated on" +msgstr "Viimeksi päivitetty" + +msgid "Launch" +msgstr "Tuoda markkinoille" + +msgid "Open an issue" +msgstr "Avaa ongelma" + +msgid "Fullscreen mode" +msgstr "Koko näytön tila" + +msgid "Sphinx Book Theme" +msgstr "Sphinx-kirjan teema" + +msgid "Contents" +msgstr "Sisällys" + +msgid "Edit this page" +msgstr "Muokkaa tätä sivua" + +msgid "next page" +msgstr "seuraava sivu" + +msgid "Source repository" +msgstr "Lähteen arkisto" + +msgid "By" +msgstr "Tekijä" + +msgid "By the" +msgstr "Mukaan" + +msgid "previous page" +msgstr "Edellinen sivu" diff --git a/_static/locales/fr/LC_MESSAGES/booktheme.mo b/_static/locales/fr/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..f663d39f0 Binary files /dev/null and b/_static/locales/fr/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/fr/LC_MESSAGES/booktheme.po b/_static/locales/fr/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..8a6c94619 --- /dev/null +++ b/_static/locales/fr/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Télécharger le fichier source" + +msgid "suggest edit" +msgstr "suggestion de modification" + +msgid "Toggle navigation" +msgstr "Basculer la navigation" + +msgid "open issue" +msgstr "signaler un problème" + +msgid "Download notebook file" +msgstr "Télécharger le fichier notebook" + +msgid "repository" +msgstr "dépôt" + +msgid "Theme by the" +msgstr "Thème par le" + +msgid "Print to PDF" +msgstr "Imprimer au format PDF" + +msgid "Download this page" +msgstr "Téléchargez cette page" + +msgid "Copyright" +msgstr "droits d'auteur" + +msgid "Last updated on" +msgstr "Dernière mise à jour le" + +msgid "Launch" +msgstr "lancement" + +msgid "Open an issue" +msgstr "Ouvrez un problème" + +msgid "Fullscreen mode" +msgstr "Mode plein écran" + +msgid "Sphinx Book Theme" +msgstr "Thème du livre Sphinx" + +msgid "Contents" +msgstr "Contenu" + +msgid "Edit this page" +msgstr "Modifier cette page" + +msgid "next page" +msgstr "page suivante" + +msgid "Source repository" +msgstr "Dépôt source" + +msgid "By" +msgstr "Par" + +msgid "By the" +msgstr "Par le" + +msgid "previous page" +msgstr "page précédente" diff --git a/_static/locales/hr/LC_MESSAGES/booktheme.mo b/_static/locales/hr/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..eca4a1a28 Binary files /dev/null and b/_static/locales/hr/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/hr/LC_MESSAGES/booktheme.po b/_static/locales/hr/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..4ceb3899f --- /dev/null +++ b/_static/locales/hr/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: hr\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Preuzmi izvornu datoteku" + +msgid "suggest edit" +msgstr "predloži uređivanje" + +msgid "Toggle navigation" +msgstr "Uključi / isključi navigaciju" + +msgid "open issue" +msgstr "otvoreno izdanje" + +msgid "Download notebook file" +msgstr "Preuzmi datoteku bilježnice" + +msgid "repository" +msgstr "spremište" + +msgid "Theme by the" +msgstr "Tema autora" + +msgid "Print to PDF" +msgstr "Ispis u PDF" + +msgid "Download this page" +msgstr "Preuzmite ovu stranicu" + +msgid "Copyright" +msgstr "Autorska prava" + +msgid "Last updated on" +msgstr "Posljednje ažuriranje:" + +msgid "Launch" +msgstr "Pokrenite" + +msgid "Open an issue" +msgstr "Otvorite izdanje" + +msgid "Fullscreen mode" +msgstr "Način preko cijelog zaslona" + +msgid "Sphinx Book Theme" +msgstr "Tema knjige Sphinx" + +msgid "Contents" +msgstr "Sadržaj" + +msgid "Edit this page" +msgstr "Uredite ovu stranicu" + +msgid "next page" +msgstr "sljedeća stranica" + +msgid "Source repository" +msgstr "Izvorno spremište" + +msgid "By" +msgstr "Po" + +msgid "By the" +msgstr "Od strane" + +msgid "previous page" +msgstr "Prethodna stranica" diff --git a/_static/locales/id/LC_MESSAGES/booktheme.mo b/_static/locales/id/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..d07a06a9d Binary files /dev/null and b/_static/locales/id/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/id/LC_MESSAGES/booktheme.po b/_static/locales/id/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..1eca59947 --- /dev/null +++ b/_static/locales/id/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: id\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Unduh file sumber" + +msgid "suggest edit" +msgstr "menyarankan edit" + +msgid "Toggle navigation" +msgstr "Alihkan navigasi" + +msgid "open issue" +msgstr "masalah terbuka" + +msgid "Download notebook file" +msgstr "Unduh file notebook" + +msgid "repository" +msgstr "gudang" + +msgid "Theme by the" +msgstr "Tema oleh" + +msgid "Print to PDF" +msgstr "Cetak ke PDF" + +msgid "Download this page" +msgstr "Unduh halaman ini" + +msgid "Copyright" +msgstr "hak cipta" + +msgid "Last updated on" +msgstr "Terakhir diperbarui saat" + +msgid "Launch" +msgstr "Meluncurkan" + +msgid "Open an issue" +msgstr "Buka masalah" + +msgid "Fullscreen mode" +msgstr "Mode layar penuh" + +msgid "Sphinx Book Theme" +msgstr "Tema Buku Sphinx" + +msgid "Contents" +msgstr "Isi" + +msgid "Edit this page" +msgstr "Edit halaman ini" + +msgid "next page" +msgstr "halaman selanjutnya" + +msgid "Source repository" +msgstr "Repositori sumber" + +msgid "By" +msgstr "Oleh" + +msgid "By the" +msgstr "Oleh" + +msgid "previous page" +msgstr "halaman sebelumnya" diff --git a/_static/locales/it/LC_MESSAGES/booktheme.mo b/_static/locales/it/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..53ba476ed Binary files /dev/null and b/_static/locales/it/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/it/LC_MESSAGES/booktheme.po b/_static/locales/it/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..f3169000c --- /dev/null +++ b/_static/locales/it/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: it\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Scarica il file sorgente" + +msgid "suggest edit" +msgstr "suggerisci modifica" + +msgid "Toggle navigation" +msgstr "Attiva / disattiva la navigazione" + +msgid "open issue" +msgstr "questione aperta" + +msgid "Download notebook file" +msgstr "Scarica il file del taccuino" + +msgid "repository" +msgstr "repository" + +msgid "Theme by the" +msgstr "Tema di" + +msgid "Print to PDF" +msgstr "Stampa in PDF" + +msgid "Download this page" +msgstr "Scarica questa pagina" + +msgid "Copyright" +msgstr "Diritto d'autore" + +msgid "Last updated on" +msgstr "Ultimo aggiornamento il" + +msgid "Launch" +msgstr "Lanciare" + +msgid "Open an issue" +msgstr "Apri un problema" + +msgid "Fullscreen mode" +msgstr "Modalità schermo intero" + +msgid "Sphinx Book Theme" +msgstr "Tema del libro della Sfinge" + +msgid "Contents" +msgstr "Contenuti" + +msgid "Edit this page" +msgstr "Modifica questa pagina" + +msgid "next page" +msgstr "pagina successiva" + +msgid "Source repository" +msgstr "Repository di origine" + +msgid "By" +msgstr "Di" + +msgid "By the" +msgstr "Dal" + +msgid "previous page" +msgstr "pagina precedente" diff --git a/_static/locales/iw/LC_MESSAGES/booktheme.mo b/_static/locales/iw/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..a45c6575e Binary files /dev/null and b/_static/locales/iw/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/iw/LC_MESSAGES/booktheme.po b/_static/locales/iw/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..9e6d753e7 --- /dev/null +++ b/_static/locales/iw/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: iw\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "הורד את קובץ המקור" + +msgid "suggest edit" +msgstr "מציע לערוך" + +msgid "Toggle navigation" +msgstr "החלף ניווט" + +msgid "open issue" +msgstr "בעיה פתוחה" + +msgid "Download notebook file" +msgstr "הורד קובץ מחברת" + +msgid "repository" +msgstr "מאגר" + +msgid "Theme by the" +msgstr "נושא מאת" + +msgid "Print to PDF" +msgstr "הדפס לקובץ PDF" + +msgid "Download this page" +msgstr "הורד דף זה" + +msgid "Copyright" +msgstr "זכויות יוצרים" + +msgid "Last updated on" +msgstr "עודכן לאחרונה ב" + +msgid "Launch" +msgstr "לְהַשִׁיק" + +msgid "Open an issue" +msgstr "פתח גיליון" + +msgid "Fullscreen mode" +msgstr "מצב מסך מלא" + +msgid "Sphinx Book Theme" +msgstr "נושא ספר ספינקס" + +msgid "Contents" +msgstr "תוכן" + +msgid "Edit this page" +msgstr "ערוך דף זה" + +msgid "next page" +msgstr "עמוד הבא" + +msgid "Source repository" +msgstr "מאגר המקורות" + +msgid "By" +msgstr "על ידי" + +msgid "By the" +msgstr "דרך" + +msgid "previous page" +msgstr "עמוד קודם" diff --git a/_static/locales/ja/LC_MESSAGES/booktheme.mo b/_static/locales/ja/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..1cefd29ce Binary files /dev/null and b/_static/locales/ja/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/ja/LC_MESSAGES/booktheme.po b/_static/locales/ja/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..c1a314896 --- /dev/null +++ b/_static/locales/ja/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ja\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "ソースファイルをダウンロード" + +msgid "suggest edit" +msgstr "編集を提案する" + +msgid "Toggle navigation" +msgstr "ナビゲーションを切り替え" + +msgid "open issue" +msgstr "未解決の問題" + +msgid "Download notebook file" +msgstr "ノートブックファイルをダウンロード" + +msgid "repository" +msgstr "リポジトリ" + +msgid "Theme by the" +msgstr "のテーマ" + +msgid "Print to PDF" +msgstr "PDFに印刷" + +msgid "Download this page" +msgstr "このページをダウンロード" + +msgid "Copyright" +msgstr "Copyright" + +msgid "Last updated on" +msgstr "最終更新日" + +msgid "Launch" +msgstr "起動" + +msgid "Open an issue" +msgstr "問題を報告" + +msgid "Fullscreen mode" +msgstr "全画面モード" + +msgid "Sphinx Book Theme" +msgstr "スフィンクスの本のテーマ" + +msgid "Contents" +msgstr "目次" + +msgid "Edit this page" +msgstr "このページを編集" + +msgid "next page" +msgstr "次のページ" + +msgid "Source repository" +msgstr "ソースリポジトリ" + +msgid "By" +msgstr "著者" + +msgid "By the" +msgstr "によって" + +msgid "previous page" +msgstr "前のページ" diff --git a/_static/locales/ko/LC_MESSAGES/booktheme.mo b/_static/locales/ko/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..06c7ec938 Binary files /dev/null and b/_static/locales/ko/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/ko/LC_MESSAGES/booktheme.po b/_static/locales/ko/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..80142313b --- /dev/null +++ b/_static/locales/ko/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ko\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "소스 파일 다운로드" + +msgid "suggest edit" +msgstr "편집 제안" + +msgid "Toggle navigation" +msgstr "탐색 전환" + +msgid "open issue" +msgstr "열린 문제" + +msgid "Download notebook file" +msgstr "노트북 파일 다운로드" + +msgid "repository" +msgstr "저장소" + +msgid "Theme by the" +msgstr "테마별" + +msgid "Print to PDF" +msgstr "PDF로 인쇄" + +msgid "Download this page" +msgstr "이 페이지 다운로드" + +msgid "Copyright" +msgstr "저작권" + +msgid "Last updated on" +msgstr "마지막 업데이트" + +msgid "Launch" +msgstr "시작하다" + +msgid "Open an issue" +msgstr "이슈 열기" + +msgid "Fullscreen mode" +msgstr "전체 화면으로보기" + +msgid "Sphinx Book Theme" +msgstr "스핑크스 도서 테마" + +msgid "Contents" +msgstr "내용" + +msgid "Edit this page" +msgstr "이 페이지 편집" + +msgid "next page" +msgstr "다음 페이지" + +msgid "Source repository" +msgstr "소스 저장소" + +msgid "By" +msgstr "으로" + +msgid "By the" +msgstr "에 의해" + +msgid "previous page" +msgstr "이전 페이지" diff --git a/_static/locales/lt/LC_MESSAGES/booktheme.mo b/_static/locales/lt/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..4468ba04b Binary files /dev/null and b/_static/locales/lt/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/lt/LC_MESSAGES/booktheme.po b/_static/locales/lt/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..2e6915a9f --- /dev/null +++ b/_static/locales/lt/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: lt\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Atsisiųsti šaltinio failą" + +msgid "suggest edit" +msgstr "pasiūlyti redaguoti" + +msgid "Toggle navigation" +msgstr "Perjungti naršymą" + +msgid "open issue" +msgstr "atviras klausimas" + +msgid "Download notebook file" +msgstr "Atsisiųsti nešiojamojo kompiuterio failą" + +msgid "repository" +msgstr "saugykla" + +msgid "Theme by the" +msgstr "Tema" + +msgid "Print to PDF" +msgstr "Spausdinti į PDF" + +msgid "Download this page" +msgstr "Atsisiųskite šį puslapį" + +msgid "Copyright" +msgstr "Autorių teisės" + +msgid "Last updated on" +msgstr "Paskutinį kartą atnaujinta" + +msgid "Launch" +msgstr "Paleiskite" + +msgid "Open an issue" +msgstr "Atidarykite problemą" + +msgid "Fullscreen mode" +msgstr "Pilno ekrano režimas" + +msgid "Sphinx Book Theme" +msgstr "Sfinkso knygos tema" + +msgid "Contents" +msgstr "Turinys" + +msgid "Edit this page" +msgstr "Redaguoti šį puslapį" + +msgid "next page" +msgstr "Kitas puslapis" + +msgid "Source repository" +msgstr "Šaltinio saugykla" + +msgid "By" +msgstr "Iki" + +msgid "By the" +msgstr "Prie" + +msgid "previous page" +msgstr "Ankstesnis puslapis" diff --git a/_static/locales/lv/LC_MESSAGES/booktheme.mo b/_static/locales/lv/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..74aa4d898 Binary files /dev/null and b/_static/locales/lv/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/lv/LC_MESSAGES/booktheme.po b/_static/locales/lv/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..d4f4b1507 --- /dev/null +++ b/_static/locales/lv/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: lv\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Lejupielādēt avota failu" + +msgid "suggest edit" +msgstr "ieteikt rediģēt" + +msgid "Toggle navigation" +msgstr "Pārslēgt navigāciju" + +msgid "open issue" +msgstr "atklāts jautājums" + +msgid "Download notebook file" +msgstr "Lejupielādēt piezīmju grāmatiņu" + +msgid "repository" +msgstr "krātuve" + +msgid "Theme by the" +msgstr "Autora tēma" + +msgid "Print to PDF" +msgstr "Drukāt PDF formātā" + +msgid "Download this page" +msgstr "Lejupielādējiet šo lapu" + +msgid "Copyright" +msgstr "Autortiesības" + +msgid "Last updated on" +msgstr "Pēdējoreiz atjaunināts" + +msgid "Launch" +msgstr "Uzsākt" + +msgid "Open an issue" +msgstr "Atveriet problēmu" + +msgid "Fullscreen mode" +msgstr "Pilnekrāna režīms" + +msgid "Sphinx Book Theme" +msgstr "Sfinksa grāmatas tēma" + +msgid "Contents" +msgstr "Saturs" + +msgid "Edit this page" +msgstr "Rediģēt šo lapu" + +msgid "next page" +msgstr "nākamā lapaspuse" + +msgid "Source repository" +msgstr "Avota krātuve" + +msgid "By" +msgstr "Autors" + +msgid "By the" +msgstr "Ar" + +msgid "previous page" +msgstr "iepriekšējā lapa" diff --git a/_static/locales/ml/LC_MESSAGES/booktheme.mo b/_static/locales/ml/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..2736e8fcf Binary files /dev/null and b/_static/locales/ml/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/ml/LC_MESSAGES/booktheme.po b/_static/locales/ml/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..5d8ed33b6 --- /dev/null +++ b/_static/locales/ml/LC_MESSAGES/booktheme.po @@ -0,0 +1,66 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ml\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "ഉറവിട ഫയൽ ഡൗൺലോഡുചെയ്യുക" + +msgid "suggest edit" +msgstr "എഡിറ്റുചെയ്യാൻ നിർദ്ദേശിക്കുക" + +msgid "Toggle navigation" +msgstr "നാവിഗേഷൻ ടോഗിൾ ചെയ്യുക" + +msgid "open issue" +msgstr "തുറന്ന പ്രശ്നം" + +msgid "Download notebook file" +msgstr "നോട്ട്ബുക്ക് ഫയൽ ഡൺലോഡ് ചെയ്യുക" + +msgid "Theme by the" +msgstr "പ്രമേയം" + +msgid "Print to PDF" +msgstr "PDF- ലേക്ക് പ്രിന്റുചെയ്യുക" + +msgid "Download this page" +msgstr "ഈ പേജ് ഡൗൺലോഡുചെയ്യുക" + +msgid "Copyright" +msgstr "പകർപ്പവകാശം" + +msgid "Last updated on" +msgstr "അവസാനം അപ്‌ഡേറ്റുചെയ്‌തത്" + +msgid "Launch" +msgstr "സമാരംഭിക്കുക" + +msgid "Open an issue" +msgstr "ഒരു പ്രശ്നം തുറക്കുക" + +msgid "Sphinx Book Theme" +msgstr "സ്ഫിങ്ക്സ് പുസ്തക തീം" + +msgid "Edit this page" +msgstr "ഈ പേജ് എഡിറ്റുചെയ്യുക" + +msgid "next page" +msgstr "അടുത്ത പേജ്" + +msgid "Source repository" +msgstr "ഉറവിട ശേഖരം" + +msgid "By" +msgstr "എഴുതിയത്" + +msgid "By the" +msgstr "എഴുതിയത്" + +msgid "previous page" +msgstr "മുൻപത്തെ താൾ" diff --git a/_static/locales/mr/LC_MESSAGES/booktheme.mo b/_static/locales/mr/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..fe530100d Binary files /dev/null and b/_static/locales/mr/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/mr/LC_MESSAGES/booktheme.po b/_static/locales/mr/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..64389fac9 --- /dev/null +++ b/_static/locales/mr/LC_MESSAGES/booktheme.po @@ -0,0 +1,66 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: mr\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "स्त्रोत फाइल डाउनलोड करा" + +msgid "suggest edit" +msgstr "संपादन सुचवा" + +msgid "Toggle navigation" +msgstr "नेव्हिगेशन टॉगल करा" + +msgid "open issue" +msgstr "खुला मुद्दा" + +msgid "Download notebook file" +msgstr "नोटबुक फाईल डाउनलोड करा" + +msgid "Theme by the" +msgstr "द्वारा थीम" + +msgid "Print to PDF" +msgstr "पीडीएफवर मुद्रित करा" + +msgid "Download this page" +msgstr "हे पृष्ठ डाउनलोड करा" + +msgid "Copyright" +msgstr "कॉपीराइट" + +msgid "Last updated on" +msgstr "अखेरचे अद्यतनित" + +msgid "Launch" +msgstr "लाँच करा" + +msgid "Open an issue" +msgstr "एक मुद्दा उघडा" + +msgid "Sphinx Book Theme" +msgstr "स्फिंक्स बुक थीम" + +msgid "Edit this page" +msgstr "हे पृष्ठ संपादित करा" + +msgid "next page" +msgstr "पुढील पृष्ठ" + +msgid "Source repository" +msgstr "स्त्रोत भांडार" + +msgid "By" +msgstr "द्वारा" + +msgid "By the" +msgstr "द्वारा" + +msgid "previous page" +msgstr "मागील पान" diff --git a/_static/locales/ms/LC_MESSAGES/booktheme.mo b/_static/locales/ms/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..f02603fa2 Binary files /dev/null and b/_static/locales/ms/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/ms/LC_MESSAGES/booktheme.po b/_static/locales/ms/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..4365ff549 --- /dev/null +++ b/_static/locales/ms/LC_MESSAGES/booktheme.po @@ -0,0 +1,66 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ms\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Muat turun fail sumber" + +msgid "suggest edit" +msgstr "cadangkan edit" + +msgid "Toggle navigation" +msgstr "Togol navigasi" + +msgid "open issue" +msgstr "isu terbuka" + +msgid "Download notebook file" +msgstr "Muat turun fail buku nota" + +msgid "Theme by the" +msgstr "Tema oleh" + +msgid "Print to PDF" +msgstr "Cetak ke PDF" + +msgid "Download this page" +msgstr "Muat turun halaman ini" + +msgid "Copyright" +msgstr "hak cipta" + +msgid "Last updated on" +msgstr "Terakhir dikemas kini pada" + +msgid "Launch" +msgstr "Lancarkan" + +msgid "Open an issue" +msgstr "Buka masalah" + +msgid "Sphinx Book Theme" +msgstr "Tema Buku Sphinx" + +msgid "Edit this page" +msgstr "Edit halaman ini" + +msgid "next page" +msgstr "muka surat seterusnya" + +msgid "Source repository" +msgstr "Repositori sumber" + +msgid "By" +msgstr "Oleh" + +msgid "By the" +msgstr "Oleh" + +msgid "previous page" +msgstr "halaman sebelumnya" diff --git a/_static/locales/nl/LC_MESSAGES/booktheme.mo b/_static/locales/nl/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..e59e7ecb3 Binary files /dev/null and b/_static/locales/nl/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/nl/LC_MESSAGES/booktheme.po b/_static/locales/nl/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..f7b18e5d5 --- /dev/null +++ b/_static/locales/nl/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: nl\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Download het bronbestand" + +msgid "suggest edit" +msgstr "suggereren bewerken" + +msgid "Toggle navigation" +msgstr "Schakel navigatie" + +msgid "open issue" +msgstr "open probleem" + +msgid "Download notebook file" +msgstr "Download notebookbestand" + +msgid "repository" +msgstr "repository" + +msgid "Theme by the" +msgstr "Thema door de" + +msgid "Print to PDF" +msgstr "Afdrukken naar pdf" + +msgid "Download this page" +msgstr "Download deze pagina" + +msgid "Copyright" +msgstr "auteursrechten" + +msgid "Last updated on" +msgstr "Laatst geupdate op" + +msgid "Launch" +msgstr "Lancering" + +msgid "Open an issue" +msgstr "Open een probleem" + +msgid "Fullscreen mode" +msgstr "Volledig scherm" + +msgid "Sphinx Book Theme" +msgstr "Sphinx-boekthema" + +msgid "Contents" +msgstr "Inhoud" + +msgid "Edit this page" +msgstr "bewerk deze pagina" + +msgid "next page" +msgstr "volgende bladzijde" + +msgid "Source repository" +msgstr "Bronopslagplaats" + +msgid "By" +msgstr "Door" + +msgid "By the" +msgstr "Door de" + +msgid "previous page" +msgstr "vorige pagina" diff --git a/_static/locales/no/LC_MESSAGES/booktheme.mo b/_static/locales/no/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..6cd15c88d Binary files /dev/null and b/_static/locales/no/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/no/LC_MESSAGES/booktheme.po b/_static/locales/no/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..274823a86 --- /dev/null +++ b/_static/locales/no/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: no\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Last ned kildefilen" + +msgid "suggest edit" +msgstr "foreslå redigering" + +msgid "Toggle navigation" +msgstr "Bytt navigasjon" + +msgid "open issue" +msgstr "åpent nummer" + +msgid "Download notebook file" +msgstr "Last ned notatbokfilen" + +msgid "repository" +msgstr "oppbevaringssted" + +msgid "Theme by the" +msgstr "Tema av" + +msgid "Print to PDF" +msgstr "Skriv ut til PDF" + +msgid "Download this page" +msgstr "Last ned denne siden" + +msgid "Copyright" +msgstr "opphavsrett" + +msgid "Last updated on" +msgstr "Sist oppdatert den" + +msgid "Launch" +msgstr "Start" + +msgid "Open an issue" +msgstr "Åpne et problem" + +msgid "Fullscreen mode" +msgstr "Fullskjerm-modus" + +msgid "Sphinx Book Theme" +msgstr "Sphinx boktema" + +msgid "Contents" +msgstr "Innhold" + +msgid "Edit this page" +msgstr "Rediger denne siden" + +msgid "next page" +msgstr "neste side" + +msgid "Source repository" +msgstr "Kildedepot" + +msgid "By" +msgstr "Av" + +msgid "By the" +msgstr "Ved" + +msgid "previous page" +msgstr "forrige side" diff --git a/_static/locales/pl/LC_MESSAGES/booktheme.mo b/_static/locales/pl/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..9ebb584f7 Binary files /dev/null and b/_static/locales/pl/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/pl/LC_MESSAGES/booktheme.po b/_static/locales/pl/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..dfba3f698 --- /dev/null +++ b/_static/locales/pl/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: pl\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Pobierz plik źródłowy" + +msgid "suggest edit" +msgstr "zaproponuj edycję" + +msgid "Toggle navigation" +msgstr "Przełącz nawigację" + +msgid "open issue" +msgstr "otwarty problem" + +msgid "Download notebook file" +msgstr "Pobierz plik notatnika" + +msgid "repository" +msgstr "magazyn" + +msgid "Theme by the" +msgstr "Motyw autorstwa" + +msgid "Print to PDF" +msgstr "Drukuj do PDF" + +msgid "Download this page" +msgstr "Pobierz tę stronę" + +msgid "Copyright" +msgstr "prawa autorskie" + +msgid "Last updated on" +msgstr "Ostatnia aktualizacja" + +msgid "Launch" +msgstr "Uruchomić" + +msgid "Open an issue" +msgstr "Otwórz problem" + +msgid "Fullscreen mode" +msgstr "Pełny ekran" + +msgid "Sphinx Book Theme" +msgstr "Motyw książki Sphinx" + +msgid "Contents" +msgstr "Zawartość" + +msgid "Edit this page" +msgstr "Edytuj tę strone" + +msgid "next page" +msgstr "Następna strona" + +msgid "Source repository" +msgstr "Repozytorium źródłowe" + +msgid "By" +msgstr "Przez" + +msgid "By the" +msgstr "Przez" + +msgid "previous page" +msgstr "Poprzednia strona" diff --git a/_static/locales/pt/LC_MESSAGES/booktheme.mo b/_static/locales/pt/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..d0ddb8728 Binary files /dev/null and b/_static/locales/pt/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/pt/LC_MESSAGES/booktheme.po b/_static/locales/pt/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..4c24eb9e2 --- /dev/null +++ b/_static/locales/pt/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: pt\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Baixar arquivo fonte" + +msgid "suggest edit" +msgstr "sugerir edição" + +msgid "Toggle navigation" +msgstr "Alternar de navegação" + +msgid "open issue" +msgstr "questão aberta" + +msgid "Download notebook file" +msgstr "Baixar arquivo de notebook" + +msgid "repository" +msgstr "repositório" + +msgid "Theme by the" +msgstr "Tema por" + +msgid "Print to PDF" +msgstr "Imprimir em PDF" + +msgid "Download this page" +msgstr "Baixe esta página" + +msgid "Copyright" +msgstr "direito autoral" + +msgid "Last updated on" +msgstr "Última atualização em" + +msgid "Launch" +msgstr "Lançamento" + +msgid "Open an issue" +msgstr "Abra um problema" + +msgid "Fullscreen mode" +msgstr "Modo tela cheia" + +msgid "Sphinx Book Theme" +msgstr "Tema do livro Sphinx" + +msgid "Contents" +msgstr "Conteúdo" + +msgid "Edit this page" +msgstr "Edite essa página" + +msgid "next page" +msgstr "próxima página" + +msgid "Source repository" +msgstr "Repositório fonte" + +msgid "By" +msgstr "De" + +msgid "By the" +msgstr "Pelo" + +msgid "previous page" +msgstr "página anterior" diff --git a/_static/locales/ro/LC_MESSAGES/booktheme.mo b/_static/locales/ro/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..3c36ab1df Binary files /dev/null and b/_static/locales/ro/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/ro/LC_MESSAGES/booktheme.po b/_static/locales/ro/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..5f03d9cdb --- /dev/null +++ b/_static/locales/ro/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ro\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Descărcați fișierul sursă" + +msgid "suggest edit" +msgstr "sugerează editare" + +msgid "Toggle navigation" +msgstr "Comutare navigare" + +msgid "open issue" +msgstr "problema deschisă" + +msgid "Download notebook file" +msgstr "Descărcați fișierul notebook" + +msgid "repository" +msgstr "repertoriu" + +msgid "Theme by the" +msgstr "Tema de" + +msgid "Print to PDF" +msgstr "Imprimați în PDF" + +msgid "Download this page" +msgstr "Descarcă această pagină" + +msgid "Copyright" +msgstr "Drepturi de autor" + +msgid "Last updated on" +msgstr "Ultima actualizare la" + +msgid "Launch" +msgstr "Lansa" + +msgid "Open an issue" +msgstr "Deschideți o problemă" + +msgid "Fullscreen mode" +msgstr "Modul ecran întreg" + +msgid "Sphinx Book Theme" +msgstr "Tema Sphinx Book" + +msgid "Contents" +msgstr "Cuprins" + +msgid "Edit this page" +msgstr "Editați această pagină" + +msgid "next page" +msgstr "pagina următoare" + +msgid "Source repository" +msgstr "Depozit sursă" + +msgid "By" +msgstr "De" + +msgid "By the" +msgstr "Langa" + +msgid "previous page" +msgstr "pagina anterioară" diff --git a/_static/locales/ru/LC_MESSAGES/booktheme.mo b/_static/locales/ru/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..6b8ca41f3 Binary files /dev/null and b/_static/locales/ru/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/ru/LC_MESSAGES/booktheme.po b/_static/locales/ru/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..2886570d0 --- /dev/null +++ b/_static/locales/ru/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ru\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Скачать исходный файл" + +msgid "suggest edit" +msgstr "предложить редактировать" + +msgid "Toggle navigation" +msgstr "Переключить навигацию" + +msgid "open issue" +msgstr "открытый вопрос" + +msgid "Download notebook file" +msgstr "Скачать файл записной книжки" + +msgid "repository" +msgstr "хранилище" + +msgid "Theme by the" +msgstr "Тема от" + +msgid "Print to PDF" +msgstr "Распечатать в PDF" + +msgid "Download this page" +msgstr "Загрузите эту страницу" + +msgid "Copyright" +msgstr "авторское право" + +msgid "Last updated on" +msgstr "Последнее обновление" + +msgid "Launch" +msgstr "Запуск" + +msgid "Open an issue" +msgstr "Открыть вопрос" + +msgid "Fullscreen mode" +msgstr "Полноэкранный режим" + +msgid "Sphinx Book Theme" +msgstr "Тема книги Сфинкс" + +msgid "Contents" +msgstr "Содержание" + +msgid "Edit this page" +msgstr "Редактировать эту страницу" + +msgid "next page" +msgstr "Следующая страница" + +msgid "Source repository" +msgstr "Исходный репозиторий" + +msgid "By" +msgstr "По" + +msgid "By the" +msgstr "Посредством" + +msgid "previous page" +msgstr "Предыдущая страница" diff --git a/_static/locales/sk/LC_MESSAGES/booktheme.mo b/_static/locales/sk/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..59bd0ddfa Binary files /dev/null and b/_static/locales/sk/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/sk/LC_MESSAGES/booktheme.po b/_static/locales/sk/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..25517aa5a --- /dev/null +++ b/_static/locales/sk/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: sk\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Stiahnite si zdrojový súbor" + +msgid "suggest edit" +msgstr "navrhnúť úpravu" + +msgid "Toggle navigation" +msgstr "Prepnúť navigáciu" + +msgid "open issue" +msgstr "otvorené vydanie" + +msgid "Download notebook file" +msgstr "Stiahnite si zošit" + +msgid "repository" +msgstr "Úložisko" + +msgid "Theme by the" +msgstr "Téma od" + +msgid "Print to PDF" +msgstr "Tlač do PDF" + +msgid "Download this page" +msgstr "Stiahnite si túto stránku" + +msgid "Copyright" +msgstr "Autorské práva" + +msgid "Last updated on" +msgstr "Posledná aktualizácia dňa" + +msgid "Launch" +msgstr "Spustiť" + +msgid "Open an issue" +msgstr "Otvorte problém" + +msgid "Fullscreen mode" +msgstr "Režim celej obrazovky" + +msgid "Sphinx Book Theme" +msgstr "Téma knihy Sfinga" + +msgid "Contents" +msgstr "Obsah" + +msgid "Edit this page" +msgstr "Upraviť túto stránku" + +msgid "next page" +msgstr "ďalšia strana" + +msgid "Source repository" +msgstr "Zdrojové úložisko" + +msgid "By" +msgstr "Autor:" + +msgid "By the" +msgstr "Podľa" + +msgid "previous page" +msgstr "predchádzajúca strana" diff --git a/_static/locales/sl/LC_MESSAGES/booktheme.mo b/_static/locales/sl/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..87bf26de6 Binary files /dev/null and b/_static/locales/sl/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/sl/LC_MESSAGES/booktheme.po b/_static/locales/sl/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..346c36484 --- /dev/null +++ b/_static/locales/sl/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: sl\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Prenesite izvorno datoteko" + +msgid "suggest edit" +msgstr "predlagajte urejanje" + +msgid "Toggle navigation" +msgstr "Preklopi navigacijo" + +msgid "open issue" +msgstr "odprto vprašanje" + +msgid "Download notebook file" +msgstr "Prenesite datoteko zvezka" + +msgid "repository" +msgstr "odlagališče" + +msgid "Theme by the" +msgstr "Tema avtorja" + +msgid "Print to PDF" +msgstr "Natisni v PDF" + +msgid "Download this page" +msgstr "Prenesite to stran" + +msgid "Copyright" +msgstr "avtorske pravice" + +msgid "Last updated on" +msgstr "Nazadnje posodobljeno dne" + +msgid "Launch" +msgstr "Kosilo" + +msgid "Open an issue" +msgstr "Odprite številko" + +msgid "Fullscreen mode" +msgstr "Celozaslonski način" + +msgid "Sphinx Book Theme" +msgstr "Tema knjige Sphinx" + +msgid "Contents" +msgstr "Vsebina" + +msgid "Edit this page" +msgstr "Uredite to stran" + +msgid "next page" +msgstr "Naslednja stran" + +msgid "Source repository" +msgstr "Izvorno skladišče" + +msgid "By" +msgstr "Avtor" + +msgid "By the" +msgstr "Avtor" + +msgid "previous page" +msgstr "Prejšnja stran" diff --git a/_static/locales/sr/LC_MESSAGES/booktheme.mo b/_static/locales/sr/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..ec740f485 Binary files /dev/null and b/_static/locales/sr/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/sr/LC_MESSAGES/booktheme.po b/_static/locales/sr/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..b1a97adac --- /dev/null +++ b/_static/locales/sr/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: sr\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Преузми изворну датотеку" + +msgid "suggest edit" +msgstr "предложи уређивање" + +msgid "Toggle navigation" +msgstr "Укључи / искључи навигацију" + +msgid "open issue" +msgstr "отворено издање" + +msgid "Download notebook file" +msgstr "Преузмите датотеку бележнице" + +msgid "repository" +msgstr "спремиште" + +msgid "Theme by the" +msgstr "Тхеме би" + +msgid "Print to PDF" +msgstr "Испис у ПДФ" + +msgid "Download this page" +msgstr "Преузмите ову страницу" + +msgid "Copyright" +msgstr "Ауторско право" + +msgid "Last updated on" +msgstr "Последње ажурирање" + +msgid "Launch" +msgstr "Лансирање" + +msgid "Open an issue" +msgstr "Отворите издање" + +msgid "Fullscreen mode" +msgstr "Режим целог екрана" + +msgid "Sphinx Book Theme" +msgstr "Тема књиге Спхинк" + +msgid "Contents" +msgstr "Садржај" + +msgid "Edit this page" +msgstr "Уредите ову страницу" + +msgid "next page" +msgstr "Следећа страна" + +msgid "Source repository" +msgstr "Изворно спремиште" + +msgid "By" +msgstr "Од стране" + +msgid "By the" +msgstr "Од" + +msgid "previous page" +msgstr "Претходна страница" diff --git a/_static/locales/sv/LC_MESSAGES/booktheme.mo b/_static/locales/sv/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..b07dc76ff Binary files /dev/null and b/_static/locales/sv/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/sv/LC_MESSAGES/booktheme.po b/_static/locales/sv/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..8fc0146e6 --- /dev/null +++ b/_static/locales/sv/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: sv\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Ladda ner källfil" + +msgid "suggest edit" +msgstr "föreslå ändring" + +msgid "Toggle navigation" +msgstr "Växla navigering" + +msgid "open issue" +msgstr "öppna problemrapport" + +msgid "Download notebook file" +msgstr "Ladda ner notebook-fil" + +msgid "repository" +msgstr "repositorium" + +msgid "Theme by the" +msgstr "Tema av" + +msgid "Print to PDF" +msgstr "Skriv ut till PDF" + +msgid "Download this page" +msgstr "Ladda ner den här sidan" + +msgid "Copyright" +msgstr "Upphovsrätt" + +msgid "Last updated on" +msgstr "Senast uppdaterad den" + +msgid "Launch" +msgstr "Öppna" + +msgid "Open an issue" +msgstr "Öppna en problemrapport" + +msgid "Fullscreen mode" +msgstr "Fullskärmsläge" + +msgid "Sphinx Book Theme" +msgstr "Sphinx Boktema" + +msgid "Contents" +msgstr "Innehåll" + +msgid "Edit this page" +msgstr "Redigera den här sidan" + +msgid "next page" +msgstr "nästa sida" + +msgid "Source repository" +msgstr "Källkodsrepositorium" + +msgid "By" +msgstr "Av" + +msgid "By the" +msgstr "Av den" + +msgid "previous page" +msgstr "föregående sida" diff --git a/_static/locales/ta/LC_MESSAGES/booktheme.mo b/_static/locales/ta/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..29f52e1f6 Binary files /dev/null and b/_static/locales/ta/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/ta/LC_MESSAGES/booktheme.po b/_static/locales/ta/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..116d74331 --- /dev/null +++ b/_static/locales/ta/LC_MESSAGES/booktheme.po @@ -0,0 +1,66 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ta\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "மூல கோப்பைப் பதிவிறக்குக" + +msgid "suggest edit" +msgstr "திருத்த பரிந்துரைக்கவும்" + +msgid "Toggle navigation" +msgstr "வழிசெலுத்தலை நிலைமாற்று" + +msgid "open issue" +msgstr "திறந்த பிரச்சினை" + +msgid "Download notebook file" +msgstr "நோட்புக் கோப்பைப் பதிவிறக்கவும்" + +msgid "Theme by the" +msgstr "வழங்கிய தீம்" + +msgid "Print to PDF" +msgstr "PDF இல் அச்சிடுக" + +msgid "Download this page" +msgstr "இந்தப் பக்கத்தைப் பதிவிறக்கவும்" + +msgid "Copyright" +msgstr "பதிப்புரிமை" + +msgid "Last updated on" +msgstr "கடைசியாக புதுப்பிக்கப்பட்டது" + +msgid "Launch" +msgstr "தொடங்க" + +msgid "Open an issue" +msgstr "சிக்கலைத் திறக்கவும்" + +msgid "Sphinx Book Theme" +msgstr "ஸ்பிங்க்ஸ் புத்தக தீம்" + +msgid "Edit this page" +msgstr "இந்தப் பக்கத்தைத் திருத்தவும்" + +msgid "next page" +msgstr "அடுத்த பக்கம்" + +msgid "Source repository" +msgstr "மூல களஞ்சியம்" + +msgid "By" +msgstr "வழங்கியவர்" + +msgid "By the" +msgstr "மூலம்" + +msgid "previous page" +msgstr "முந்தைய பக்கம்" diff --git a/_static/locales/te/LC_MESSAGES/booktheme.mo b/_static/locales/te/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..0a5f4b46a Binary files /dev/null and b/_static/locales/te/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/te/LC_MESSAGES/booktheme.po b/_static/locales/te/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..635bdf962 --- /dev/null +++ b/_static/locales/te/LC_MESSAGES/booktheme.po @@ -0,0 +1,66 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: te\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "మూల ఫైల్‌ను డౌన్‌లోడ్ చేయండి" + +msgid "suggest edit" +msgstr "సవరించమని సూచించండి" + +msgid "Toggle navigation" +msgstr "నావిగేషన్‌ను టోగుల్ చేయండి" + +msgid "open issue" +msgstr "ఓపెన్ ఇష్యూ" + +msgid "Download notebook file" +msgstr "నోట్బుక్ ఫైల్ను డౌన్లోడ్ చేయండి" + +msgid "Theme by the" +msgstr "ద్వారా థీమ్" + +msgid "Print to PDF" +msgstr "PDF కి ముద్రించండి" + +msgid "Download this page" +msgstr "ఈ పేజీని డౌన్‌లోడ్ చేయండి" + +msgid "Copyright" +msgstr "కాపీరైట్" + +msgid "Last updated on" +msgstr "చివరిగా నవీకరించబడింది" + +msgid "Launch" +msgstr "ప్రారంభించండి" + +msgid "Open an issue" +msgstr "సమస్యను తెరవండి" + +msgid "Sphinx Book Theme" +msgstr "సింహిక పుస్తక థీమ్" + +msgid "Edit this page" +msgstr "ఈ పేజీని సవరించండి" + +msgid "next page" +msgstr "తరువాతి పేజీ" + +msgid "Source repository" +msgstr "మూల రిపోజిటరీ" + +msgid "By" +msgstr "ద్వారా" + +msgid "By the" +msgstr "ద్వారా" + +msgid "previous page" +msgstr "ముందు పేజి" diff --git a/_static/locales/tg/LC_MESSAGES/booktheme.mo b/_static/locales/tg/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..b21c6c634 Binary files /dev/null and b/_static/locales/tg/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/tg/LC_MESSAGES/booktheme.po b/_static/locales/tg/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..a92c069a4 --- /dev/null +++ b/_static/locales/tg/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: tg\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Файли манбаъро зеркашӣ кунед" + +msgid "suggest edit" +msgstr "пешниҳод вироиш" + +msgid "Toggle navigation" +msgstr "Гузаришро иваз кунед" + +msgid "open issue" +msgstr "барориши кушод" + +msgid "Download notebook file" +msgstr "Файли дафтарро зеркашӣ кунед" + +msgid "repository" +msgstr "анбор" + +msgid "Theme by the" +msgstr "Мавзӯъи аз" + +msgid "Print to PDF" +msgstr "Чоп ба PDF" + +msgid "Download this page" +msgstr "Ин саҳифаро зеркашӣ кунед" + +msgid "Copyright" +msgstr "Ҳуқуқи муаллиф" + +msgid "Last updated on" +msgstr "Last навсозӣ дар" + +msgid "Launch" +msgstr "Оғоз" + +msgid "Open an issue" +msgstr "Масъаларо кушоед" + +msgid "Fullscreen mode" +msgstr "Ҳолати экрани пурра" + +msgid "Sphinx Book Theme" +msgstr "Сфинкс Мавзӯи китоб" + +msgid "Contents" +msgstr "Мундариҷа" + +msgid "Edit this page" +msgstr "Ин саҳифаро таҳрир кунед" + +msgid "next page" +msgstr "саҳифаи оянда" + +msgid "Source repository" +msgstr "Анбори манбаъ" + +msgid "By" +msgstr "Бо" + +msgid "By the" +msgstr "Бо" + +msgid "previous page" +msgstr "саҳифаи қаблӣ" diff --git a/_static/locales/th/LC_MESSAGES/booktheme.mo b/_static/locales/th/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..abede98aa Binary files /dev/null and b/_static/locales/th/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/th/LC_MESSAGES/booktheme.po b/_static/locales/th/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..25d9837f8 --- /dev/null +++ b/_static/locales/th/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: th\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "ดาวน์โหลดไฟล์ต้นฉบับ" + +msgid "suggest edit" +msgstr "แนะนำแก้ไข" + +msgid "Toggle navigation" +msgstr "ไม่ต้องสลับช่องทาง" + +msgid "open issue" +msgstr "เปิดปัญหา" + +msgid "Download notebook file" +msgstr "ดาวน์โหลดไฟล์สมุดบันทึก" + +msgid "repository" +msgstr "ที่เก็บ" + +msgid "Theme by the" +msgstr "ธีมโดย" + +msgid "Print to PDF" +msgstr "พิมพ์เป็น PDF" + +msgid "Download this page" +msgstr "ดาวน์โหลดหน้านี้" + +msgid "Copyright" +msgstr "ลิขสิทธิ์" + +msgid "Last updated on" +msgstr "ปรับปรุงล่าสุดเมื่อ" + +msgid "Launch" +msgstr "เปิด" + +msgid "Open an issue" +msgstr "เปิดปัญหา" + +msgid "Fullscreen mode" +msgstr "โหมดเต็มหน้าจอ" + +msgid "Sphinx Book Theme" +msgstr "ธีมหนังสือสฟิงซ์" + +msgid "Contents" +msgstr "สารบัญ" + +msgid "Edit this page" +msgstr "แก้ไขหน้านี้" + +msgid "next page" +msgstr "หน้าต่อไป" + +msgid "Source repository" +msgstr "ที่เก็บซอร์ส" + +msgid "By" +msgstr "โดย" + +msgid "By the" +msgstr "โดย" + +msgid "previous page" +msgstr "หน้าที่แล้ว" diff --git a/_static/locales/tl/LC_MESSAGES/booktheme.mo b/_static/locales/tl/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..8df1b7331 Binary files /dev/null and b/_static/locales/tl/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/tl/LC_MESSAGES/booktheme.po b/_static/locales/tl/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..7e28b05fa --- /dev/null +++ b/_static/locales/tl/LC_MESSAGES/booktheme.po @@ -0,0 +1,66 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: tl\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Mag-download ng file ng pinagmulan" + +msgid "suggest edit" +msgstr "iminumungkahi i-edit" + +msgid "Toggle navigation" +msgstr "I-toggle ang pag-navigate" + +msgid "open issue" +msgstr "bukas na isyu" + +msgid "Download notebook file" +msgstr "Mag-download ng file ng notebook" + +msgid "Theme by the" +msgstr "Tema ng" + +msgid "Print to PDF" +msgstr "I-print sa PDF" + +msgid "Download this page" +msgstr "I-download ang pahinang ito" + +msgid "Copyright" +msgstr "Copyright" + +msgid "Last updated on" +msgstr "Huling na-update noong" + +msgid "Launch" +msgstr "Ilunsad" + +msgid "Open an issue" +msgstr "Magbukas ng isyu" + +msgid "Sphinx Book Theme" +msgstr "Tema ng Sphinx Book" + +msgid "Edit this page" +msgstr "I-edit ang pahinang ito" + +msgid "next page" +msgstr "Susunod na pahina" + +msgid "Source repository" +msgstr "Pinagmulan ng imbakan" + +msgid "By" +msgstr "Ni" + +msgid "By the" +msgstr "Sa pamamagitan ng" + +msgid "previous page" +msgstr "Nakaraang pahina" diff --git a/_static/locales/tr/LC_MESSAGES/booktheme.mo b/_static/locales/tr/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..029ae18af Binary files /dev/null and b/_static/locales/tr/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/tr/LC_MESSAGES/booktheme.po b/_static/locales/tr/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..54d6f5836 --- /dev/null +++ b/_static/locales/tr/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: tr\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Kaynak dosyayı indirin" + +msgid "suggest edit" +msgstr "düzenleme öner" + +msgid "Toggle navigation" +msgstr "Gezinmeyi değiştir" + +msgid "open issue" +msgstr "Açık konu" + +msgid "Download notebook file" +msgstr "Defter dosyasını indirin" + +msgid "repository" +msgstr "depo" + +msgid "Theme by the" +msgstr "Tarafından tema" + +msgid "Print to PDF" +msgstr "PDF olarak yazdır" + +msgid "Download this page" +msgstr "Bu sayfayı indirin" + +msgid "Copyright" +msgstr "Telif hakkı" + +msgid "Last updated on" +msgstr "Son güncelleme tarihi" + +msgid "Launch" +msgstr "Başlatmak" + +msgid "Open an issue" +msgstr "Bir sorunu açın" + +msgid "Fullscreen mode" +msgstr "Tam ekran modu" + +msgid "Sphinx Book Theme" +msgstr "Sfenks Kitap Teması" + +msgid "Contents" +msgstr "İçindekiler" + +msgid "Edit this page" +msgstr "Bu sayfayı düzenle" + +msgid "next page" +msgstr "sonraki Sayfa" + +msgid "Source repository" +msgstr "Kaynak kod deposu" + +msgid "By" +msgstr "Tarafından" + +msgid "By the" +msgstr "Tarafından" + +msgid "previous page" +msgstr "önceki sayfa" diff --git a/_static/locales/uk/LC_MESSAGES/booktheme.mo b/_static/locales/uk/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..16ab78909 Binary files /dev/null and b/_static/locales/uk/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/uk/LC_MESSAGES/booktheme.po b/_static/locales/uk/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..6ecaec679 --- /dev/null +++ b/_static/locales/uk/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: uk\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Завантажити вихідний файл" + +msgid "suggest edit" +msgstr "запропонувати редагувати" + +msgid "Toggle navigation" +msgstr "Переключити навігацію" + +msgid "open issue" +msgstr "відкритий випуск" + +msgid "Download notebook file" +msgstr "Завантажте файл блокнота" + +msgid "repository" +msgstr "сховище" + +msgid "Theme by the" +msgstr "Тема від" + +msgid "Print to PDF" +msgstr "Друк у форматі PDF" + +msgid "Download this page" +msgstr "Завантажте цю сторінку" + +msgid "Copyright" +msgstr "Авторське право" + +msgid "Last updated on" +msgstr "Останнє оновлення:" + +msgid "Launch" +msgstr "Запуск" + +msgid "Open an issue" +msgstr "Відкрийте випуск" + +msgid "Fullscreen mode" +msgstr "Повноекранний режим" + +msgid "Sphinx Book Theme" +msgstr "Тема книги \"Сфінкс\"" + +msgid "Contents" +msgstr "Зміст" + +msgid "Edit this page" +msgstr "Редагувати цю сторінку" + +msgid "next page" +msgstr "Наступна сторінка" + +msgid "Source repository" +msgstr "Джерело сховища" + +msgid "By" +msgstr "Автор" + +msgid "By the" +msgstr "По" + +msgid "previous page" +msgstr "Попередня сторінка" diff --git a/_static/locales/ur/LC_MESSAGES/booktheme.mo b/_static/locales/ur/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..de8c84b93 Binary files /dev/null and b/_static/locales/ur/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/ur/LC_MESSAGES/booktheme.po b/_static/locales/ur/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..818e03de7 --- /dev/null +++ b/_static/locales/ur/LC_MESSAGES/booktheme.po @@ -0,0 +1,66 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ur\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "سورس فائل ڈاؤن لوڈ کریں" + +msgid "suggest edit" +msgstr "ترمیم کی تجویز کریں" + +msgid "Toggle navigation" +msgstr "نیویگیشن ٹوگل کریں" + +msgid "open issue" +msgstr "کھلا مسئلہ" + +msgid "Download notebook file" +msgstr "نوٹ بک فائل ڈاؤن لوڈ کریں" + +msgid "Theme by the" +msgstr "کے ذریعہ تھیم" + +msgid "Print to PDF" +msgstr "پی ڈی ایف پرنٹ کریں" + +msgid "Download this page" +msgstr "اس صفحے کو ڈاؤن لوڈ کریں" + +msgid "Copyright" +msgstr "کاپی رائٹ" + +msgid "Last updated on" +msgstr "آخری بار تازہ کاری ہوئی" + +msgid "Launch" +msgstr "لانچ کریں" + +msgid "Open an issue" +msgstr "ایک مسئلہ کھولیں" + +msgid "Sphinx Book Theme" +msgstr "سپنکس بک تھیم" + +msgid "Edit this page" +msgstr "اس صفحے میں ترمیم کریں" + +msgid "next page" +msgstr "اگلا صفحہ" + +msgid "Source repository" +msgstr "ماخذ ذخیرہ" + +msgid "By" +msgstr "بذریعہ" + +msgid "By the" +msgstr "کی طرف" + +msgid "previous page" +msgstr "سابقہ ​​صفحہ" diff --git a/_static/locales/vi/LC_MESSAGES/booktheme.mo b/_static/locales/vi/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..2bb32555c Binary files /dev/null and b/_static/locales/vi/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/vi/LC_MESSAGES/booktheme.po b/_static/locales/vi/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..59db26698 --- /dev/null +++ b/_static/locales/vi/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: vi\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "Tải xuống tệp nguồn" + +msgid "suggest edit" +msgstr "đề nghị chỉnh sửa" + +msgid "Toggle navigation" +msgstr "Chuyển đổi điều hướng thành" + +msgid "open issue" +msgstr "vấn đề mở" + +msgid "Download notebook file" +msgstr "Tải xuống tệp sổ tay" + +msgid "repository" +msgstr "kho" + +msgid "Theme by the" +msgstr "Chủ đề của" + +msgid "Print to PDF" +msgstr "In sang PDF" + +msgid "Download this page" +msgstr "Tải xuống trang này" + +msgid "Copyright" +msgstr "Bản quyền" + +msgid "Last updated on" +msgstr "Cập nhật lần cuối vào" + +msgid "Launch" +msgstr "Phóng" + +msgid "Open an issue" +msgstr "Mở một vấn đề" + +msgid "Fullscreen mode" +msgstr "Chế độ toàn màn hình" + +msgid "Sphinx Book Theme" +msgstr "Chủ đề sách nhân sư" + +msgid "Contents" +msgstr "Nội dung" + +msgid "Edit this page" +msgstr "chỉnh sửa trang này" + +msgid "next page" +msgstr "Trang tiếp theo" + +msgid "Source repository" +msgstr "Kho nguồn" + +msgid "By" +msgstr "Bởi" + +msgid "By the" +msgstr "Bằng" + +msgid "previous page" +msgstr "trang trước" diff --git a/_static/locales/zh_CN/LC_MESSAGES/booktheme.mo b/_static/locales/zh_CN/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..0e3235d09 Binary files /dev/null and b/_static/locales/zh_CN/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/zh_CN/LC_MESSAGES/booktheme.po b/_static/locales/zh_CN/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..e8d12dd74 --- /dev/null +++ b/_static/locales/zh_CN/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh_CN\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "下载源文件" + +msgid "suggest edit" +msgstr "提出修改建议" + +msgid "Toggle navigation" +msgstr "显示或隐藏导航栏" + +msgid "open issue" +msgstr "创建议题" + +msgid "Download notebook file" +msgstr "下载笔记本文件" + +msgid "repository" +msgstr "仓库" + +msgid "Theme by the" +msgstr "主题作者:" + +msgid "Print to PDF" +msgstr "列印成 PDF" + +msgid "Download this page" +msgstr "下载此页面" + +msgid "Copyright" +msgstr "版权" + +msgid "Last updated on" +msgstr "上次更新时间:" + +msgid "Launch" +msgstr "启动" + +msgid "Open an issue" +msgstr "创建议题" + +msgid "Fullscreen mode" +msgstr "全屏模式" + +msgid "Sphinx Book Theme" +msgstr "Sphinx Book 主题" + +msgid "Contents" +msgstr "目录" + +msgid "Edit this page" +msgstr "编辑此页面" + +msgid "next page" +msgstr "下一页" + +msgid "Source repository" +msgstr "源码库" + +msgid "By" +msgstr "作者:" + +msgid "By the" +msgstr "作者:" + +msgid "previous page" +msgstr "上一页" diff --git a/_static/locales/zh_TW/LC_MESSAGES/booktheme.mo b/_static/locales/zh_TW/LC_MESSAGES/booktheme.mo new file mode 100644 index 000000000..9116fa95d Binary files /dev/null and b/_static/locales/zh_TW/LC_MESSAGES/booktheme.mo differ diff --git a/_static/locales/zh_TW/LC_MESSAGES/booktheme.po b/_static/locales/zh_TW/LC_MESSAGES/booktheme.po new file mode 100644 index 000000000..0ed32f74e --- /dev/null +++ b/_static/locales/zh_TW/LC_MESSAGES/booktheme.po @@ -0,0 +1,75 @@ + +msgid "" +msgstr "" +"Project-Id-Version: Sphinx-Book-Theme\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh_TW\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Download source file" +msgstr "下載原始檔" + +msgid "suggest edit" +msgstr "提出修改建議" + +msgid "Toggle navigation" +msgstr "顯示或隱藏導覽列" + +msgid "open issue" +msgstr "公開的問題" + +msgid "Download notebook file" +msgstr "下載 Notebook 檔案" + +msgid "repository" +msgstr "儲存庫" + +msgid "Theme by the" +msgstr "佈景主題作者:" + +msgid "Print to PDF" +msgstr "列印成 PDF" + +msgid "Download this page" +msgstr "下載此頁面" + +msgid "Copyright" +msgstr "Copyright" + +msgid "Last updated on" +msgstr "最後更新時間:" + +msgid "Launch" +msgstr "啟動" + +msgid "Open an issue" +msgstr "開啟議題" + +msgid "Fullscreen mode" +msgstr "全螢幕模式" + +msgid "Sphinx Book Theme" +msgstr "Sphinx Book 佈景主題" + +msgid "Contents" +msgstr "目錄" + +msgid "Edit this page" +msgstr "編輯此頁面" + +msgid "next page" +msgstr "下一頁" + +msgid "Source repository" +msgstr "來源儲存庫" + +msgid "By" +msgstr "作者:" + +msgid "By the" +msgstr "作者:" + +msgid "previous page" +msgstr "上一頁" diff --git a/_static/minus.png b/_static/minus.png new file mode 100644 index 000000000..d96755fda Binary files /dev/null and b/_static/minus.png differ diff --git a/_static/orcid.svg b/_static/orcid.svg new file mode 100644 index 000000000..83d027ab7 --- /dev/null +++ b/_static/orcid.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/_static/plus.png b/_static/plus.png new file mode 100644 index 000000000..7107cec93 Binary files /dev/null and b/_static/plus.png differ diff --git a/_static/pygments.css b/_static/pygments.css new file mode 100644 index 000000000..d7dd57783 --- /dev/null +++ b/_static/pygments.css @@ -0,0 +1,152 @@ +html[data-theme="light"] .highlight pre { line-height: 125%; } +html[data-theme="light"] .highlight td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +html[data-theme="light"] .highlight span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +html[data-theme="light"] .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +html[data-theme="light"] .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +html[data-theme="light"] .highlight .hll { background-color: #fae4c2 } +html[data-theme="light"] .highlight { background: #fefefe; color: #080808 } +html[data-theme="light"] .highlight .c { color: #515151 } /* Comment */ +html[data-theme="light"] .highlight .err { color: #A12236 } /* Error */ +html[data-theme="light"] .highlight .k { color: #6730C5 } /* Keyword */ +html[data-theme="light"] .highlight .l { color: #7F4707 } /* Literal */ +html[data-theme="light"] .highlight .n { color: #080808 } /* Name */ +html[data-theme="light"] .highlight .o { color: #00622F } /* Operator */ +html[data-theme="light"] .highlight .p { color: #080808 } /* Punctuation */ +html[data-theme="light"] .highlight .ch { color: #515151 } /* Comment.Hashbang */ +html[data-theme="light"] .highlight .cm { color: #515151 } /* Comment.Multiline */ +html[data-theme="light"] .highlight .cp { color: #515151 } /* Comment.Preproc */ +html[data-theme="light"] .highlight .cpf { color: #515151 } /* Comment.PreprocFile */ +html[data-theme="light"] .highlight .c1 { color: #515151 } /* Comment.Single */ +html[data-theme="light"] .highlight .cs { color: #515151 } /* Comment.Special */ +html[data-theme="light"] .highlight .gd { color: #005B82 } /* Generic.Deleted */ +html[data-theme="light"] .highlight .ge { font-style: italic } /* Generic.Emph */ +html[data-theme="light"] .highlight .gh { color: #005B82 } /* Generic.Heading */ +html[data-theme="light"] .highlight .gs { font-weight: bold } /* Generic.Strong */ +html[data-theme="light"] .highlight .gu { color: #005B82 } /* Generic.Subheading */ +html[data-theme="light"] .highlight .kc { color: #6730C5 } /* Keyword.Constant */ +html[data-theme="light"] .highlight .kd { color: #6730C5 } /* Keyword.Declaration */ +html[data-theme="light"] .highlight .kn { color: #6730C5 } /* Keyword.Namespace */ +html[data-theme="light"] .highlight .kp { color: #6730C5 } /* Keyword.Pseudo */ +html[data-theme="light"] .highlight .kr { color: #6730C5 } /* Keyword.Reserved */ +html[data-theme="light"] .highlight .kt { color: #7F4707 } /* Keyword.Type */ +html[data-theme="light"] .highlight .ld { color: #7F4707 } /* Literal.Date */ +html[data-theme="light"] .highlight .m { color: #7F4707 } /* Literal.Number */ +html[data-theme="light"] .highlight .s { color: #00622F } /* Literal.String */ +html[data-theme="light"] .highlight .na { color: #912583 } /* Name.Attribute */ +html[data-theme="light"] .highlight .nb { color: #7F4707 } /* Name.Builtin */ +html[data-theme="light"] .highlight .nc { color: #005B82 } /* Name.Class */ +html[data-theme="light"] .highlight .no { color: #005B82 } /* Name.Constant */ +html[data-theme="light"] .highlight .nd { color: #7F4707 } /* Name.Decorator */ +html[data-theme="light"] .highlight .ni { color: #00622F } /* Name.Entity */ +html[data-theme="light"] .highlight .ne { color: #6730C5 } /* Name.Exception */ +html[data-theme="light"] .highlight .nf { color: #005B82 } /* Name.Function */ +html[data-theme="light"] .highlight .nl { color: #7F4707 } /* Name.Label */ +html[data-theme="light"] .highlight .nn { color: #080808 } /* Name.Namespace */ +html[data-theme="light"] .highlight .nx { color: #080808 } /* Name.Other */ +html[data-theme="light"] .highlight .py { color: #005B82 } /* Name.Property */ +html[data-theme="light"] .highlight .nt { color: #005B82 } /* Name.Tag */ +html[data-theme="light"] .highlight .nv { color: #A12236 } /* Name.Variable */ +html[data-theme="light"] .highlight .ow { color: #6730C5 } /* Operator.Word */ +html[data-theme="light"] .highlight .pm { color: #080808 } /* Punctuation.Marker */ +html[data-theme="light"] .highlight .w { color: #080808 } /* Text.Whitespace */ +html[data-theme="light"] .highlight .mb { color: #7F4707 } /* Literal.Number.Bin */ +html[data-theme="light"] .highlight .mf { color: #7F4707 } /* Literal.Number.Float */ +html[data-theme="light"] .highlight .mh { color: #7F4707 } /* Literal.Number.Hex */ +html[data-theme="light"] .highlight .mi { color: #7F4707 } /* Literal.Number.Integer */ +html[data-theme="light"] .highlight .mo { color: #7F4707 } /* Literal.Number.Oct */ +html[data-theme="light"] .highlight .sa { color: #00622F } /* Literal.String.Affix */ +html[data-theme="light"] .highlight .sb { color: #00622F } /* Literal.String.Backtick */ +html[data-theme="light"] .highlight .sc { color: #00622F } /* Literal.String.Char */ +html[data-theme="light"] .highlight .dl { color: #00622F } /* Literal.String.Delimiter */ +html[data-theme="light"] .highlight .sd { color: #00622F } /* Literal.String.Doc */ +html[data-theme="light"] .highlight .s2 { color: #00622F } /* Literal.String.Double */ +html[data-theme="light"] .highlight .se { color: #00622F } /* Literal.String.Escape */ +html[data-theme="light"] .highlight .sh { color: #00622F } /* Literal.String.Heredoc */ +html[data-theme="light"] .highlight .si { color: #00622F } /* Literal.String.Interpol */ +html[data-theme="light"] .highlight .sx { color: #00622F } /* Literal.String.Other */ +html[data-theme="light"] .highlight .sr { color: #A12236 } /* Literal.String.Regex */ +html[data-theme="light"] .highlight .s1 { color: #00622F } /* Literal.String.Single */ +html[data-theme="light"] .highlight .ss { color: #005B82 } /* Literal.String.Symbol */ +html[data-theme="light"] .highlight .bp { color: #7F4707 } /* Name.Builtin.Pseudo */ +html[data-theme="light"] .highlight .fm { color: #005B82 } /* Name.Function.Magic */ +html[data-theme="light"] .highlight .vc { color: #A12236 } /* Name.Variable.Class */ +html[data-theme="light"] .highlight .vg { color: #A12236 } /* Name.Variable.Global */ +html[data-theme="light"] .highlight .vi { color: #A12236 } /* Name.Variable.Instance */ +html[data-theme="light"] .highlight .vm { color: #7F4707 } /* Name.Variable.Magic */ +html[data-theme="light"] .highlight .il { color: #7F4707 } /* Literal.Number.Integer.Long */ +html[data-theme="dark"] .highlight pre { line-height: 125%; } +html[data-theme="dark"] .highlight td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +html[data-theme="dark"] .highlight span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +html[data-theme="dark"] .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +html[data-theme="dark"] .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +html[data-theme="dark"] .highlight .hll { background-color: #ffd9002e } +html[data-theme="dark"] .highlight { background: #2b2b2b; color: #F8F8F2 } +html[data-theme="dark"] .highlight .c { color: #FFD900 } /* Comment */ +html[data-theme="dark"] .highlight .err { color: #FFA07A } /* Error */ +html[data-theme="dark"] .highlight .k { color: #DCC6E0 } /* Keyword */ +html[data-theme="dark"] .highlight .l { color: #FFD900 } /* Literal */ +html[data-theme="dark"] .highlight .n { color: #F8F8F2 } /* Name */ +html[data-theme="dark"] .highlight .o { color: #ABE338 } /* Operator */ +html[data-theme="dark"] .highlight .p { color: #F8F8F2 } /* Punctuation */ +html[data-theme="dark"] .highlight .ch { color: #FFD900 } /* Comment.Hashbang */ +html[data-theme="dark"] .highlight .cm { color: #FFD900 } /* Comment.Multiline */ +html[data-theme="dark"] .highlight .cp { color: #FFD900 } /* Comment.Preproc */ +html[data-theme="dark"] .highlight .cpf { color: #FFD900 } /* Comment.PreprocFile */ +html[data-theme="dark"] .highlight .c1 { color: #FFD900 } /* Comment.Single */ +html[data-theme="dark"] .highlight .cs { color: #FFD900 } /* Comment.Special */ +html[data-theme="dark"] .highlight .gd { color: #00E0E0 } /* Generic.Deleted */ +html[data-theme="dark"] .highlight .ge { font-style: italic } /* Generic.Emph */ +html[data-theme="dark"] .highlight .gh { color: #00E0E0 } /* Generic.Heading */ +html[data-theme="dark"] .highlight .gs { font-weight: bold } /* Generic.Strong */ +html[data-theme="dark"] .highlight .gu { color: #00E0E0 } /* Generic.Subheading */ +html[data-theme="dark"] .highlight .kc { color: #DCC6E0 } /* Keyword.Constant */ +html[data-theme="dark"] .highlight .kd { color: #DCC6E0 } /* Keyword.Declaration */ +html[data-theme="dark"] .highlight .kn { color: #DCC6E0 } /* Keyword.Namespace */ +html[data-theme="dark"] .highlight .kp { color: #DCC6E0 } /* Keyword.Pseudo */ +html[data-theme="dark"] .highlight .kr { color: #DCC6E0 } /* Keyword.Reserved */ +html[data-theme="dark"] .highlight .kt { color: #FFD900 } /* Keyword.Type */ +html[data-theme="dark"] .highlight .ld { color: #FFD900 } /* Literal.Date */ +html[data-theme="dark"] .highlight .m { color: #FFD900 } /* Literal.Number */ +html[data-theme="dark"] .highlight .s { color: #ABE338 } /* Literal.String */ +html[data-theme="dark"] .highlight .na { color: #FFD900 } /* Name.Attribute */ +html[data-theme="dark"] .highlight .nb { color: #FFD900 } /* Name.Builtin */ +html[data-theme="dark"] .highlight .nc { color: #00E0E0 } /* Name.Class */ +html[data-theme="dark"] .highlight .no { color: #00E0E0 } /* Name.Constant */ +html[data-theme="dark"] .highlight .nd { color: #FFD900 } /* Name.Decorator */ +html[data-theme="dark"] .highlight .ni { color: #ABE338 } /* Name.Entity */ +html[data-theme="dark"] .highlight .ne { color: #DCC6E0 } /* Name.Exception */ +html[data-theme="dark"] .highlight .nf { color: #00E0E0 } /* Name.Function */ +html[data-theme="dark"] .highlight .nl { color: #FFD900 } /* Name.Label */ +html[data-theme="dark"] .highlight .nn { color: #F8F8F2 } /* Name.Namespace */ +html[data-theme="dark"] .highlight .nx { color: #F8F8F2 } /* Name.Other */ +html[data-theme="dark"] .highlight .py { color: #00E0E0 } /* Name.Property */ +html[data-theme="dark"] .highlight .nt { color: #00E0E0 } /* Name.Tag */ +html[data-theme="dark"] .highlight .nv { color: #FFA07A } /* Name.Variable */ +html[data-theme="dark"] .highlight .ow { color: #DCC6E0 } /* Operator.Word */ +html[data-theme="dark"] .highlight .pm { color: #F8F8F2 } /* Punctuation.Marker */ +html[data-theme="dark"] .highlight .w { color: #F8F8F2 } /* Text.Whitespace */ +html[data-theme="dark"] .highlight .mb { color: #FFD900 } /* Literal.Number.Bin */ +html[data-theme="dark"] .highlight .mf { color: #FFD900 } /* Literal.Number.Float */ +html[data-theme="dark"] .highlight .mh { color: #FFD900 } /* Literal.Number.Hex */ +html[data-theme="dark"] .highlight .mi { color: #FFD900 } /* Literal.Number.Integer */ +html[data-theme="dark"] .highlight .mo { color: #FFD900 } /* Literal.Number.Oct */ +html[data-theme="dark"] .highlight .sa { color: #ABE338 } /* Literal.String.Affix */ +html[data-theme="dark"] .highlight .sb { color: #ABE338 } /* Literal.String.Backtick */ +html[data-theme="dark"] .highlight .sc { color: #ABE338 } /* Literal.String.Char */ +html[data-theme="dark"] .highlight .dl { color: #ABE338 } /* Literal.String.Delimiter */ +html[data-theme="dark"] .highlight .sd { color: #ABE338 } /* Literal.String.Doc */ +html[data-theme="dark"] .highlight .s2 { color: #ABE338 } /* Literal.String.Double */ +html[data-theme="dark"] .highlight .se { color: #ABE338 } /* Literal.String.Escape */ +html[data-theme="dark"] .highlight .sh { color: #ABE338 } /* Literal.String.Heredoc */ +html[data-theme="dark"] .highlight .si { color: #ABE338 } /* Literal.String.Interpol */ +html[data-theme="dark"] .highlight .sx { color: #ABE338 } /* Literal.String.Other */ +html[data-theme="dark"] .highlight .sr { color: #FFA07A } /* Literal.String.Regex */ +html[data-theme="dark"] .highlight .s1 { color: #ABE338 } /* Literal.String.Single */ +html[data-theme="dark"] .highlight .ss { color: #00E0E0 } /* Literal.String.Symbol */ +html[data-theme="dark"] .highlight .bp { color: #FFD900 } /* Name.Builtin.Pseudo */ +html[data-theme="dark"] .highlight .fm { color: #00E0E0 } /* Name.Function.Magic */ +html[data-theme="dark"] .highlight .vc { color: #FFA07A } /* Name.Variable.Class */ +html[data-theme="dark"] .highlight .vg { color: #FFA07A } /* Name.Variable.Global */ +html[data-theme="dark"] .highlight .vi { color: #FFA07A } /* Name.Variable.Instance */ +html[data-theme="dark"] .highlight .vm { color: #FFD900 } /* Name.Variable.Magic */ +html[data-theme="dark"] .highlight .il { color: #FFD900 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/_static/sbt-webpack-macros.html b/_static/sbt-webpack-macros.html new file mode 100644 index 000000000..6cbf559fa --- /dev/null +++ b/_static/sbt-webpack-macros.html @@ -0,0 +1,11 @@ + +{% macro head_pre_bootstrap() %} + +{% endmacro %} + +{% macro body_post() %} + +{% endmacro %} diff --git a/_static/scripts/bootstrap.js b/_static/scripts/bootstrap.js new file mode 100644 index 000000000..c8178debb --- /dev/null +++ b/_static/scripts/bootstrap.js @@ -0,0 +1,3 @@ +/*! For license information please see bootstrap.js.LICENSE.txt */ +(()=>{"use strict";var t={d:(e,i)=>{for(var n in i)t.o(i,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:i[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};t.r(e),t.d(e,{afterMain:()=>E,afterRead:()=>v,afterWrite:()=>C,applyStyles:()=>$,arrow:()=>J,auto:()=>a,basePlacements:()=>l,beforeMain:()=>y,beforeRead:()=>_,beforeWrite:()=>A,bottom:()=>s,clippingParents:()=>d,computeStyles:()=>it,createPopper:()=>Dt,createPopperBase:()=>St,createPopperLite:()=>$t,detectOverflow:()=>_t,end:()=>h,eventListeners:()=>st,flip:()=>bt,hide:()=>wt,left:()=>r,main:()=>w,modifierPhases:()=>O,offset:()=>Et,placements:()=>g,popper:()=>f,popperGenerator:()=>Lt,popperOffsets:()=>At,preventOverflow:()=>Tt,read:()=>b,reference:()=>p,right:()=>o,start:()=>c,top:()=>n,variationPlacements:()=>m,viewport:()=>u,write:()=>T});var i={};t.r(i),t.d(i,{Alert:()=>Oe,Button:()=>ke,Carousel:()=>li,Collapse:()=>Ei,Dropdown:()=>Ki,Modal:()=>Ln,Offcanvas:()=>Kn,Popover:()=>bs,ScrollSpy:()=>Ls,Tab:()=>Js,Toast:()=>po,Tooltip:()=>fs});var n="top",s="bottom",o="right",r="left",a="auto",l=[n,s,o,r],c="start",h="end",d="clippingParents",u="viewport",f="popper",p="reference",m=l.reduce((function(t,e){return t.concat([e+"-"+c,e+"-"+h])}),[]),g=[].concat(l,[a]).reduce((function(t,e){return t.concat([e,e+"-"+c,e+"-"+h])}),[]),_="beforeRead",b="read",v="afterRead",y="beforeMain",w="main",E="afterMain",A="beforeWrite",T="write",C="afterWrite",O=[_,b,v,y,w,E,A,T,C];function x(t){return t?(t.nodeName||"").toLowerCase():null}function k(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function L(t){return t instanceof k(t).Element||t instanceof Element}function S(t){return t instanceof k(t).HTMLElement||t instanceof HTMLElement}function D(t){return"undefined"!=typeof ShadowRoot&&(t instanceof k(t).ShadowRoot||t instanceof ShadowRoot)}const $={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];S(s)&&x(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});S(n)&&x(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function I(t){return t.split("-")[0]}var N=Math.max,P=Math.min,M=Math.round;function j(){var t=navigator.userAgentData;return null!=t&&t.brands&&Array.isArray(t.brands)?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function F(){return!/^((?!chrome|android).)*safari/i.test(j())}function H(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&S(t)&&(s=t.offsetWidth>0&&M(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&M(n.height)/t.offsetHeight||1);var r=(L(t)?k(t):window).visualViewport,a=!F()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,d=n.height/o;return{width:h,height:d,top:c,right:l+h,bottom:c+d,left:l,x:l,y:c}}function B(t){var e=H(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function W(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&D(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function z(t){return k(t).getComputedStyle(t)}function R(t){return["table","td","th"].indexOf(x(t))>=0}function q(t){return((L(t)?t.ownerDocument:t.document)||window.document).documentElement}function V(t){return"html"===x(t)?t:t.assignedSlot||t.parentNode||(D(t)?t.host:null)||q(t)}function Y(t){return S(t)&&"fixed"!==z(t).position?t.offsetParent:null}function K(t){for(var e=k(t),i=Y(t);i&&R(i)&&"static"===z(i).position;)i=Y(i);return i&&("html"===x(i)||"body"===x(i)&&"static"===z(i).position)?e:i||function(t){var e=/firefox/i.test(j());if(/Trident/i.test(j())&&S(t)&&"fixed"===z(t).position)return null;var i=V(t);for(D(i)&&(i=i.host);S(i)&&["html","body"].indexOf(x(i))<0;){var n=z(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Q(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function X(t,e,i){return N(t,P(e,i))}function U(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function G(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const J={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,a=t.name,c=t.options,h=i.elements.arrow,d=i.modifiersData.popperOffsets,u=I(i.placement),f=Q(u),p=[r,o].indexOf(u)>=0?"height":"width";if(h&&d){var m=function(t,e){return U("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:G(t,l))}(c.padding,i),g=B(h),_="y"===f?n:r,b="y"===f?s:o,v=i.rects.reference[p]+i.rects.reference[f]-d[f]-i.rects.popper[p],y=d[f]-i.rects.reference[f],w=K(h),E=w?"y"===f?w.clientHeight||0:w.clientWidth||0:0,A=v/2-y/2,T=m[_],C=E-g[p]-m[b],O=E/2-g[p]/2+A,x=X(T,O,C),k=f;i.modifiersData[a]=((e={})[k]=x,e.centerOffset=x-O,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&W(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Z(t){return t.split("-")[1]}var tt={top:"auto",right:"auto",bottom:"auto",left:"auto"};function et(t){var e,i=t.popper,a=t.popperRect,l=t.placement,c=t.variation,d=t.offsets,u=t.position,f=t.gpuAcceleration,p=t.adaptive,m=t.roundOffsets,g=t.isFixed,_=d.x,b=void 0===_?0:_,v=d.y,y=void 0===v?0:v,w="function"==typeof m?m({x:b,y}):{x:b,y};b=w.x,y=w.y;var E=d.hasOwnProperty("x"),A=d.hasOwnProperty("y"),T=r,C=n,O=window;if(p){var x=K(i),L="clientHeight",S="clientWidth";x===k(i)&&"static"!==z(x=q(i)).position&&"absolute"===u&&(L="scrollHeight",S="scrollWidth"),(l===n||(l===r||l===o)&&c===h)&&(C=s,y-=(g&&x===O&&O.visualViewport?O.visualViewport.height:x[L])-a.height,y*=f?1:-1),l!==r&&(l!==n&&l!==s||c!==h)||(T=o,b-=(g&&x===O&&O.visualViewport?O.visualViewport.width:x[S])-a.width,b*=f?1:-1)}var D,$=Object.assign({position:u},p&&tt),I=!0===m?function(t,e){var i=t.x,n=t.y,s=e.devicePixelRatio||1;return{x:M(i*s)/s||0,y:M(n*s)/s||0}}({x:b,y},k(i)):{x:b,y};return b=I.x,y=I.y,f?Object.assign({},$,((D={})[C]=A?"0":"",D[T]=E?"0":"",D.transform=(O.devicePixelRatio||1)<=1?"translate("+b+"px, "+y+"px)":"translate3d("+b+"px, "+y+"px, 0)",D)):Object.assign({},$,((e={})[C]=A?y+"px":"",e[T]=E?b+"px":"",e.transform="",e))}const it={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:I(e.placement),variation:Z(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,et(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,et(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var nt={passive:!0};const st={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=k(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,nt)})),a&&l.addEventListener("resize",i.update,nt),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,nt)})),a&&l.removeEventListener("resize",i.update,nt)}},data:{}};var ot={left:"right",right:"left",bottom:"top",top:"bottom"};function rt(t){return t.replace(/left|right|bottom|top/g,(function(t){return ot[t]}))}var at={start:"end",end:"start"};function lt(t){return t.replace(/start|end/g,(function(t){return at[t]}))}function ct(t){var e=k(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ht(t){return H(q(t)).left+ct(t).scrollLeft}function dt(t){var e=z(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function ut(t){return["html","body","#document"].indexOf(x(t))>=0?t.ownerDocument.body:S(t)&&dt(t)?t:ut(V(t))}function ft(t,e){var i;void 0===e&&(e=[]);var n=ut(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=k(n),r=s?[o].concat(o.visualViewport||[],dt(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(ft(V(r)))}function pt(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function mt(t,e,i){return e===u?pt(function(t,e){var i=k(t),n=q(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=F();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+ht(t),y:l}}(t,i)):L(e)?function(t,e){var i=H(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):pt(function(t){var e,i=q(t),n=ct(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=N(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=N(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ht(t),l=-n.scrollTop;return"rtl"===z(s||i).direction&&(a+=N(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(q(t)))}function gt(t){var e,i=t.reference,a=t.element,l=t.placement,d=l?I(l):null,u=l?Z(l):null,f=i.x+i.width/2-a.width/2,p=i.y+i.height/2-a.height/2;switch(d){case n:e={x:f,y:i.y-a.height};break;case s:e={x:f,y:i.y+i.height};break;case o:e={x:i.x+i.width,y:p};break;case r:e={x:i.x-a.width,y:p};break;default:e={x:i.x,y:i.y}}var m=d?Q(d):null;if(null!=m){var g="y"===m?"height":"width";switch(u){case c:e[m]=e[m]-(i[g]/2-a[g]/2);break;case h:e[m]=e[m]+(i[g]/2-a[g]/2)}}return e}function _t(t,e){void 0===e&&(e={});var i=e,r=i.placement,a=void 0===r?t.placement:r,c=i.strategy,h=void 0===c?t.strategy:c,m=i.boundary,g=void 0===m?d:m,_=i.rootBoundary,b=void 0===_?u:_,v=i.elementContext,y=void 0===v?f:v,w=i.altBoundary,E=void 0!==w&&w,A=i.padding,T=void 0===A?0:A,C=U("number"!=typeof T?T:G(T,l)),O=y===f?p:f,k=t.rects.popper,D=t.elements[E?O:y],$=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=ft(V(t)),i=["absolute","fixed"].indexOf(z(t).position)>=0&&S(t)?K(t):t;return L(i)?e.filter((function(t){return L(t)&&W(t,i)&&"body"!==x(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=mt(t,i,n);return e.top=N(s.top,e.top),e.right=P(s.right,e.right),e.bottom=P(s.bottom,e.bottom),e.left=N(s.left,e.left),e}),mt(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(L(D)?D:D.contextElement||q(t.elements.popper),g,b,h),I=H(t.elements.reference),M=gt({reference:I,element:k,strategy:"absolute",placement:a}),j=pt(Object.assign({},k,M)),F=y===f?j:I,B={top:$.top-F.top+C.top,bottom:F.bottom-$.bottom+C.bottom,left:$.left-F.left+C.left,right:F.right-$.right+C.right},R=t.modifiersData.offset;if(y===f&&R){var Y=R[a];Object.keys(B).forEach((function(t){var e=[o,s].indexOf(t)>=0?1:-1,i=[n,s].indexOf(t)>=0?"y":"x";B[t]+=Y[i]*e}))}return B}const bt={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,h=t.name;if(!e.modifiersData[h]._skip){for(var d=i.mainAxis,u=void 0===d||d,f=i.altAxis,p=void 0===f||f,_=i.fallbackPlacements,b=i.padding,v=i.boundary,y=i.rootBoundary,w=i.altBoundary,E=i.flipVariations,A=void 0===E||E,T=i.allowedAutoPlacements,C=e.options.placement,O=I(C),x=_||(O!==C&&A?function(t){if(I(t)===a)return[];var e=rt(t);return[lt(t),e,lt(e)]}(C):[rt(C)]),k=[C].concat(x).reduce((function(t,i){return t.concat(I(i)===a?function(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,c=i.allowedAutoPlacements,h=void 0===c?g:c,d=Z(n),u=d?a?m:m.filter((function(t){return Z(t)===d})):l,f=u.filter((function(t){return h.indexOf(t)>=0}));0===f.length&&(f=u);var p=f.reduce((function(e,i){return e[i]=_t(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[I(i)],e}),{});return Object.keys(p).sort((function(t,e){return p[t]-p[e]}))}(e,{placement:i,boundary:v,rootBoundary:y,padding:b,flipVariations:A,allowedAutoPlacements:T}):i)}),[]),L=e.rects.reference,S=e.rects.popper,D=new Map,$=!0,N=k[0],P=0;P=0,B=H?"width":"height",W=_t(e,{placement:M,boundary:v,rootBoundary:y,altBoundary:w,padding:b}),z=H?F?o:r:F?s:n;L[B]>S[B]&&(z=rt(z));var R=rt(z),q=[];if(u&&q.push(W[j]<=0),p&&q.push(W[z]<=0,W[R]<=0),q.every((function(t){return t}))){N=M,$=!1;break}D.set(M,q)}if($)for(var V=function(t){var e=k.find((function(e){var i=D.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return N=e,"break"},Y=A?3:1;Y>0&&"break"!==V(Y);Y--);e.placement!==N&&(e.modifiersData[h]._skip=!0,e.placement=N,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function vt(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function yt(t){return[n,o,s,r].some((function(e){return t[e]>=0}))}const wt={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=_t(e,{elementContext:"reference"}),a=_t(e,{altBoundary:!0}),l=vt(r,n),c=vt(a,s,o),h=yt(l),d=yt(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},Et={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,s=t.name,a=i.offset,l=void 0===a?[0,0]:a,c=g.reduce((function(t,i){return t[i]=function(t,e,i){var s=I(t),a=[r,n].indexOf(s)>=0?-1:1,l="function"==typeof i?i(Object.assign({},e,{placement:t})):i,c=l[0],h=l[1];return c=c||0,h=(h||0)*a,[r,o].indexOf(s)>=0?{x:h,y:c}:{x:c,y:h}}(i,e.rects,l),t}),{}),h=c[e.placement],d=h.x,u=h.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=d,e.modifiersData.popperOffsets.y+=u),e.modifiersData[s]=c}},At={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=gt({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},Tt={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,a=t.name,l=i.mainAxis,h=void 0===l||l,d=i.altAxis,u=void 0!==d&&d,f=i.boundary,p=i.rootBoundary,m=i.altBoundary,g=i.padding,_=i.tether,b=void 0===_||_,v=i.tetherOffset,y=void 0===v?0:v,w=_t(e,{boundary:f,rootBoundary:p,padding:g,altBoundary:m}),E=I(e.placement),A=Z(e.placement),T=!A,C=Q(E),O="x"===C?"y":"x",x=e.modifiersData.popperOffsets,k=e.rects.reference,L=e.rects.popper,S="function"==typeof y?y(Object.assign({},e.rects,{placement:e.placement})):y,D="number"==typeof S?{mainAxis:S,altAxis:S}:Object.assign({mainAxis:0,altAxis:0},S),$=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,M={x:0,y:0};if(x){if(h){var j,F="y"===C?n:r,H="y"===C?s:o,W="y"===C?"height":"width",z=x[C],R=z+w[F],q=z-w[H],V=b?-L[W]/2:0,Y=A===c?k[W]:L[W],U=A===c?-L[W]:-k[W],G=e.elements.arrow,J=b&&G?B(G):{width:0,height:0},tt=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},et=tt[F],it=tt[H],nt=X(0,k[W],J[W]),st=T?k[W]/2-V-nt-et-D.mainAxis:Y-nt-et-D.mainAxis,ot=T?-k[W]/2+V+nt+it+D.mainAxis:U+nt+it+D.mainAxis,rt=e.elements.arrow&&K(e.elements.arrow),at=rt?"y"===C?rt.clientTop||0:rt.clientLeft||0:0,lt=null!=(j=null==$?void 0:$[C])?j:0,ct=z+ot-lt,ht=X(b?P(R,z+st-lt-at):R,z,b?N(q,ct):q);x[C]=ht,M[C]=ht-z}if(u){var dt,ut="x"===C?n:r,ft="x"===C?s:o,pt=x[O],mt="y"===O?"height":"width",gt=pt+w[ut],bt=pt-w[ft],vt=-1!==[n,r].indexOf(E),yt=null!=(dt=null==$?void 0:$[O])?dt:0,wt=vt?gt:pt-k[mt]-L[mt]-yt+D.altAxis,Et=vt?pt+k[mt]+L[mt]-yt-D.altAxis:bt,At=b&&vt?function(t,e,i){var n=X(t,e,i);return n>i?i:n}(wt,pt,Et):X(b?wt:gt,pt,b?Et:bt);x[O]=At,M[O]=At-pt}e.modifiersData[a]=M}},requiresIfExists:["offset"]};function Ct(t,e,i){void 0===i&&(i=!1);var n,s,o=S(e),r=S(e)&&function(t){var e=t.getBoundingClientRect(),i=M(e.width)/t.offsetWidth||1,n=M(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=q(e),l=H(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==x(e)||dt(a))&&(c=(n=e)!==k(n)&&S(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:ct(n)),S(e)?((h=H(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=ht(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function Ot(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var xt={placement:"bottom",modifiers:[],strategy:"absolute"};function kt(){for(var t=arguments.length,e=new Array(t),i=0;iIt.has(t)&&It.get(t).get(e)||null,remove(t,e){if(!It.has(t))return;const i=It.get(t);i.delete(e),0===i.size&&It.delete(t)}},Pt="transitionend",Mt=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),jt=t=>{t.dispatchEvent(new Event(Pt))},Ft=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),Ht=t=>Ft(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(Mt(t)):null,Bt=t=>{if(!Ft(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},Wt=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),zt=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?zt(t.parentNode):null},Rt=()=>{},qt=t=>{t.offsetHeight},Vt=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,Yt=[],Kt=()=>"rtl"===document.documentElement.dir,Qt=t=>{var e;e=()=>{const e=Vt();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(Yt.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of Yt)t()})),Yt.push(e)):e()},Xt=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,Ut=(t,e,i=!0)=>{if(!i)return void Xt(t);const n=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let s=!1;const o=({target:i})=>{i===e&&(s=!0,e.removeEventListener(Pt,o),Xt(t))};e.addEventListener(Pt,o),setTimeout((()=>{s||jt(e)}),n)},Gt=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},Jt=/[^.]*(?=\..*)\.|.*/,Zt=/\..*/,te=/::\d+$/,ee={};let ie=1;const ne={mouseenter:"mouseover",mouseleave:"mouseout"},se=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function oe(t,e){return e&&`${e}::${ie++}`||t.uidEvent||ie++}function re(t){const e=oe(t);return t.uidEvent=e,ee[e]=ee[e]||{},ee[e]}function ae(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function le(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=ue(t);return se.has(o)||(o=t),[n,s,o]}function ce(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=le(e,i,n);if(e in ne){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=re(t),c=l[a]||(l[a]={}),h=ae(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=oe(r,e.replace(Jt,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return pe(s,{delegateTarget:r}),n.oneOff&&fe.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return pe(n,{delegateTarget:t}),i.oneOff&&fe.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function he(t,e,i,n,s){const o=ae(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function de(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&he(t,e,i,r.callable,r.delegationSelector)}function ue(t){return t=t.replace(Zt,""),ne[t]||t}const fe={on(t,e,i,n){ce(t,e,i,n,!1)},one(t,e,i,n){ce(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=le(e,i,n),a=r!==e,l=re(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))de(t,l,i,e.slice(1));for(const[i,n]of Object.entries(c)){const s=i.replace(te,"");a&&!e.includes(s)||he(t,l,r,n.callable,n.delegationSelector)}}else{if(!Object.keys(c).length)return;he(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=Vt();let s=null,o=!0,r=!0,a=!1;e!==ue(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());const l=pe(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function pe(t,e={}){for(const[i,n]of Object.entries(e))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}function me(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function ge(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const _e={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${ge(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${ge(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=me(t.dataset[n])}return e},getDataAttribute:(t,e)=>me(t.getAttribute(`data-bs-${ge(e)}`))};class be{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=Ft(e)?_e.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...Ft(e)?_e.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[n,s]of Object.entries(e)){const e=t[n],o=Ft(e)?"element":null==(i=e)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(s).test(o))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${o}" but expected type "${s}".`)}var i}}class ve extends be{constructor(t,e){super(),(t=Ht(t))&&(this._element=t,this._config=this._getConfig(e),Nt.set(this._element,this.constructor.DATA_KEY,this))}dispose(){Nt.remove(this._element,this.constructor.DATA_KEY),fe.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){Ut(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return Nt.get(Ht(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.3"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const ye=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e?e.split(",").map((t=>Mt(t))).join(","):null},we={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!Wt(t)&&Bt(t)))},getSelectorFromElement(t){const e=ye(t);return e&&we.findOne(e)?e:null},getElementFromSelector(t){const e=ye(t);return e?we.findOne(e):null},getMultipleElementsFromSelector(t){const e=ye(t);return e?we.find(e):[]}},Ee=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,n=t.NAME;fe.on(document,i,`[data-bs-dismiss="${n}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),Wt(this))return;const s=we.getElementFromSelector(this)||this.closest(`.${n}`);t.getOrCreateInstance(s)[e]()}))},Ae=".bs.alert",Te=`close${Ae}`,Ce=`closed${Ae}`;class Oe extends ve{static get NAME(){return"alert"}close(){if(fe.trigger(this._element,Te).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),fe.trigger(this._element,Ce),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=Oe.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}Ee(Oe,"close"),Qt(Oe);const xe='[data-bs-toggle="button"]';class ke extends ve{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=ke.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}fe.on(document,"click.bs.button.data-api",xe,(t=>{t.preventDefault();const e=t.target.closest(xe);ke.getOrCreateInstance(e).toggle()})),Qt(ke);const Le=".bs.swipe",Se=`touchstart${Le}`,De=`touchmove${Le}`,$e=`touchend${Le}`,Ie=`pointerdown${Le}`,Ne=`pointerup${Le}`,Pe={endCallback:null,leftCallback:null,rightCallback:null},Me={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class je extends be{constructor(t,e){super(),this._element=t,t&&je.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return Pe}static get DefaultType(){return Me}static get NAME(){return"swipe"}dispose(){fe.off(this._element,Le)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),Xt(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&Xt(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(fe.on(this._element,Ie,(t=>this._start(t))),fe.on(this._element,Ne,(t=>this._end(t))),this._element.classList.add("pointer-event")):(fe.on(this._element,Se,(t=>this._start(t))),fe.on(this._element,De,(t=>this._move(t))),fe.on(this._element,$e,(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const Fe=".bs.carousel",He=".data-api",Be="ArrowLeft",We="ArrowRight",ze="next",Re="prev",qe="left",Ve="right",Ye=`slide${Fe}`,Ke=`slid${Fe}`,Qe=`keydown${Fe}`,Xe=`mouseenter${Fe}`,Ue=`mouseleave${Fe}`,Ge=`dragstart${Fe}`,Je=`load${Fe}${He}`,Ze=`click${Fe}${He}`,ti="carousel",ei="active",ii=".active",ni=".carousel-item",si=ii+ni,oi={[Be]:Ve,[We]:qe},ri={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},ai={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class li extends ve{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=we.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===ti&&this.cycle()}static get Default(){return ri}static get DefaultType(){return ai}static get NAME(){return"carousel"}next(){this._slide(ze)}nextWhenVisible(){!document.hidden&&Bt(this._element)&&this.next()}prev(){this._slide(Re)}pause(){this._isSliding&&jt(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?fe.one(this._element,Ke,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void fe.one(this._element,Ke,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?ze:Re;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&fe.on(this._element,Qe,(t=>this._keydown(t))),"hover"===this._config.pause&&(fe.on(this._element,Xe,(()=>this.pause())),fe.on(this._element,Ue,(()=>this._maybeEnableCycle()))),this._config.touch&&je.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of we.find(".carousel-item img",this._element))fe.on(t,Ge,(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(qe)),rightCallback:()=>this._slide(this._directionToOrder(Ve)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new je(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=oi[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=we.findOne(ii,this._indicatorsElement);e.classList.remove(ei),e.removeAttribute("aria-current");const i=we.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(ei),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===ze,s=e||Gt(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>fe.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(Ye).defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),qt(s),i.classList.add(l),s.classList.add(l),this._queueCallback((()=>{s.classList.remove(l,c),s.classList.add(ei),i.classList.remove(ei,c,l),this._isSliding=!1,r(Ke)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return we.findOne(si,this._element)}_getItems(){return we.find(ni,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return Kt()?t===qe?Re:ze:t===qe?ze:Re}_orderToDirection(t){return Kt()?t===Re?qe:Ve:t===Re?Ve:qe}static jQueryInterface(t){return this.each((function(){const e=li.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}fe.on(document,Ze,"[data-bs-slide], [data-bs-slide-to]",(function(t){const e=we.getElementFromSelector(this);if(!e||!e.classList.contains(ti))return;t.preventDefault();const i=li.getOrCreateInstance(e),n=this.getAttribute("data-bs-slide-to");return n?(i.to(n),void i._maybeEnableCycle()):"next"===_e.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),fe.on(window,Je,(()=>{const t=we.find('[data-bs-ride="carousel"]');for(const e of t)li.getOrCreateInstance(e)})),Qt(li);const ci=".bs.collapse",hi=`show${ci}`,di=`shown${ci}`,ui=`hide${ci}`,fi=`hidden${ci}`,pi=`click${ci}.data-api`,mi="show",gi="collapse",_i="collapsing",bi=`:scope .${gi} .${gi}`,vi='[data-bs-toggle="collapse"]',yi={parent:null,toggle:!0},wi={parent:"(null|element)",toggle:"boolean"};class Ei extends ve{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=we.find(vi);for(const t of i){const e=we.getSelectorFromElement(t),i=we.find(e).filter((t=>t===this._element));null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return yi}static get DefaultType(){return wi}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>Ei.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(fe.trigger(this._element,hi).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(gi),this._element.classList.add(_i),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(_i),this._element.classList.add(gi,mi),this._element.style[e]="",fe.trigger(this._element,di)}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(fe.trigger(this._element,ui).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,qt(this._element),this._element.classList.add(_i),this._element.classList.remove(gi,mi);for(const t of this._triggerArray){const e=we.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(_i),this._element.classList.add(gi),fe.trigger(this._element,fi)}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(mi)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=Ht(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(vi);for(const e of t){const t=we.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=we.find(bi,this._config.parent);return we.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=Ei.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}fe.on(document,pi,vi,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of we.getMultipleElementsFromSelector(this))Ei.getOrCreateInstance(t,{toggle:!1}).toggle()})),Qt(Ei);const Ai="dropdown",Ti=".bs.dropdown",Ci=".data-api",Oi="ArrowUp",xi="ArrowDown",ki=`hide${Ti}`,Li=`hidden${Ti}`,Si=`show${Ti}`,Di=`shown${Ti}`,$i=`click${Ti}${Ci}`,Ii=`keydown${Ti}${Ci}`,Ni=`keyup${Ti}${Ci}`,Pi="show",Mi='[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)',ji=`${Mi}.${Pi}`,Fi=".dropdown-menu",Hi=Kt()?"top-end":"top-start",Bi=Kt()?"top-start":"top-end",Wi=Kt()?"bottom-end":"bottom-start",zi=Kt()?"bottom-start":"bottom-end",Ri=Kt()?"left-start":"right-start",qi=Kt()?"right-start":"left-start",Vi={autoClose:!0,boundary:"clippingParents",display:"dynamic",offset:[0,2],popperConfig:null,reference:"toggle"},Yi={autoClose:"(boolean|string)",boundary:"(string|element)",display:"string",offset:"(array|string|function)",popperConfig:"(null|object|function)",reference:"(string|element|object)"};class Ki extends ve{constructor(t,e){super(t,e),this._popper=null,this._parent=this._element.parentNode,this._menu=we.next(this._element,Fi)[0]||we.prev(this._element,Fi)[0]||we.findOne(Fi,this._parent),this._inNavbar=this._detectNavbar()}static get Default(){return Vi}static get DefaultType(){return Yi}static get NAME(){return Ai}toggle(){return this._isShown()?this.hide():this.show()}show(){if(Wt(this._element)||this._isShown())return;const t={relatedTarget:this._element};if(!fe.trigger(this._element,Si,t).defaultPrevented){if(this._createPopper(),"ontouchstart"in document.documentElement&&!this._parent.closest(".navbar-nav"))for(const t of[].concat(...document.body.children))fe.on(t,"mouseover",Rt);this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Pi),this._element.classList.add(Pi),fe.trigger(this._element,Di,t)}}hide(){if(Wt(this._element)||!this._isShown())return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){if(!fe.trigger(this._element,ki,t).defaultPrevented){if("ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))fe.off(t,"mouseover",Rt);this._popper&&this._popper.destroy(),this._menu.classList.remove(Pi),this._element.classList.remove(Pi),this._element.setAttribute("aria-expanded","false"),_e.removeDataAttribute(this._menu,"popper"),fe.trigger(this._element,Li,t)}}_getConfig(t){if("object"==typeof(t=super._getConfig(t)).reference&&!Ft(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Ai.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(){if(void 0===e)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let t=this._element;"parent"===this._config.reference?t=this._parent:Ft(this._config.reference)?t=Ht(this._config.reference):"object"==typeof this._config.reference&&(t=this._config.reference);const i=this._getPopperConfig();this._popper=Dt(t,this._menu,i)}_isShown(){return this._menu.classList.contains(Pi)}_getPlacement(){const t=this._parent;if(t.classList.contains("dropend"))return Ri;if(t.classList.contains("dropstart"))return qi;if(t.classList.contains("dropup-center"))return"top";if(t.classList.contains("dropdown-center"))return"bottom";const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?Bi:Hi:e?zi:Wi}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(_e.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...Xt(this._config.popperConfig,[t])}}_selectMenuItem({key:t,target:e}){const i=we.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>Bt(t)));i.length&&Gt(i,e,t===xi,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=Ki.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=we.find(ji);for(const i of e){const e=Ki.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[Oi,xi].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(Mi)?this:we.prev(this,Mi)[0]||we.next(this,Mi)[0]||we.findOne(Mi,t.delegateTarget.parentNode),o=Ki.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}fe.on(document,Ii,Mi,Ki.dataApiKeydownHandler),fe.on(document,Ii,Fi,Ki.dataApiKeydownHandler),fe.on(document,$i,Ki.clearMenus),fe.on(document,Ni,Ki.clearMenus),fe.on(document,$i,Mi,(function(t){t.preventDefault(),Ki.getOrCreateInstance(this).toggle()})),Qt(Ki);const Qi="backdrop",Xi="show",Ui=`mousedown.bs.${Qi}`,Gi={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Ji={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class Zi extends be{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return Gi}static get DefaultType(){return Ji}static get NAME(){return Qi}show(t){if(!this._config.isVisible)return void Xt(t);this._append();const e=this._getElement();this._config.isAnimated&&qt(e),e.classList.add(Xi),this._emulateAnimation((()=>{Xt(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(Xi),this._emulateAnimation((()=>{this.dispose(),Xt(t)}))):Xt(t)}dispose(){this._isAppended&&(fe.off(this._element,Ui),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=Ht(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),fe.on(t,Ui,(()=>{Xt(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){Ut(t,this._getElement(),this._config.isAnimated)}}const tn=".bs.focustrap",en=`focusin${tn}`,nn=`keydown.tab${tn}`,sn="backward",on={autofocus:!0,trapElement:null},rn={autofocus:"boolean",trapElement:"element"};class an extends be{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return on}static get DefaultType(){return rn}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),fe.off(document,tn),fe.on(document,en,(t=>this._handleFocusin(t))),fe.on(document,nn,(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,fe.off(document,tn))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=we.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===sn?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?sn:"forward")}}const ln=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",cn=".sticky-top",hn="padding-right",dn="margin-right";class un{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,hn,(e=>e+t)),this._setElementAttributes(ln,hn,(e=>e+t)),this._setElementAttributes(cn,dn,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,hn),this._resetElementAttributes(ln,hn),this._resetElementAttributes(cn,dn)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&_e.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=_e.getDataAttribute(t,e);null!==i?(_e.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(Ft(t))e(t);else for(const i of we.find(t,this._element))e(i)}}const fn=".bs.modal",pn=`hide${fn}`,mn=`hidePrevented${fn}`,gn=`hidden${fn}`,_n=`show${fn}`,bn=`shown${fn}`,vn=`resize${fn}`,yn=`click.dismiss${fn}`,wn=`mousedown.dismiss${fn}`,En=`keydown.dismiss${fn}`,An=`click${fn}.data-api`,Tn="modal-open",Cn="show",On="modal-static",xn={backdrop:!0,focus:!0,keyboard:!0},kn={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class Ln extends ve{constructor(t,e){super(t,e),this._dialog=we.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new un,this._addEventListeners()}static get Default(){return xn}static get DefaultType(){return kn}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||fe.trigger(this._element,_n,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(Tn),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){this._isShown&&!this._isTransitioning&&(fe.trigger(this._element,pn).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(Cn),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated())))}dispose(){fe.off(window,fn),fe.off(this._dialog,fn),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Zi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new an({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=we.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),qt(this._element),this._element.classList.add(Cn),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,fe.trigger(this._element,bn,{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){fe.on(this._element,En,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())})),fe.on(window,vn,(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),fe.on(this._element,wn,(t=>{fe.one(this._element,yn,(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(Tn),this._resetAdjustments(),this._scrollBar.reset(),fe.trigger(this._element,gn)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(fe.trigger(this._element,mn).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(On)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(On),this._queueCallback((()=>{this._element.classList.remove(On),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=Kt()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=Kt()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Ln.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}fe.on(document,An,'[data-bs-toggle="modal"]',(function(t){const e=we.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),fe.one(e,_n,(t=>{t.defaultPrevented||fe.one(e,gn,(()=>{Bt(this)&&this.focus()}))}));const i=we.findOne(".modal.show");i&&Ln.getInstance(i).hide(),Ln.getOrCreateInstance(e).toggle(this)})),Ee(Ln),Qt(Ln);const Sn=".bs.offcanvas",Dn=".data-api",$n=`load${Sn}${Dn}`,In="show",Nn="showing",Pn="hiding",Mn=".offcanvas.show",jn=`show${Sn}`,Fn=`shown${Sn}`,Hn=`hide${Sn}`,Bn=`hidePrevented${Sn}`,Wn=`hidden${Sn}`,zn=`resize${Sn}`,Rn=`click${Sn}${Dn}`,qn=`keydown.dismiss${Sn}`,Vn={backdrop:!0,keyboard:!0,scroll:!1},Yn={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class Kn extends ve{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return Vn}static get DefaultType(){return Yn}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||fe.trigger(this._element,jn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new un).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Nn),this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(In),this._element.classList.remove(Nn),fe.trigger(this._element,Fn,{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(fe.trigger(this._element,Hn).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(Pn),this._backdrop.hide(),this._queueCallback((()=>{this._element.classList.remove(In,Pn),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new un).reset(),fe.trigger(this._element,Wn)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new Zi({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():fe.trigger(this._element,Bn)}:null})}_initializeFocusTrap(){return new an({trapElement:this._element})}_addEventListeners(){fe.on(this._element,qn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():fe.trigger(this._element,Bn))}))}static jQueryInterface(t){return this.each((function(){const e=Kn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}fe.on(document,Rn,'[data-bs-toggle="offcanvas"]',(function(t){const e=we.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),Wt(this))return;fe.one(e,Wn,(()=>{Bt(this)&&this.focus()}));const i=we.findOne(Mn);i&&i!==e&&Kn.getInstance(i).hide(),Kn.getOrCreateInstance(e).toggle(this)})),fe.on(window,$n,(()=>{for(const t of we.find(Mn))Kn.getOrCreateInstance(t).show()})),fe.on(window,zn,(()=>{for(const t of we.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&Kn.getOrCreateInstance(t).hide()})),Ee(Kn),Qt(Kn);const Qn={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],dd:[],div:[],dl:[],dt:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Xn=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Un=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Gn=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!Xn.has(i)||Boolean(Un.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))},Jn={allowList:Qn,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
"},Zn={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},ts={entry:"(string|element|function|null)",selector:"(string|element)"};class es extends be{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Jn}static get DefaultType(){return Zn}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},ts)}_setContent(t,e,i){const n=we.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?Ft(e)?this._putElementInTemplate(Ht(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Gn(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return Xt(t,[this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const is=new Set(["sanitize","allowList","sanitizeFn"]),ns="fade",ss="show",os=".tooltip-inner",rs=".modal",as="hide.bs.modal",ls="hover",cs="focus",hs={AUTO:"auto",TOP:"top",RIGHT:Kt()?"left":"right",BOTTOM:"bottom",LEFT:Kt()?"right":"left"},ds={allowList:Qn,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},us={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class fs extends ve{constructor(t,i){if(void 0===e)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t,i),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return ds}static get DefaultType(){return us}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),fe.off(this._element.closest(rs),as,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=fe.trigger(this._element,this.constructor.eventName("show")),e=(zt(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),fe.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(ss),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))fe.on(t,"mouseover",Rt);this._queueCallback((()=>{fe.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(this._isShown()&&!fe.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(ss),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))fe.off(t,"mouseover",Rt);this._activeTrigger.click=!1,this._activeTrigger[cs]=!1,this._activeTrigger[ls]=!1,this._isHovered=null,this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),fe.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(ns,ss),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(ns),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new es({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{[os]:this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(ns)}_isShown(){return this.tip&&this.tip.classList.contains(ss)}_createPopper(t){const e=Xt(this._config.placement,[this,t,this._element]),i=hs[e.toUpperCase()];return Dt(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return Xt(t,[this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...Xt(this._config.popperConfig,[e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)fe.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===ls?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===ls?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");fe.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?cs:ls]=!0,e._enter()})),fe.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?cs:ls]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},fe.on(this._element.closest(rs),as,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=_e.getDataAttributes(this._element);for(const t of Object.keys(e))is.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:Ht(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each((function(){const e=fs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}Qt(fs);const ps=".popover-header",ms=".popover-body",gs={...fs.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},_s={...fs.DefaultType,content:"(null|string|element|function)"};class bs extends fs{static get Default(){return gs}static get DefaultType(){return _s}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{[ps]:this._getTitle(),[ms]:this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=bs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}Qt(bs);const vs=".bs.scrollspy",ys=`activate${vs}`,ws=`click${vs}`,Es=`load${vs}.data-api`,As="active",Ts="[href]",Cs=".nav-link",Os=`${Cs}, .nav-item > ${Cs}, .list-group-item`,xs={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},ks={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class Ls extends ve{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return xs}static get DefaultType(){return ks}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=Ht(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(fe.off(this._config.target,ws),fe.on(this._config.target,ws,Ts,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=we.find(Ts,this._config.target);for(const e of t){if(!e.hash||Wt(e))continue;const t=we.findOne(decodeURI(e.hash),this._element);Bt(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(As),this._activateParents(t),fe.trigger(this._element,ys,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))we.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(As);else for(const e of we.parents(t,".nav, .list-group"))for(const t of we.prev(e,Os))t.classList.add(As)}_clearActiveClass(t){t.classList.remove(As);const e=we.find(`${Ts}.${As}`,t);for(const t of e)t.classList.remove(As)}static jQueryInterface(t){return this.each((function(){const e=Ls.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}fe.on(window,Es,(()=>{for(const t of we.find('[data-bs-spy="scroll"]'))Ls.getOrCreateInstance(t)})),Qt(Ls);const Ss=".bs.tab",Ds=`hide${Ss}`,$s=`hidden${Ss}`,Is=`show${Ss}`,Ns=`shown${Ss}`,Ps=`click${Ss}`,Ms=`keydown${Ss}`,js=`load${Ss}`,Fs="ArrowLeft",Hs="ArrowRight",Bs="ArrowUp",Ws="ArrowDown",zs="Home",Rs="End",qs="active",Vs="fade",Ys="show",Ks=".dropdown-toggle",Qs=`:not(${Ks})`,Xs='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',Us=`.nav-link${Qs}, .list-group-item${Qs}, [role="tab"]${Qs}, ${Xs}`,Gs=`.${qs}[data-bs-toggle="tab"], .${qs}[data-bs-toggle="pill"], .${qs}[data-bs-toggle="list"]`;class Js extends ve{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),fe.on(this._element,Ms,(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?fe.trigger(e,Ds,{relatedTarget:t}):null;fe.trigger(t,Is,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(qs),this._activate(we.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),fe.trigger(t,Ns,{relatedTarget:e})):t.classList.add(Ys)}),t,t.classList.contains(Vs)))}_deactivate(t,e){t&&(t.classList.remove(qs),t.blur(),this._deactivate(we.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),fe.trigger(t,$s,{relatedTarget:e})):t.classList.remove(Ys)}),t,t.classList.contains(Vs)))}_keydown(t){if(![Fs,Hs,Bs,Ws,zs,Rs].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter((t=>!Wt(t)));let i;if([zs,Rs].includes(t.key))i=e[t.key===zs?0:e.length-1];else{const n=[Hs,Ws].includes(t.key);i=Gt(e,t.target,n,!0)}i&&(i.focus({preventScroll:!0}),Js.getOrCreateInstance(i).show())}_getChildren(){return we.find(Us,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=we.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=we.findOne(t,i);s&&s.classList.toggle(n,e)};n(Ks,qs),n(".dropdown-menu",Ys),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(qs)}_getInnerElement(t){return t.matches(Us)?t:we.findOne(Us,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=Js.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}fe.on(document,Ps,Xs,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),Wt(this)||Js.getOrCreateInstance(this).show()})),fe.on(window,js,(()=>{for(const t of we.find(Gs))Js.getOrCreateInstance(t)})),Qt(Js);const Zs=".bs.toast",to=`mouseover${Zs}`,eo=`mouseout${Zs}`,io=`focusin${Zs}`,no=`focusout${Zs}`,so=`hide${Zs}`,oo=`hidden${Zs}`,ro=`show${Zs}`,ao=`shown${Zs}`,lo="hide",co="show",ho="showing",uo={animation:"boolean",autohide:"boolean",delay:"number"},fo={animation:!0,autohide:!0,delay:5e3};class po extends ve{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return fo}static get DefaultType(){return uo}static get NAME(){return"toast"}show(){fe.trigger(this._element,ro).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(lo),qt(this._element),this._element.classList.add(co,ho),this._queueCallback((()=>{this._element.classList.remove(ho),fe.trigger(this._element,ao),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this.isShown()&&(fe.trigger(this._element,so).defaultPrevented||(this._element.classList.add(ho),this._queueCallback((()=>{this._element.classList.add(lo),this._element.classList.remove(ho,co),fe.trigger(this._element,oo)}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(co),super.dispose()}isShown(){return this._element.classList.contains(co)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){fe.on(this._element,to,(t=>this._onInteraction(t,!0))),fe.on(this._element,eo,(t=>this._onInteraction(t,!1))),fe.on(this._element,io,(t=>this._onInteraction(t,!0))),fe.on(this._element,no,(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=po.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}function mo(t){"loading"!=document.readyState?t():document.addEventListener("DOMContentLoaded",t)}Ee(po),Qt(po),mo((function(){[].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')).map((function(t){return new fs(t,{delay:{show:500,hide:100}})}))})),mo((function(){document.getElementById("pst-back-to-top").addEventListener("click",(function(){document.body.scrollTop=0,document.documentElement.scrollTop=0}))})),mo((function(){var t=document.getElementById("pst-back-to-top"),e=document.getElementsByClassName("bd-header")[0].getBoundingClientRect();window.addEventListener("scroll",(function(){this.oldScroll>this.scrollY&&this.scrollY>e.bottom?t.style.display="block":t.style.display="none",this.oldScroll=this.scrollY}))})),window.bootstrap=i})(); +//# sourceMappingURL=bootstrap.js.map \ No newline at end of file diff --git a/_static/scripts/bootstrap.js.LICENSE.txt b/_static/scripts/bootstrap.js.LICENSE.txt new file mode 100644 index 000000000..28755c2c5 --- /dev/null +++ b/_static/scripts/bootstrap.js.LICENSE.txt @@ -0,0 +1,5 @@ +/*! + * Bootstrap v5.3.3 (https://getbootstrap.com/) + * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ diff --git a/_static/scripts/bootstrap.js.map b/_static/scripts/bootstrap.js.map new file mode 100644 index 000000000..4a3502aeb --- /dev/null +++ b/_static/scripts/bootstrap.js.map @@ -0,0 +1 @@ +{"version":3,"file":"scripts/bootstrap.js","mappings":"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","sources":["webpack://pydata_sphinx_theme/webpack/bootstrap","webpack://pydata_sphinx_theme/webpack/runtime/define property getters","webpack://pydata_sphinx_theme/webpack/runtime/hasOwnProperty shorthand","webpack://pydata_sphinx_theme/webpack/runtime/make namespace object","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/enums.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getNodeName.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getWindow.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/instanceOf.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/applyStyles.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getBasePlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/math.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/userAgent.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/isLayoutViewport.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getBoundingClientRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getLayoutRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/contains.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getComputedStyle.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/isTableElement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getDocumentElement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getParentNode.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getOffsetParent.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getMainAxisFromPlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/within.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/mergePaddingObject.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getFreshSideObject.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/expandToHashMap.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/arrow.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getVariation.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/computeStyles.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/eventListeners.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getOppositePlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getOppositeVariationPlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getWindowScroll.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getWindowScrollBarX.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/isScrollParent.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getScrollParent.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/listScrollParents.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/rectToClientRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getClippingRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getViewportRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getDocumentRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/computeOffsets.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/detectOverflow.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/flip.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/computeAutoPlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/hide.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/offset.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/popperOffsets.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/preventOverflow.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getAltAxis.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getCompositeRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getNodeScroll.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getHTMLElementScroll.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/orderModifiers.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/createPopper.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/debounce.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/mergeByName.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/popper.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/popper-lite.js","webpack://pydata_sphinx_theme/./node_modules/bootstrap/dist/js/bootstrap.esm.js","webpack://pydata_sphinx_theme/./src/pydata_sphinx_theme/assets/scripts/mixin.js","webpack://pydata_sphinx_theme/./src/pydata_sphinx_theme/assets/scripts/bootstrap.js"],"sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","export var top = 'top';\nexport var bottom = 'bottom';\nexport var right = 'right';\nexport var left = 'left';\nexport var auto = 'auto';\nexport var basePlacements = [top, bottom, right, left];\nexport var start = 'start';\nexport var end = 'end';\nexport var clippingParents = 'clippingParents';\nexport var viewport = 'viewport';\nexport var popper = 'popper';\nexport var reference = 'reference';\nexport var variationPlacements = /*#__PURE__*/basePlacements.reduce(function (acc, placement) {\n return acc.concat([placement + \"-\" + start, placement + \"-\" + end]);\n}, []);\nexport var placements = /*#__PURE__*/[].concat(basePlacements, [auto]).reduce(function (acc, placement) {\n return acc.concat([placement, placement + \"-\" + start, placement + \"-\" + end]);\n}, []); // modifiers that need to read the DOM\n\nexport var beforeRead = 'beforeRead';\nexport var read = 'read';\nexport var afterRead = 'afterRead'; // pure-logic modifiers\n\nexport var beforeMain = 'beforeMain';\nexport var main = 'main';\nexport var afterMain = 'afterMain'; // modifier with the purpose to write to the DOM (or write into a framework state)\n\nexport var beforeWrite = 'beforeWrite';\nexport var write = 'write';\nexport var afterWrite = 'afterWrite';\nexport var modifierPhases = [beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite];","export default function getNodeName(element) {\n return element ? (element.nodeName || '').toLowerCase() : null;\n}","export default function getWindow(node) {\n if (node == null) {\n return window;\n }\n\n if (node.toString() !== '[object Window]') {\n var ownerDocument = node.ownerDocument;\n return ownerDocument ? ownerDocument.defaultView || window : window;\n }\n\n return node;\n}","import getWindow from \"./getWindow.js\";\n\nfunction isElement(node) {\n var OwnElement = getWindow(node).Element;\n return node instanceof OwnElement || node instanceof Element;\n}\n\nfunction isHTMLElement(node) {\n var OwnElement = getWindow(node).HTMLElement;\n return node instanceof OwnElement || node instanceof HTMLElement;\n}\n\nfunction isShadowRoot(node) {\n // IE 11 has no ShadowRoot\n if (typeof ShadowRoot === 'undefined') {\n return false;\n }\n\n var OwnElement = getWindow(node).ShadowRoot;\n return node instanceof OwnElement || node instanceof ShadowRoot;\n}\n\nexport { isElement, isHTMLElement, isShadowRoot };","import getNodeName from \"../dom-utils/getNodeName.js\";\nimport { isHTMLElement } from \"../dom-utils/instanceOf.js\"; // This modifier takes the styles prepared by the `computeStyles` modifier\n// and applies them to the HTMLElements such as popper and arrow\n\nfunction applyStyles(_ref) {\n var state = _ref.state;\n Object.keys(state.elements).forEach(function (name) {\n var style = state.styles[name] || {};\n var attributes = state.attributes[name] || {};\n var element = state.elements[name]; // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n } // Flow doesn't support to extend this property, but it's the most\n // effective way to apply styles to an HTMLElement\n // $FlowFixMe[cannot-write]\n\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (name) {\n var value = attributes[name];\n\n if (value === false) {\n element.removeAttribute(name);\n } else {\n element.setAttribute(name, value === true ? '' : value);\n }\n });\n });\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state;\n var initialStyles = {\n popper: {\n position: state.options.strategy,\n left: '0',\n top: '0',\n margin: '0'\n },\n arrow: {\n position: 'absolute'\n },\n reference: {}\n };\n Object.assign(state.elements.popper.style, initialStyles.popper);\n state.styles = initialStyles;\n\n if (state.elements.arrow) {\n Object.assign(state.elements.arrow.style, initialStyles.arrow);\n }\n\n return function () {\n Object.keys(state.elements).forEach(function (name) {\n var element = state.elements[name];\n var attributes = state.attributes[name] || {};\n var styleProperties = Object.keys(state.styles.hasOwnProperty(name) ? state.styles[name] : initialStyles[name]); // Set all values to an empty string to unset them\n\n var style = styleProperties.reduce(function (style, property) {\n style[property] = '';\n return style;\n }, {}); // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n }\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (attribute) {\n element.removeAttribute(attribute);\n });\n });\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'applyStyles',\n enabled: true,\n phase: 'write',\n fn: applyStyles,\n effect: effect,\n requires: ['computeStyles']\n};","import { auto } from \"../enums.js\";\nexport default function getBasePlacement(placement) {\n return placement.split('-')[0];\n}","export var max = Math.max;\nexport var min = Math.min;\nexport var round = Math.round;","export default function getUAString() {\n var uaData = navigator.userAgentData;\n\n if (uaData != null && uaData.brands && Array.isArray(uaData.brands)) {\n return uaData.brands.map(function (item) {\n return item.brand + \"/\" + item.version;\n }).join(' ');\n }\n\n return navigator.userAgent;\n}","import getUAString from \"../utils/userAgent.js\";\nexport default function isLayoutViewport() {\n return !/^((?!chrome|android).)*safari/i.test(getUAString());\n}","import { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport { round } from \"../utils/math.js\";\nimport getWindow from \"./getWindow.js\";\nimport isLayoutViewport from \"./isLayoutViewport.js\";\nexport default function getBoundingClientRect(element, includeScale, isFixedStrategy) {\n if (includeScale === void 0) {\n includeScale = false;\n }\n\n if (isFixedStrategy === void 0) {\n isFixedStrategy = false;\n }\n\n var clientRect = element.getBoundingClientRect();\n var scaleX = 1;\n var scaleY = 1;\n\n if (includeScale && isHTMLElement(element)) {\n scaleX = element.offsetWidth > 0 ? round(clientRect.width) / element.offsetWidth || 1 : 1;\n scaleY = element.offsetHeight > 0 ? round(clientRect.height) / element.offsetHeight || 1 : 1;\n }\n\n var _ref = isElement(element) ? getWindow(element) : window,\n visualViewport = _ref.visualViewport;\n\n var addVisualOffsets = !isLayoutViewport() && isFixedStrategy;\n var x = (clientRect.left + (addVisualOffsets && visualViewport ? visualViewport.offsetLeft : 0)) / scaleX;\n var y = (clientRect.top + (addVisualOffsets && visualViewport ? visualViewport.offsetTop : 0)) / scaleY;\n var width = clientRect.width / scaleX;\n var height = clientRect.height / scaleY;\n return {\n width: width,\n height: height,\n top: y,\n right: x + width,\n bottom: y + height,\n left: x,\n x: x,\n y: y\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\"; // Returns the layout rect of an element relative to its offsetParent. Layout\n// means it doesn't take into account transforms.\n\nexport default function getLayoutRect(element) {\n var clientRect = getBoundingClientRect(element); // Use the clientRect sizes if it's not been transformed.\n // Fixes https://github.com/popperjs/popper-core/issues/1223\n\n var width = element.offsetWidth;\n var height = element.offsetHeight;\n\n if (Math.abs(clientRect.width - width) <= 1) {\n width = clientRect.width;\n }\n\n if (Math.abs(clientRect.height - height) <= 1) {\n height = clientRect.height;\n }\n\n return {\n x: element.offsetLeft,\n y: element.offsetTop,\n width: width,\n height: height\n };\n}","import { isShadowRoot } from \"./instanceOf.js\";\nexport default function contains(parent, child) {\n var rootNode = child.getRootNode && child.getRootNode(); // First, attempt with faster native method\n\n if (parent.contains(child)) {\n return true;\n } // then fallback to custom implementation with Shadow DOM support\n else if (rootNode && isShadowRoot(rootNode)) {\n var next = child;\n\n do {\n if (next && parent.isSameNode(next)) {\n return true;\n } // $FlowFixMe[prop-missing]: need a better way to handle this...\n\n\n next = next.parentNode || next.host;\n } while (next);\n } // Give up, the result is false\n\n\n return false;\n}","import getWindow from \"./getWindow.js\";\nexport default function getComputedStyle(element) {\n return getWindow(element).getComputedStyle(element);\n}","import getNodeName from \"./getNodeName.js\";\nexport default function isTableElement(element) {\n return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0;\n}","import { isElement } from \"./instanceOf.js\";\nexport default function getDocumentElement(element) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return ((isElement(element) ? element.ownerDocument : // $FlowFixMe[prop-missing]\n element.document) || window.document).documentElement;\n}","import getNodeName from \"./getNodeName.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport { isShadowRoot } from \"./instanceOf.js\";\nexport default function getParentNode(element) {\n if (getNodeName(element) === 'html') {\n return element;\n }\n\n return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle\n // $FlowFixMe[incompatible-return]\n // $FlowFixMe[prop-missing]\n element.assignedSlot || // step into the shadow DOM of the parent of a slotted node\n element.parentNode || ( // DOM Element detected\n isShadowRoot(element) ? element.host : null) || // ShadowRoot detected\n // $FlowFixMe[incompatible-call]: HTMLElement is a Node\n getDocumentElement(element) // fallback\n\n );\n}","import getWindow from \"./getWindow.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isHTMLElement, isShadowRoot } from \"./instanceOf.js\";\nimport isTableElement from \"./isTableElement.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getUAString from \"../utils/userAgent.js\";\n\nfunction getTrueOffsetParent(element) {\n if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837\n getComputedStyle(element).position === 'fixed') {\n return null;\n }\n\n return element.offsetParent;\n} // `.offsetParent` reports `null` for fixed elements, while absolute elements\n// return the containing block\n\n\nfunction getContainingBlock(element) {\n var isFirefox = /firefox/i.test(getUAString());\n var isIE = /Trident/i.test(getUAString());\n\n if (isIE && isHTMLElement(element)) {\n // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport\n var elementCss = getComputedStyle(element);\n\n if (elementCss.position === 'fixed') {\n return null;\n }\n }\n\n var currentNode = getParentNode(element);\n\n if (isShadowRoot(currentNode)) {\n currentNode = currentNode.host;\n }\n\n while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) {\n var css = getComputedStyle(currentNode); // This is non-exhaustive but covers the most common CSS properties that\n // create a containing block.\n // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block\n\n if (css.transform !== 'none' || css.perspective !== 'none' || css.contain === 'paint' || ['transform', 'perspective'].indexOf(css.willChange) !== -1 || isFirefox && css.willChange === 'filter' || isFirefox && css.filter && css.filter !== 'none') {\n return currentNode;\n } else {\n currentNode = currentNode.parentNode;\n }\n }\n\n return null;\n} // Gets the closest ancestor positioned element. Handles some edge cases,\n// such as table ancestors and cross browser bugs.\n\n\nexport default function getOffsetParent(element) {\n var window = getWindow(element);\n var offsetParent = getTrueOffsetParent(element);\n\n while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === 'static') {\n offsetParent = getTrueOffsetParent(offsetParent);\n }\n\n if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static')) {\n return window;\n }\n\n return offsetParent || getContainingBlock(element) || window;\n}","export default function getMainAxisFromPlacement(placement) {\n return ['top', 'bottom'].indexOf(placement) >= 0 ? 'x' : 'y';\n}","import { max as mathMax, min as mathMin } from \"./math.js\";\nexport function within(min, value, max) {\n return mathMax(min, mathMin(value, max));\n}\nexport function withinMaxClamp(min, value, max) {\n var v = within(min, value, max);\n return v > max ? max : v;\n}","import getFreshSideObject from \"./getFreshSideObject.js\";\nexport default function mergePaddingObject(paddingObject) {\n return Object.assign({}, getFreshSideObject(), paddingObject);\n}","export default function getFreshSideObject() {\n return {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0\n };\n}","export default function expandToHashMap(value, keys) {\n return keys.reduce(function (hashMap, key) {\n hashMap[key] = value;\n return hashMap;\n }, {});\n}","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport contains from \"../dom-utils/contains.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport { within } from \"../utils/within.js\";\nimport mergePaddingObject from \"../utils/mergePaddingObject.js\";\nimport expandToHashMap from \"../utils/expandToHashMap.js\";\nimport { left, right, basePlacements, top, bottom } from \"../enums.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar toPaddingObject = function toPaddingObject(padding, state) {\n padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, {\n placement: state.placement\n })) : padding;\n return mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n};\n\nfunction arrow(_ref) {\n var _state$modifiersData$;\n\n var state = _ref.state,\n name = _ref.name,\n options = _ref.options;\n var arrowElement = state.elements.arrow;\n var popperOffsets = state.modifiersData.popperOffsets;\n var basePlacement = getBasePlacement(state.placement);\n var axis = getMainAxisFromPlacement(basePlacement);\n var isVertical = [left, right].indexOf(basePlacement) >= 0;\n var len = isVertical ? 'height' : 'width';\n\n if (!arrowElement || !popperOffsets) {\n return;\n }\n\n var paddingObject = toPaddingObject(options.padding, state);\n var arrowRect = getLayoutRect(arrowElement);\n var minProp = axis === 'y' ? top : left;\n var maxProp = axis === 'y' ? bottom : right;\n var endDiff = state.rects.reference[len] + state.rects.reference[axis] - popperOffsets[axis] - state.rects.popper[len];\n var startDiff = popperOffsets[axis] - state.rects.reference[axis];\n var arrowOffsetParent = getOffsetParent(arrowElement);\n var clientSize = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientHeight || 0 : arrowOffsetParent.clientWidth || 0 : 0;\n var centerToReference = endDiff / 2 - startDiff / 2; // Make sure the arrow doesn't overflow the popper if the center point is\n // outside of the popper bounds\n\n var min = paddingObject[minProp];\n var max = clientSize - arrowRect[len] - paddingObject[maxProp];\n var center = clientSize / 2 - arrowRect[len] / 2 + centerToReference;\n var offset = within(min, center, max); // Prevents breaking syntax highlighting...\n\n var axisProp = axis;\n state.modifiersData[name] = (_state$modifiersData$ = {}, _state$modifiersData$[axisProp] = offset, _state$modifiersData$.centerOffset = offset - center, _state$modifiersData$);\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state,\n options = _ref2.options;\n var _options$element = options.element,\n arrowElement = _options$element === void 0 ? '[data-popper-arrow]' : _options$element;\n\n if (arrowElement == null) {\n return;\n } // CSS selector\n\n\n if (typeof arrowElement === 'string') {\n arrowElement = state.elements.popper.querySelector(arrowElement);\n\n if (!arrowElement) {\n return;\n }\n }\n\n if (!contains(state.elements.popper, arrowElement)) {\n return;\n }\n\n state.elements.arrow = arrowElement;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'arrow',\n enabled: true,\n phase: 'main',\n fn: arrow,\n effect: effect,\n requires: ['popperOffsets'],\n requiresIfExists: ['preventOverflow']\n};","export default function getVariation(placement) {\n return placement.split('-')[1];\n}","import { top, left, right, bottom, end } from \"../enums.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getWindow from \"../dom-utils/getWindow.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getComputedStyle from \"../dom-utils/getComputedStyle.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport { round } from \"../utils/math.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar unsetSides = {\n top: 'auto',\n right: 'auto',\n bottom: 'auto',\n left: 'auto'\n}; // Round the offsets to the nearest suitable subpixel based on the DPR.\n// Zooming can change the DPR, but it seems to report a value that will\n// cleanly divide the values into the appropriate subpixels.\n\nfunction roundOffsetsByDPR(_ref, win) {\n var x = _ref.x,\n y = _ref.y;\n var dpr = win.devicePixelRatio || 1;\n return {\n x: round(x * dpr) / dpr || 0,\n y: round(y * dpr) / dpr || 0\n };\n}\n\nexport function mapToStyles(_ref2) {\n var _Object$assign2;\n\n var popper = _ref2.popper,\n popperRect = _ref2.popperRect,\n placement = _ref2.placement,\n variation = _ref2.variation,\n offsets = _ref2.offsets,\n position = _ref2.position,\n gpuAcceleration = _ref2.gpuAcceleration,\n adaptive = _ref2.adaptive,\n roundOffsets = _ref2.roundOffsets,\n isFixed = _ref2.isFixed;\n var _offsets$x = offsets.x,\n x = _offsets$x === void 0 ? 0 : _offsets$x,\n _offsets$y = offsets.y,\n y = _offsets$y === void 0 ? 0 : _offsets$y;\n\n var _ref3 = typeof roundOffsets === 'function' ? roundOffsets({\n x: x,\n y: y\n }) : {\n x: x,\n y: y\n };\n\n x = _ref3.x;\n y = _ref3.y;\n var hasX = offsets.hasOwnProperty('x');\n var hasY = offsets.hasOwnProperty('y');\n var sideX = left;\n var sideY = top;\n var win = window;\n\n if (adaptive) {\n var offsetParent = getOffsetParent(popper);\n var heightProp = 'clientHeight';\n var widthProp = 'clientWidth';\n\n if (offsetParent === getWindow(popper)) {\n offsetParent = getDocumentElement(popper);\n\n if (getComputedStyle(offsetParent).position !== 'static' && position === 'absolute') {\n heightProp = 'scrollHeight';\n widthProp = 'scrollWidth';\n }\n } // $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it\n\n\n offsetParent = offsetParent;\n\n if (placement === top || (placement === left || placement === right) && variation === end) {\n sideY = bottom;\n var offsetY = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.height : // $FlowFixMe[prop-missing]\n offsetParent[heightProp];\n y -= offsetY - popperRect.height;\n y *= gpuAcceleration ? 1 : -1;\n }\n\n if (placement === left || (placement === top || placement === bottom) && variation === end) {\n sideX = right;\n var offsetX = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.width : // $FlowFixMe[prop-missing]\n offsetParent[widthProp];\n x -= offsetX - popperRect.width;\n x *= gpuAcceleration ? 1 : -1;\n }\n }\n\n var commonStyles = Object.assign({\n position: position\n }, adaptive && unsetSides);\n\n var _ref4 = roundOffsets === true ? roundOffsetsByDPR({\n x: x,\n y: y\n }, getWindow(popper)) : {\n x: x,\n y: y\n };\n\n x = _ref4.x;\n y = _ref4.y;\n\n if (gpuAcceleration) {\n var _Object$assign;\n\n return Object.assign({}, commonStyles, (_Object$assign = {}, _Object$assign[sideY] = hasY ? '0' : '', _Object$assign[sideX] = hasX ? '0' : '', _Object$assign.transform = (win.devicePixelRatio || 1) <= 1 ? \"translate(\" + x + \"px, \" + y + \"px)\" : \"translate3d(\" + x + \"px, \" + y + \"px, 0)\", _Object$assign));\n }\n\n return Object.assign({}, commonStyles, (_Object$assign2 = {}, _Object$assign2[sideY] = hasY ? y + \"px\" : '', _Object$assign2[sideX] = hasX ? x + \"px\" : '', _Object$assign2.transform = '', _Object$assign2));\n}\n\nfunction computeStyles(_ref5) {\n var state = _ref5.state,\n options = _ref5.options;\n var _options$gpuAccelerat = options.gpuAcceleration,\n gpuAcceleration = _options$gpuAccelerat === void 0 ? true : _options$gpuAccelerat,\n _options$adaptive = options.adaptive,\n adaptive = _options$adaptive === void 0 ? true : _options$adaptive,\n _options$roundOffsets = options.roundOffsets,\n roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;\n var commonStyles = {\n placement: getBasePlacement(state.placement),\n variation: getVariation(state.placement),\n popper: state.elements.popper,\n popperRect: state.rects.popper,\n gpuAcceleration: gpuAcceleration,\n isFixed: state.options.strategy === 'fixed'\n };\n\n if (state.modifiersData.popperOffsets != null) {\n state.styles.popper = Object.assign({}, state.styles.popper, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.popperOffsets,\n position: state.options.strategy,\n adaptive: adaptive,\n roundOffsets: roundOffsets\n })));\n }\n\n if (state.modifiersData.arrow != null) {\n state.styles.arrow = Object.assign({}, state.styles.arrow, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.arrow,\n position: 'absolute',\n adaptive: false,\n roundOffsets: roundOffsets\n })));\n }\n\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-placement': state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'computeStyles',\n enabled: true,\n phase: 'beforeWrite',\n fn: computeStyles,\n data: {}\n};","import getWindow from \"../dom-utils/getWindow.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar passive = {\n passive: true\n};\n\nfunction effect(_ref) {\n var state = _ref.state,\n instance = _ref.instance,\n options = _ref.options;\n var _options$scroll = options.scroll,\n scroll = _options$scroll === void 0 ? true : _options$scroll,\n _options$resize = options.resize,\n resize = _options$resize === void 0 ? true : _options$resize;\n var window = getWindow(state.elements.popper);\n var scrollParents = [].concat(state.scrollParents.reference, state.scrollParents.popper);\n\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.addEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.addEventListener('resize', instance.update, passive);\n }\n\n return function () {\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.removeEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.removeEventListener('resize', instance.update, passive);\n }\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'eventListeners',\n enabled: true,\n phase: 'write',\n fn: function fn() {},\n effect: effect,\n data: {}\n};","var hash = {\n left: 'right',\n right: 'left',\n bottom: 'top',\n top: 'bottom'\n};\nexport default function getOppositePlacement(placement) {\n return placement.replace(/left|right|bottom|top/g, function (matched) {\n return hash[matched];\n });\n}","var hash = {\n start: 'end',\n end: 'start'\n};\nexport default function getOppositeVariationPlacement(placement) {\n return placement.replace(/start|end/g, function (matched) {\n return hash[matched];\n });\n}","import getWindow from \"./getWindow.js\";\nexport default function getWindowScroll(node) {\n var win = getWindow(node);\n var scrollLeft = win.pageXOffset;\n var scrollTop = win.pageYOffset;\n return {\n scrollLeft: scrollLeft,\n scrollTop: scrollTop\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nexport default function getWindowScrollBarX(element) {\n // If has a CSS width greater than the viewport, then this will be\n // incorrect for RTL.\n // Popper 1 is broken in this case and never had a bug report so let's assume\n // it's not an issue. I don't think anyone ever specifies width on \n // anyway.\n // Browsers where the left scrollbar doesn't cause an issue report `0` for\n // this (e.g. Edge 2019, IE11, Safari)\n return getBoundingClientRect(getDocumentElement(element)).left + getWindowScroll(element).scrollLeft;\n}","import getComputedStyle from \"./getComputedStyle.js\";\nexport default function isScrollParent(element) {\n // Firefox wants us to check `-x` and `-y` variations as well\n var _getComputedStyle = getComputedStyle(element),\n overflow = _getComputedStyle.overflow,\n overflowX = _getComputedStyle.overflowX,\n overflowY = _getComputedStyle.overflowY;\n\n return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX);\n}","import getParentNode from \"./getParentNode.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nexport default function getScrollParent(node) {\n if (['html', 'body', '#document'].indexOf(getNodeName(node)) >= 0) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return node.ownerDocument.body;\n }\n\n if (isHTMLElement(node) && isScrollParent(node)) {\n return node;\n }\n\n return getScrollParent(getParentNode(node));\n}","import getScrollParent from \"./getScrollParent.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getWindow from \"./getWindow.js\";\nimport isScrollParent from \"./isScrollParent.js\";\n/*\ngiven a DOM element, return the list of all scroll parents, up the list of ancesors\nuntil we get to the top window object. This list is what we attach scroll listeners\nto, because if any of these parent elements scroll, we'll need to re-calculate the\nreference element's position.\n*/\n\nexport default function listScrollParents(element, list) {\n var _element$ownerDocumen;\n\n if (list === void 0) {\n list = [];\n }\n\n var scrollParent = getScrollParent(element);\n var isBody = scrollParent === ((_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body);\n var win = getWindow(scrollParent);\n var target = isBody ? [win].concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : []) : scrollParent;\n var updatedList = list.concat(target);\n return isBody ? updatedList : // $FlowFixMe[incompatible-call]: isBody tells us target will be an HTMLElement here\n updatedList.concat(listScrollParents(getParentNode(target)));\n}","export default function rectToClientRect(rect) {\n return Object.assign({}, rect, {\n left: rect.x,\n top: rect.y,\n right: rect.x + rect.width,\n bottom: rect.y + rect.height\n });\n}","import { viewport } from \"../enums.js\";\nimport getViewportRect from \"./getViewportRect.js\";\nimport getDocumentRect from \"./getDocumentRect.js\";\nimport listScrollParents from \"./listScrollParents.js\";\nimport getOffsetParent from \"./getOffsetParent.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport contains from \"./contains.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport rectToClientRect from \"../utils/rectToClientRect.js\";\nimport { max, min } from \"../utils/math.js\";\n\nfunction getInnerBoundingClientRect(element, strategy) {\n var rect = getBoundingClientRect(element, false, strategy === 'fixed');\n rect.top = rect.top + element.clientTop;\n rect.left = rect.left + element.clientLeft;\n rect.bottom = rect.top + element.clientHeight;\n rect.right = rect.left + element.clientWidth;\n rect.width = element.clientWidth;\n rect.height = element.clientHeight;\n rect.x = rect.left;\n rect.y = rect.top;\n return rect;\n}\n\nfunction getClientRectFromMixedType(element, clippingParent, strategy) {\n return clippingParent === viewport ? rectToClientRect(getViewportRect(element, strategy)) : isElement(clippingParent) ? getInnerBoundingClientRect(clippingParent, strategy) : rectToClientRect(getDocumentRect(getDocumentElement(element)));\n} // A \"clipping parent\" is an overflowable container with the characteristic of\n// clipping (or hiding) overflowing elements with a position different from\n// `initial`\n\n\nfunction getClippingParents(element) {\n var clippingParents = listScrollParents(getParentNode(element));\n var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle(element).position) >= 0;\n var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element;\n\n if (!isElement(clipperElement)) {\n return [];\n } // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414\n\n\n return clippingParents.filter(function (clippingParent) {\n return isElement(clippingParent) && contains(clippingParent, clipperElement) && getNodeName(clippingParent) !== 'body';\n });\n} // Gets the maximum area that the element is visible in due to any number of\n// clipping parents\n\n\nexport default function getClippingRect(element, boundary, rootBoundary, strategy) {\n var mainClippingParents = boundary === 'clippingParents' ? getClippingParents(element) : [].concat(boundary);\n var clippingParents = [].concat(mainClippingParents, [rootBoundary]);\n var firstClippingParent = clippingParents[0];\n var clippingRect = clippingParents.reduce(function (accRect, clippingParent) {\n var rect = getClientRectFromMixedType(element, clippingParent, strategy);\n accRect.top = max(rect.top, accRect.top);\n accRect.right = min(rect.right, accRect.right);\n accRect.bottom = min(rect.bottom, accRect.bottom);\n accRect.left = max(rect.left, accRect.left);\n return accRect;\n }, getClientRectFromMixedType(element, firstClippingParent, strategy));\n clippingRect.width = clippingRect.right - clippingRect.left;\n clippingRect.height = clippingRect.bottom - clippingRect.top;\n clippingRect.x = clippingRect.left;\n clippingRect.y = clippingRect.top;\n return clippingRect;\n}","import getWindow from \"./getWindow.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport isLayoutViewport from \"./isLayoutViewport.js\";\nexport default function getViewportRect(element, strategy) {\n var win = getWindow(element);\n var html = getDocumentElement(element);\n var visualViewport = win.visualViewport;\n var width = html.clientWidth;\n var height = html.clientHeight;\n var x = 0;\n var y = 0;\n\n if (visualViewport) {\n width = visualViewport.width;\n height = visualViewport.height;\n var layoutViewport = isLayoutViewport();\n\n if (layoutViewport || !layoutViewport && strategy === 'fixed') {\n x = visualViewport.offsetLeft;\n y = visualViewport.offsetTop;\n }\n }\n\n return {\n width: width,\n height: height,\n x: x + getWindowScrollBarX(element),\n y: y\n };\n}","import getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nimport { max } from \"../utils/math.js\"; // Gets the entire size of the scrollable document area, even extending outside\n// of the `` and `` rect bounds if horizontally scrollable\n\nexport default function getDocumentRect(element) {\n var _element$ownerDocumen;\n\n var html = getDocumentElement(element);\n var winScroll = getWindowScroll(element);\n var body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body;\n var width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0);\n var height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0);\n var x = -winScroll.scrollLeft + getWindowScrollBarX(element);\n var y = -winScroll.scrollTop;\n\n if (getComputedStyle(body || html).direction === 'rtl') {\n x += max(html.clientWidth, body ? body.clientWidth : 0) - width;\n }\n\n return {\n width: width,\n height: height,\n x: x,\n y: y\n };\n}","import getBasePlacement from \"./getBasePlacement.js\";\nimport getVariation from \"./getVariation.js\";\nimport getMainAxisFromPlacement from \"./getMainAxisFromPlacement.js\";\nimport { top, right, bottom, left, start, end } from \"../enums.js\";\nexport default function computeOffsets(_ref) {\n var reference = _ref.reference,\n element = _ref.element,\n placement = _ref.placement;\n var basePlacement = placement ? getBasePlacement(placement) : null;\n var variation = placement ? getVariation(placement) : null;\n var commonX = reference.x + reference.width / 2 - element.width / 2;\n var commonY = reference.y + reference.height / 2 - element.height / 2;\n var offsets;\n\n switch (basePlacement) {\n case top:\n offsets = {\n x: commonX,\n y: reference.y - element.height\n };\n break;\n\n case bottom:\n offsets = {\n x: commonX,\n y: reference.y + reference.height\n };\n break;\n\n case right:\n offsets = {\n x: reference.x + reference.width,\n y: commonY\n };\n break;\n\n case left:\n offsets = {\n x: reference.x - element.width,\n y: commonY\n };\n break;\n\n default:\n offsets = {\n x: reference.x,\n y: reference.y\n };\n }\n\n var mainAxis = basePlacement ? getMainAxisFromPlacement(basePlacement) : null;\n\n if (mainAxis != null) {\n var len = mainAxis === 'y' ? 'height' : 'width';\n\n switch (variation) {\n case start:\n offsets[mainAxis] = offsets[mainAxis] - (reference[len] / 2 - element[len] / 2);\n break;\n\n case end:\n offsets[mainAxis] = offsets[mainAxis] + (reference[len] / 2 - element[len] / 2);\n break;\n\n default:\n }\n }\n\n return offsets;\n}","import getClippingRect from \"../dom-utils/getClippingRect.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getBoundingClientRect from \"../dom-utils/getBoundingClientRect.js\";\nimport computeOffsets from \"./computeOffsets.js\";\nimport rectToClientRect from \"./rectToClientRect.js\";\nimport { clippingParents, reference, popper, bottom, top, right, basePlacements, viewport } from \"../enums.js\";\nimport { isElement } from \"../dom-utils/instanceOf.js\";\nimport mergePaddingObject from \"./mergePaddingObject.js\";\nimport expandToHashMap from \"./expandToHashMap.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport default function detectOverflow(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n _options$placement = _options.placement,\n placement = _options$placement === void 0 ? state.placement : _options$placement,\n _options$strategy = _options.strategy,\n strategy = _options$strategy === void 0 ? state.strategy : _options$strategy,\n _options$boundary = _options.boundary,\n boundary = _options$boundary === void 0 ? clippingParents : _options$boundary,\n _options$rootBoundary = _options.rootBoundary,\n rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary,\n _options$elementConte = _options.elementContext,\n elementContext = _options$elementConte === void 0 ? popper : _options$elementConte,\n _options$altBoundary = _options.altBoundary,\n altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary,\n _options$padding = _options.padding,\n padding = _options$padding === void 0 ? 0 : _options$padding;\n var paddingObject = mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n var altContext = elementContext === popper ? reference : popper;\n var popperRect = state.rects.popper;\n var element = state.elements[altBoundary ? altContext : elementContext];\n var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary, strategy);\n var referenceClientRect = getBoundingClientRect(state.elements.reference);\n var popperOffsets = computeOffsets({\n reference: referenceClientRect,\n element: popperRect,\n strategy: 'absolute',\n placement: placement\n });\n var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets));\n var elementClientRect = elementContext === popper ? popperClientRect : referenceClientRect; // positive = overflowing the clipping rect\n // 0 or negative = within the clipping rect\n\n var overflowOffsets = {\n top: clippingClientRect.top - elementClientRect.top + paddingObject.top,\n bottom: elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom,\n left: clippingClientRect.left - elementClientRect.left + paddingObject.left,\n right: elementClientRect.right - clippingClientRect.right + paddingObject.right\n };\n var offsetData = state.modifiersData.offset; // Offsets can be applied only to the popper element\n\n if (elementContext === popper && offsetData) {\n var offset = offsetData[placement];\n Object.keys(overflowOffsets).forEach(function (key) {\n var multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1;\n var axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x';\n overflowOffsets[key] += offset[axis] * multiply;\n });\n }\n\n return overflowOffsets;\n}","import getOppositePlacement from \"../utils/getOppositePlacement.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getOppositeVariationPlacement from \"../utils/getOppositeVariationPlacement.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport computeAutoPlacement from \"../utils/computeAutoPlacement.js\";\nimport { bottom, top, start, right, left, auto } from \"../enums.js\";\nimport getVariation from \"../utils/getVariation.js\"; // eslint-disable-next-line import/no-unused-modules\n\nfunction getExpandedFallbackPlacements(placement) {\n if (getBasePlacement(placement) === auto) {\n return [];\n }\n\n var oppositePlacement = getOppositePlacement(placement);\n return [getOppositeVariationPlacement(placement), oppositePlacement, getOppositeVariationPlacement(oppositePlacement)];\n}\n\nfunction flip(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n\n if (state.modifiersData[name]._skip) {\n return;\n }\n\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? true : _options$altAxis,\n specifiedFallbackPlacements = options.fallbackPlacements,\n padding = options.padding,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n _options$flipVariatio = options.flipVariations,\n flipVariations = _options$flipVariatio === void 0 ? true : _options$flipVariatio,\n allowedAutoPlacements = options.allowedAutoPlacements;\n var preferredPlacement = state.options.placement;\n var basePlacement = getBasePlacement(preferredPlacement);\n var isBasePlacement = basePlacement === preferredPlacement;\n var fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipVariations ? [getOppositePlacement(preferredPlacement)] : getExpandedFallbackPlacements(preferredPlacement));\n var placements = [preferredPlacement].concat(fallbackPlacements).reduce(function (acc, placement) {\n return acc.concat(getBasePlacement(placement) === auto ? computeAutoPlacement(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n flipVariations: flipVariations,\n allowedAutoPlacements: allowedAutoPlacements\n }) : placement);\n }, []);\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var checksMap = new Map();\n var makeFallbackChecks = true;\n var firstFittingPlacement = placements[0];\n\n for (var i = 0; i < placements.length; i++) {\n var placement = placements[i];\n\n var _basePlacement = getBasePlacement(placement);\n\n var isStartVariation = getVariation(placement) === start;\n var isVertical = [top, bottom].indexOf(_basePlacement) >= 0;\n var len = isVertical ? 'width' : 'height';\n var overflow = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n altBoundary: altBoundary,\n padding: padding\n });\n var mainVariationSide = isVertical ? isStartVariation ? right : left : isStartVariation ? bottom : top;\n\n if (referenceRect[len] > popperRect[len]) {\n mainVariationSide = getOppositePlacement(mainVariationSide);\n }\n\n var altVariationSide = getOppositePlacement(mainVariationSide);\n var checks = [];\n\n if (checkMainAxis) {\n checks.push(overflow[_basePlacement] <= 0);\n }\n\n if (checkAltAxis) {\n checks.push(overflow[mainVariationSide] <= 0, overflow[altVariationSide] <= 0);\n }\n\n if (checks.every(function (check) {\n return check;\n })) {\n firstFittingPlacement = placement;\n makeFallbackChecks = false;\n break;\n }\n\n checksMap.set(placement, checks);\n }\n\n if (makeFallbackChecks) {\n // `2` may be desired in some cases – research later\n var numberOfChecks = flipVariations ? 3 : 1;\n\n var _loop = function _loop(_i) {\n var fittingPlacement = placements.find(function (placement) {\n var checks = checksMap.get(placement);\n\n if (checks) {\n return checks.slice(0, _i).every(function (check) {\n return check;\n });\n }\n });\n\n if (fittingPlacement) {\n firstFittingPlacement = fittingPlacement;\n return \"break\";\n }\n };\n\n for (var _i = numberOfChecks; _i > 0; _i--) {\n var _ret = _loop(_i);\n\n if (_ret === \"break\") break;\n }\n }\n\n if (state.placement !== firstFittingPlacement) {\n state.modifiersData[name]._skip = true;\n state.placement = firstFittingPlacement;\n state.reset = true;\n }\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'flip',\n enabled: true,\n phase: 'main',\n fn: flip,\n requiresIfExists: ['offset'],\n data: {\n _skip: false\n }\n};","import getVariation from \"./getVariation.js\";\nimport { variationPlacements, basePlacements, placements as allPlacements } from \"../enums.js\";\nimport detectOverflow from \"./detectOverflow.js\";\nimport getBasePlacement from \"./getBasePlacement.js\";\nexport default function computeAutoPlacement(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n placement = _options.placement,\n boundary = _options.boundary,\n rootBoundary = _options.rootBoundary,\n padding = _options.padding,\n flipVariations = _options.flipVariations,\n _options$allowedAutoP = _options.allowedAutoPlacements,\n allowedAutoPlacements = _options$allowedAutoP === void 0 ? allPlacements : _options$allowedAutoP;\n var variation = getVariation(placement);\n var placements = variation ? flipVariations ? variationPlacements : variationPlacements.filter(function (placement) {\n return getVariation(placement) === variation;\n }) : basePlacements;\n var allowedPlacements = placements.filter(function (placement) {\n return allowedAutoPlacements.indexOf(placement) >= 0;\n });\n\n if (allowedPlacements.length === 0) {\n allowedPlacements = placements;\n } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...\n\n\n var overflows = allowedPlacements.reduce(function (acc, placement) {\n acc[placement] = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding\n })[getBasePlacement(placement)];\n return acc;\n }, {});\n return Object.keys(overflows).sort(function (a, b) {\n return overflows[a] - overflows[b];\n });\n}","import { top, bottom, left, right } from \"../enums.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\n\nfunction getSideOffsets(overflow, rect, preventedOffsets) {\n if (preventedOffsets === void 0) {\n preventedOffsets = {\n x: 0,\n y: 0\n };\n }\n\n return {\n top: overflow.top - rect.height - preventedOffsets.y,\n right: overflow.right - rect.width + preventedOffsets.x,\n bottom: overflow.bottom - rect.height + preventedOffsets.y,\n left: overflow.left - rect.width - preventedOffsets.x\n };\n}\n\nfunction isAnySideFullyClipped(overflow) {\n return [top, right, bottom, left].some(function (side) {\n return overflow[side] >= 0;\n });\n}\n\nfunction hide(_ref) {\n var state = _ref.state,\n name = _ref.name;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var preventedOffsets = state.modifiersData.preventOverflow;\n var referenceOverflow = detectOverflow(state, {\n elementContext: 'reference'\n });\n var popperAltOverflow = detectOverflow(state, {\n altBoundary: true\n });\n var referenceClippingOffsets = getSideOffsets(referenceOverflow, referenceRect);\n var popperEscapeOffsets = getSideOffsets(popperAltOverflow, popperRect, preventedOffsets);\n var isReferenceHidden = isAnySideFullyClipped(referenceClippingOffsets);\n var hasPopperEscaped = isAnySideFullyClipped(popperEscapeOffsets);\n state.modifiersData[name] = {\n referenceClippingOffsets: referenceClippingOffsets,\n popperEscapeOffsets: popperEscapeOffsets,\n isReferenceHidden: isReferenceHidden,\n hasPopperEscaped: hasPopperEscaped\n };\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-reference-hidden': isReferenceHidden,\n 'data-popper-escaped': hasPopperEscaped\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'hide',\n enabled: true,\n phase: 'main',\n requiresIfExists: ['preventOverflow'],\n fn: hide\n};","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport { top, left, right, placements } from \"../enums.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport function distanceAndSkiddingToXY(placement, rects, offset) {\n var basePlacement = getBasePlacement(placement);\n var invertDistance = [left, top].indexOf(basePlacement) >= 0 ? -1 : 1;\n\n var _ref = typeof offset === 'function' ? offset(Object.assign({}, rects, {\n placement: placement\n })) : offset,\n skidding = _ref[0],\n distance = _ref[1];\n\n skidding = skidding || 0;\n distance = (distance || 0) * invertDistance;\n return [left, right].indexOf(basePlacement) >= 0 ? {\n x: distance,\n y: skidding\n } : {\n x: skidding,\n y: distance\n };\n}\n\nfunction offset(_ref2) {\n var state = _ref2.state,\n options = _ref2.options,\n name = _ref2.name;\n var _options$offset = options.offset,\n offset = _options$offset === void 0 ? [0, 0] : _options$offset;\n var data = placements.reduce(function (acc, placement) {\n acc[placement] = distanceAndSkiddingToXY(placement, state.rects, offset);\n return acc;\n }, {});\n var _data$state$placement = data[state.placement],\n x = _data$state$placement.x,\n y = _data$state$placement.y;\n\n if (state.modifiersData.popperOffsets != null) {\n state.modifiersData.popperOffsets.x += x;\n state.modifiersData.popperOffsets.y += y;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'offset',\n enabled: true,\n phase: 'main',\n requires: ['popperOffsets'],\n fn: offset\n};","import computeOffsets from \"../utils/computeOffsets.js\";\n\nfunction popperOffsets(_ref) {\n var state = _ref.state,\n name = _ref.name;\n // Offsets are the actual position the popper needs to have to be\n // properly positioned near its reference element\n // This is the most basic placement, and will be adjusted by\n // the modifiers in the next step\n state.modifiersData[name] = computeOffsets({\n reference: state.rects.reference,\n element: state.rects.popper,\n strategy: 'absolute',\n placement: state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'popperOffsets',\n enabled: true,\n phase: 'read',\n fn: popperOffsets,\n data: {}\n};","import { top, left, right, bottom, start } from \"../enums.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport getAltAxis from \"../utils/getAltAxis.js\";\nimport { within, withinMaxClamp } from \"../utils/within.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport getFreshSideObject from \"../utils/getFreshSideObject.js\";\nimport { min as mathMin, max as mathMax } from \"../utils/math.js\";\n\nfunction preventOverflow(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? false : _options$altAxis,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n padding = options.padding,\n _options$tether = options.tether,\n tether = _options$tether === void 0 ? true : _options$tether,\n _options$tetherOffset = options.tetherOffset,\n tetherOffset = _options$tetherOffset === void 0 ? 0 : _options$tetherOffset;\n var overflow = detectOverflow(state, {\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n altBoundary: altBoundary\n });\n var basePlacement = getBasePlacement(state.placement);\n var variation = getVariation(state.placement);\n var isBasePlacement = !variation;\n var mainAxis = getMainAxisFromPlacement(basePlacement);\n var altAxis = getAltAxis(mainAxis);\n var popperOffsets = state.modifiersData.popperOffsets;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var tetherOffsetValue = typeof tetherOffset === 'function' ? tetherOffset(Object.assign({}, state.rects, {\n placement: state.placement\n })) : tetherOffset;\n var normalizedTetherOffsetValue = typeof tetherOffsetValue === 'number' ? {\n mainAxis: tetherOffsetValue,\n altAxis: tetherOffsetValue\n } : Object.assign({\n mainAxis: 0,\n altAxis: 0\n }, tetherOffsetValue);\n var offsetModifierState = state.modifiersData.offset ? state.modifiersData.offset[state.placement] : null;\n var data = {\n x: 0,\n y: 0\n };\n\n if (!popperOffsets) {\n return;\n }\n\n if (checkMainAxis) {\n var _offsetModifierState$;\n\n var mainSide = mainAxis === 'y' ? top : left;\n var altSide = mainAxis === 'y' ? bottom : right;\n var len = mainAxis === 'y' ? 'height' : 'width';\n var offset = popperOffsets[mainAxis];\n var min = offset + overflow[mainSide];\n var max = offset - overflow[altSide];\n var additive = tether ? -popperRect[len] / 2 : 0;\n var minLen = variation === start ? referenceRect[len] : popperRect[len];\n var maxLen = variation === start ? -popperRect[len] : -referenceRect[len]; // We need to include the arrow in the calculation so the arrow doesn't go\n // outside the reference bounds\n\n var arrowElement = state.elements.arrow;\n var arrowRect = tether && arrowElement ? getLayoutRect(arrowElement) : {\n width: 0,\n height: 0\n };\n var arrowPaddingObject = state.modifiersData['arrow#persistent'] ? state.modifiersData['arrow#persistent'].padding : getFreshSideObject();\n var arrowPaddingMin = arrowPaddingObject[mainSide];\n var arrowPaddingMax = arrowPaddingObject[altSide]; // If the reference length is smaller than the arrow length, we don't want\n // to include its full size in the calculation. If the reference is small\n // and near the edge of a boundary, the popper can overflow even if the\n // reference is not overflowing as well (e.g. virtual elements with no\n // width or height)\n\n var arrowLen = within(0, referenceRect[len], arrowRect[len]);\n var minOffset = isBasePlacement ? referenceRect[len] / 2 - additive - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis : minLen - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis;\n var maxOffset = isBasePlacement ? -referenceRect[len] / 2 + additive + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis : maxLen + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis;\n var arrowOffsetParent = state.elements.arrow && getOffsetParent(state.elements.arrow);\n var clientOffset = arrowOffsetParent ? mainAxis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0;\n var offsetModifierValue = (_offsetModifierState$ = offsetModifierState == null ? void 0 : offsetModifierState[mainAxis]) != null ? _offsetModifierState$ : 0;\n var tetherMin = offset + minOffset - offsetModifierValue - clientOffset;\n var tetherMax = offset + maxOffset - offsetModifierValue;\n var preventedOffset = within(tether ? mathMin(min, tetherMin) : min, offset, tether ? mathMax(max, tetherMax) : max);\n popperOffsets[mainAxis] = preventedOffset;\n data[mainAxis] = preventedOffset - offset;\n }\n\n if (checkAltAxis) {\n var _offsetModifierState$2;\n\n var _mainSide = mainAxis === 'x' ? top : left;\n\n var _altSide = mainAxis === 'x' ? bottom : right;\n\n var _offset = popperOffsets[altAxis];\n\n var _len = altAxis === 'y' ? 'height' : 'width';\n\n var _min = _offset + overflow[_mainSide];\n\n var _max = _offset - overflow[_altSide];\n\n var isOriginSide = [top, left].indexOf(basePlacement) !== -1;\n\n var _offsetModifierValue = (_offsetModifierState$2 = offsetModifierState == null ? void 0 : offsetModifierState[altAxis]) != null ? _offsetModifierState$2 : 0;\n\n var _tetherMin = isOriginSide ? _min : _offset - referenceRect[_len] - popperRect[_len] - _offsetModifierValue + normalizedTetherOffsetValue.altAxis;\n\n var _tetherMax = isOriginSide ? _offset + referenceRect[_len] + popperRect[_len] - _offsetModifierValue - normalizedTetherOffsetValue.altAxis : _max;\n\n var _preventedOffset = tether && isOriginSide ? withinMaxClamp(_tetherMin, _offset, _tetherMax) : within(tether ? _tetherMin : _min, _offset, tether ? _tetherMax : _max);\n\n popperOffsets[altAxis] = _preventedOffset;\n data[altAxis] = _preventedOffset - _offset;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'preventOverflow',\n enabled: true,\n phase: 'main',\n fn: preventOverflow,\n requiresIfExists: ['offset']\n};","export default function getAltAxis(axis) {\n return axis === 'x' ? 'y' : 'x';\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getNodeScroll from \"./getNodeScroll.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport { round } from \"../utils/math.js\";\n\nfunction isElementScaled(element) {\n var rect = element.getBoundingClientRect();\n var scaleX = round(rect.width) / element.offsetWidth || 1;\n var scaleY = round(rect.height) / element.offsetHeight || 1;\n return scaleX !== 1 || scaleY !== 1;\n} // Returns the composite rect of an element relative to its offsetParent.\n// Composite means it takes into account transforms as well as layout.\n\n\nexport default function getCompositeRect(elementOrVirtualElement, offsetParent, isFixed) {\n if (isFixed === void 0) {\n isFixed = false;\n }\n\n var isOffsetParentAnElement = isHTMLElement(offsetParent);\n var offsetParentIsScaled = isHTMLElement(offsetParent) && isElementScaled(offsetParent);\n var documentElement = getDocumentElement(offsetParent);\n var rect = getBoundingClientRect(elementOrVirtualElement, offsetParentIsScaled, isFixed);\n var scroll = {\n scrollLeft: 0,\n scrollTop: 0\n };\n var offsets = {\n x: 0,\n y: 0\n };\n\n if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {\n if (getNodeName(offsetParent) !== 'body' || // https://github.com/popperjs/popper-core/issues/1078\n isScrollParent(documentElement)) {\n scroll = getNodeScroll(offsetParent);\n }\n\n if (isHTMLElement(offsetParent)) {\n offsets = getBoundingClientRect(offsetParent, true);\n offsets.x += offsetParent.clientLeft;\n offsets.y += offsetParent.clientTop;\n } else if (documentElement) {\n offsets.x = getWindowScrollBarX(documentElement);\n }\n }\n\n return {\n x: rect.left + scroll.scrollLeft - offsets.x,\n y: rect.top + scroll.scrollTop - offsets.y,\n width: rect.width,\n height: rect.height\n };\n}","import getWindowScroll from \"./getWindowScroll.js\";\nimport getWindow from \"./getWindow.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getHTMLElementScroll from \"./getHTMLElementScroll.js\";\nexport default function getNodeScroll(node) {\n if (node === getWindow(node) || !isHTMLElement(node)) {\n return getWindowScroll(node);\n } else {\n return getHTMLElementScroll(node);\n }\n}","export default function getHTMLElementScroll(element) {\n return {\n scrollLeft: element.scrollLeft,\n scrollTop: element.scrollTop\n };\n}","import { modifierPhases } from \"../enums.js\"; // source: https://stackoverflow.com/questions/49875255\n\nfunction order(modifiers) {\n var map = new Map();\n var visited = new Set();\n var result = [];\n modifiers.forEach(function (modifier) {\n map.set(modifier.name, modifier);\n }); // On visiting object, check for its dependencies and visit them recursively\n\n function sort(modifier) {\n visited.add(modifier.name);\n var requires = [].concat(modifier.requires || [], modifier.requiresIfExists || []);\n requires.forEach(function (dep) {\n if (!visited.has(dep)) {\n var depModifier = map.get(dep);\n\n if (depModifier) {\n sort(depModifier);\n }\n }\n });\n result.push(modifier);\n }\n\n modifiers.forEach(function (modifier) {\n if (!visited.has(modifier.name)) {\n // check for visited object\n sort(modifier);\n }\n });\n return result;\n}\n\nexport default function orderModifiers(modifiers) {\n // order based on dependencies\n var orderedModifiers = order(modifiers); // order based on phase\n\n return modifierPhases.reduce(function (acc, phase) {\n return acc.concat(orderedModifiers.filter(function (modifier) {\n return modifier.phase === phase;\n }));\n }, []);\n}","import getCompositeRect from \"./dom-utils/getCompositeRect.js\";\nimport getLayoutRect from \"./dom-utils/getLayoutRect.js\";\nimport listScrollParents from \"./dom-utils/listScrollParents.js\";\nimport getOffsetParent from \"./dom-utils/getOffsetParent.js\";\nimport orderModifiers from \"./utils/orderModifiers.js\";\nimport debounce from \"./utils/debounce.js\";\nimport mergeByName from \"./utils/mergeByName.js\";\nimport detectOverflow from \"./utils/detectOverflow.js\";\nimport { isElement } from \"./dom-utils/instanceOf.js\";\nvar DEFAULT_OPTIONS = {\n placement: 'bottom',\n modifiers: [],\n strategy: 'absolute'\n};\n\nfunction areValidElements() {\n for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n args[_key] = arguments[_key];\n }\n\n return !args.some(function (element) {\n return !(element && typeof element.getBoundingClientRect === 'function');\n });\n}\n\nexport function popperGenerator(generatorOptions) {\n if (generatorOptions === void 0) {\n generatorOptions = {};\n }\n\n var _generatorOptions = generatorOptions,\n _generatorOptions$def = _generatorOptions.defaultModifiers,\n defaultModifiers = _generatorOptions$def === void 0 ? [] : _generatorOptions$def,\n _generatorOptions$def2 = _generatorOptions.defaultOptions,\n defaultOptions = _generatorOptions$def2 === void 0 ? DEFAULT_OPTIONS : _generatorOptions$def2;\n return function createPopper(reference, popper, options) {\n if (options === void 0) {\n options = defaultOptions;\n }\n\n var state = {\n placement: 'bottom',\n orderedModifiers: [],\n options: Object.assign({}, DEFAULT_OPTIONS, defaultOptions),\n modifiersData: {},\n elements: {\n reference: reference,\n popper: popper\n },\n attributes: {},\n styles: {}\n };\n var effectCleanupFns = [];\n var isDestroyed = false;\n var instance = {\n state: state,\n setOptions: function setOptions(setOptionsAction) {\n var options = typeof setOptionsAction === 'function' ? setOptionsAction(state.options) : setOptionsAction;\n cleanupModifierEffects();\n state.options = Object.assign({}, defaultOptions, state.options, options);\n state.scrollParents = {\n reference: isElement(reference) ? listScrollParents(reference) : reference.contextElement ? listScrollParents(reference.contextElement) : [],\n popper: listScrollParents(popper)\n }; // Orders the modifiers based on their dependencies and `phase`\n // properties\n\n var orderedModifiers = orderModifiers(mergeByName([].concat(defaultModifiers, state.options.modifiers))); // Strip out disabled modifiers\n\n state.orderedModifiers = orderedModifiers.filter(function (m) {\n return m.enabled;\n });\n runModifierEffects();\n return instance.update();\n },\n // Sync update – it will always be executed, even if not necessary. This\n // is useful for low frequency updates where sync behavior simplifies the\n // logic.\n // For high frequency updates (e.g. `resize` and `scroll` events), always\n // prefer the async Popper#update method\n forceUpdate: function forceUpdate() {\n if (isDestroyed) {\n return;\n }\n\n var _state$elements = state.elements,\n reference = _state$elements.reference,\n popper = _state$elements.popper; // Don't proceed if `reference` or `popper` are not valid elements\n // anymore\n\n if (!areValidElements(reference, popper)) {\n return;\n } // Store the reference and popper rects to be read by modifiers\n\n\n state.rects = {\n reference: getCompositeRect(reference, getOffsetParent(popper), state.options.strategy === 'fixed'),\n popper: getLayoutRect(popper)\n }; // Modifiers have the ability to reset the current update cycle. The\n // most common use case for this is the `flip` modifier changing the\n // placement, which then needs to re-run all the modifiers, because the\n // logic was previously ran for the previous placement and is therefore\n // stale/incorrect\n\n state.reset = false;\n state.placement = state.options.placement; // On each update cycle, the `modifiersData` property for each modifier\n // is filled with the initial data specified by the modifier. This means\n // it doesn't persist and is fresh on each update.\n // To ensure persistent data, use `${name}#persistent`\n\n state.orderedModifiers.forEach(function (modifier) {\n return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);\n });\n\n for (var index = 0; index < state.orderedModifiers.length; index++) {\n if (state.reset === true) {\n state.reset = false;\n index = -1;\n continue;\n }\n\n var _state$orderedModifie = state.orderedModifiers[index],\n fn = _state$orderedModifie.fn,\n _state$orderedModifie2 = _state$orderedModifie.options,\n _options = _state$orderedModifie2 === void 0 ? {} : _state$orderedModifie2,\n name = _state$orderedModifie.name;\n\n if (typeof fn === 'function') {\n state = fn({\n state: state,\n options: _options,\n name: name,\n instance: instance\n }) || state;\n }\n }\n },\n // Async and optimistically optimized update – it will not be executed if\n // not necessary (debounced to run at most once-per-tick)\n update: debounce(function () {\n return new Promise(function (resolve) {\n instance.forceUpdate();\n resolve(state);\n });\n }),\n destroy: function destroy() {\n cleanupModifierEffects();\n isDestroyed = true;\n }\n };\n\n if (!areValidElements(reference, popper)) {\n return instance;\n }\n\n instance.setOptions(options).then(function (state) {\n if (!isDestroyed && options.onFirstUpdate) {\n options.onFirstUpdate(state);\n }\n }); // Modifiers have the ability to execute arbitrary code before the first\n // update cycle runs. They will be executed in the same order as the update\n // cycle. This is useful when a modifier adds some persistent data that\n // other modifiers need to use, but the modifier is run after the dependent\n // one.\n\n function runModifierEffects() {\n state.orderedModifiers.forEach(function (_ref) {\n var name = _ref.name,\n _ref$options = _ref.options,\n options = _ref$options === void 0 ? {} : _ref$options,\n effect = _ref.effect;\n\n if (typeof effect === 'function') {\n var cleanupFn = effect({\n state: state,\n name: name,\n instance: instance,\n options: options\n });\n\n var noopFn = function noopFn() {};\n\n effectCleanupFns.push(cleanupFn || noopFn);\n }\n });\n }\n\n function cleanupModifierEffects() {\n effectCleanupFns.forEach(function (fn) {\n return fn();\n });\n effectCleanupFns = [];\n }\n\n return instance;\n };\n}\nexport var createPopper = /*#__PURE__*/popperGenerator(); // eslint-disable-next-line import/no-unused-modules\n\nexport { detectOverflow };","export default function debounce(fn) {\n var pending;\n return function () {\n if (!pending) {\n pending = new Promise(function (resolve) {\n Promise.resolve().then(function () {\n pending = undefined;\n resolve(fn());\n });\n });\n }\n\n return pending;\n };\n}","export default function mergeByName(modifiers) {\n var merged = modifiers.reduce(function (merged, current) {\n var existing = merged[current.name];\n merged[current.name] = existing ? Object.assign({}, existing, current, {\n options: Object.assign({}, existing.options, current.options),\n data: Object.assign({}, existing.data, current.data)\n }) : current;\n return merged;\n }, {}); // IE11 does not support Object.values\n\n return Object.keys(merged).map(function (key) {\n return merged[key];\n });\n}","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nimport offset from \"./modifiers/offset.js\";\nimport flip from \"./modifiers/flip.js\";\nimport preventOverflow from \"./modifiers/preventOverflow.js\";\nimport arrow from \"./modifiers/arrow.js\";\nimport hide from \"./modifiers/hide.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles, offset, flip, preventOverflow, arrow, hide];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow }; // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper as createPopperLite } from \"./popper-lite.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport * from \"./modifiers/index.js\";","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow };","/*!\n * Bootstrap v5.3.3 (https://getbootstrap.com/)\n * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\nimport * as Popper from '@popperjs/core';\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * Constants\n */\n\nconst elementMap = new Map();\nconst Data = {\n set(element, key, instance) {\n if (!elementMap.has(element)) {\n elementMap.set(element, new Map());\n }\n const instanceMap = elementMap.get(element);\n\n // make it clear we only want one instance per element\n // can be removed later when multiple key/instances are fine to be used\n if (!instanceMap.has(key) && instanceMap.size !== 0) {\n // eslint-disable-next-line no-console\n console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`);\n return;\n }\n instanceMap.set(key, instance);\n },\n get(element, key) {\n if (elementMap.has(element)) {\n return elementMap.get(element).get(key) || null;\n }\n return null;\n },\n remove(element, key) {\n if (!elementMap.has(element)) {\n return;\n }\n const instanceMap = elementMap.get(element);\n instanceMap.delete(key);\n\n // free up element references if there are no instances left for an element\n if (instanceMap.size === 0) {\n elementMap.delete(element);\n }\n }\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/index.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst MAX_UID = 1000000;\nconst MILLISECONDS_MULTIPLIER = 1000;\nconst TRANSITION_END = 'transitionend';\n\n/**\n * Properly escape IDs selectors to handle weird IDs\n * @param {string} selector\n * @returns {string}\n */\nconst parseSelector = selector => {\n if (selector && window.CSS && window.CSS.escape) {\n // document.querySelector needs escaping to handle IDs (html5+) containing for instance /\n selector = selector.replace(/#([^\\s\"#']+)/g, (match, id) => `#${CSS.escape(id)}`);\n }\n return selector;\n};\n\n// Shout-out Angus Croll (https://goo.gl/pxwQGp)\nconst toType = object => {\n if (object === null || object === undefined) {\n return `${object}`;\n }\n return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase();\n};\n\n/**\n * Public Util API\n */\n\nconst getUID = prefix => {\n do {\n prefix += Math.floor(Math.random() * MAX_UID);\n } while (document.getElementById(prefix));\n return prefix;\n};\nconst getTransitionDurationFromElement = element => {\n if (!element) {\n return 0;\n }\n\n // Get transition-duration of the element\n let {\n transitionDuration,\n transitionDelay\n } = window.getComputedStyle(element);\n const floatTransitionDuration = Number.parseFloat(transitionDuration);\n const floatTransitionDelay = Number.parseFloat(transitionDelay);\n\n // Return 0 if element or transition duration is not found\n if (!floatTransitionDuration && !floatTransitionDelay) {\n return 0;\n }\n\n // If multiple durations are defined, take the first\n transitionDuration = transitionDuration.split(',')[0];\n transitionDelay = transitionDelay.split(',')[0];\n return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER;\n};\nconst triggerTransitionEnd = element => {\n element.dispatchEvent(new Event(TRANSITION_END));\n};\nconst isElement = object => {\n if (!object || typeof object !== 'object') {\n return false;\n }\n if (typeof object.jquery !== 'undefined') {\n object = object[0];\n }\n return typeof object.nodeType !== 'undefined';\n};\nconst getElement = object => {\n // it's a jQuery object or a node element\n if (isElement(object)) {\n return object.jquery ? object[0] : object;\n }\n if (typeof object === 'string' && object.length > 0) {\n return document.querySelector(parseSelector(object));\n }\n return null;\n};\nconst isVisible = element => {\n if (!isElement(element) || element.getClientRects().length === 0) {\n return false;\n }\n const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible';\n // Handle `details` element as its content may falsie appear visible when it is closed\n const closedDetails = element.closest('details:not([open])');\n if (!closedDetails) {\n return elementIsVisible;\n }\n if (closedDetails !== element) {\n const summary = element.closest('summary');\n if (summary && summary.parentNode !== closedDetails) {\n return false;\n }\n if (summary === null) {\n return false;\n }\n }\n return elementIsVisible;\n};\nconst isDisabled = element => {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n return true;\n }\n if (element.classList.contains('disabled')) {\n return true;\n }\n if (typeof element.disabled !== 'undefined') {\n return element.disabled;\n }\n return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false';\n};\nconst findShadowRoot = element => {\n if (!document.documentElement.attachShadow) {\n return null;\n }\n\n // Can find the shadow root otherwise it'll return the document\n if (typeof element.getRootNode === 'function') {\n const root = element.getRootNode();\n return root instanceof ShadowRoot ? root : null;\n }\n if (element instanceof ShadowRoot) {\n return element;\n }\n\n // when we don't find a shadow root\n if (!element.parentNode) {\n return null;\n }\n return findShadowRoot(element.parentNode);\n};\nconst noop = () => {};\n\n/**\n * Trick to restart an element's animation\n *\n * @param {HTMLElement} element\n * @return void\n *\n * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n */\nconst reflow = element => {\n element.offsetHeight; // eslint-disable-line no-unused-expressions\n};\nconst getjQuery = () => {\n if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n return window.jQuery;\n }\n return null;\n};\nconst DOMContentLoadedCallbacks = [];\nconst onDOMContentLoaded = callback => {\n if (document.readyState === 'loading') {\n // add listener on the first call when the document is in loading state\n if (!DOMContentLoadedCallbacks.length) {\n document.addEventListener('DOMContentLoaded', () => {\n for (const callback of DOMContentLoadedCallbacks) {\n callback();\n }\n });\n }\n DOMContentLoadedCallbacks.push(callback);\n } else {\n callback();\n }\n};\nconst isRTL = () => document.documentElement.dir === 'rtl';\nconst defineJQueryPlugin = plugin => {\n onDOMContentLoaded(() => {\n const $ = getjQuery();\n /* istanbul ignore if */\n if ($) {\n const name = plugin.NAME;\n const JQUERY_NO_CONFLICT = $.fn[name];\n $.fn[name] = plugin.jQueryInterface;\n $.fn[name].Constructor = plugin;\n $.fn[name].noConflict = () => {\n $.fn[name] = JQUERY_NO_CONFLICT;\n return plugin.jQueryInterface;\n };\n }\n });\n};\nconst execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {\n return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue;\n};\nconst executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n if (!waitForTransition) {\n execute(callback);\n return;\n }\n const durationPadding = 5;\n const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding;\n let called = false;\n const handler = ({\n target\n }) => {\n if (target !== transitionElement) {\n return;\n }\n called = true;\n transitionElement.removeEventListener(TRANSITION_END, handler);\n execute(callback);\n };\n transitionElement.addEventListener(TRANSITION_END, handler);\n setTimeout(() => {\n if (!called) {\n triggerTransitionEnd(transitionElement);\n }\n }, emulatedDuration);\n};\n\n/**\n * Return the previous/next element of a list.\n *\n * @param {array} list The list of elements\n * @param activeElement The active element\n * @param shouldGetNext Choose to get next or previous element\n * @param isCycleAllowed\n * @return {Element|elem} The proper element\n */\nconst getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n const listLength = list.length;\n let index = list.indexOf(activeElement);\n\n // if the element does not exist in the list return an element\n // depending on the direction and if cycle is allowed\n if (index === -1) {\n return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0];\n }\n index += shouldGetNext ? 1 : -1;\n if (isCycleAllowed) {\n index = (index + listLength) % listLength;\n }\n return list[Math.max(0, Math.min(index, listLength - 1))];\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/event-handler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst namespaceRegex = /[^.]*(?=\\..*)\\.|.*/;\nconst stripNameRegex = /\\..*/;\nconst stripUidRegex = /::\\d+$/;\nconst eventRegistry = {}; // Events storage\nlet uidEvent = 1;\nconst customEvents = {\n mouseenter: 'mouseover',\n mouseleave: 'mouseout'\n};\nconst nativeEvents = new Set(['click', 'dblclick', 'mouseup', 'mousedown', 'contextmenu', 'mousewheel', 'DOMMouseScroll', 'mouseover', 'mouseout', 'mousemove', 'selectstart', 'selectend', 'keydown', 'keypress', 'keyup', 'orientationchange', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'pointerdown', 'pointermove', 'pointerup', 'pointerleave', 'pointercancel', 'gesturestart', 'gesturechange', 'gestureend', 'focus', 'blur', 'change', 'reset', 'select', 'submit', 'focusin', 'focusout', 'load', 'unload', 'beforeunload', 'resize', 'move', 'DOMContentLoaded', 'readystatechange', 'error', 'abort', 'scroll']);\n\n/**\n * Private methods\n */\n\nfunction makeEventUid(element, uid) {\n return uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++;\n}\nfunction getElementEvents(element) {\n const uid = makeEventUid(element);\n element.uidEvent = uid;\n eventRegistry[uid] = eventRegistry[uid] || {};\n return eventRegistry[uid];\n}\nfunction bootstrapHandler(element, fn) {\n return function handler(event) {\n hydrateObj(event, {\n delegateTarget: element\n });\n if (handler.oneOff) {\n EventHandler.off(element, event.type, fn);\n }\n return fn.apply(element, [event]);\n };\n}\nfunction bootstrapDelegationHandler(element, selector, fn) {\n return function handler(event) {\n const domElements = element.querySelectorAll(selector);\n for (let {\n target\n } = event; target && target !== this; target = target.parentNode) {\n for (const domElement of domElements) {\n if (domElement !== target) {\n continue;\n }\n hydrateObj(event, {\n delegateTarget: target\n });\n if (handler.oneOff) {\n EventHandler.off(element, event.type, selector, fn);\n }\n return fn.apply(target, [event]);\n }\n }\n };\n}\nfunction findHandler(events, callable, delegationSelector = null) {\n return Object.values(events).find(event => event.callable === callable && event.delegationSelector === delegationSelector);\n}\nfunction normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n const isDelegated = typeof handler === 'string';\n // TODO: tooltip passes `false` instead of selector, so we need to check\n const callable = isDelegated ? delegationFunction : handler || delegationFunction;\n let typeEvent = getTypeEvent(originalTypeEvent);\n if (!nativeEvents.has(typeEvent)) {\n typeEvent = originalTypeEvent;\n }\n return [isDelegated, callable, typeEvent];\n}\nfunction addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return;\n }\n let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction);\n\n // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n if (originalTypeEvent in customEvents) {\n const wrapFunction = fn => {\n return function (event) {\n if (!event.relatedTarget || event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget)) {\n return fn.call(this, event);\n }\n };\n };\n callable = wrapFunction(callable);\n }\n const events = getElementEvents(element);\n const handlers = events[typeEvent] || (events[typeEvent] = {});\n const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null);\n if (previousFunction) {\n previousFunction.oneOff = previousFunction.oneOff && oneOff;\n return;\n }\n const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''));\n const fn = isDelegated ? bootstrapDelegationHandler(element, handler, callable) : bootstrapHandler(element, callable);\n fn.delegationSelector = isDelegated ? handler : null;\n fn.callable = callable;\n fn.oneOff = oneOff;\n fn.uidEvent = uid;\n handlers[uid] = fn;\n element.addEventListener(typeEvent, fn, isDelegated);\n}\nfunction removeHandler(element, events, typeEvent, handler, delegationSelector) {\n const fn = findHandler(events[typeEvent], handler, delegationSelector);\n if (!fn) {\n return;\n }\n element.removeEventListener(typeEvent, fn, Boolean(delegationSelector));\n delete events[typeEvent][fn.uidEvent];\n}\nfunction removeNamespacedHandlers(element, events, typeEvent, namespace) {\n const storeElementEvent = events[typeEvent] || {};\n for (const [handlerKey, event] of Object.entries(storeElementEvent)) {\n if (handlerKey.includes(namespace)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);\n }\n }\n}\nfunction getTypeEvent(event) {\n // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n event = event.replace(stripNameRegex, '');\n return customEvents[event] || event;\n}\nconst EventHandler = {\n on(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, false);\n },\n one(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, true);\n },\n off(element, originalTypeEvent, handler, delegationFunction) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return;\n }\n const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction);\n const inNamespace = typeEvent !== originalTypeEvent;\n const events = getElementEvents(element);\n const storeElementEvent = events[typeEvent] || {};\n const isNamespace = originalTypeEvent.startsWith('.');\n if (typeof callable !== 'undefined') {\n // Simplest case: handler is passed, remove that listener ONLY.\n if (!Object.keys(storeElementEvent).length) {\n return;\n }\n removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null);\n return;\n }\n if (isNamespace) {\n for (const elementEvent of Object.keys(events)) {\n removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1));\n }\n }\n for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {\n const handlerKey = keyHandlers.replace(stripUidRegex, '');\n if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);\n }\n }\n },\n trigger(element, event, args) {\n if (typeof event !== 'string' || !element) {\n return null;\n }\n const $ = getjQuery();\n const typeEvent = getTypeEvent(event);\n const inNamespace = event !== typeEvent;\n let jQueryEvent = null;\n let bubbles = true;\n let nativeDispatch = true;\n let defaultPrevented = false;\n if (inNamespace && $) {\n jQueryEvent = $.Event(event, args);\n $(element).trigger(jQueryEvent);\n bubbles = !jQueryEvent.isPropagationStopped();\n nativeDispatch = !jQueryEvent.isImmediatePropagationStopped();\n defaultPrevented = jQueryEvent.isDefaultPrevented();\n }\n const evt = hydrateObj(new Event(event, {\n bubbles,\n cancelable: true\n }), args);\n if (defaultPrevented) {\n evt.preventDefault();\n }\n if (nativeDispatch) {\n element.dispatchEvent(evt);\n }\n if (evt.defaultPrevented && jQueryEvent) {\n jQueryEvent.preventDefault();\n }\n return evt;\n }\n};\nfunction hydrateObj(obj, meta = {}) {\n for (const [key, value] of Object.entries(meta)) {\n try {\n obj[key] = value;\n } catch (_unused) {\n Object.defineProperty(obj, key, {\n configurable: true,\n get() {\n return value;\n }\n });\n }\n }\n return obj;\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true;\n }\n if (value === 'false') {\n return false;\n }\n if (value === Number(value).toString()) {\n return Number(value);\n }\n if (value === '' || value === 'null') {\n return null;\n }\n if (typeof value !== 'string') {\n return value;\n }\n try {\n return JSON.parse(decodeURIComponent(value));\n } catch (_unused) {\n return value;\n }\n}\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`);\n}\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value);\n },\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`);\n },\n getDataAttributes(element) {\n if (!element) {\n return {};\n }\n const attributes = {};\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'));\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '');\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length);\n attributes[pureKey] = normalizeData(element.dataset[key]);\n }\n return attributes;\n },\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`));\n }\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/config.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Class definition\n */\n\nclass Config {\n // Getters\n static get Default() {\n return {};\n }\n static get DefaultType() {\n return {};\n }\n static get NAME() {\n throw new Error('You have to implement the static method \"NAME\", for each component!');\n }\n _getConfig(config) {\n config = this._mergeConfigObj(config);\n config = this._configAfterMerge(config);\n this._typeCheckConfig(config);\n return config;\n }\n _configAfterMerge(config) {\n return config;\n }\n _mergeConfigObj(config, element) {\n const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {}; // try to parse\n\n return {\n ...this.constructor.Default,\n ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n ...(typeof config === 'object' ? config : {})\n };\n }\n _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n for (const [property, expectedTypes] of Object.entries(configTypes)) {\n const value = config[property];\n const valueType = isElement(value) ? 'element' : toType(value);\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`);\n }\n }\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst VERSION = '5.3.3';\n\n/**\n * Class definition\n */\n\nclass BaseComponent extends Config {\n constructor(element, config) {\n super();\n element = getElement(element);\n if (!element) {\n return;\n }\n this._element = element;\n this._config = this._getConfig(config);\n Data.set(this._element, this.constructor.DATA_KEY, this);\n }\n\n // Public\n dispose() {\n Data.remove(this._element, this.constructor.DATA_KEY);\n EventHandler.off(this._element, this.constructor.EVENT_KEY);\n for (const propertyName of Object.getOwnPropertyNames(this)) {\n this[propertyName] = null;\n }\n }\n _queueCallback(callback, element, isAnimated = true) {\n executeAfterTransition(callback, element, isAnimated);\n }\n _getConfig(config) {\n config = this._mergeConfigObj(config, this._element);\n config = this._configAfterMerge(config);\n this._typeCheckConfig(config);\n return config;\n }\n\n // Static\n static getInstance(element) {\n return Data.get(getElement(element), this.DATA_KEY);\n }\n static getOrCreateInstance(element, config = {}) {\n return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null);\n }\n static get VERSION() {\n return VERSION;\n }\n static get DATA_KEY() {\n return `bs.${this.NAME}`;\n }\n static get EVENT_KEY() {\n return `.${this.DATA_KEY}`;\n }\n static eventName(name) {\n return `${name}${this.EVENT_KEY}`;\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/selector-engine.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst getSelector = element => {\n let selector = element.getAttribute('data-bs-target');\n if (!selector || selector === '#') {\n let hrefAttribute = element.getAttribute('href');\n\n // The only valid content that could double as a selector are IDs or classes,\n // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n // `document.querySelector` will rightfully complain it is invalid.\n // See https://github.com/twbs/bootstrap/issues/32273\n if (!hrefAttribute || !hrefAttribute.includes('#') && !hrefAttribute.startsWith('.')) {\n return null;\n }\n\n // Just in case some CMS puts out a full URL with the anchor appended\n if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n hrefAttribute = `#${hrefAttribute.split('#')[1]}`;\n }\n selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null;\n }\n return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null;\n};\nconst SelectorEngine = {\n find(selector, element = document.documentElement) {\n return [].concat(...Element.prototype.querySelectorAll.call(element, selector));\n },\n findOne(selector, element = document.documentElement) {\n return Element.prototype.querySelector.call(element, selector);\n },\n children(element, selector) {\n return [].concat(...element.children).filter(child => child.matches(selector));\n },\n parents(element, selector) {\n const parents = [];\n let ancestor = element.parentNode.closest(selector);\n while (ancestor) {\n parents.push(ancestor);\n ancestor = ancestor.parentNode.closest(selector);\n }\n return parents;\n },\n prev(element, selector) {\n let previous = element.previousElementSibling;\n while (previous) {\n if (previous.matches(selector)) {\n return [previous];\n }\n previous = previous.previousElementSibling;\n }\n return [];\n },\n // TODO: this is now unused; remove later along with prev()\n next(element, selector) {\n let next = element.nextElementSibling;\n while (next) {\n if (next.matches(selector)) {\n return [next];\n }\n next = next.nextElementSibling;\n }\n return [];\n },\n focusableChildren(element) {\n const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable=\"true\"]'].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',');\n return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el));\n },\n getSelectorFromElement(element) {\n const selector = getSelector(element);\n if (selector) {\n return SelectorEngine.findOne(selector) ? selector : null;\n }\n return null;\n },\n getElementFromSelector(element) {\n const selector = getSelector(element);\n return selector ? SelectorEngine.findOne(selector) : null;\n },\n getMultipleElementsFromSelector(element) {\n const selector = getSelector(element);\n return selector ? SelectorEngine.find(selector) : [];\n }\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`;\n const name = component.NAME;\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault();\n }\n if (isDisabled(this)) {\n return;\n }\n const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`);\n const instance = component.getOrCreateInstance(target);\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]();\n });\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$f = 'alert';\nconst DATA_KEY$a = 'bs.alert';\nconst EVENT_KEY$b = `.${DATA_KEY$a}`;\nconst EVENT_CLOSE = `close${EVENT_KEY$b}`;\nconst EVENT_CLOSED = `closed${EVENT_KEY$b}`;\nconst CLASS_NAME_FADE$5 = 'fade';\nconst CLASS_NAME_SHOW$8 = 'show';\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME$f;\n }\n\n // Public\n close() {\n const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE);\n if (closeEvent.defaultPrevented) {\n return;\n }\n this._element.classList.remove(CLASS_NAME_SHOW$8);\n const isAnimated = this._element.classList.contains(CLASS_NAME_FADE$5);\n this._queueCallback(() => this._destroyElement(), this._element, isAnimated);\n }\n\n // Private\n _destroyElement() {\n this._element.remove();\n EventHandler.trigger(this._element, EVENT_CLOSED);\n this.dispose();\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Alert.getOrCreateInstance(this);\n if (typeof config !== 'string') {\n return;\n }\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config](this);\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close');\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$e = 'button';\nconst DATA_KEY$9 = 'bs.button';\nconst EVENT_KEY$a = `.${DATA_KEY$9}`;\nconst DATA_API_KEY$6 = '.data-api';\nconst CLASS_NAME_ACTIVE$3 = 'active';\nconst SELECTOR_DATA_TOGGLE$5 = '[data-bs-toggle=\"button\"]';\nconst EVENT_CLICK_DATA_API$6 = `click${EVENT_KEY$a}${DATA_API_KEY$6}`;\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME$e;\n }\n\n // Public\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE$3));\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Button.getOrCreateInstance(this);\n if (config === 'toggle') {\n data[config]();\n }\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$6, SELECTOR_DATA_TOGGLE$5, event => {\n event.preventDefault();\n const button = event.target.closest(SELECTOR_DATA_TOGGLE$5);\n const data = Button.getOrCreateInstance(button);\n data.toggle();\n});\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/swipe.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$d = 'swipe';\nconst EVENT_KEY$9 = '.bs.swipe';\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY$9}`;\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY$9}`;\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY$9}`;\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY$9}`;\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY$9}`;\nconst POINTER_TYPE_TOUCH = 'touch';\nconst POINTER_TYPE_PEN = 'pen';\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event';\nconst SWIPE_THRESHOLD = 40;\nconst Default$c = {\n endCallback: null,\n leftCallback: null,\n rightCallback: null\n};\nconst DefaultType$c = {\n endCallback: '(function|null)',\n leftCallback: '(function|null)',\n rightCallback: '(function|null)'\n};\n\n/**\n * Class definition\n */\n\nclass Swipe extends Config {\n constructor(element, config) {\n super();\n this._element = element;\n if (!element || !Swipe.isSupported()) {\n return;\n }\n this._config = this._getConfig(config);\n this._deltaX = 0;\n this._supportPointerEvents = Boolean(window.PointerEvent);\n this._initEvents();\n }\n\n // Getters\n static get Default() {\n return Default$c;\n }\n static get DefaultType() {\n return DefaultType$c;\n }\n static get NAME() {\n return NAME$d;\n }\n\n // Public\n dispose() {\n EventHandler.off(this._element, EVENT_KEY$9);\n }\n\n // Private\n _start(event) {\n if (!this._supportPointerEvents) {\n this._deltaX = event.touches[0].clientX;\n return;\n }\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX;\n }\n }\n _end(event) {\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX - this._deltaX;\n }\n this._handleSwipe();\n execute(this._config.endCallback);\n }\n _move(event) {\n this._deltaX = event.touches && event.touches.length > 1 ? 0 : event.touches[0].clientX - this._deltaX;\n }\n _handleSwipe() {\n const absDeltaX = Math.abs(this._deltaX);\n if (absDeltaX <= SWIPE_THRESHOLD) {\n return;\n }\n const direction = absDeltaX / this._deltaX;\n this._deltaX = 0;\n if (!direction) {\n return;\n }\n execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback);\n }\n _initEvents() {\n if (this._supportPointerEvents) {\n EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event));\n EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event));\n this._element.classList.add(CLASS_NAME_POINTER_EVENT);\n } else {\n EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event));\n EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event));\n EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event));\n }\n }\n _eventIsPointerPenTouch(event) {\n return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH);\n }\n\n // Static\n static isSupported() {\n return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0;\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$c = 'carousel';\nconst DATA_KEY$8 = 'bs.carousel';\nconst EVENT_KEY$8 = `.${DATA_KEY$8}`;\nconst DATA_API_KEY$5 = '.data-api';\nconst ARROW_LEFT_KEY$1 = 'ArrowLeft';\nconst ARROW_RIGHT_KEY$1 = 'ArrowRight';\nconst TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch\n\nconst ORDER_NEXT = 'next';\nconst ORDER_PREV = 'prev';\nconst DIRECTION_LEFT = 'left';\nconst DIRECTION_RIGHT = 'right';\nconst EVENT_SLIDE = `slide${EVENT_KEY$8}`;\nconst EVENT_SLID = `slid${EVENT_KEY$8}`;\nconst EVENT_KEYDOWN$1 = `keydown${EVENT_KEY$8}`;\nconst EVENT_MOUSEENTER$1 = `mouseenter${EVENT_KEY$8}`;\nconst EVENT_MOUSELEAVE$1 = `mouseleave${EVENT_KEY$8}`;\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY$8}`;\nconst EVENT_LOAD_DATA_API$3 = `load${EVENT_KEY$8}${DATA_API_KEY$5}`;\nconst EVENT_CLICK_DATA_API$5 = `click${EVENT_KEY$8}${DATA_API_KEY$5}`;\nconst CLASS_NAME_CAROUSEL = 'carousel';\nconst CLASS_NAME_ACTIVE$2 = 'active';\nconst CLASS_NAME_SLIDE = 'slide';\nconst CLASS_NAME_END = 'carousel-item-end';\nconst CLASS_NAME_START = 'carousel-item-start';\nconst CLASS_NAME_NEXT = 'carousel-item-next';\nconst CLASS_NAME_PREV = 'carousel-item-prev';\nconst SELECTOR_ACTIVE = '.active';\nconst SELECTOR_ITEM = '.carousel-item';\nconst SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM;\nconst SELECTOR_ITEM_IMG = '.carousel-item img';\nconst SELECTOR_INDICATORS = '.carousel-indicators';\nconst SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]';\nconst SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]';\nconst KEY_TO_DIRECTION = {\n [ARROW_LEFT_KEY$1]: DIRECTION_RIGHT,\n [ARROW_RIGHT_KEY$1]: DIRECTION_LEFT\n};\nconst Default$b = {\n interval: 5000,\n keyboard: true,\n pause: 'hover',\n ride: false,\n touch: true,\n wrap: true\n};\nconst DefaultType$b = {\n interval: '(number|boolean)',\n // TODO:v6 remove boolean support\n keyboard: 'boolean',\n pause: '(string|boolean)',\n ride: '(boolean|string)',\n touch: 'boolean',\n wrap: 'boolean'\n};\n\n/**\n * Class definition\n */\n\nclass Carousel extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._interval = null;\n this._activeElement = null;\n this._isSliding = false;\n this.touchTimeout = null;\n this._swipeHelper = null;\n this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element);\n this._addEventListeners();\n if (this._config.ride === CLASS_NAME_CAROUSEL) {\n this.cycle();\n }\n }\n\n // Getters\n static get Default() {\n return Default$b;\n }\n static get DefaultType() {\n return DefaultType$b;\n }\n static get NAME() {\n return NAME$c;\n }\n\n // Public\n next() {\n this._slide(ORDER_NEXT);\n }\n nextWhenVisible() {\n // FIXME TODO use `document.visibilityState`\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden && isVisible(this._element)) {\n this.next();\n }\n }\n prev() {\n this._slide(ORDER_PREV);\n }\n pause() {\n if (this._isSliding) {\n triggerTransitionEnd(this._element);\n }\n this._clearInterval();\n }\n cycle() {\n this._clearInterval();\n this._updateInterval();\n this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval);\n }\n _maybeEnableCycle() {\n if (!this._config.ride) {\n return;\n }\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.cycle());\n return;\n }\n this.cycle();\n }\n to(index) {\n const items = this._getItems();\n if (index > items.length - 1 || index < 0) {\n return;\n }\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.to(index));\n return;\n }\n const activeIndex = this._getItemIndex(this._getActive());\n if (activeIndex === index) {\n return;\n }\n const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV;\n this._slide(order, items[index]);\n }\n dispose() {\n if (this._swipeHelper) {\n this._swipeHelper.dispose();\n }\n super.dispose();\n }\n\n // Private\n _configAfterMerge(config) {\n config.defaultInterval = config.interval;\n return config;\n }\n _addEventListeners() {\n if (this._config.keyboard) {\n EventHandler.on(this._element, EVENT_KEYDOWN$1, event => this._keydown(event));\n }\n if (this._config.pause === 'hover') {\n EventHandler.on(this._element, EVENT_MOUSEENTER$1, () => this.pause());\n EventHandler.on(this._element, EVENT_MOUSELEAVE$1, () => this._maybeEnableCycle());\n }\n if (this._config.touch && Swipe.isSupported()) {\n this._addTouchEventListeners();\n }\n }\n _addTouchEventListeners() {\n for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault());\n }\n const endCallBack = () => {\n if (this._config.pause !== 'hover') {\n return;\n }\n\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause();\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout);\n }\n this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval);\n };\n const swipeConfig = {\n leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n endCallback: endCallBack\n };\n this._swipeHelper = new Swipe(this._element, swipeConfig);\n }\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return;\n }\n const direction = KEY_TO_DIRECTION[event.key];\n if (direction) {\n event.preventDefault();\n this._slide(this._directionToOrder(direction));\n }\n }\n _getItemIndex(element) {\n return this._getItems().indexOf(element);\n }\n _setActiveIndicatorElement(index) {\n if (!this._indicatorsElement) {\n return;\n }\n const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement);\n activeIndicator.classList.remove(CLASS_NAME_ACTIVE$2);\n activeIndicator.removeAttribute('aria-current');\n const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement);\n if (newActiveIndicator) {\n newActiveIndicator.classList.add(CLASS_NAME_ACTIVE$2);\n newActiveIndicator.setAttribute('aria-current', 'true');\n }\n }\n _updateInterval() {\n const element = this._activeElement || this._getActive();\n if (!element) {\n return;\n }\n const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10);\n this._config.interval = elementInterval || this._config.defaultInterval;\n }\n _slide(order, element = null) {\n if (this._isSliding) {\n return;\n }\n const activeElement = this._getActive();\n const isNext = order === ORDER_NEXT;\n const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap);\n if (nextElement === activeElement) {\n return;\n }\n const nextElementIndex = this._getItemIndex(nextElement);\n const triggerEvent = eventName => {\n return EventHandler.trigger(this._element, eventName, {\n relatedTarget: nextElement,\n direction: this._orderToDirection(order),\n from: this._getItemIndex(activeElement),\n to: nextElementIndex\n });\n };\n const slideEvent = triggerEvent(EVENT_SLIDE);\n if (slideEvent.defaultPrevented) {\n return;\n }\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n // TODO: change tests that use empty divs to avoid this check\n return;\n }\n const isCycling = Boolean(this._interval);\n this.pause();\n this._isSliding = true;\n this._setActiveIndicatorElement(nextElementIndex);\n this._activeElement = nextElement;\n const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END;\n const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV;\n nextElement.classList.add(orderClassName);\n reflow(nextElement);\n activeElement.classList.add(directionalClassName);\n nextElement.classList.add(directionalClassName);\n const completeCallBack = () => {\n nextElement.classList.remove(directionalClassName, orderClassName);\n nextElement.classList.add(CLASS_NAME_ACTIVE$2);\n activeElement.classList.remove(CLASS_NAME_ACTIVE$2, orderClassName, directionalClassName);\n this._isSliding = false;\n triggerEvent(EVENT_SLID);\n };\n this._queueCallback(completeCallBack, activeElement, this._isAnimated());\n if (isCycling) {\n this.cycle();\n }\n }\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_SLIDE);\n }\n _getActive() {\n return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element);\n }\n _getItems() {\n return SelectorEngine.find(SELECTOR_ITEM, this._element);\n }\n _clearInterval() {\n if (this._interval) {\n clearInterval(this._interval);\n this._interval = null;\n }\n }\n _directionToOrder(direction) {\n if (isRTL()) {\n return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT;\n }\n return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV;\n }\n _orderToDirection(order) {\n if (isRTL()) {\n return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT;\n }\n return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT;\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Carousel.getOrCreateInstance(this, config);\n if (typeof config === 'number') {\n data.to(config);\n return;\n }\n if (typeof config === 'string') {\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n }\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$5, SELECTOR_DATA_SLIDE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this);\n if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n return;\n }\n event.preventDefault();\n const carousel = Carousel.getOrCreateInstance(target);\n const slideIndex = this.getAttribute('data-bs-slide-to');\n if (slideIndex) {\n carousel.to(slideIndex);\n carousel._maybeEnableCycle();\n return;\n }\n if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n carousel.next();\n carousel._maybeEnableCycle();\n return;\n }\n carousel.prev();\n carousel._maybeEnableCycle();\n});\nEventHandler.on(window, EVENT_LOAD_DATA_API$3, () => {\n const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE);\n for (const carousel of carousels) {\n Carousel.getOrCreateInstance(carousel);\n }\n});\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Carousel);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$b = 'collapse';\nconst DATA_KEY$7 = 'bs.collapse';\nconst EVENT_KEY$7 = `.${DATA_KEY$7}`;\nconst DATA_API_KEY$4 = '.data-api';\nconst EVENT_SHOW$6 = `show${EVENT_KEY$7}`;\nconst EVENT_SHOWN$6 = `shown${EVENT_KEY$7}`;\nconst EVENT_HIDE$6 = `hide${EVENT_KEY$7}`;\nconst EVENT_HIDDEN$6 = `hidden${EVENT_KEY$7}`;\nconst EVENT_CLICK_DATA_API$4 = `click${EVENT_KEY$7}${DATA_API_KEY$4}`;\nconst CLASS_NAME_SHOW$7 = 'show';\nconst CLASS_NAME_COLLAPSE = 'collapse';\nconst CLASS_NAME_COLLAPSING = 'collapsing';\nconst CLASS_NAME_COLLAPSED = 'collapsed';\nconst CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`;\nconst CLASS_NAME_HORIZONTAL = 'collapse-horizontal';\nconst WIDTH = 'width';\nconst HEIGHT = 'height';\nconst SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing';\nconst SELECTOR_DATA_TOGGLE$4 = '[data-bs-toggle=\"collapse\"]';\nconst Default$a = {\n parent: null,\n toggle: true\n};\nconst DefaultType$a = {\n parent: '(null|element)',\n toggle: 'boolean'\n};\n\n/**\n * Class definition\n */\n\nclass Collapse extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._isTransitioning = false;\n this._triggerArray = [];\n const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE$4);\n for (const elem of toggleList) {\n const selector = SelectorEngine.getSelectorFromElement(elem);\n const filterElement = SelectorEngine.find(selector).filter(foundElement => foundElement === this._element);\n if (selector !== null && filterElement.length) {\n this._triggerArray.push(elem);\n }\n }\n this._initializeChildren();\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._triggerArray, this._isShown());\n }\n if (this._config.toggle) {\n this.toggle();\n }\n }\n\n // Getters\n static get Default() {\n return Default$a;\n }\n static get DefaultType() {\n return DefaultType$a;\n }\n static get NAME() {\n return NAME$b;\n }\n\n // Public\n toggle() {\n if (this._isShown()) {\n this.hide();\n } else {\n this.show();\n }\n }\n show() {\n if (this._isTransitioning || this._isShown()) {\n return;\n }\n let activeChildren = [];\n\n // find active children\n if (this._config.parent) {\n activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES).filter(element => element !== this._element).map(element => Collapse.getOrCreateInstance(element, {\n toggle: false\n }));\n }\n if (activeChildren.length && activeChildren[0]._isTransitioning) {\n return;\n }\n const startEvent = EventHandler.trigger(this._element, EVENT_SHOW$6);\n if (startEvent.defaultPrevented) {\n return;\n }\n for (const activeInstance of activeChildren) {\n activeInstance.hide();\n }\n const dimension = this._getDimension();\n this._element.classList.remove(CLASS_NAME_COLLAPSE);\n this._element.classList.add(CLASS_NAME_COLLAPSING);\n this._element.style[dimension] = 0;\n this._addAriaAndCollapsedClass(this._triggerArray, true);\n this._isTransitioning = true;\n const complete = () => {\n this._isTransitioning = false;\n this._element.classList.remove(CLASS_NAME_COLLAPSING);\n this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7);\n this._element.style[dimension] = '';\n EventHandler.trigger(this._element, EVENT_SHOWN$6);\n };\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1);\n const scrollSize = `scroll${capitalizedDimension}`;\n this._queueCallback(complete, this._element, true);\n this._element.style[dimension] = `${this._element[scrollSize]}px`;\n }\n hide() {\n if (this._isTransitioning || !this._isShown()) {\n return;\n }\n const startEvent = EventHandler.trigger(this._element, EVENT_HIDE$6);\n if (startEvent.defaultPrevented) {\n return;\n }\n const dimension = this._getDimension();\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`;\n reflow(this._element);\n this._element.classList.add(CLASS_NAME_COLLAPSING);\n this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7);\n for (const trigger of this._triggerArray) {\n const element = SelectorEngine.getElementFromSelector(trigger);\n if (element && !this._isShown(element)) {\n this._addAriaAndCollapsedClass([trigger], false);\n }\n }\n this._isTransitioning = true;\n const complete = () => {\n this._isTransitioning = false;\n this._element.classList.remove(CLASS_NAME_COLLAPSING);\n this._element.classList.add(CLASS_NAME_COLLAPSE);\n EventHandler.trigger(this._element, EVENT_HIDDEN$6);\n };\n this._element.style[dimension] = '';\n this._queueCallback(complete, this._element, true);\n }\n _isShown(element = this._element) {\n return element.classList.contains(CLASS_NAME_SHOW$7);\n }\n\n // Private\n _configAfterMerge(config) {\n config.toggle = Boolean(config.toggle); // Coerce string values\n config.parent = getElement(config.parent);\n return config;\n }\n _getDimension() {\n return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT;\n }\n _initializeChildren() {\n if (!this._config.parent) {\n return;\n }\n const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE$4);\n for (const element of children) {\n const selected = SelectorEngine.getElementFromSelector(element);\n if (selected) {\n this._addAriaAndCollapsedClass([element], this._isShown(selected));\n }\n }\n }\n _getFirstLevelChildren(selector) {\n const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent);\n // remove children if greater depth\n return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element));\n }\n _addAriaAndCollapsedClass(triggerArray, isOpen) {\n if (!triggerArray.length) {\n return;\n }\n for (const element of triggerArray) {\n element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen);\n element.setAttribute('aria-expanded', isOpen);\n }\n }\n\n // Static\n static jQueryInterface(config) {\n const _config = {};\n if (typeof config === 'string' && /show|hide/.test(config)) {\n _config.toggle = false;\n }\n return this.each(function () {\n const data = Collapse.getOrCreateInstance(this, _config);\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n }\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$4, SELECTOR_DATA_TOGGLE$4, function (event) {\n // preventDefault only for elements (which change the URL) not inside the collapsible element\n if (event.target.tagName === 'A' || event.delegateTarget && event.delegateTarget.tagName === 'A') {\n event.preventDefault();\n }\n for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {\n Collapse.getOrCreateInstance(element, {\n toggle: false\n }).toggle();\n }\n});\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Collapse);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$a = 'dropdown';\nconst DATA_KEY$6 = 'bs.dropdown';\nconst EVENT_KEY$6 = `.${DATA_KEY$6}`;\nconst DATA_API_KEY$3 = '.data-api';\nconst ESCAPE_KEY$2 = 'Escape';\nconst TAB_KEY$1 = 'Tab';\nconst ARROW_UP_KEY$1 = 'ArrowUp';\nconst ARROW_DOWN_KEY$1 = 'ArrowDown';\nconst RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button\n\nconst EVENT_HIDE$5 = `hide${EVENT_KEY$6}`;\nconst EVENT_HIDDEN$5 = `hidden${EVENT_KEY$6}`;\nconst EVENT_SHOW$5 = `show${EVENT_KEY$6}`;\nconst EVENT_SHOWN$5 = `shown${EVENT_KEY$6}`;\nconst EVENT_CLICK_DATA_API$3 = `click${EVENT_KEY$6}${DATA_API_KEY$3}`;\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY$6}${DATA_API_KEY$3}`;\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY$6}${DATA_API_KEY$3}`;\nconst CLASS_NAME_SHOW$6 = 'show';\nconst CLASS_NAME_DROPUP = 'dropup';\nconst CLASS_NAME_DROPEND = 'dropend';\nconst CLASS_NAME_DROPSTART = 'dropstart';\nconst CLASS_NAME_DROPUP_CENTER = 'dropup-center';\nconst CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center';\nconst SELECTOR_DATA_TOGGLE$3 = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)';\nconst SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE$3}.${CLASS_NAME_SHOW$6}`;\nconst SELECTOR_MENU = '.dropdown-menu';\nconst SELECTOR_NAVBAR = '.navbar';\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav';\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)';\nconst PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start';\nconst PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end';\nconst PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start';\nconst PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end';\nconst PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start';\nconst PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start';\nconst PLACEMENT_TOPCENTER = 'top';\nconst PLACEMENT_BOTTOMCENTER = 'bottom';\nconst Default$9 = {\n autoClose: true,\n boundary: 'clippingParents',\n display: 'dynamic',\n offset: [0, 2],\n popperConfig: null,\n reference: 'toggle'\n};\nconst DefaultType$9 = {\n autoClose: '(boolean|string)',\n boundary: '(string|element)',\n display: 'string',\n offset: '(array|string|function)',\n popperConfig: '(null|object|function)',\n reference: '(string|element|object)'\n};\n\n/**\n * Class definition\n */\n\nclass Dropdown extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._popper = null;\n this._parent = this._element.parentNode; // dropdown wrapper\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent);\n this._inNavbar = this._detectNavbar();\n }\n\n // Getters\n static get Default() {\n return Default$9;\n }\n static get DefaultType() {\n return DefaultType$9;\n }\n static get NAME() {\n return NAME$a;\n }\n\n // Public\n toggle() {\n return this._isShown() ? this.hide() : this.show();\n }\n show() {\n if (isDisabled(this._element) || this._isShown()) {\n return;\n }\n const relatedTarget = {\n relatedTarget: this._element\n };\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$5, relatedTarget);\n if (showEvent.defaultPrevented) {\n return;\n }\n this._createPopper();\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop);\n }\n }\n this._element.focus();\n this._element.setAttribute('aria-expanded', true);\n this._menu.classList.add(CLASS_NAME_SHOW$6);\n this._element.classList.add(CLASS_NAME_SHOW$6);\n EventHandler.trigger(this._element, EVENT_SHOWN$5, relatedTarget);\n }\n hide() {\n if (isDisabled(this._element) || !this._isShown()) {\n return;\n }\n const relatedTarget = {\n relatedTarget: this._element\n };\n this._completeHide(relatedTarget);\n }\n dispose() {\n if (this._popper) {\n this._popper.destroy();\n }\n super.dispose();\n }\n update() {\n this._inNavbar = this._detectNavbar();\n if (this._popper) {\n this._popper.update();\n }\n }\n\n // Private\n _completeHide(relatedTarget) {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$5, relatedTarget);\n if (hideEvent.defaultPrevented) {\n return;\n }\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop);\n }\n }\n if (this._popper) {\n this._popper.destroy();\n }\n this._menu.classList.remove(CLASS_NAME_SHOW$6);\n this._element.classList.remove(CLASS_NAME_SHOW$6);\n this._element.setAttribute('aria-expanded', 'false');\n Manipulator.removeDataAttribute(this._menu, 'popper');\n EventHandler.trigger(this._element, EVENT_HIDDEN$5, relatedTarget);\n }\n _getConfig(config) {\n config = super._getConfig(config);\n if (typeof config.reference === 'object' && !isElement(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') {\n // Popper virtual elements require a getBoundingClientRect method\n throw new TypeError(`${NAME$a.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`);\n }\n return config;\n }\n _createPopper() {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org)');\n }\n let referenceElement = this._element;\n if (this._config.reference === 'parent') {\n referenceElement = this._parent;\n } else if (isElement(this._config.reference)) {\n referenceElement = getElement(this._config.reference);\n } else if (typeof this._config.reference === 'object') {\n referenceElement = this._config.reference;\n }\n const popperConfig = this._getPopperConfig();\n this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig);\n }\n _isShown() {\n return this._menu.classList.contains(CLASS_NAME_SHOW$6);\n }\n _getPlacement() {\n const parentDropdown = this._parent;\n if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n return PLACEMENT_RIGHT;\n }\n if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n return PLACEMENT_LEFT;\n }\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n return PLACEMENT_TOPCENTER;\n }\n if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n return PLACEMENT_BOTTOMCENTER;\n }\n\n // We need to trim the value because custom properties can also include spaces\n const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end';\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP;\n }\n return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM;\n }\n _detectNavbar() {\n return this._element.closest(SELECTOR_NAVBAR) !== null;\n }\n _getOffset() {\n const {\n offset\n } = this._config;\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10));\n }\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element);\n }\n return offset;\n }\n _getPopperConfig() {\n const defaultBsPopperConfig = {\n placement: this._getPlacement(),\n modifiers: [{\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n }, {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }]\n };\n\n // Disable Popper if we have a static display or Dropdown is in Navbar\n if (this._inNavbar || this._config.display === 'static') {\n Manipulator.setDataAttribute(this._menu, 'popper', 'static'); // TODO: v6 remove\n defaultBsPopperConfig.modifiers = [{\n name: 'applyStyles',\n enabled: false\n }];\n }\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n };\n }\n _selectMenuItem({\n key,\n target\n }) {\n const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element));\n if (!items.length) {\n return;\n }\n\n // if target isn't included in items (e.g. when expanding the dropdown)\n // allow cycling to get the last item in case key equals ARROW_UP_KEY\n getNextActiveElement(items, target, key === ARROW_DOWN_KEY$1, !items.includes(target)).focus();\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Dropdown.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n });\n }\n static clearMenus(event) {\n if (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY$1) {\n return;\n }\n const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN);\n for (const toggle of openToggles) {\n const context = Dropdown.getInstance(toggle);\n if (!context || context._config.autoClose === false) {\n continue;\n }\n const composedPath = event.composedPath();\n const isMenuTarget = composedPath.includes(context._menu);\n if (composedPath.includes(context._element) || context._config.autoClose === 'inside' && !isMenuTarget || context._config.autoClose === 'outside' && isMenuTarget) {\n continue;\n }\n\n // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n if (context._menu.contains(event.target) && (event.type === 'keyup' && event.key === TAB_KEY$1 || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n continue;\n }\n const relatedTarget = {\n relatedTarget: context._element\n };\n if (event.type === 'click') {\n relatedTarget.clickEvent = event;\n }\n context._completeHide(relatedTarget);\n }\n }\n static dataApiKeydownHandler(event) {\n // If not an UP | DOWN | ESCAPE key => not a dropdown command\n // If input/textarea && if key is other than ESCAPE => not a dropdown command\n\n const isInput = /input|textarea/i.test(event.target.tagName);\n const isEscapeEvent = event.key === ESCAPE_KEY$2;\n const isUpOrDownEvent = [ARROW_UP_KEY$1, ARROW_DOWN_KEY$1].includes(event.key);\n if (!isUpOrDownEvent && !isEscapeEvent) {\n return;\n }\n if (isInput && !isEscapeEvent) {\n return;\n }\n event.preventDefault();\n\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE$3) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE$3, event.delegateTarget.parentNode);\n const instance = Dropdown.getOrCreateInstance(getToggleButton);\n if (isUpOrDownEvent) {\n event.stopPropagation();\n instance.show();\n instance._selectMenuItem(event);\n return;\n }\n if (instance._isShown()) {\n // else is escape and we check if it is shown\n event.stopPropagation();\n instance.hide();\n getToggleButton.focus();\n }\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE$3, Dropdown.dataApiKeydownHandler);\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler);\nEventHandler.on(document, EVENT_CLICK_DATA_API$3, Dropdown.clearMenus);\nEventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus);\nEventHandler.on(document, EVENT_CLICK_DATA_API$3, SELECTOR_DATA_TOGGLE$3, function (event) {\n event.preventDefault();\n Dropdown.getOrCreateInstance(this).toggle();\n});\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Dropdown);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/backdrop.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$9 = 'backdrop';\nconst CLASS_NAME_FADE$4 = 'fade';\nconst CLASS_NAME_SHOW$5 = 'show';\nconst EVENT_MOUSEDOWN = `mousedown.bs.${NAME$9}`;\nconst Default$8 = {\n className: 'modal-backdrop',\n clickCallback: null,\n isAnimated: false,\n isVisible: true,\n // if false, we use the backdrop helper without adding any element to the dom\n rootElement: 'body' // give the choice to place backdrop under different elements\n};\nconst DefaultType$8 = {\n className: 'string',\n clickCallback: '(function|null)',\n isAnimated: 'boolean',\n isVisible: 'boolean',\n rootElement: '(element|string)'\n};\n\n/**\n * Class definition\n */\n\nclass Backdrop extends Config {\n constructor(config) {\n super();\n this._config = this._getConfig(config);\n this._isAppended = false;\n this._element = null;\n }\n\n // Getters\n static get Default() {\n return Default$8;\n }\n static get DefaultType() {\n return DefaultType$8;\n }\n static get NAME() {\n return NAME$9;\n }\n\n // Public\n show(callback) {\n if (!this._config.isVisible) {\n execute(callback);\n return;\n }\n this._append();\n const element = this._getElement();\n if (this._config.isAnimated) {\n reflow(element);\n }\n element.classList.add(CLASS_NAME_SHOW$5);\n this._emulateAnimation(() => {\n execute(callback);\n });\n }\n hide(callback) {\n if (!this._config.isVisible) {\n execute(callback);\n return;\n }\n this._getElement().classList.remove(CLASS_NAME_SHOW$5);\n this._emulateAnimation(() => {\n this.dispose();\n execute(callback);\n });\n }\n dispose() {\n if (!this._isAppended) {\n return;\n }\n EventHandler.off(this._element, EVENT_MOUSEDOWN);\n this._element.remove();\n this._isAppended = false;\n }\n\n // Private\n _getElement() {\n if (!this._element) {\n const backdrop = document.createElement('div');\n backdrop.className = this._config.className;\n if (this._config.isAnimated) {\n backdrop.classList.add(CLASS_NAME_FADE$4);\n }\n this._element = backdrop;\n }\n return this._element;\n }\n _configAfterMerge(config) {\n // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n config.rootElement = getElement(config.rootElement);\n return config;\n }\n _append() {\n if (this._isAppended) {\n return;\n }\n const element = this._getElement();\n this._config.rootElement.append(element);\n EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n execute(this._config.clickCallback);\n });\n this._isAppended = true;\n }\n _emulateAnimation(callback) {\n executeAfterTransition(callback, this._getElement(), this._config.isAnimated);\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$8 = 'focustrap';\nconst DATA_KEY$5 = 'bs.focustrap';\nconst EVENT_KEY$5 = `.${DATA_KEY$5}`;\nconst EVENT_FOCUSIN$2 = `focusin${EVENT_KEY$5}`;\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY$5}`;\nconst TAB_KEY = 'Tab';\nconst TAB_NAV_FORWARD = 'forward';\nconst TAB_NAV_BACKWARD = 'backward';\nconst Default$7 = {\n autofocus: true,\n trapElement: null // The element to trap focus inside of\n};\nconst DefaultType$7 = {\n autofocus: 'boolean',\n trapElement: 'element'\n};\n\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n constructor(config) {\n super();\n this._config = this._getConfig(config);\n this._isActive = false;\n this._lastTabNavDirection = null;\n }\n\n // Getters\n static get Default() {\n return Default$7;\n }\n static get DefaultType() {\n return DefaultType$7;\n }\n static get NAME() {\n return NAME$8;\n }\n\n // Public\n activate() {\n if (this._isActive) {\n return;\n }\n if (this._config.autofocus) {\n this._config.trapElement.focus();\n }\n EventHandler.off(document, EVENT_KEY$5); // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN$2, event => this._handleFocusin(event));\n EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event));\n this._isActive = true;\n }\n deactivate() {\n if (!this._isActive) {\n return;\n }\n this._isActive = false;\n EventHandler.off(document, EVENT_KEY$5);\n }\n\n // Private\n _handleFocusin(event) {\n const {\n trapElement\n } = this._config;\n if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n return;\n }\n const elements = SelectorEngine.focusableChildren(trapElement);\n if (elements.length === 0) {\n trapElement.focus();\n } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n elements[elements.length - 1].focus();\n } else {\n elements[0].focus();\n }\n }\n _handleKeydown(event) {\n if (event.key !== TAB_KEY) {\n return;\n }\n this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD;\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/scrollBar.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top';\nconst SELECTOR_STICKY_CONTENT = '.sticky-top';\nconst PROPERTY_PADDING = 'padding-right';\nconst PROPERTY_MARGIN = 'margin-right';\n\n/**\n * Class definition\n */\n\nclass ScrollBarHelper {\n constructor() {\n this._element = document.body;\n }\n\n // Public\n getWidth() {\n // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n const documentWidth = document.documentElement.clientWidth;\n return Math.abs(window.innerWidth - documentWidth);\n }\n hide() {\n const width = this.getWidth();\n this._disableOverFlow();\n // give padding to element to balance the hidden scrollbar width\n this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width);\n // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width);\n this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width);\n }\n reset() {\n this._resetElementAttributes(this._element, 'overflow');\n this._resetElementAttributes(this._element, PROPERTY_PADDING);\n this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING);\n this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN);\n }\n isOverflowing() {\n return this.getWidth() > 0;\n }\n\n // Private\n _disableOverFlow() {\n this._saveInitialAttribute(this._element, 'overflow');\n this._element.style.overflow = 'hidden';\n }\n _setElementAttributes(selector, styleProperty, callback) {\n const scrollbarWidth = this.getWidth();\n const manipulationCallBack = element => {\n if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n return;\n }\n this._saveInitialAttribute(element, styleProperty);\n const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty);\n element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`);\n };\n this._applyManipulationCallback(selector, manipulationCallBack);\n }\n _saveInitialAttribute(element, styleProperty) {\n const actualValue = element.style.getPropertyValue(styleProperty);\n if (actualValue) {\n Manipulator.setDataAttribute(element, styleProperty, actualValue);\n }\n }\n _resetElementAttributes(selector, styleProperty) {\n const manipulationCallBack = element => {\n const value = Manipulator.getDataAttribute(element, styleProperty);\n // We only want to remove the property if the value is `null`; the value can also be zero\n if (value === null) {\n element.style.removeProperty(styleProperty);\n return;\n }\n Manipulator.removeDataAttribute(element, styleProperty);\n element.style.setProperty(styleProperty, value);\n };\n this._applyManipulationCallback(selector, manipulationCallBack);\n }\n _applyManipulationCallback(selector, callBack) {\n if (isElement(selector)) {\n callBack(selector);\n return;\n }\n for (const sel of SelectorEngine.find(selector, this._element)) {\n callBack(sel);\n }\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$7 = 'modal';\nconst DATA_KEY$4 = 'bs.modal';\nconst EVENT_KEY$4 = `.${DATA_KEY$4}`;\nconst DATA_API_KEY$2 = '.data-api';\nconst ESCAPE_KEY$1 = 'Escape';\nconst EVENT_HIDE$4 = `hide${EVENT_KEY$4}`;\nconst EVENT_HIDE_PREVENTED$1 = `hidePrevented${EVENT_KEY$4}`;\nconst EVENT_HIDDEN$4 = `hidden${EVENT_KEY$4}`;\nconst EVENT_SHOW$4 = `show${EVENT_KEY$4}`;\nconst EVENT_SHOWN$4 = `shown${EVENT_KEY$4}`;\nconst EVENT_RESIZE$1 = `resize${EVENT_KEY$4}`;\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY$4}`;\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY$4}`;\nconst EVENT_KEYDOWN_DISMISS$1 = `keydown.dismiss${EVENT_KEY$4}`;\nconst EVENT_CLICK_DATA_API$2 = `click${EVENT_KEY$4}${DATA_API_KEY$2}`;\nconst CLASS_NAME_OPEN = 'modal-open';\nconst CLASS_NAME_FADE$3 = 'fade';\nconst CLASS_NAME_SHOW$4 = 'show';\nconst CLASS_NAME_STATIC = 'modal-static';\nconst OPEN_SELECTOR$1 = '.modal.show';\nconst SELECTOR_DIALOG = '.modal-dialog';\nconst SELECTOR_MODAL_BODY = '.modal-body';\nconst SELECTOR_DATA_TOGGLE$2 = '[data-bs-toggle=\"modal\"]';\nconst Default$6 = {\n backdrop: true,\n focus: true,\n keyboard: true\n};\nconst DefaultType$6 = {\n backdrop: '(boolean|string)',\n focus: 'boolean',\n keyboard: 'boolean'\n};\n\n/**\n * Class definition\n */\n\nclass Modal extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element);\n this._backdrop = this._initializeBackDrop();\n this._focustrap = this._initializeFocusTrap();\n this._isShown = false;\n this._isTransitioning = false;\n this._scrollBar = new ScrollBarHelper();\n this._addEventListeners();\n }\n\n // Getters\n static get Default() {\n return Default$6;\n }\n static get DefaultType() {\n return DefaultType$6;\n }\n static get NAME() {\n return NAME$7;\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget);\n }\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return;\n }\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$4, {\n relatedTarget\n });\n if (showEvent.defaultPrevented) {\n return;\n }\n this._isShown = true;\n this._isTransitioning = true;\n this._scrollBar.hide();\n document.body.classList.add(CLASS_NAME_OPEN);\n this._adjustDialog();\n this._backdrop.show(() => this._showElement(relatedTarget));\n }\n hide() {\n if (!this._isShown || this._isTransitioning) {\n return;\n }\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$4);\n if (hideEvent.defaultPrevented) {\n return;\n }\n this._isShown = false;\n this._isTransitioning = true;\n this._focustrap.deactivate();\n this._element.classList.remove(CLASS_NAME_SHOW$4);\n this._queueCallback(() => this._hideModal(), this._element, this._isAnimated());\n }\n dispose() {\n EventHandler.off(window, EVENT_KEY$4);\n EventHandler.off(this._dialog, EVENT_KEY$4);\n this._backdrop.dispose();\n this._focustrap.deactivate();\n super.dispose();\n }\n handleUpdate() {\n this._adjustDialog();\n }\n\n // Private\n _initializeBackDrop() {\n return new Backdrop({\n isVisible: Boolean(this._config.backdrop),\n // 'static' option will be translated to true, and booleans will keep their value,\n isAnimated: this._isAnimated()\n });\n }\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n });\n }\n _showElement(relatedTarget) {\n // try to append dynamic modal\n if (!document.body.contains(this._element)) {\n document.body.append(this._element);\n }\n this._element.style.display = 'block';\n this._element.removeAttribute('aria-hidden');\n this._element.setAttribute('aria-modal', true);\n this._element.setAttribute('role', 'dialog');\n this._element.scrollTop = 0;\n const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog);\n if (modalBody) {\n modalBody.scrollTop = 0;\n }\n reflow(this._element);\n this._element.classList.add(CLASS_NAME_SHOW$4);\n const transitionComplete = () => {\n if (this._config.focus) {\n this._focustrap.activate();\n }\n this._isTransitioning = false;\n EventHandler.trigger(this._element, EVENT_SHOWN$4, {\n relatedTarget\n });\n };\n this._queueCallback(transitionComplete, this._dialog, this._isAnimated());\n }\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS$1, event => {\n if (event.key !== ESCAPE_KEY$1) {\n return;\n }\n if (this._config.keyboard) {\n this.hide();\n return;\n }\n this._triggerBackdropTransition();\n });\n EventHandler.on(window, EVENT_RESIZE$1, () => {\n if (this._isShown && !this._isTransitioning) {\n this._adjustDialog();\n }\n });\n EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n if (this._element !== event.target || this._element !== event2.target) {\n return;\n }\n if (this._config.backdrop === 'static') {\n this._triggerBackdropTransition();\n return;\n }\n if (this._config.backdrop) {\n this.hide();\n }\n });\n });\n }\n _hideModal() {\n this._element.style.display = 'none';\n this._element.setAttribute('aria-hidden', true);\n this._element.removeAttribute('aria-modal');\n this._element.removeAttribute('role');\n this._isTransitioning = false;\n this._backdrop.hide(() => {\n document.body.classList.remove(CLASS_NAME_OPEN);\n this._resetAdjustments();\n this._scrollBar.reset();\n EventHandler.trigger(this._element, EVENT_HIDDEN$4);\n });\n }\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_FADE$3);\n }\n _triggerBackdropTransition() {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED$1);\n if (hideEvent.defaultPrevented) {\n return;\n }\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;\n const initialOverflowY = this._element.style.overflowY;\n // return if the following background transition hasn't yet completed\n if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n return;\n }\n if (!isModalOverflowing) {\n this._element.style.overflowY = 'hidden';\n }\n this._element.classList.add(CLASS_NAME_STATIC);\n this._queueCallback(() => {\n this._element.classList.remove(CLASS_NAME_STATIC);\n this._queueCallback(() => {\n this._element.style.overflowY = initialOverflowY;\n }, this._dialog);\n }, this._dialog);\n this._element.focus();\n }\n\n /**\n * The following methods are used to handle overflowing modals\n */\n\n _adjustDialog() {\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;\n const scrollbarWidth = this._scrollBar.getWidth();\n const isBodyOverflowing = scrollbarWidth > 0;\n if (isBodyOverflowing && !isModalOverflowing) {\n const property = isRTL() ? 'paddingLeft' : 'paddingRight';\n this._element.style[property] = `${scrollbarWidth}px`;\n }\n if (!isBodyOverflowing && isModalOverflowing) {\n const property = isRTL() ? 'paddingRight' : 'paddingLeft';\n this._element.style[property] = `${scrollbarWidth}px`;\n }\n }\n _resetAdjustments() {\n this._element.style.paddingLeft = '';\n this._element.style.paddingRight = '';\n }\n\n // Static\n static jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n const data = Modal.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config](relatedTarget);\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$2, SELECTOR_DATA_TOGGLE$2, function (event) {\n const target = SelectorEngine.getElementFromSelector(this);\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault();\n }\n EventHandler.one(target, EVENT_SHOW$4, showEvent => {\n if (showEvent.defaultPrevented) {\n // only register focus restorer if modal will actually get shown\n return;\n }\n EventHandler.one(target, EVENT_HIDDEN$4, () => {\n if (isVisible(this)) {\n this.focus();\n }\n });\n });\n\n // avoid conflict when clicking modal toggler while another one is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR$1);\n if (alreadyOpen) {\n Modal.getInstance(alreadyOpen).hide();\n }\n const data = Modal.getOrCreateInstance(target);\n data.toggle(this);\n});\nenableDismissTrigger(Modal);\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Modal);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap offcanvas.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$6 = 'offcanvas';\nconst DATA_KEY$3 = 'bs.offcanvas';\nconst EVENT_KEY$3 = `.${DATA_KEY$3}`;\nconst DATA_API_KEY$1 = '.data-api';\nconst EVENT_LOAD_DATA_API$2 = `load${EVENT_KEY$3}${DATA_API_KEY$1}`;\nconst ESCAPE_KEY = 'Escape';\nconst CLASS_NAME_SHOW$3 = 'show';\nconst CLASS_NAME_SHOWING$1 = 'showing';\nconst CLASS_NAME_HIDING = 'hiding';\nconst CLASS_NAME_BACKDROP = 'offcanvas-backdrop';\nconst OPEN_SELECTOR = '.offcanvas.show';\nconst EVENT_SHOW$3 = `show${EVENT_KEY$3}`;\nconst EVENT_SHOWN$3 = `shown${EVENT_KEY$3}`;\nconst EVENT_HIDE$3 = `hide${EVENT_KEY$3}`;\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY$3}`;\nconst EVENT_HIDDEN$3 = `hidden${EVENT_KEY$3}`;\nconst EVENT_RESIZE = `resize${EVENT_KEY$3}`;\nconst EVENT_CLICK_DATA_API$1 = `click${EVENT_KEY$3}${DATA_API_KEY$1}`;\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY$3}`;\nconst SELECTOR_DATA_TOGGLE$1 = '[data-bs-toggle=\"offcanvas\"]';\nconst Default$5 = {\n backdrop: true,\n keyboard: true,\n scroll: false\n};\nconst DefaultType$5 = {\n backdrop: '(boolean|string)',\n keyboard: 'boolean',\n scroll: 'boolean'\n};\n\n/**\n * Class definition\n */\n\nclass Offcanvas extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._isShown = false;\n this._backdrop = this._initializeBackDrop();\n this._focustrap = this._initializeFocusTrap();\n this._addEventListeners();\n }\n\n // Getters\n static get Default() {\n return Default$5;\n }\n static get DefaultType() {\n return DefaultType$5;\n }\n static get NAME() {\n return NAME$6;\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget);\n }\n show(relatedTarget) {\n if (this._isShown) {\n return;\n }\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$3, {\n relatedTarget\n });\n if (showEvent.defaultPrevented) {\n return;\n }\n this._isShown = true;\n this._backdrop.show();\n if (!this._config.scroll) {\n new ScrollBarHelper().hide();\n }\n this._element.setAttribute('aria-modal', true);\n this._element.setAttribute('role', 'dialog');\n this._element.classList.add(CLASS_NAME_SHOWING$1);\n const completeCallBack = () => {\n if (!this._config.scroll || this._config.backdrop) {\n this._focustrap.activate();\n }\n this._element.classList.add(CLASS_NAME_SHOW$3);\n this._element.classList.remove(CLASS_NAME_SHOWING$1);\n EventHandler.trigger(this._element, EVENT_SHOWN$3, {\n relatedTarget\n });\n };\n this._queueCallback(completeCallBack, this._element, true);\n }\n hide() {\n if (!this._isShown) {\n return;\n }\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$3);\n if (hideEvent.defaultPrevented) {\n return;\n }\n this._focustrap.deactivate();\n this._element.blur();\n this._isShown = false;\n this._element.classList.add(CLASS_NAME_HIDING);\n this._backdrop.hide();\n const completeCallback = () => {\n this._element.classList.remove(CLASS_NAME_SHOW$3, CLASS_NAME_HIDING);\n this._element.removeAttribute('aria-modal');\n this._element.removeAttribute('role');\n if (!this._config.scroll) {\n new ScrollBarHelper().reset();\n }\n EventHandler.trigger(this._element, EVENT_HIDDEN$3);\n };\n this._queueCallback(completeCallback, this._element, true);\n }\n dispose() {\n this._backdrop.dispose();\n this._focustrap.deactivate();\n super.dispose();\n }\n\n // Private\n _initializeBackDrop() {\n const clickCallback = () => {\n if (this._config.backdrop === 'static') {\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);\n return;\n }\n this.hide();\n };\n\n // 'static' option will be translated to true, and booleans will keep their value\n const isVisible = Boolean(this._config.backdrop);\n return new Backdrop({\n className: CLASS_NAME_BACKDROP,\n isVisible,\n isAnimated: true,\n rootElement: this._element.parentNode,\n clickCallback: isVisible ? clickCallback : null\n });\n }\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n });\n }\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return;\n }\n if (this._config.keyboard) {\n this.hide();\n return;\n }\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);\n });\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Offcanvas.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config](this);\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$1, SELECTOR_DATA_TOGGLE$1, function (event) {\n const target = SelectorEngine.getElementFromSelector(this);\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault();\n }\n if (isDisabled(this)) {\n return;\n }\n EventHandler.one(target, EVENT_HIDDEN$3, () => {\n // focus on trigger when it is closed\n if (isVisible(this)) {\n this.focus();\n }\n });\n\n // avoid conflict when clicking a toggler of an offcanvas, while another is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR);\n if (alreadyOpen && alreadyOpen !== target) {\n Offcanvas.getInstance(alreadyOpen).hide();\n }\n const data = Offcanvas.getOrCreateInstance(target);\n data.toggle(this);\n});\nEventHandler.on(window, EVENT_LOAD_DATA_API$2, () => {\n for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n Offcanvas.getOrCreateInstance(selector).show();\n }\n});\nEventHandler.on(window, EVENT_RESIZE, () => {\n for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n if (getComputedStyle(element).position !== 'fixed') {\n Offcanvas.getOrCreateInstance(element).hide();\n }\n }\n});\nenableDismissTrigger(Offcanvas);\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Offcanvas);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n// js-docs-start allow-list\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i;\nconst DefaultAllowlist = {\n // Global attributes allowed on any supplied element below.\n '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n a: ['target', 'href', 'title', 'rel'],\n area: [],\n b: [],\n br: [],\n col: [],\n code: [],\n dd: [],\n div: [],\n dl: [],\n dt: [],\n em: [],\n hr: [],\n h1: [],\n h2: [],\n h3: [],\n h4: [],\n h5: [],\n h6: [],\n i: [],\n img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n li: [],\n ol: [],\n p: [],\n pre: [],\n s: [],\n small: [],\n span: [],\n sub: [],\n sup: [],\n strong: [],\n u: [],\n ul: []\n};\n// js-docs-end allow-list\n\nconst uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']);\n\n/**\n * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation\n * contexts.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38\n */\n// eslint-disable-next-line unicorn/better-regex\nconst SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i;\nconst allowedAttribute = (attribute, allowedAttributeList) => {\n const attributeName = attribute.nodeName.toLowerCase();\n if (allowedAttributeList.includes(attributeName)) {\n if (uriAttributes.has(attributeName)) {\n return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue));\n }\n return true;\n }\n\n // Check if a regular expression validates the attribute.\n return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName));\n};\nfunction sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n if (!unsafeHtml.length) {\n return unsafeHtml;\n }\n if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n return sanitizeFunction(unsafeHtml);\n }\n const domParser = new window.DOMParser();\n const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html');\n const elements = [].concat(...createdDocument.body.querySelectorAll('*'));\n for (const element of elements) {\n const elementName = element.nodeName.toLowerCase();\n if (!Object.keys(allowList).includes(elementName)) {\n element.remove();\n continue;\n }\n const attributeList = [].concat(...element.attributes);\n const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []);\n for (const attribute of attributeList) {\n if (!allowedAttribute(attribute, allowedAttributes)) {\n element.removeAttribute(attribute.nodeName);\n }\n }\n }\n return createdDocument.body.innerHTML;\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/template-factory.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$5 = 'TemplateFactory';\nconst Default$4 = {\n allowList: DefaultAllowlist,\n content: {},\n // { selector : text , selector2 : text2 , }\n extraClass: '',\n html: false,\n sanitize: true,\n sanitizeFn: null,\n template: '
'\n};\nconst DefaultType$4 = {\n allowList: 'object',\n content: 'object',\n extraClass: '(string|function)',\n html: 'boolean',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n template: 'string'\n};\nconst DefaultContentType = {\n entry: '(string|element|function|null)',\n selector: '(string|element)'\n};\n\n/**\n * Class definition\n */\n\nclass TemplateFactory extends Config {\n constructor(config) {\n super();\n this._config = this._getConfig(config);\n }\n\n // Getters\n static get Default() {\n return Default$4;\n }\n static get DefaultType() {\n return DefaultType$4;\n }\n static get NAME() {\n return NAME$5;\n }\n\n // Public\n getContent() {\n return Object.values(this._config.content).map(config => this._resolvePossibleFunction(config)).filter(Boolean);\n }\n hasContent() {\n return this.getContent().length > 0;\n }\n changeContent(content) {\n this._checkContent(content);\n this._config.content = {\n ...this._config.content,\n ...content\n };\n return this;\n }\n toHtml() {\n const templateWrapper = document.createElement('div');\n templateWrapper.innerHTML = this._maybeSanitize(this._config.template);\n for (const [selector, text] of Object.entries(this._config.content)) {\n this._setContent(templateWrapper, text, selector);\n }\n const template = templateWrapper.children[0];\n const extraClass = this._resolvePossibleFunction(this._config.extraClass);\n if (extraClass) {\n template.classList.add(...extraClass.split(' '));\n }\n return template;\n }\n\n // Private\n _typeCheckConfig(config) {\n super._typeCheckConfig(config);\n this._checkContent(config.content);\n }\n _checkContent(arg) {\n for (const [selector, content] of Object.entries(arg)) {\n super._typeCheckConfig({\n selector,\n entry: content\n }, DefaultContentType);\n }\n }\n _setContent(template, content, selector) {\n const templateElement = SelectorEngine.findOne(selector, template);\n if (!templateElement) {\n return;\n }\n content = this._resolvePossibleFunction(content);\n if (!content) {\n templateElement.remove();\n return;\n }\n if (isElement(content)) {\n this._putElementInTemplate(getElement(content), templateElement);\n return;\n }\n if (this._config.html) {\n templateElement.innerHTML = this._maybeSanitize(content);\n return;\n }\n templateElement.textContent = content;\n }\n _maybeSanitize(arg) {\n return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg;\n }\n _resolvePossibleFunction(arg) {\n return execute(arg, [this]);\n }\n _putElementInTemplate(element, templateElement) {\n if (this._config.html) {\n templateElement.innerHTML = '';\n templateElement.append(element);\n return;\n }\n templateElement.textContent = element.textContent;\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$4 = 'tooltip';\nconst DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']);\nconst CLASS_NAME_FADE$2 = 'fade';\nconst CLASS_NAME_MODAL = 'modal';\nconst CLASS_NAME_SHOW$2 = 'show';\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner';\nconst SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`;\nconst EVENT_MODAL_HIDE = 'hide.bs.modal';\nconst TRIGGER_HOVER = 'hover';\nconst TRIGGER_FOCUS = 'focus';\nconst TRIGGER_CLICK = 'click';\nconst TRIGGER_MANUAL = 'manual';\nconst EVENT_HIDE$2 = 'hide';\nconst EVENT_HIDDEN$2 = 'hidden';\nconst EVENT_SHOW$2 = 'show';\nconst EVENT_SHOWN$2 = 'shown';\nconst EVENT_INSERTED = 'inserted';\nconst EVENT_CLICK$1 = 'click';\nconst EVENT_FOCUSIN$1 = 'focusin';\nconst EVENT_FOCUSOUT$1 = 'focusout';\nconst EVENT_MOUSEENTER = 'mouseenter';\nconst EVENT_MOUSELEAVE = 'mouseleave';\nconst AttachmentMap = {\n AUTO: 'auto',\n TOP: 'top',\n RIGHT: isRTL() ? 'left' : 'right',\n BOTTOM: 'bottom',\n LEFT: isRTL() ? 'right' : 'left'\n};\nconst Default$3 = {\n allowList: DefaultAllowlist,\n animation: true,\n boundary: 'clippingParents',\n container: false,\n customClass: '',\n delay: 0,\n fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n html: false,\n offset: [0, 6],\n placement: 'top',\n popperConfig: null,\n sanitize: true,\n sanitizeFn: null,\n selector: false,\n template: '
' + '
' + '
' + '
',\n title: '',\n trigger: 'hover focus'\n};\nconst DefaultType$3 = {\n allowList: 'object',\n animation: 'boolean',\n boundary: '(string|element)',\n container: '(string|element|boolean)',\n customClass: '(string|function)',\n delay: '(number|object)',\n fallbackPlacements: 'array',\n html: 'boolean',\n offset: '(array|string|function)',\n placement: '(string|function)',\n popperConfig: '(null|object|function)',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n selector: '(string|boolean)',\n template: 'string',\n title: '(string|element|function)',\n trigger: 'string'\n};\n\n/**\n * Class definition\n */\n\nclass Tooltip extends BaseComponent {\n constructor(element, config) {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org)');\n }\n super(element, config);\n\n // Private\n this._isEnabled = true;\n this._timeout = 0;\n this._isHovered = null;\n this._activeTrigger = {};\n this._popper = null;\n this._templateFactory = null;\n this._newContent = null;\n\n // Protected\n this.tip = null;\n this._setListeners();\n if (!this._config.selector) {\n this._fixTitle();\n }\n }\n\n // Getters\n static get Default() {\n return Default$3;\n }\n static get DefaultType() {\n return DefaultType$3;\n }\n static get NAME() {\n return NAME$4;\n }\n\n // Public\n enable() {\n this._isEnabled = true;\n }\n disable() {\n this._isEnabled = false;\n }\n toggleEnabled() {\n this._isEnabled = !this._isEnabled;\n }\n toggle() {\n if (!this._isEnabled) {\n return;\n }\n this._activeTrigger.click = !this._activeTrigger.click;\n if (this._isShown()) {\n this._leave();\n return;\n }\n this._enter();\n }\n dispose() {\n clearTimeout(this._timeout);\n EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);\n if (this._element.getAttribute('data-bs-original-title')) {\n this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'));\n }\n this._disposePopper();\n super.dispose();\n }\n show() {\n if (this._element.style.display === 'none') {\n throw new Error('Please use show on visible elements');\n }\n if (!(this._isWithContent() && this._isEnabled)) {\n return;\n }\n const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW$2));\n const shadowRoot = findShadowRoot(this._element);\n const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element);\n if (showEvent.defaultPrevented || !isInTheDom) {\n return;\n }\n\n // TODO: v6 remove this or make it optional\n this._disposePopper();\n const tip = this._getTipElement();\n this._element.setAttribute('aria-describedby', tip.getAttribute('id'));\n const {\n container\n } = this._config;\n if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n container.append(tip);\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED));\n }\n this._popper = this._createPopper(tip);\n tip.classList.add(CLASS_NAME_SHOW$2);\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop);\n }\n }\n const complete = () => {\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN$2));\n if (this._isHovered === false) {\n this._leave();\n }\n this._isHovered = false;\n };\n this._queueCallback(complete, this.tip, this._isAnimated());\n }\n hide() {\n if (!this._isShown()) {\n return;\n }\n const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE$2));\n if (hideEvent.defaultPrevented) {\n return;\n }\n const tip = this._getTipElement();\n tip.classList.remove(CLASS_NAME_SHOW$2);\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop);\n }\n }\n this._activeTrigger[TRIGGER_CLICK] = false;\n this._activeTrigger[TRIGGER_FOCUS] = false;\n this._activeTrigger[TRIGGER_HOVER] = false;\n this._isHovered = null; // it is a trick to support manual triggering\n\n const complete = () => {\n if (this._isWithActiveTrigger()) {\n return;\n }\n if (!this._isHovered) {\n this._disposePopper();\n }\n this._element.removeAttribute('aria-describedby');\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN$2));\n };\n this._queueCallback(complete, this.tip, this._isAnimated());\n }\n update() {\n if (this._popper) {\n this._popper.update();\n }\n }\n\n // Protected\n _isWithContent() {\n return Boolean(this._getTitle());\n }\n _getTipElement() {\n if (!this.tip) {\n this.tip = this._createTipElement(this._newContent || this._getContentForTemplate());\n }\n return this.tip;\n }\n _createTipElement(content) {\n const tip = this._getTemplateFactory(content).toHtml();\n\n // TODO: remove this check in v6\n if (!tip) {\n return null;\n }\n tip.classList.remove(CLASS_NAME_FADE$2, CLASS_NAME_SHOW$2);\n // TODO: v6 the following can be achieved with CSS only\n tip.classList.add(`bs-${this.constructor.NAME}-auto`);\n const tipId = getUID(this.constructor.NAME).toString();\n tip.setAttribute('id', tipId);\n if (this._isAnimated()) {\n tip.classList.add(CLASS_NAME_FADE$2);\n }\n return tip;\n }\n setContent(content) {\n this._newContent = content;\n if (this._isShown()) {\n this._disposePopper();\n this.show();\n }\n }\n _getTemplateFactory(content) {\n if (this._templateFactory) {\n this._templateFactory.changeContent(content);\n } else {\n this._templateFactory = new TemplateFactory({\n ...this._config,\n // the `content` var has to be after `this._config`\n // to override config.content in case of popover\n content,\n extraClass: this._resolvePossibleFunction(this._config.customClass)\n });\n }\n return this._templateFactory;\n }\n _getContentForTemplate() {\n return {\n [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n };\n }\n _getTitle() {\n return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title');\n }\n\n // Private\n _initializeOnDelegatedTarget(event) {\n return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig());\n }\n _isAnimated() {\n return this._config.animation || this.tip && this.tip.classList.contains(CLASS_NAME_FADE$2);\n }\n _isShown() {\n return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW$2);\n }\n _createPopper(tip) {\n const placement = execute(this._config.placement, [this, tip, this._element]);\n const attachment = AttachmentMap[placement.toUpperCase()];\n return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment));\n }\n _getOffset() {\n const {\n offset\n } = this._config;\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10));\n }\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element);\n }\n return offset;\n }\n _resolvePossibleFunction(arg) {\n return execute(arg, [this._element]);\n }\n _getPopperConfig(attachment) {\n const defaultBsPopperConfig = {\n placement: attachment,\n modifiers: [{\n name: 'flip',\n options: {\n fallbackPlacements: this._config.fallbackPlacements\n }\n }, {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }, {\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n }, {\n name: 'arrow',\n options: {\n element: `.${this.constructor.NAME}-arrow`\n }\n }, {\n name: 'preSetPlacement',\n enabled: true,\n phase: 'beforeMain',\n fn: data => {\n // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n this._getTipElement().setAttribute('data-popper-placement', data.state.placement);\n }\n }]\n };\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n };\n }\n _setListeners() {\n const triggers = this._config.trigger.split(' ');\n for (const trigger of triggers) {\n if (trigger === 'click') {\n EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK$1), this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event);\n context.toggle();\n });\n } else if (trigger !== TRIGGER_MANUAL) {\n const eventIn = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSEENTER) : this.constructor.eventName(EVENT_FOCUSIN$1);\n const eventOut = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSELEAVE) : this.constructor.eventName(EVENT_FOCUSOUT$1);\n EventHandler.on(this._element, eventIn, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event);\n context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true;\n context._enter();\n });\n EventHandler.on(this._element, eventOut, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event);\n context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] = context._element.contains(event.relatedTarget);\n context._leave();\n });\n }\n }\n this._hideModalHandler = () => {\n if (this._element) {\n this.hide();\n }\n };\n EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);\n }\n _fixTitle() {\n const title = this._element.getAttribute('title');\n if (!title) {\n return;\n }\n if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n this._element.setAttribute('aria-label', title);\n }\n this._element.setAttribute('data-bs-original-title', title); // DO NOT USE IT. Is only for backwards compatibility\n this._element.removeAttribute('title');\n }\n _enter() {\n if (this._isShown() || this._isHovered) {\n this._isHovered = true;\n return;\n }\n this._isHovered = true;\n this._setTimeout(() => {\n if (this._isHovered) {\n this.show();\n }\n }, this._config.delay.show);\n }\n _leave() {\n if (this._isWithActiveTrigger()) {\n return;\n }\n this._isHovered = false;\n this._setTimeout(() => {\n if (!this._isHovered) {\n this.hide();\n }\n }, this._config.delay.hide);\n }\n _setTimeout(handler, timeout) {\n clearTimeout(this._timeout);\n this._timeout = setTimeout(handler, timeout);\n }\n _isWithActiveTrigger() {\n return Object.values(this._activeTrigger).includes(true);\n }\n _getConfig(config) {\n const dataAttributes = Manipulator.getDataAttributes(this._element);\n for (const dataAttribute of Object.keys(dataAttributes)) {\n if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n delete dataAttributes[dataAttribute];\n }\n }\n config = {\n ...dataAttributes,\n ...(typeof config === 'object' && config ? config : {})\n };\n config = this._mergeConfigObj(config);\n config = this._configAfterMerge(config);\n this._typeCheckConfig(config);\n return config;\n }\n _configAfterMerge(config) {\n config.container = config.container === false ? document.body : getElement(config.container);\n if (typeof config.delay === 'number') {\n config.delay = {\n show: config.delay,\n hide: config.delay\n };\n }\n if (typeof config.title === 'number') {\n config.title = config.title.toString();\n }\n if (typeof config.content === 'number') {\n config.content = config.content.toString();\n }\n return config;\n }\n _getDelegateConfig() {\n const config = {};\n for (const [key, value] of Object.entries(this._config)) {\n if (this.constructor.Default[key] !== value) {\n config[key] = value;\n }\n }\n config.selector = false;\n config.trigger = 'manual';\n\n // In the future can be replaced with:\n // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n // `Object.fromEntries(keysWithDifferentValues)`\n return config;\n }\n _disposePopper() {\n if (this._popper) {\n this._popper.destroy();\n this._popper = null;\n }\n if (this.tip) {\n this.tip.remove();\n this.tip = null;\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tooltip.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n });\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tooltip);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$3 = 'popover';\nconst SELECTOR_TITLE = '.popover-header';\nconst SELECTOR_CONTENT = '.popover-body';\nconst Default$2 = {\n ...Tooltip.Default,\n content: '',\n offset: [0, 8],\n placement: 'right',\n template: '
' + '
' + '

' + '
' + '
',\n trigger: 'click'\n};\nconst DefaultType$2 = {\n ...Tooltip.DefaultType,\n content: '(null|string|element|function)'\n};\n\n/**\n * Class definition\n */\n\nclass Popover extends Tooltip {\n // Getters\n static get Default() {\n return Default$2;\n }\n static get DefaultType() {\n return DefaultType$2;\n }\n static get NAME() {\n return NAME$3;\n }\n\n // Overrides\n _isWithContent() {\n return this._getTitle() || this._getContent();\n }\n\n // Private\n _getContentForTemplate() {\n return {\n [SELECTOR_TITLE]: this._getTitle(),\n [SELECTOR_CONTENT]: this._getContent()\n };\n }\n _getContent() {\n return this._resolvePossibleFunction(this._config.content);\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Popover.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n });\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Popover);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$2 = 'scrollspy';\nconst DATA_KEY$2 = 'bs.scrollspy';\nconst EVENT_KEY$2 = `.${DATA_KEY$2}`;\nconst DATA_API_KEY = '.data-api';\nconst EVENT_ACTIVATE = `activate${EVENT_KEY$2}`;\nconst EVENT_CLICK = `click${EVENT_KEY$2}`;\nconst EVENT_LOAD_DATA_API$1 = `load${EVENT_KEY$2}${DATA_API_KEY}`;\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item';\nconst CLASS_NAME_ACTIVE$1 = 'active';\nconst SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]';\nconst SELECTOR_TARGET_LINKS = '[href]';\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group';\nconst SELECTOR_NAV_LINKS = '.nav-link';\nconst SELECTOR_NAV_ITEMS = '.nav-item';\nconst SELECTOR_LIST_ITEMS = '.list-group-item';\nconst SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`;\nconst SELECTOR_DROPDOWN = '.dropdown';\nconst SELECTOR_DROPDOWN_TOGGLE$1 = '.dropdown-toggle';\nconst Default$1 = {\n offset: null,\n // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: '0px 0px -25%',\n smoothScroll: false,\n target: null,\n threshold: [0.1, 0.5, 1]\n};\nconst DefaultType$1 = {\n offset: '(number|null)',\n // TODO v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: 'string',\n smoothScroll: 'boolean',\n target: 'element',\n threshold: 'array'\n};\n\n/**\n * Class definition\n */\n\nclass ScrollSpy extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n\n // this._element is the observablesContainer and config.target the menu links wrapper\n this._targetLinks = new Map();\n this._observableSections = new Map();\n this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element;\n this._activeTarget = null;\n this._observer = null;\n this._previousScrollData = {\n visibleEntryTop: 0,\n parentScrollTop: 0\n };\n this.refresh(); // initialize\n }\n\n // Getters\n static get Default() {\n return Default$1;\n }\n static get DefaultType() {\n return DefaultType$1;\n }\n static get NAME() {\n return NAME$2;\n }\n\n // Public\n refresh() {\n this._initializeTargetsAndObservables();\n this._maybeEnableSmoothScroll();\n if (this._observer) {\n this._observer.disconnect();\n } else {\n this._observer = this._getNewObserver();\n }\n for (const section of this._observableSections.values()) {\n this._observer.observe(section);\n }\n }\n dispose() {\n this._observer.disconnect();\n super.dispose();\n }\n\n // Private\n _configAfterMerge(config) {\n // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n config.target = getElement(config.target) || document.body;\n\n // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin;\n if (typeof config.threshold === 'string') {\n config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value));\n }\n return config;\n }\n _maybeEnableSmoothScroll() {\n if (!this._config.smoothScroll) {\n return;\n }\n\n // unregister any previous listeners\n EventHandler.off(this._config.target, EVENT_CLICK);\n EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n const observableSection = this._observableSections.get(event.target.hash);\n if (observableSection) {\n event.preventDefault();\n const root = this._rootElement || window;\n const height = observableSection.offsetTop - this._element.offsetTop;\n if (root.scrollTo) {\n root.scrollTo({\n top: height,\n behavior: 'smooth'\n });\n return;\n }\n\n // Chrome 60 doesn't support `scrollTo`\n root.scrollTop = height;\n }\n });\n }\n _getNewObserver() {\n const options = {\n root: this._rootElement,\n threshold: this._config.threshold,\n rootMargin: this._config.rootMargin\n };\n return new IntersectionObserver(entries => this._observerCallback(entries), options);\n }\n\n // The logic of selection\n _observerCallback(entries) {\n const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`);\n const activate = entry => {\n this._previousScrollData.visibleEntryTop = entry.target.offsetTop;\n this._process(targetElement(entry));\n };\n const parentScrollTop = (this._rootElement || document.documentElement).scrollTop;\n const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop;\n this._previousScrollData.parentScrollTop = parentScrollTop;\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n this._activeTarget = null;\n this._clearActiveClass(targetElement(entry));\n continue;\n }\n const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop;\n // if we are scrolling down, pick the bigger offsetTop\n if (userScrollsDown && entryIsLowerThanPrevious) {\n activate(entry);\n // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n if (!parentScrollTop) {\n return;\n }\n continue;\n }\n\n // if we are scrolling up, pick the smallest offsetTop\n if (!userScrollsDown && !entryIsLowerThanPrevious) {\n activate(entry);\n }\n }\n }\n _initializeTargetsAndObservables() {\n this._targetLinks = new Map();\n this._observableSections = new Map();\n const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target);\n for (const anchor of targetLinks) {\n // ensure that the anchor has an id and is not disabled\n if (!anchor.hash || isDisabled(anchor)) {\n continue;\n }\n const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element);\n\n // ensure that the observableSection exists & is visible\n if (isVisible(observableSection)) {\n this._targetLinks.set(decodeURI(anchor.hash), anchor);\n this._observableSections.set(anchor.hash, observableSection);\n }\n }\n }\n _process(target) {\n if (this._activeTarget === target) {\n return;\n }\n this._clearActiveClass(this._config.target);\n this._activeTarget = target;\n target.classList.add(CLASS_NAME_ACTIVE$1);\n this._activateParents(target);\n EventHandler.trigger(this._element, EVENT_ACTIVATE, {\n relatedTarget: target\n });\n }\n _activateParents(target) {\n // Activate dropdown parents\n if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE$1, target.closest(SELECTOR_DROPDOWN)).classList.add(CLASS_NAME_ACTIVE$1);\n return;\n }\n for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n // Set triggered links parents as active\n // With both