From 65f50d037d9006e0b1a6b16ca5d521ec32072ada Mon Sep 17 00:00:00 2001 From: ElliottKasoar <45317199+ElliottKasoar@users.noreply.github.com> Date: Tue, 4 Mar 2025 18:03:30 +0000 Subject: [PATCH 01/12] Set default output directory --- janus_core/helpers/utils.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/janus_core/helpers/utils.py b/janus_core/helpers/utils.py index abb549df..ab4070ba 100644 --- a/janus_core/helpers/utils.py +++ b/janus_core/helpers/utils.py @@ -67,7 +67,7 @@ def __init__( Components to add to default file_prefix (joined by hyphens). """ self.file_prefix = Path( - self._get_default_prefix(file_prefix, struct, struct_path, *additional) + self._get_default_prefix(file_prefix, struct, struct_path, *additional), ) @staticmethod @@ -78,12 +78,12 @@ def _get_default_prefix( *additional, ) -> str: """ - Determine the default prefix from the structure or provided file_prefix. + Determine the default prefix from the structure or provided file_prefix. Parameters ---------- file_prefix - Given file_prefix. + File prefix. struct Structure(s) from which to derive the default name. If `struct` is a sequence, the first structure will be used. @@ -108,7 +108,10 @@ def _get_default_prefix( else: struct_name = struct.get_chemical_formula() - return "-".join((struct_name, *filter(None, additional))) + # Set default output directory + prefix = Path("./results") / struct_name + + return "-".join((str(prefix), *filter(None, additional))) def _build_filename( self, From 34f188e27386af6d4c936fa48ff764e7b12880b8 Mon Sep 17 00:00:00 2001 From: ElliottKasoar <45317199+ElliottKasoar@users.noreply.github.com> Date: Tue, 4 Mar 2025 18:03:40 +0000 Subject: [PATCH 02/12] Test output directory --- tests/test_descriptors_cli.py | 6 +-- tests/test_eos_cli.py | 8 ++-- tests/test_geomopt_cli.py | 6 +-- tests/test_md.py | 60 ++++++++++++++-------------- tests/test_md_cli.py | 16 ++++---- tests/test_neb_cli.py | 10 ++--- tests/test_phonons_cli.py | 8 ++-- tests/test_post_process.py | 73 +++++++++++++++++++---------------- tests/test_single_point.py | 2 +- tests/test_singlepoint_cli.py | 6 +-- 10 files changed, 100 insertions(+), 95 deletions(-) diff --git a/tests/test_descriptors_cli.py b/tests/test_descriptors_cli.py index cbec3b31..ab3df3a6 100644 --- a/tests/test_descriptors_cli.py +++ b/tests/test_descriptors_cli.py @@ -26,9 +26,9 @@ def test_help(): def test_descriptors(): """Test calculating MLIP descriptors.""" - out_path = Path("./NaCl-descriptors.extxyz").absolute() - log_path = Path("./NaCl-descriptors-log.yml").absolute() - summary_path = Path("./NaCl-descriptors-summary.yml").absolute() + out_path = Path("./results/NaCl-descriptors.extxyz").absolute() + log_path = Path("./results/NaCl-descriptors-log.yml").absolute() + summary_path = Path("./results/NaCl-descriptors-summary.yml").absolute() assert not out_path.exists() assert not log_path.exists() diff --git a/tests/test_eos_cli.py b/tests/test_eos_cli.py index 2a21387d..99c32193 100644 --- a/tests/test_eos_cli.py +++ b/tests/test_eos_cli.py @@ -26,10 +26,10 @@ def test_help(): def test_eos(): """Test calculating the equation of state.""" - eos_raw_path = Path("./NaCl-eos-raw.dat").absolute() - eos_fit_path = Path("./NaCl-eos-fit.dat").absolute() - log_path = Path("./NaCl-eos-log.yml").absolute() - summary_path = Path("./NaCl-eos-summary.yml").absolute() + eos_raw_path = Path("./results/NaCl-eos-raw.dat").absolute() + eos_fit_path = Path("./results/NaCl-eos-fit.dat").absolute() + log_path = Path("./results/NaCl-eos-log.yml").absolute() + summary_path = Path("./results/NaCl-eos-summary.yml").absolute() assert not eos_raw_path.exists() assert not eos_fit_path.exists() diff --git a/tests/test_geomopt_cli.py b/tests/test_geomopt_cli.py index 4f17885b..0120e564 100644 --- a/tests/test_geomopt_cli.py +++ b/tests/test_geomopt_cli.py @@ -32,9 +32,9 @@ def test_help(): def test_geomopt(): """Test geomopt calculation.""" - results_path = Path("./NaCl-opt.extxyz").absolute() - log_path = Path("./NaCl-geomopt-log.yml").absolute() - summary_path = Path("./NaCl-geomopt-summary.yml").absolute() + results_path = Path("./results/NaCl-opt.extxyz").absolute() + log_path = Path("./results/NaCl-geomopt-log.yml").absolute() + summary_path = Path("./results/NaCl-geomopt-summary.yml").absolute() assert not results_path.exists() assert not log_path.exists() diff --git a/tests/test_md.py b/tests/test_md.py index 6c5f4524..00d385ba 100644 --- a/tests/test_md.py +++ b/tests/test_md.py @@ -75,11 +75,11 @@ def test_init(ensemble, expected): def test_npt(): """Test NPT molecular dynamics.""" - restart_path_1 = Path("Cl4Na4-npt-T300.0-p1.0-res-2.extxyz") - restart_path_2 = Path("Cl4Na4-npt-T300.0-p1.0-res-4.extxyz") - restart_final = Path("Cl4Na4-npt-T300.0-p1.0-final.extxyz") - traj_path = Path("Cl4Na4-npt-T300.0-p1.0-traj.extxyz") - stats_path = Path("Cl4Na4-npt-T300.0-p1.0-stats.dat") + restart_path_1 = Path("results/Cl4Na4-npt-T300.0-p1.0-res-2.extxyz") + restart_path_2 = Path("results/Cl4Na4-npt-T300.0-p1.0-res-4.extxyz") + restart_final = Path("results/Cl4Na4-npt-T300.0-p1.0-final.extxyz") + traj_path = Path("results/Cl4Na4-npt-T300.0-p1.0-traj.extxyz") + stats_path = Path("results/Cl4Na4-npt-T300.0-p1.0-stats.dat") assert not restart_path_1.exists() assert not restart_path_2.exists() @@ -128,10 +128,10 @@ def test_npt(): def test_nvt_nh(): """Test NVT-Nosé–Hoover molecular dynamics.""" - restart_path = Path("Cl4Na4-nvt-nh-T300.0-res-3.extxyz") - restart_final_path = Path("Cl4Na4-nvt-nh-T300.0-final.extxyz") - traj_path = Path("Cl4Na4-nvt-nh-T300.0-traj.extxyz") - stats_path = Path("Cl4Na4-nvt-nh-T300.0-stats.dat") + restart_path = Path("results/Cl4Na4-nvt-nh-T300.0-res-3.extxyz") + restart_final_path = Path("results/Cl4Na4-nvt-nh-T300.0-final.extxyz") + traj_path = Path("results/Cl4Na4-nvt-nh-T300.0-traj.extxyz") + stats_path = Path("results/Cl4Na4-nvt-nh-T300.0-stats.dat") assert not restart_path.exists() assert not restart_final_path.exists() @@ -210,10 +210,10 @@ def test_nve(tmp_path): def test_nph(): """Test NPH molecular dynamics.""" - restart_path = Path("Cl4Na4-nph-T300.0-p0.0-res-2.extxyz") - restart_final_path = Path("Cl4Na4-nph-T300.0-p0.0-final.extxyz") - traj_path = Path("Cl4Na4-nph-T300.0-p0.0-traj.extxyz") - stats_path = Path("Cl4Na4-nph-T300.0-p0.0-stats.dat") + restart_path = Path("results/Cl4Na4-nph-T300.0-p0.0-res-2.extxyz") + restart_final_path = Path("results/Cl4Na4-nph-T300.0-p0.0-final.extxyz") + traj_path = Path("results/Cl4Na4-nph-T300.0-p0.0-traj.extxyz") + stats_path = Path("results/Cl4Na4-nph-T300.0-p0.0-stats.dat") assert not restart_path.exists() assert not restart_final_path.exists() @@ -260,11 +260,11 @@ def test_nph(): def test_nvt_csvr(): """Test NVT CSVR molecular dynamics.""" - restart_path_1 = Path("NaCl-nvt-csvr-T300.0-res-2.extxyz") - restart_path_2 = Path("NaCl-nvt-csvr-T300.0-res-4.extxyz") - restart_final = Path("NaCl-nvt-csvr-T300.0-final.extxyz") - traj_path = Path("NaCl-nvt-csvr-T300.0-traj.extxyz") - stats_path = Path("NaCl-nvt-csvr-T300.0-stats.dat") + restart_path_1 = Path("results/NaCl-nvt-csvr-T300.0-res-2.extxyz") + restart_path_2 = Path("results/NaCl-nvt-csvr-T300.0-res-4.extxyz") + restart_final = Path("results/NaCl-nvt-csvr-T300.0-final.extxyz") + traj_path = Path("results/NaCl-nvt-csvr-T300.0-traj.extxyz") + stats_path = Path("results/NaCl-nvt-csvr-T300.0-stats.dat") assert not restart_path_1.exists() assert not restart_path_2.exists() @@ -311,11 +311,11 @@ def test_nvt_csvr(): @pytest.mark.skipif(MTK_IMPORT_FAILED, reason="Requires updated version of ASE") def test_npt_mtk(): """Test NPT MTK molecular dynamics.""" - restart_path_1 = Path("NaCl-npt-mtk-T300.0-p0.0001-res-2.extxyz") - restart_path_2 = Path("NaCl-npt-mtk-T300.0-p0.0001-res-4.extxyz") - restart_final = Path("NaCl-npt-mtk-T300.0-p0.0001-final.extxyz") - traj_path = Path("NaCl-npt-mtk-T300.0-p0.0001-traj.extxyz") - stats_path = Path("NaCl-npt-mtk-T300.0-p0.0001-stats.dat") + restart_path_1 = Path("results/NaCl-npt-mtk-T300.0-p0.0001-res-2.extxyz") + restart_path_2 = Path("results/NaCl-npt-mtk-T300.0-p0.0001-res-4.extxyz") + restart_final = Path("results/NaCl-npt-mtk-T300.0-p0.0001-final.extxyz") + traj_path = Path("results/NaCl-npt-mtk-T300.0-p0.0001-traj.extxyz") + stats_path = Path("results/NaCl-npt-mtk-T300.0-p0.0001-stats.dat") assert not restart_path_1.exists() assert not restart_path_2.exists() @@ -903,9 +903,9 @@ def test_heating_restart(tmp_path): def test_heating_files(): """Test default heating file names.""" - traj_heating_path = Path("Cl4Na4-nvt-T10-T20-traj.extxyz") - stats_heating_path = Path("Cl4Na4-nvt-T10-T20-stats.dat") - final_path = Path("Cl4Na4-nvt-T10-T20-final.extxyz") + traj_heating_path = Path("results/Cl4Na4-nvt-T10-T20-traj.extxyz") + stats_heating_path = Path("results/Cl4Na4-nvt-T10-T20-stats.dat") + final_path = Path("results/Cl4Na4-nvt-T10-T20-final.extxyz") assert not traj_heating_path.exists() assert not stats_heating_path.exists() @@ -951,9 +951,9 @@ def test_heating_files(): def test_heating_md_files(): """Test default heating files when also running md.""" - traj_heating_path = Path("Cl4Na4-nvt-T10-T20-T25.0-traj.extxyz") - stats_heating_path = Path("Cl4Na4-nvt-T10-T20-T25.0-stats.dat") - final_path = Path("Cl4Na4-nvt-T10-T20-T25.0-final.extxyz") + traj_heating_path = Path("results/Cl4Na4-nvt-T10-T20-T25.0-traj.extxyz") + stats_heating_path = Path("results/Cl4Na4-nvt-T10-T20-T25.0-stats.dat") + final_path = Path("results/Cl4Na4-nvt-T10-T20-T25.0-final.extxyz") assert not traj_heating_path.exists() assert not stats_heating_path.exists() @@ -1174,7 +1174,7 @@ def test_auto_restart(tmp_path): log_file = tmp_path / "md.log" # Predicted restart file, from defaults - restart_path = Path(".").absolute() / "NaCl-nvt-T300.0-res-4.extxyz" + restart_path = Path("./results").absolute() / "NaCl-nvt-T300.0-res-4.extxyz" assert not restart_path.exists() try: diff --git a/tests/test_md_cli.py b/tests/test_md_cli.py index 0cd0315c..2247831e 100644 --- a/tests/test_md_cli.py +++ b/tests/test_md_cli.py @@ -62,14 +62,14 @@ def test_md(ensemble): "npt-mtk": "NaCl-npt-mtk-T300.0-p0.0-", } - final_path = Path(f"{file_prefix[ensemble]}final.extxyz").absolute() - restart_path = Path(f"{file_prefix[ensemble]}res-2.extxyz").absolute() - stats_path = Path(f"{file_prefix[ensemble]}stats.dat").absolute() - traj_path = Path(f"{file_prefix[ensemble]}traj.extxyz").absolute() - rdf_path = Path(f"{file_prefix[ensemble]}rdf.dat").absolute() - vaf_path = Path(f"{file_prefix[ensemble]}vaf.dat").absolute() - log_path = Path(f"{file_prefix[ensemble]}md-log.yml").absolute() - summary_path = Path(f"{file_prefix[ensemble]}md-summary.yml").absolute() + final_path = Path(f"results/{file_prefix[ensemble]}final.extxyz").absolute() + restart_path = Path(f"results/{file_prefix[ensemble]}res-2.extxyz").absolute() + stats_path = Path(f"results/{file_prefix[ensemble]}stats.dat").absolute() + traj_path = Path(f"results/{file_prefix[ensemble]}traj.extxyz").absolute() + rdf_path = Path(f"results/{file_prefix[ensemble]}rdf.dat").absolute() + vaf_path = Path(f"results/{file_prefix[ensemble]}vaf.dat").absolute() + log_path = Path(f"results/{file_prefix[ensemble]}md-log.yml").absolute() + summary_path = Path(f"results/{file_prefix[ensemble]}md-summary.yml").absolute() assert not final_path.exists() assert not restart_path.exists() diff --git a/tests/test_neb_cli.py b/tests/test_neb_cli.py index 21bc7632..2ae887b4 100644 --- a/tests/test_neb_cli.py +++ b/tests/test_neb_cli.py @@ -27,11 +27,11 @@ def test_help(): def test_neb(): """Test calculating force constants and band structure.""" - results_path = Path("./LiFePO4_start-neb-results.dat").absolute() - band_path = Path("./LiFePO4_start-neb-band.extxyz").absolute() - plot_path = Path("./LiFePO4_start-neb-plot.svg").absolute() - log_path = Path("./LiFePO4_start-neb-log.yml").absolute() - summary_path = Path("./LiFePO4_start-neb-summary.yml").absolute() + results_path = Path("./results/LiFePO4_start-neb-results.dat").absolute() + band_path = Path("./results/LiFePO4_start-neb-band.extxyz").absolute() + plot_path = Path("./results/LiFePO4_start-neb-plot.svg").absolute() + log_path = Path("./results/LiFePO4_start-neb-log.yml").absolute() + summary_path = Path("./results/LiFePO4_start-neb-summary.yml").absolute() assert not results_path.exists() assert not band_path.exists() diff --git a/tests/test_phonons_cli.py b/tests/test_phonons_cli.py index b66c8d2a..6fbc19c9 100644 --- a/tests/test_phonons_cli.py +++ b/tests/test_phonons_cli.py @@ -26,10 +26,10 @@ def test_help(): def test_phonons(): """Test calculating force constants and band structure.""" - phonopy_path = Path("./NaCl-phonopy.yml").absolute() - bands_path = Path("./NaCl-auto_bands.yml.xz").absolute() - log_path = Path("./NaCl-phonons-log.yml").absolute() - summary_path = Path("./NaCl-phonons-summary.yml").absolute() + phonopy_path = Path("./results/NaCl-phonopy.yml").absolute() + bands_path = Path("./results/NaCl-auto_bands.yml.xz").absolute() + log_path = Path("./results/NaCl-phonons-log.yml").absolute() + summary_path = Path("./results/NaCl-phonons-summary.yml").absolute() assert not phonopy_path.exists() assert not bands_path.exists() diff --git a/tests/test_post_process.py b/tests/test_post_process.py index 97b91550..7781ba40 100644 --- a/tests/test_post_process.py +++ b/tests/test_post_process.py @@ -75,44 +75,49 @@ def test_md_pp_cli(tmp_path): rdf_path = tmp_path / "nve-T300-rdf.dat" vaf_na_path = Path("vaf_na.dat") vaf_cl_path = Path("vaf_cl.dat") - result = runner.invoke( - app, - [ - "md", - "--ensemble", - "nve", - "--struct", - DATA_PATH / "NaCl.cif", - "--file-prefix", - file_prefix, - "--steps", - 10, - "--traj-every", - 2, - "--log", - log_path, - "--summary", - summary_path, - "--post-process-kwargs", - """{'vaf_compute': True, - 'vaf_atoms': (('Na',),('Cl',)), - 'vaf_output_files': ('vaf_na.dat', 'vaf_cl.dat'), - 'rdf_compute': True, - 'rdf_rmax': 2.5}""", - ], - ) - assert result.exit_code == 0 + try: + result = runner.invoke( + app, + [ + "md", + "--ensemble", + "nve", + "--struct", + DATA_PATH / "NaCl.cif", + "--file-prefix", + file_prefix, + "--steps", + 10, + "--traj-every", + 2, + "--log", + log_path, + "--summary", + summary_path, + "--post-process-kwargs", + """{'vaf_compute': True, + 'vaf_atoms': (('Na',),('Cl',)), + 'vaf_output_files': ('vaf_na.dat', 'vaf_cl.dat'), + 'rdf_compute': True, + 'rdf_rmax': 2.5}""", + ], + ) - assert rdf_path.exists() - rdf = np.loadtxt(rdf_path) - assert len(rdf) == 50 + assert result.exit_code == 0 + + assert rdf_path.exists() + rdf = np.loadtxt(rdf_path) + assert len(rdf) == 50 - # Cell too small to really compute RDF - assert np.all(rdf[:, 1] == 0) + # Cell too small to really compute RDF + assert np.all(rdf[:, 1] == 0) - assert vaf_na_path.exists() - assert vaf_cl_path.exists() + assert vaf_na_path.exists() + assert vaf_cl_path.exists() + finally: + vaf_na_path.unlink(missing_ok=True) + vaf_cl_path.unlink(missing_ok=True) def test_rdf(): diff --git a/tests/test_single_point.py b/tests/test_single_point.py index b13650ab..b31f1c04 100644 --- a/tests/test_single_point.py +++ b/tests/test_single_point.py @@ -187,7 +187,7 @@ def test_single_point_write(): skip_extras("mace") data_path = DATA_PATH / "NaCl.cif" - results_path = Path("./NaCl-results.extxyz").absolute() + results_path = Path("./results/NaCl-results.extxyz").absolute() assert not results_path.exists() single_point = SinglePoint( diff --git a/tests/test_singlepoint_cli.py b/tests/test_singlepoint_cli.py index 099fe6b7..3acf0717 100644 --- a/tests/test_singlepoint_cli.py +++ b/tests/test_singlepoint_cli.py @@ -31,9 +31,9 @@ def test_singlepoint_help(): def test_singlepoint(): """Test singlepoint calculation.""" - results_path = Path("./NaCl-results.extxyz").absolute() - log_path = Path("./NaCl-singlepoint-log.yml").absolute() - summary_path = Path("./NaCl-singlepoint-summary.yml").absolute() + results_path = Path("./results/NaCl-results.extxyz").absolute() + log_path = Path("./results/NaCl-singlepoint-log.yml").absolute() + summary_path = Path("./results/NaCl-singlepoint-summary.yml").absolute() assert not results_path.exists() assert not log_path.exists() From a03895ee4c349d5b58898fe65976a679ea223daa Mon Sep 17 00:00:00 2001 From: ElliottKasoar <45317199+ElliottKasoar@users.noreply.github.com> Date: Wed, 5 Mar 2025 01:26:17 +0000 Subject: [PATCH 03/12] Rename output directory --- janus_core/helpers/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/janus_core/helpers/utils.py b/janus_core/helpers/utils.py index ab4070ba..04a555b4 100644 --- a/janus_core/helpers/utils.py +++ b/janus_core/helpers/utils.py @@ -109,7 +109,7 @@ def _get_default_prefix( struct_name = struct.get_chemical_formula() # Set default output directory - prefix = Path("./results") / struct_name + prefix = Path("./janus_results") / struct_name return "-".join((str(prefix), *filter(None, additional))) From 6dce4349e04ed4202f85376d93d9be6b0fe17b00 Mon Sep 17 00:00:00 2001 From: ElliottKasoar <45317199+ElliottKasoar@users.noreply.github.com> Date: Wed, 5 Mar 2025 01:26:25 +0000 Subject: [PATCH 04/12] Update tests --- tests/test_descriptors_cli.py | 6 ++-- tests/test_eos_cli.py | 8 ++--- tests/test_geomopt_cli.py | 6 ++-- tests/test_md.py | 60 +++++++++++++++++------------------ tests/test_md_cli.py | 16 +++++----- tests/test_neb_cli.py | 10 +++--- tests/test_phonons_cli.py | 8 ++--- tests/test_single_point.py | 2 +- tests/test_singlepoint_cli.py | 6 ++-- 9 files changed, 61 insertions(+), 61 deletions(-) diff --git a/tests/test_descriptors_cli.py b/tests/test_descriptors_cli.py index ab3df3a6..7e246750 100644 --- a/tests/test_descriptors_cli.py +++ b/tests/test_descriptors_cli.py @@ -26,9 +26,9 @@ def test_help(): def test_descriptors(): """Test calculating MLIP descriptors.""" - out_path = Path("./results/NaCl-descriptors.extxyz").absolute() - log_path = Path("./results/NaCl-descriptors-log.yml").absolute() - summary_path = Path("./results/NaCl-descriptors-summary.yml").absolute() + out_path = Path("./janus_results/NaCl-descriptors.extxyz") + log_path = Path("./janus_results/NaCl-descriptors-log.yml") + summary_path = Path("./janus_results/NaCl-descriptors-summary.yml") assert not out_path.exists() assert not log_path.exists() diff --git a/tests/test_eos_cli.py b/tests/test_eos_cli.py index 99c32193..981a7a1a 100644 --- a/tests/test_eos_cli.py +++ b/tests/test_eos_cli.py @@ -26,10 +26,10 @@ def test_help(): def test_eos(): """Test calculating the equation of state.""" - eos_raw_path = Path("./results/NaCl-eos-raw.dat").absolute() - eos_fit_path = Path("./results/NaCl-eos-fit.dat").absolute() - log_path = Path("./results/NaCl-eos-log.yml").absolute() - summary_path = Path("./results/NaCl-eos-summary.yml").absolute() + eos_raw_path = Path("./janus_results/NaCl-eos-raw.dat") + eos_fit_path = Path("./janus_results/NaCl-eos-fit.dat") + log_path = Path("./janus_results/NaCl-eos-log.yml") + summary_path = Path("./janus_results/NaCl-eos-summary.yml") assert not eos_raw_path.exists() assert not eos_fit_path.exists() diff --git a/tests/test_geomopt_cli.py b/tests/test_geomopt_cli.py index 0120e564..c8d20acc 100644 --- a/tests/test_geomopt_cli.py +++ b/tests/test_geomopt_cli.py @@ -32,9 +32,9 @@ def test_help(): def test_geomopt(): """Test geomopt calculation.""" - results_path = Path("./results/NaCl-opt.extxyz").absolute() - log_path = Path("./results/NaCl-geomopt-log.yml").absolute() - summary_path = Path("./results/NaCl-geomopt-summary.yml").absolute() + results_path = Path("./janus_results/NaCl-opt.extxyz") + log_path = Path("./janus_results/NaCl-geomopt-log.yml") + summary_path = Path("./janus_results/NaCl-geomopt-summary.yml") assert not results_path.exists() assert not log_path.exists() diff --git a/tests/test_md.py b/tests/test_md.py index 00d385ba..460aa537 100644 --- a/tests/test_md.py +++ b/tests/test_md.py @@ -75,11 +75,11 @@ def test_init(ensemble, expected): def test_npt(): """Test NPT molecular dynamics.""" - restart_path_1 = Path("results/Cl4Na4-npt-T300.0-p1.0-res-2.extxyz") - restart_path_2 = Path("results/Cl4Na4-npt-T300.0-p1.0-res-4.extxyz") - restart_final = Path("results/Cl4Na4-npt-T300.0-p1.0-final.extxyz") - traj_path = Path("results/Cl4Na4-npt-T300.0-p1.0-traj.extxyz") - stats_path = Path("results/Cl4Na4-npt-T300.0-p1.0-stats.dat") + restart_path_1 = Path("./janus_results/Cl4Na4-npt-T300.0-p1.0-res-2.extxyz") + restart_path_2 = Path("./janus_results/Cl4Na4-npt-T300.0-p1.0-res-4.extxyz") + restart_final = Path("./janus_results/Cl4Na4-npt-T300.0-p1.0-final.extxyz") + traj_path = Path("./janus_results/Cl4Na4-npt-T300.0-p1.0-traj.extxyz") + stats_path = Path("./janus_results/Cl4Na4-npt-T300.0-p1.0-stats.dat") assert not restart_path_1.exists() assert not restart_path_2.exists() @@ -128,10 +128,10 @@ def test_npt(): def test_nvt_nh(): """Test NVT-Nosé–Hoover molecular dynamics.""" - restart_path = Path("results/Cl4Na4-nvt-nh-T300.0-res-3.extxyz") - restart_final_path = Path("results/Cl4Na4-nvt-nh-T300.0-final.extxyz") - traj_path = Path("results/Cl4Na4-nvt-nh-T300.0-traj.extxyz") - stats_path = Path("results/Cl4Na4-nvt-nh-T300.0-stats.dat") + restart_path = Path("./janus_results/Cl4Na4-nvt-nh-T300.0-res-3.extxyz") + restart_final_path = Path("./janus_results/Cl4Na4-nvt-nh-T300.0-final.extxyz") + traj_path = Path("./janus_results/Cl4Na4-nvt-nh-T300.0-traj.extxyz") + stats_path = Path("./janus_results/Cl4Na4-nvt-nh-T300.0-stats.dat") assert not restart_path.exists() assert not restart_final_path.exists() @@ -210,10 +210,10 @@ def test_nve(tmp_path): def test_nph(): """Test NPH molecular dynamics.""" - restart_path = Path("results/Cl4Na4-nph-T300.0-p0.0-res-2.extxyz") - restart_final_path = Path("results/Cl4Na4-nph-T300.0-p0.0-final.extxyz") - traj_path = Path("results/Cl4Na4-nph-T300.0-p0.0-traj.extxyz") - stats_path = Path("results/Cl4Na4-nph-T300.0-p0.0-stats.dat") + restart_path = Path("./janus_results/Cl4Na4-nph-T300.0-p0.0-res-2.extxyz") + restart_final_path = Path("./janus_results/Cl4Na4-nph-T300.0-p0.0-final.extxyz") + traj_path = Path("./janus_results/Cl4Na4-nph-T300.0-p0.0-traj.extxyz") + stats_path = Path("./janus_results/Cl4Na4-nph-T300.0-p0.0-stats.dat") assert not restart_path.exists() assert not restart_final_path.exists() @@ -260,11 +260,11 @@ def test_nph(): def test_nvt_csvr(): """Test NVT CSVR molecular dynamics.""" - restart_path_1 = Path("results/NaCl-nvt-csvr-T300.0-res-2.extxyz") - restart_path_2 = Path("results/NaCl-nvt-csvr-T300.0-res-4.extxyz") - restart_final = Path("results/NaCl-nvt-csvr-T300.0-final.extxyz") - traj_path = Path("results/NaCl-nvt-csvr-T300.0-traj.extxyz") - stats_path = Path("results/NaCl-nvt-csvr-T300.0-stats.dat") + restart_path_1 = Path("./janus_results/NaCl-nvt-csvr-T300.0-res-2.extxyz") + restart_path_2 = Path("./janus_results/NaCl-nvt-csvr-T300.0-res-4.extxyz") + restart_final = Path("./janus_results/NaCl-nvt-csvr-T300.0-final.extxyz") + traj_path = Path("./janus_results/NaCl-nvt-csvr-T300.0-traj.extxyz") + stats_path = Path("./janus_results/NaCl-nvt-csvr-T300.0-stats.dat") assert not restart_path_1.exists() assert not restart_path_2.exists() @@ -311,11 +311,11 @@ def test_nvt_csvr(): @pytest.mark.skipif(MTK_IMPORT_FAILED, reason="Requires updated version of ASE") def test_npt_mtk(): """Test NPT MTK molecular dynamics.""" - restart_path_1 = Path("results/NaCl-npt-mtk-T300.0-p0.0001-res-2.extxyz") - restart_path_2 = Path("results/NaCl-npt-mtk-T300.0-p0.0001-res-4.extxyz") - restart_final = Path("results/NaCl-npt-mtk-T300.0-p0.0001-final.extxyz") - traj_path = Path("results/NaCl-npt-mtk-T300.0-p0.0001-traj.extxyz") - stats_path = Path("results/NaCl-npt-mtk-T300.0-p0.0001-stats.dat") + restart_path_1 = Path("./janus_results/NaCl-npt-mtk-T300.0-p0.0001-res-2.extxyz") + restart_path_2 = Path("./janus_results/NaCl-npt-mtk-T300.0-p0.0001-res-4.extxyz") + restart_final = Path("./janus_results/NaCl-npt-mtk-T300.0-p0.0001-final.extxyz") + traj_path = Path("./janus_results/NaCl-npt-mtk-T300.0-p0.0001-traj.extxyz") + stats_path = Path("./janus_results/NaCl-npt-mtk-T300.0-p0.0001-stats.dat") assert not restart_path_1.exists() assert not restart_path_2.exists() @@ -903,9 +903,9 @@ def test_heating_restart(tmp_path): def test_heating_files(): """Test default heating file names.""" - traj_heating_path = Path("results/Cl4Na4-nvt-T10-T20-traj.extxyz") - stats_heating_path = Path("results/Cl4Na4-nvt-T10-T20-stats.dat") - final_path = Path("results/Cl4Na4-nvt-T10-T20-final.extxyz") + traj_heating_path = Path("./janus_results/Cl4Na4-nvt-T10-T20-traj.extxyz") + stats_heating_path = Path("./janus_results/Cl4Na4-nvt-T10-T20-stats.dat") + final_path = Path("./janus_results/Cl4Na4-nvt-T10-T20-final.extxyz") assert not traj_heating_path.exists() assert not stats_heating_path.exists() @@ -951,9 +951,9 @@ def test_heating_files(): def test_heating_md_files(): """Test default heating files when also running md.""" - traj_heating_path = Path("results/Cl4Na4-nvt-T10-T20-T25.0-traj.extxyz") - stats_heating_path = Path("results/Cl4Na4-nvt-T10-T20-T25.0-stats.dat") - final_path = Path("results/Cl4Na4-nvt-T10-T20-T25.0-final.extxyz") + traj_heating_path = Path("./janus_results/Cl4Na4-nvt-T10-T20-T25.0-traj.extxyz") + stats_heating_path = Path("./janus_results/Cl4Na4-nvt-T10-T20-T25.0-stats.dat") + final_path = Path("./janus_results/Cl4Na4-nvt-T10-T20-T25.0-final.extxyz") assert not traj_heating_path.exists() assert not stats_heating_path.exists() @@ -1174,7 +1174,7 @@ def test_auto_restart(tmp_path): log_file = tmp_path / "md.log" # Predicted restart file, from defaults - restart_path = Path("./results").absolute() / "NaCl-nvt-T300.0-res-4.extxyz" + restart_path = Path("./janus_results") / "NaCl-nvt-T300.0-res-4.extxyz" assert not restart_path.exists() try: diff --git a/tests/test_md_cli.py b/tests/test_md_cli.py index 2247831e..ab2b65ac 100644 --- a/tests/test_md_cli.py +++ b/tests/test_md_cli.py @@ -62,14 +62,14 @@ def test_md(ensemble): "npt-mtk": "NaCl-npt-mtk-T300.0-p0.0-", } - final_path = Path(f"results/{file_prefix[ensemble]}final.extxyz").absolute() - restart_path = Path(f"results/{file_prefix[ensemble]}res-2.extxyz").absolute() - stats_path = Path(f"results/{file_prefix[ensemble]}stats.dat").absolute() - traj_path = Path(f"results/{file_prefix[ensemble]}traj.extxyz").absolute() - rdf_path = Path(f"results/{file_prefix[ensemble]}rdf.dat").absolute() - vaf_path = Path(f"results/{file_prefix[ensemble]}vaf.dat").absolute() - log_path = Path(f"results/{file_prefix[ensemble]}md-log.yml").absolute() - summary_path = Path(f"results/{file_prefix[ensemble]}md-summary.yml").absolute() + final_path = Path(f"./janus_results/{file_prefix[ensemble]}final.extxyz") + restart_path = Path(f"./janus_results/{file_prefix[ensemble]}res-2.extxyz") + stats_path = Path(f"./janus_results/{file_prefix[ensemble]}stats.dat") + traj_path = Path(f"./janus_results/{file_prefix[ensemble]}traj.extxyz") + rdf_path = Path(f"./janus_results/{file_prefix[ensemble]}rdf.dat") + vaf_path = Path(f"./janus_results/{file_prefix[ensemble]}vaf.dat") + log_path = Path(f"./janus_results/{file_prefix[ensemble]}md-log.yml") + summary_path = Path(f"./janus_results/{file_prefix[ensemble]}md-summary.yml") assert not final_path.exists() assert not restart_path.exists() diff --git a/tests/test_neb_cli.py b/tests/test_neb_cli.py index 2ae887b4..745fc17e 100644 --- a/tests/test_neb_cli.py +++ b/tests/test_neb_cli.py @@ -27,11 +27,11 @@ def test_help(): def test_neb(): """Test calculating force constants and band structure.""" - results_path = Path("./results/LiFePO4_start-neb-results.dat").absolute() - band_path = Path("./results/LiFePO4_start-neb-band.extxyz").absolute() - plot_path = Path("./results/LiFePO4_start-neb-plot.svg").absolute() - log_path = Path("./results/LiFePO4_start-neb-log.yml").absolute() - summary_path = Path("./results/LiFePO4_start-neb-summary.yml").absolute() + results_path = Path("./janus_results/LiFePO4_start-neb-results.dat") + band_path = Path("./janus_results/LiFePO4_start-neb-band.extxyz") + plot_path = Path("./janus_results/LiFePO4_start-neb-plot.svg") + log_path = Path("./janus_results/LiFePO4_start-neb-log.yml") + summary_path = Path("./janus_results/LiFePO4_start-neb-summary.yml") assert not results_path.exists() assert not band_path.exists() diff --git a/tests/test_phonons_cli.py b/tests/test_phonons_cli.py index 6fbc19c9..cc252d81 100644 --- a/tests/test_phonons_cli.py +++ b/tests/test_phonons_cli.py @@ -26,10 +26,10 @@ def test_help(): def test_phonons(): """Test calculating force constants and band structure.""" - phonopy_path = Path("./results/NaCl-phonopy.yml").absolute() - bands_path = Path("./results/NaCl-auto_bands.yml.xz").absolute() - log_path = Path("./results/NaCl-phonons-log.yml").absolute() - summary_path = Path("./results/NaCl-phonons-summary.yml").absolute() + phonopy_path = Path("./janus_results/NaCl-phonopy.yml") + bands_path = Path("./janus_results/NaCl-auto_bands.yml.xz") + log_path = Path("./janus_results/NaCl-phonons-log.yml") + summary_path = Path("./janus_results/NaCl-phonons-summary.yml") assert not phonopy_path.exists() assert not bands_path.exists() diff --git a/tests/test_single_point.py b/tests/test_single_point.py index b31f1c04..b0059b37 100644 --- a/tests/test_single_point.py +++ b/tests/test_single_point.py @@ -187,7 +187,7 @@ def test_single_point_write(): skip_extras("mace") data_path = DATA_PATH / "NaCl.cif" - results_path = Path("./results/NaCl-results.extxyz").absolute() + results_path = Path("./janus_results/NaCl-results.extxyz") assert not results_path.exists() single_point = SinglePoint( diff --git a/tests/test_singlepoint_cli.py b/tests/test_singlepoint_cli.py index 3acf0717..adea2222 100644 --- a/tests/test_singlepoint_cli.py +++ b/tests/test_singlepoint_cli.py @@ -31,9 +31,9 @@ def test_singlepoint_help(): def test_singlepoint(): """Test singlepoint calculation.""" - results_path = Path("./results/NaCl-results.extxyz").absolute() - log_path = Path("./results/NaCl-singlepoint-log.yml").absolute() - summary_path = Path("./results/NaCl-singlepoint-summary.yml").absolute() + results_path = Path("./janus_results/NaCl-results.extxyz") + log_path = Path("./janus_results/NaCl-singlepoint-log.yml") + summary_path = Path("./janus_results/NaCl-singlepoint-summary.yml") assert not results_path.exists() assert not log_path.exists() From cb4ca784d14bc49b7cd885621f7a6c01ed33eb02 Mon Sep 17 00:00:00 2001 From: ElliottKasoar <45317199+ElliottKasoar@users.noreply.github.com> Date: Wed, 5 Mar 2025 01:39:28 +0000 Subject: [PATCH 05/12] Update Mixin tests --- tests/test_filenamemixin.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/tests/test_filenamemixin.py b/tests/test_filenamemixin.py index 2de75d91..12d49978 100644 --- a/tests/test_filenamemixin.py +++ b/tests/test_filenamemixin.py @@ -25,17 +25,17 @@ def build_filename(self, *args, **kwargs): "params,file_prefix", ( # Defaults to structure atoms from ASE - ((STRUCT, None, None), "C6H6"), + ((STRUCT, None, None), "janus_results/C6H6"), # Defaults to stem over ASE structure - ((STRUCT, "mybenzene", None), "mybenzene"), + ((STRUCT, "mybenzene", None), "janus_results/mybenzene"), # file_prefix just sets itself ((STRUCT, "mybenzene", "benzene"), "benzene"), # file_prefix ignores additional ((STRUCT, None, "benzene", "wowzers"), "benzene"), # Additional only applies where no file_prefix - ((STRUCT, None, None, "wowzers"), "C6H6-wowzers"), + ((STRUCT, None, None, "wowzers"), "janus_results/C6H6-wowzers"), # Additional only applies where no file_prefix - ((STRUCT, "mybenzene", None, "wowzers"), "mybenzene-wowzers"), + ((STRUCT, "mybenzene", None, "wowzers"), "janus_results/mybenzene-wowzers"), ), ) def test_file_name_mixin_init(params, file_prefix): @@ -48,22 +48,27 @@ def test_file_name_mixin_init(params, file_prefix): @pytest.mark.parametrize( "mixin_params,file_args,file_kwargs,file_name", ( - ((STRUCT, None, None), ("data.xyz",), {}, "C6H6-data.xyz"), + ((STRUCT, None, None), ("data.xyz",), {}, "janus_results/C6H6-data.xyz"), ((STRUCT, None, "benzene"), ("data.xyz",), {}, "benzene-data.xyz"), ((STRUCT, None, "benzene", "wowzers"), ("data.xyz",), {}, "benzene-data.xyz"), - ((STRUCT, None, None, "wowzers"), ("data.xyz",), {}, "C6H6-wowzers-data.xyz"), + ( + (STRUCT, None, None, "wowzers"), + ("data.xyz",), + {}, + "janus_results/C6H6-wowzers-data.xyz", + ), # Additional stacks with base ( (STRUCT, None, None, "wowzers"), ("data.xyz", "beef"), {}, - "C6H6-wowzers-beef-data.xyz", + "janus_results/C6H6-wowzers-beef-data.xyz", ), ( (STRUCT, "mybenzene", None, "wowzers"), ("data.xyz", "beef"), {}, - "mybenzene-wowzers-beef-data.xyz", + "janus_results/mybenzene-wowzers-beef-data.xyz", ), # but not file_prefix ( From 26d236704cb90678188fc795b05227006e3b1253 Mon Sep 17 00:00:00 2001 From: ElliottKasoar <45317199+ElliottKasoar@users.noreply.github.com> Date: Wed, 5 Mar 2025 01:57:04 +0000 Subject: [PATCH 06/12] Update training and preprocess files --- janus_core/cli/preprocess.py | 4 ++-- janus_core/cli/train.py | 6 ++++-- tests/test_preprocess_cli.py | 4 ++-- tests/test_train_cli.py | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/janus_core/cli/preprocess.py b/janus_core/cli/preprocess.py index 2eac487d..3f8324fb 100644 --- a/janus_core/cli/preprocess.py +++ b/janus_core/cli/preprocess.py @@ -16,7 +16,7 @@ def preprocess( Path, Option(help="Configuration file to pass to MLIP CLI.") ], log: Annotated[Path, Option(help="Path to save logs to.")] = Path( - "preprocess-log.yml" + "./janus_results/preprocess-log.yml" ), tracker: Annotated[ bool, Option(help="Whether to save carbon emissions of calculation") @@ -28,7 +28,7 @@ def preprocess( "Path to save summary of inputs, start/end time, and carbon emissions." ) ), - ] = Path("preprocess-summary.yml"), + ] = Path("./janus_results/preprocess-summary.yml"), ): """ Convert training data to hdf5 by passing a configuration file to the MLIP's CLI. diff --git a/janus_core/cli/train.py b/janus_core/cli/train.py index 52077dfa..affb05fe 100644 --- a/janus_core/cli/train.py +++ b/janus_core/cli/train.py @@ -19,7 +19,9 @@ def train( fine_tune: Annotated[ bool, Option(help="Whether to fine-tune a foundational model.") ] = False, - log: Annotated[Path, Option(help="Path to save logs to.")] = Path("train-log.yml"), + log: Annotated[Path, Option(help="Path to save logs to.")] = Path( + "./janus_results/train-log.yml" + ), tracker: Annotated[ bool, Option(help="Whether to save carbon emissions of calculation") ] = True, @@ -30,7 +32,7 @@ def train( "Path to save summary of inputs, start/end time, and carbon emissions." ) ), - ] = Path("train-summary.yml"), + ] = Path("./janus_results/train-summary.yml"), ) -> None: """ Run training for MLIP by passing a configuration file to the MLIP's CLI. diff --git a/tests/test_preprocess_cli.py b/tests/test_preprocess_cli.py index 37129a7c..bb6fb2b3 100644 --- a/tests/test_preprocess_cli.py +++ b/tests/test_preprocess_cli.py @@ -62,8 +62,8 @@ def test_preprocess(tmp_path): val_path = Path("val") test_path = Path("test") stats_path = Path("./statistics.json") - log_path = Path("./preprocess-log.yml").absolute() - summary_path = Path("./preprocess-summary.yml").absolute() + log_path = Path("./janus_results/preprocess-log.yml") + summary_path = Path("./janus_results/preprocess-summary.yml") assert not train_path.exists() assert not val_path.exists() diff --git a/tests/test_train_cli.py b/tests/test_train_cli.py index db9db2d5..b451cbb0 100644 --- a/tests/test_train_cli.py +++ b/tests/test_train_cli.py @@ -70,8 +70,8 @@ def test_train(tmp_path): results_path = Path("results") checkpoints_path = Path("checkpoints") logs_path = Path("logs") - log_path = Path("./train-log.yml").absolute() - summary_path = Path("./train-summary.yml").absolute() + log_path = Path("./janus_results/train-log.yml") + summary_path = Path("./janus_results/train-summary.yml") assert not model.exists() assert not compiled_model.exists() From cfda1fe27272bbea244208890eb9f7757aad171b Mon Sep 17 00:00:00 2001 From: ElliottKasoar <45317199+ElliottKasoar@users.noreply.github.com> Date: Wed, 5 Mar 2025 01:58:19 +0000 Subject: [PATCH 07/12] Fix phonons test --- tests/test_phonons.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_phonons.py b/tests/test_phonons.py index a59e8e28..9e75d3e1 100644 --- a/tests/test_phonons.py +++ b/tests/test_phonons.py @@ -24,7 +24,7 @@ def test_init(): calc_kwargs={"model": MODEL_PATH}, ) phonons = Phonons(struct=single_point.struct) - assert str(phonons.file_prefix) == "Cl4Na4" + assert str(phonons.file_prefix) == "janus_results/Cl4Na4" def test_calc_phonons(): From bc0397eb626c4de45d67832c4e16c0eda5cdfb07 Mon Sep 17 00:00:00 2001 From: ElliottKasoar <45317199+ElliottKasoar@users.noreply.github.com> Date: Wed, 5 Mar 2025 12:11:01 +0000 Subject: [PATCH 08/12] Update janus_core/helpers/utils.py Co-authored-by: Jacob Wilkins <46597752+oerc0122@users.noreply.github.com> --- janus_core/helpers/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/janus_core/helpers/utils.py b/janus_core/helpers/utils.py index 04a555b4..69181b4b 100644 --- a/janus_core/helpers/utils.py +++ b/janus_core/helpers/utils.py @@ -83,7 +83,8 @@ def _get_default_prefix( Parameters ---------- file_prefix - File prefix. + Name to be given to any result files. May also include path to file + directory. struct Structure(s) from which to derive the default name. If `struct` is a sequence, the first structure will be used. From 861532b5fbf62248ed801a90597723e316b81c63 Mon Sep 17 00:00:00 2001 From: ElliottKasoar <45317199+ElliottKasoar@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:02:21 +0000 Subject: [PATCH 09/12] Update file_prefix docstrings and help --- janus_core/cli/eos.py | 23 +++++++---------------- janus_core/cli/geomopt.py | 19 +++++++++---------- janus_core/cli/md.py | 22 +++++++--------------- janus_core/cli/neb.py | 23 +++++++---------------- janus_core/cli/phonons.py | 22 +++++++--------------- janus_core/cli/singlepoint.py | 22 ++++++++++------------ janus_core/cli/types.py | 22 +++++++++++++++------- 7 files changed, 62 insertions(+), 91 deletions(-) diff --git a/janus_core/cli/eos.py b/janus_core/cli/eos.py index f9202c0d..52133ef5 100644 --- a/janus_core/cli/eos.py +++ b/janus_core/cli/eos.py @@ -2,7 +2,6 @@ from __future__ import annotations -from pathlib import Path from typing import Annotated, get_args from typer import Context, Option, Typer @@ -12,6 +11,7 @@ Architecture, CalcKwargs, Device, + FilePrefix, LogPath, MinimizeKwargs, ModelPath, @@ -62,17 +62,7 @@ def eos( model_path: ModelPath = None, read_kwargs: ReadKwargsLast = None, calc_kwargs: CalcKwargs = None, - file_prefix: Annotated[ - Path | None, - Option( - help=( - """ - Prefix for output filenames. Default is inferred from structure name, - or chemical formula. - """ - ), - ), - ] = None, + file_prefix: FilePrefix = None, log: LogPath = None, tracker: Annotated[ bool, Option(help="Whether to save carbon emissions of calculation") @@ -125,16 +115,17 @@ def eos( calc_kwargs Keyword arguments to pass to the selected calculator. Default is {}. file_prefix - Prefix for output filenames. Default is inferred from structure name, or - chemical formula. + Prefix for output files, including directories. Default directory is + ./janus_results, and default filename prefix is inferred from the input + stucture filename. log - Path to write logs to. Default is inferred from the name of the structure file. + Path to write logs to. Default is inferred from `file_prefix`. tracker Whether to save carbon emissions of calculation in log file and summary. Default is True. summary Path to save summary of inputs, start/end time, and carbon emissions. Default - is inferred from the name of the structure file. + is inferred from `file_prefix`. config Path to yaml configuration file to define the above options. Default is None. """ diff --git a/janus_core/cli/geomopt.py b/janus_core/cli/geomopt.py index 4adb89bd..eba1a8a8 100644 --- a/janus_core/cli/geomopt.py +++ b/janus_core/cli/geomopt.py @@ -12,6 +12,7 @@ Architecture, CalcKwargs, Device, + FilePrefix, LogPath, MinimizeKwargs, ModelPath, @@ -115,16 +116,12 @@ def geomopt( help="Atom displacement tolerance for spglib symmetry determination, in Å." ), ] = 0.001, - file_prefix: Annotated[ - Path | None, - Option(help="Prefix for output filenames. Default is inferred from structure."), - ] = None, + file_prefix: FilePrefix = None, out: Annotated[ Path | None, Option( help=( - "Path to save optimized structure. Default is inferred from name " - "of structure file." + "Path to save optimized structure. Default is inferred `file_prefix`." ), ), ] = None, @@ -190,10 +187,12 @@ def geomopt( Atom displacement tolerance for spglib symmetry determination, in Å. Default is 0.001. file_prefix - Prefix for output filenames. Default is inferred from structure. + Prefix for output files, including directories. Default directory is + ./janus_results, and default filename prefix is inferred from the input + stucture filename. out Path to save optimized structure, or last structure if optimization did not - converge. Default is inferred from name of structure file. + converge. Default is inferred from `file_prefix`. write_traj Whether to save a trajectory file of optimization frames. If traj_kwargs["filename"] is not specified, it is inferred from `file_prefix`. @@ -208,13 +207,13 @@ def geomopt( Keyword arguments to pass to ase.io.write when saving optimized structure. Default is {}. log - Path to write logs to. Default is inferred from the name of the structure file. + Path to write logs to. Default is inferred from `file_prefix`. tracker Whether to save carbon emissions of calculation in log file and summary. Default is True. summary Path to save summary of inputs, start/end time, and carbon emissions. Default - is inferred from the name of the structure file. + is inferred from `file_prefix`. config Path to yaml configuration file to define the above options. Default is None. """ diff --git a/janus_core/cli/md.py b/janus_core/cli/md.py index d802cd1e..435512d9 100644 --- a/janus_core/cli/md.py +++ b/janus_core/cli/md.py @@ -13,6 +13,7 @@ CalcKwargs, Device, EnsembleKwargs, + FilePrefix, LogPath, MinimizeKwargs, ModelPath, @@ -131,17 +132,7 @@ def md( rescale_every: Annotated[ int, Option(help="Frequency to rescale velocities during equilibration.") ] = 10, - file_prefix: Annotated[ - Path | None, - Option( - help=( - """ - Prefix for output filenames. Default is inferred from structure, - ensemble, and temperature. - """ - ), - ), - ] = None, + file_prefix: FilePrefix = None, restart: Annotated[bool, Option(help="Whether restarting dynamics.")] = False, restart_auto: Annotated[ bool, Option(help="Whether to infer restart file if restarting dynamics.") @@ -289,8 +280,9 @@ def md( rescale_every Frequency to rescale velocities. Default is 10. file_prefix - Prefix for output filenames. Default is inferred from structure, ensemble, - and temperature. + Prefix for output files, including directories. Default directory is + ./janus_results, and default filename prefix is inferred from the input + stucture filename. restart Whether restarting dynamics. Default is False. restart_auto @@ -339,13 +331,13 @@ def md( Random seed used by numpy.random and random functions, such as in Langevin. Default is None. log - Path to write logs to. Default is inferred from the name of the structure file. + Path to write logs to. Default is inferred from `file_prefix`. tracker Whether to save carbon emissions of calculation in log file and summary. Default is True. summary Path to save summary of inputs, start/end time, and carbon emissions. Default - is inferred from the name of the structure file. + is inferred from `file_prefix`. config Path to yaml configuration file to define the above options. Default is None. """ diff --git a/janus_core/cli/neb.py b/janus_core/cli/neb.py index 2e111301..3051499e 100644 --- a/janus_core/cli/neb.py +++ b/janus_core/cli/neb.py @@ -2,7 +2,6 @@ from __future__ import annotations -from pathlib import Path from typing import Annotated, get_args from click import Choice @@ -13,6 +12,7 @@ Architecture, CalcKwargs, Device, + FilePrefix, InterpolationKwargs, LogPath, MinimizeKwargs, @@ -78,17 +78,7 @@ def neb( model_path: ModelPath = None, read_kwargs: ReadKwargsLast = None, calc_kwargs: CalcKwargs = None, - file_prefix: Annotated[ - Path | None, - Option( - help=( - """ - Prefix for output filenames. Default is inferred from structure name, - or chemical formula. - """ - ), - ), - ] = None, + file_prefix: FilePrefix = None, log: LogPath = None, tracker: Annotated[ bool, Option(help="Whether to save carbon emissions of calculation") @@ -157,16 +147,17 @@ def neb( calc_kwargs Keyword arguments to pass to the selected calculator. Default is {}. file_prefix - Prefix for output filenames. Default is inferred from the intial structure - name, or chemical formula of the intial structure. + Prefix for output files, including directories. Default directory is + ./janus_results, and default filename prefix is inferred from the input + stucture filename. log - Path to write logs to. Default is inferred from the name of the structure file. + Path to write logs to. Default is inferred from `file_prefix`. tracker Whether to save carbon emissions of calculation in log file and summary. Default is True. summary Path to save summary of inputs, start/end time, and carbon emissions. Default - is inferred from the name of the structure file. + is inferred from `file_prefix`. config Path to yaml configuration file to define the above options. Default is None. """ diff --git a/janus_core/cli/phonons.py b/janus_core/cli/phonons.py index 18beb94d..378b2073 100644 --- a/janus_core/cli/phonons.py +++ b/janus_core/cli/phonons.py @@ -14,6 +14,7 @@ Device, DisplacementKwargs, DoSKwargs, + FilePrefix, LogPath, MinimizeKwargs, ModelPath, @@ -121,17 +122,7 @@ def phonons( model_path: ModelPath = None, read_kwargs: ReadKwargsLast = None, calc_kwargs: CalcKwargs = None, - file_prefix: Annotated[ - Path | None, - Option( - help=( - """ - Prefix for output filenames. Default is inferred from structure name, - or chemical formula. - """ - ), - ), - ] = None, + file_prefix: FilePrefix = None, log: LogPath = None, tracker: Annotated[ bool, Option(help="Whether to save carbon emissions of calculation") @@ -215,16 +206,17 @@ def phonons( calc_kwargs Keyword arguments to pass to the selected calculator. Default is {}. file_prefix - Prefix for output filenames. Default is inferred from structure name, or - chemical formula. + Prefix for output files, including directories. Default directory is + ./janus_results, and default filename prefix is inferred from the input + stucture filename. log - Path to write logs to. Default is inferred from the name of the structure file. + Path to write logs to. Default is inferred from `file_prefix`. tracker Whether to save carbon emissions of calculation in log file and summary. Default is True. summary Path to save summary of inputs, start/end time, and carbon emissions. Default - is inferred from the name of the structure file. + is inferred from `file_prefix`. config Path to yaml configuration file to define the above options. Default is None. """ diff --git a/janus_core/cli/singlepoint.py b/janus_core/cli/singlepoint.py index 4ee457e5..4caaf42e 100644 --- a/janus_core/cli/singlepoint.py +++ b/janus_core/cli/singlepoint.py @@ -12,6 +12,7 @@ Architecture, CalcKwargs, Device, + FilePrefix, LogPath, ModelPath, ReadKwargsAll, @@ -42,18 +43,13 @@ def singlepoint( ), ), ] = None, - file_prefix: Annotated[ - Path | None, - Option( - help=("Prefix for output filenames. Default is inferred from structure.") - ), - ] = None, + file_prefix: FilePrefix = None, out: Annotated[ Path | None, Option( help=( "Path to save structure with calculated results. Default is inferred " - "from name of structure file." + "from `file_prefix`." ), ), ] = None, @@ -85,10 +81,12 @@ def singlepoint( properties Physical properties to calculate. Default is ("energy", "forces", "stress"). file_prefix - Prefix for output filenames. Default is inferred from structure. + Prefix for output files, including directories. Default directory is + ./janus_results, and default filename prefix is inferred from the input + stucture filename. out - Path to save structure with calculated results. Default is inferred from name - of the structure file. + Path to save structure with calculated results. Default is inferred from + `file_prefix`. read_kwargs Keyword arguments to pass to ase.io.read. By default, read_kwargs["index"] is ":". @@ -97,13 +95,13 @@ def singlepoint( write_kwargs Keyword arguments to pass to ase.io.write when saving results. Default is {}. log - Path to write logs to. Default is inferred from the name of the structure file. + Path to write logs to. Default is inferred from `file_prefix`. tracker Whether to save carbon emissions of calculation in log file and summary. Default is True. summary Path to save summary of inputs, start/end time, and carbon emissions. Default - is inferred from the name of the structure file. + is inferred from `file_prefix`. config Path to yaml configuration file to define the above options. Default is None. """ diff --git a/janus_core/cli/types.py b/janus_core/cli/types.py index b9e2099f..a3a095e4 100644 --- a/janus_core/cli/types.py +++ b/janus_core/cli/types.py @@ -72,6 +72,19 @@ def __str__(self) -> str: Device = Annotated[str | None, Option(help="Device to run calculations on.")] ModelPath = Annotated[str | None, Option(help="Path to MLIP model.")] +FilePrefix = Annotated[ + Path | None, + Option( + help=( + """ + Prefix for output files, including directories. Default directory is + ./janus_results, and default filename prefix is inferred from the + input stucture filename. + """ + ) + ), +] + ReadKwargsAll = Annotated[ TyperDict | None, Option( @@ -287,12 +300,7 @@ def __str__(self) -> str: LogPath = Annotated[ Path | None, - Option( - help=( - "Path to save logs to. Default is inferred from the name of the structure " - "file." - ) - ), + Option(help=("Path to save logs to. Default is inferred from `file_prefix`")), ] Summary = Annotated[ @@ -300,7 +308,7 @@ def __str__(self) -> str: Option( help=( "Path to save summary of inputs, start/end time, and carbon emissions. " - "Default is inferred from the name of the structure file." + "Default is inferred from `file_prefix`." ) ), ] From 1112c7824df8f06e90fc366c5a4b22d2015e38d1 Mon Sep 17 00:00:00 2001 From: ElliottKasoar <45317199+ElliottKasoar@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:10:59 +0000 Subject: [PATCH 10/12] Add file prefix to descriptors --- janus_core/calculations/descriptors.py | 6 ++++++ janus_core/cli/descriptors.py | 15 +++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/janus_core/calculations/descriptors.py b/janus_core/calculations/descriptors.py index 95420da8..a58ba0cc 100644 --- a/janus_core/calculations/descriptors.py +++ b/janus_core/calculations/descriptors.py @@ -36,6 +36,8 @@ class Descriptors(BaseCalculation): Device to run MLIP model on. Default is "cpu". model_path Path to MLIP model. Default is `None`. + file_prefix + Prefix for output filenames. Default is inferred from structure. read_kwargs Keyword arguments to pass to ase.io.read. By default, read_kwargs["index"] is -1. @@ -72,6 +74,7 @@ def __init__( arch: Architectures = "mace_mp", device: Devices = "cpu", model_path: PathLike | None = None, + file_prefix: PathLike | None = None, read_kwargs: ASEReadArgs | None = None, calc_kwargs: dict[str, Any] | None = None, set_calc: bool | None = None, @@ -98,6 +101,8 @@ def __init__( Device to run MLIP model on. Default is "cpu". model_path Path to MLIP model. Default is `None`. + file_prefix + Prefix for output filenames. Default is inferred from structure. read_kwargs Keyword arguments to pass to ase.io.read. By default, read_kwargs["index"] is -1. @@ -153,6 +158,7 @@ def __init__( log_kwargs=log_kwargs, track_carbon=track_carbon, tracker_kwargs=tracker_kwargs, + file_prefix=file_prefix, ) if isinstance(self.struct, Atoms) and not self.struct.calc: diff --git a/janus_core/cli/descriptors.py b/janus_core/cli/descriptors.py index d5c475d7..4351f1bb 100644 --- a/janus_core/cli/descriptors.py +++ b/janus_core/cli/descriptors.py @@ -12,6 +12,7 @@ Architecture, CalcKwargs, Device, + FilePrefix, LogPath, ModelPath, ReadKwargsAll, @@ -45,6 +46,7 @@ def descriptors( arch: Architecture = "mace_mp", device: Device = "cpu", model_path: ModelPath = None, + file_prefix: FilePrefix = None, out: Annotated[ Path | None, Option( @@ -85,9 +87,13 @@ def descriptors( Device to run model on. Default is "cpu". model_path Path to MLIP model. Default is `None`. + file_prefix + Prefix for output files, including directories. Default directory is + ./janus_results, and default filename prefix is inferred from the input + stucture filename. out - Path to save structure with calculated results. Default is inferred from name - of the structure file. + Path to save structure with calculated results. Default is inferred + `file_prefix`. read_kwargs Keyword arguments to pass to ase.io.read. By default, read_kwargs["index"] is ":". @@ -96,13 +102,13 @@ def descriptors( write_kwargs Keyword arguments to pass to ase.io.write when saving results. Default is {}. log - Path to write logs to. Default is inferred from the name of the structure file. + Path to write logs to. Default is inferred from `file_prefix`. tracker Whether to save carbon emissions of calculation in log file and summary. Default is True. summary Path to save summary of inputs, start/end time, and carbon emissions. Default - is inferred from the name of the structure file. + is inferred from `file_prefix`. config Path to yaml configuration file to define the above options. Default is None. """ @@ -151,6 +157,7 @@ def descriptors( "calc_per_atom": calc_per_atom, "write_results": True, "write_kwargs": write_kwargs, + "file_prefix": file_prefix, } # Initialise descriptors From 9a03b828d7764869dc3072eda3f4b219ae93ed8d Mon Sep 17 00:00:00 2001 From: ElliottKasoar <45317199+ElliottKasoar@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:11:17 +0000 Subject: [PATCH 11/12] Add missing file prefix tests --- tests/test_descriptors_cli.py | 23 +++++++++++++++++++++++ tests/test_singlepoint_cli.py | 23 +++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/tests/test_descriptors_cli.py b/tests/test_descriptors_cli.py index 7e246750..50f7b0b2 100644 --- a/tests/test_descriptors_cli.py +++ b/tests/test_descriptors_cli.py @@ -270,3 +270,26 @@ def test_no_carbon(tmp_path): with open(summary_path, encoding="utf8") as file: descriptors_summary = yaml.safe_load(file) assert "emissions" not in descriptors_summary + + +def test_file_prefix(tmp_path): + """Test file prefix creates directories and affects all files.""" + file_prefix = tmp_path / "test/test" + result = runner.invoke( + app, + [ + "descriptors", + "--struct", + DATA_PATH / "NaCl.cif", + "--file-prefix", + file_prefix, + ], + ) + assert result.exit_code == 0 + test_path = tmp_path / "test" + assert list(tmp_path.iterdir()) == [test_path] + assert set(test_path.iterdir()) == { + test_path / "test-descriptors.extxyz", + test_path / "test-descriptors-summary.yml", + test_path / "test-descriptors-log.yml", + } diff --git a/tests/test_singlepoint_cli.py b/tests/test_singlepoint_cli.py index adea2222..4b4ec12c 100644 --- a/tests/test_singlepoint_cli.py +++ b/tests/test_singlepoint_cli.py @@ -436,3 +436,26 @@ def test_no_carbon(tmp_path): with open(summary_path, encoding="utf8") as file: sp_summary = yaml.safe_load(file) assert "emissions" not in sp_summary + + +def test_file_prefix(tmp_path): + """Test file prefix creates directories and affects all files.""" + file_prefix = tmp_path / "test/test" + result = runner.invoke( + app, + [ + "singlepoint", + "--struct", + DATA_PATH / "NaCl.cif", + "--file-prefix", + file_prefix, + ], + ) + assert result.exit_code == 0 + test_path = tmp_path / "test" + assert list(tmp_path.iterdir()) == [test_path] + assert set(test_path.iterdir()) == { + test_path / "test-results.extxyz", + test_path / "test-singlepoint-summary.yml", + test_path / "test-singlepoint-log.yml", + } From 66cbdb9ba1d5b760977fe20c4798a4cae0e26ccd Mon Sep 17 00:00:00 2001 From: ElliottKasoar <45317199+ElliottKasoar@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:28:08 +0000 Subject: [PATCH 12/12] Update docs --- docs/source/user_guide/command_line.rst | 86 ++++++++++++++++--------- 1 file changed, 56 insertions(+), 30 deletions(-) diff --git a/docs/source/user_guide/command_line.rst b/docs/source/user_guide/command_line.rst index 90157bf3..f3c27c25 100644 --- a/docs/source/user_guide/command_line.rst +++ b/docs/source/user_guide/command_line.rst @@ -53,36 +53,38 @@ prints the following: Usage: janus singlepoint [OPTIONS] - Perform single point calculations and save to file. - - Options: - --struct PATH Path of structure to simulate. [required] - --arch TEXT MLIP architecture to use for calculations. [default: - mace_mp] - --device TEXT Device to run calculations on. [default: cpu] - --model-path TEXT Path to MLIP model. [default: None] - --properties TEXT Properties to calculate. If not specified, 'energy', - 'forces' and 'stress' will be returned. - --out PATH Path to save structure with calculated results. Default - is inferred from name of structure file. - --read-kwargs DICT Keyword arguments to pass to ase.io.read. Must be - passed as a dictionary wrapped in quotes, e.g. "{'key' - : value}". [default: "{}"] - --calc-kwargs DICT Keyword arguments to pass to selected calculator. Must - be passed as a dictionary wrapped in quotes, e.g. - "{'key' : value}". For the default architecture - ('mace_mp'), "{'model':'small'}" is set unless - overwritten. - --write-kwargs DICT Keyword arguments to pass to ase.io.write when saving - results. Must be passed as a dictionary wrapped in - quotes, e.g. "{'key' : value}". [default: "{}"] - --log PATH Path to save logs to. Default is inferred from the name - of the structure file. - --summary PATH Path to save summary of inputs, start/end time, and - carbon emissions. Default is inferred from the name of - the structure file. - --config TEXT Configuration file. - --help Show this message and exit. + Perform single point calculations and save to file. + + Options: + * --struct PATH Path of structure to simulate. [default: None] [required] + --arch TEXT MLIP architecture to use for calculations. [default: mace_mp] + --device TEXT Device to run calculations on. [default:] + --model-path TEXT Path to MLIP model. [default: None] + --properties TEXT Properties to calculate. If not specified, 'energy', 'forces' and + 'stress' will be returned. [default: None] + --file-prefix PATH Prefix for output files, including directories. Default directory is + ./janus_results, and default filename prefix is inferred from the + input stucture filename. + --out PATH Path to save structure with calculated results. Default is inferred + from name of structure file. [default: None] + --read-kwargs DICT Keyword arguments to pass to ase.io.read. Must be passed as a + dictionary wrapped in quotes, e.g. "{'key': value}". + By default, read_kwargs['index'] = ':', so all structures are read. + [default: None] + --calc-kwargs DICT Keyword arguments to pass to selected calculator. Must be passed as a + dictionary wrapped in quotes, e.g. "{'key': value}". + For the default architecture ('mace_mp'), "{'model': 'small'}" is set + unless overwritten. [default: None] + --write-kwargs DICT Keyword arguments to pass to ase.io.write when saving results. Must be + passed as a dictionary wrapped in quotes, e.g. "{'key': value}". + [default: None] + --log PATH Path to save logs to. Default is inferred from the name of the + structure file. [default: None] + --tracker --no-tracker Whether to save carbon emissions of calculation [default: tracker] + --summary PATH Path to save summary of inputs, start/end time, and carbon emissions. + Default is inferred from the name of the structure file. [default: None] + --config TEXT Configuration file. + --help Show this message and exit. Using configuration files @@ -122,6 +124,30 @@ Example configurations for all commands can be found in `janus-tutorials `_ object to store information in the ``Atoms.info`` and ``Atoms.arrays`` dictionaries about the MLIP used.