Skip to content

Commit c94cdf4

Browse files
authored
Merge branch 'stfc:main' into orb
2 parents 7efa3eb + 6a3e176 commit c94cdf4

File tree

16 files changed

+280
-80
lines changed

16 files changed

+280
-80
lines changed

docs/source/user_guide/command_line.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ Calculate phonons with a 2x2x2 supercell, after geometry optimization (using the
314314

315315
.. code-block:: bash
316316
317-
janus phonons --struct tests/data/NaCl.cif --supercell 2x2x2 --minimize --arch mace_mp --model-path small
317+
janus phonons --struct tests/data/NaCl.cif --supercell 2 2 2 --minimize --arch mace_mp --model-path small
318318
319319
320320
This will save the Phonopy parameters, including displacements and force constants, to ``NaCl-phonopy.yml`` and ``NaCl-force_constants.hdf5``,
@@ -324,7 +324,7 @@ Additionally, the ``--bands`` option can be added to calculate the band structur
324324

325325
.. code-block:: bash
326326
327-
janus phonons --struct tests/data/NaCl.cif --supercell 2x2x2 --minimize --arch mace_mp --model-path small --bands
327+
janus phonons --struct tests/data/NaCl.cif --supercell 2 2 2 --minimize --arch mace_mp --model-path small --bands
328328
329329
330330
If you need eigenvectors and group velocities written, add the ``--write-full`` option. This will generate a much larger file, but can be used to visualise phonon modes.
@@ -333,7 +333,7 @@ Further calculations, including thermal properties, DOS, and PDOS, can also be c
333333

334334
.. code-block:: bash
335335
336-
janus phonons --struct tests/data/NaCl.cif --supercell 2x3x4 --dos --pdos --thermal --temp-start 0 --temp-end 300 --temp-step 50
336+
janus phonons --struct tests/data/NaCl.cif --supercell 2 3 4 --dos --pdos --thermal --temp-start 0 --temp-end 300 --temp-step 50
337337
338338
339339
This will create additional output files: ``NaCl-thermal.dat`` for the thermal properties (heat capacity, entropy, and free energy)

janus_core/calculations/descriptors.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from typing import Any, Optional
55

66
from ase import Atoms
7+
from ase.calculators.calculator import Calculator
8+
from ase.calculators.mixing import SumCalculator
79
import numpy as np
810

911
from janus_core.calculations.base import BaseCalculation
@@ -163,12 +165,43 @@ def __init__(
163165
):
164166
raise ValueError("Please attach a calculator to `struct`.")
165167

168+
if isinstance(self.struct, Atoms):
169+
self._check_calculator(self.struct.calc)
170+
if isinstance(self.struct, Sequence):
171+
for image in self.struct:
172+
self._check_calculator(image.calc)
173+
166174
# Set output file
167175
self.write_kwargs.setdefault("filename", None)
168176
self.write_kwargs["filename"] = self._build_filename(
169177
"descriptors.extxyz", filename=self.write_kwargs["filename"]
170178
).absolute()
171179

180+
@staticmethod
181+
def _check_calculator(calc: Calculator) -> None:
182+
"""
183+
Ensure calculator has ability to calculate descriptors.
184+
185+
Parameters
186+
----------
187+
calc : Calculator
188+
ASE Calculator to calculate descriptors.
189+
"""
190+
# If dispersion added to MLIP calculator, use MLIP calculator for descriptors
191+
if isinstance(calc, SumCalculator):
192+
if (
193+
len(calc.mixer.calcs) == 2
194+
and calc.mixer.calcs[1].name == "TorchDFTD3Calculator"
195+
and hasattr(calc.mixer.calcs[0], "get_descriptors")
196+
):
197+
calc.get_descriptors = calc.mixer.calcs[0].get_descriptors
198+
199+
if not hasattr(calc, "get_descriptors") or not callable(calc.get_descriptors):
200+
raise NotImplementedError(
201+
"The attached calculator does not currently support calculating "
202+
"descriptors"
203+
)
204+
172205
def run(self) -> None:
173206
"""Calculate descriptors for structure(s)."""
174207
if self.logger:

janus_core/calculations/geom_opt.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,8 @@ def run(self) -> None:
300300

301301
if self.logger:
302302
self.logger.info("After optimization spacegroup: %s", s_grp)
303-
self.logger.info("Max force: %.6f", max_force)
303+
self.logger.info("Max force: %s", max_force)
304+
self.logger.info("Final energy: %s", self.struct.get_potential_energy())
304305

305306
if not converged:
306307
warnings.warn(

janus_core/calculations/md.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -509,12 +509,21 @@ def __init__(
509509

510510
self._parse_correlations()
511511

512-
def _set_time_step(self):
513-
"""Set time in fs and current dynamics step to info."""
512+
def _set_info(self):
513+
"""Set time in fs, current dynamics step, and density to info."""
514514
time = (self.offset * self.timestep + self.dyn.get_time()) / units.fs
515515
step = self.offset + self.dyn.nsteps
516516
self.dyn.atoms.info["time_fs"] = time
517517
self.dyn.atoms.info["step"] = step
518+
try:
519+
density = (
520+
np.sum(self.dyn.atoms.get_masses())
521+
/ self.dyn.atoms.get_volume()
522+
* DENS_FACT
523+
)
524+
self.dyn.atoms.info["density"] = density
525+
except ValueError:
526+
self.dyn.atoms.info["density"] = 0.0
518527

519528
def _prepare_restart(self) -> None:
520529
"""Prepare restart files, structure and offset."""
@@ -726,19 +735,13 @@ def get_stats(self) -> dict[str, float]:
726735
e_kin = self.dyn.atoms.get_kinetic_energy() / self.n_atoms
727736
current_temp = e_kin / (1.5 * units.kB)
728737

729-
self._set_time_step()
738+
self._set_info()
730739

731740
time_now = datetime.datetime.now()
732741
real_time = time_now - self.dyn.atoms.info["real_time"]
733742
self.dyn.atoms.info["real_time"] = time_now
734743

735744
try:
736-
density = (
737-
np.sum(self.dyn.atoms.get_masses())
738-
/ self.dyn.atoms.get_volume()
739-
* DENS_FACT
740-
)
741-
self.dyn.atoms.info["density"] = density
742745
volume = self.dyn.atoms.get_volume()
743746
pressure = (
744747
-np.trace(
@@ -754,7 +757,6 @@ def get_stats(self) -> dict[str, float]:
754757
except ValueError:
755758
volume = 0.0
756759
pressure = 0.0
757-
density = 0.0
758760
pressure_tensor = np.zeros(6)
759761

760762
return {
@@ -765,7 +767,7 @@ def get_stats(self) -> dict[str, float]:
765767
"EKin/N": e_kin,
766768
"T": current_temp,
767769
"ETot/N": e_pot + e_kin,
768-
"Density": density,
770+
"Density": self.dyn.atoms.info["density"],
769771
"Volume": volume,
770772
"P": pressure,
771773
"Pxx": pressure_tensor[0],
@@ -874,7 +876,7 @@ def _write_traj(self) -> None:
874876
self.dyn.nsteps > self.traj_start + self.traj_start % self.traj_every
875877
)
876878

877-
self._set_time_step()
879+
self._set_info()
878880
write_kwargs = self.write_kwargs
879881
write_kwargs["filename"] = self.traj_file
880882
write_kwargs["append"] = append
@@ -895,7 +897,7 @@ def _write_final_state(self) -> None:
895897
# Append if final file has been created
896898
append = self.created_final_file
897899

898-
self._set_time_step()
900+
self._set_info()
899901
write_kwargs = self.write_kwargs
900902
write_kwargs["filename"] = self.final_file
901903
write_kwargs["append"] = append
@@ -998,7 +1000,7 @@ def _write_restart(self) -> None:
9981000
if step > 0:
9991001
write_kwargs = self.write_kwargs
10001002
write_kwargs["filename"] = self._restart_file
1001-
self._set_time_step()
1003+
self._set_info()
10021004

10031005
output_structs(
10041006
images=self.struct,

janus_core/calculations/phonons.py

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
PathLike,
2121
PhononCalcs,
2222
)
23-
from janus_core.helpers.utils import none_to_dict, write_table
23+
from janus_core.helpers.utils import none_to_dict, track_progress, write_table
2424

2525

2626
class Phonons(BaseCalculation):
@@ -60,6 +60,8 @@ class Phonons(BaseCalculation):
6060
Size of supercell for calculation. Default is 2.
6161
displacement : float
6262
Displacement for force constants calculation, in A. Default is 0.01.
63+
mesh : tuple[int, int, int]
64+
Mesh for sampling. Default is (10, 10, 10).
6365
symmetrize : bool
6466
Whether to symmetrize force constants after calculation.
6567
Default is False.
@@ -88,6 +90,8 @@ class Phonons(BaseCalculation):
8890
file_prefix : Optional[PathLike]
8991
Prefix for output filenames. Default is inferred from chemical formula of the
9092
structure.
93+
enable_progress_bar : bool
94+
Whether to show a progress bar during phonon calculations. Default is False.
9195
9296
Attributes
9397
----------
@@ -106,7 +110,7 @@ class Phonons(BaseCalculation):
106110
Calculate band structure and optionally write and plot results.
107111
write_bands(bands_file, save_plots, plot_file)
108112
Write results of band structure calculations.
109-
calc_thermal_props(write_thermal)
113+
calc_thermal_props(mesh, write_thermal)
110114
Calculate thermal properties and optionally write results.
111115
write_thermal_props(thermal_file)
112116
Write results of thermal properties calculations.
@@ -138,6 +142,7 @@ def __init__(
138142
calcs: MaybeSequence[PhononCalcs] = (),
139143
supercell: MaybeList[int] = 2,
140144
displacement: float = 0.01,
145+
mesh: tuple[int, int, int] = (10, 10, 10),
141146
symmetrize: bool = False,
142147
minimize: bool = False,
143148
minimize_kwargs: Optional[dict[str, Any]] = None,
@@ -149,6 +154,7 @@ def __init__(
149154
write_results: bool = True,
150155
write_full: bool = True,
151156
file_prefix: Optional[PathLike] = None,
157+
enable_progress_bar: bool = False,
152158
) -> None:
153159
"""
154160
Initialise Phonons class.
@@ -186,6 +192,8 @@ def __init__(
186192
Size of supercell for calculation. Default is 2.
187193
displacement : float
188194
Displacement for force constants calculation, in A. Default is 0.01.
195+
mesh : tuple[int, int, int]
196+
Mesh for sampling. Default is (10, 10, 10).
189197
symmetrize : bool
190198
Whether to symmetrize force constants after calculations.
191199
Default is False.
@@ -214,11 +222,14 @@ def __init__(
214222
file_prefix : Optional[PathLike]
215223
Prefix for output filenames. Default is inferred from structure name, or
216224
chemical formula of the structure.
225+
enable_progress_bar : bool
226+
Whether to show a progress bar during phonon calculations. Default is False.
217227
"""
218228
(read_kwargs, minimize_kwargs) = none_to_dict((read_kwargs, minimize_kwargs))
219229

220230
self.calcs = calcs
221231
self.displacement = displacement
232+
self.mesh = mesh
222233
self.symmetrize = symmetrize
223234
self.minimize = minimize
224235
self.minimize_kwargs = minimize_kwargs
@@ -229,6 +240,7 @@ def __init__(
229240
self.plot_to_file = plot_to_file
230241
self.write_results = write_results
231242
self.write_full = write_full
243+
self.enable_progress_bar = enable_progress_bar
232244

233245
# Ensure supercell is a valid list
234246
self.supercell = [supercell] * 3 if isinstance(supercell, int) else supercell
@@ -357,6 +369,11 @@ def calc_force_constants(
357369
phonon.generate_displacements(distance=self.displacement)
358370
disp_supercells = phonon.supercells_with_displacements
359371

372+
if self.enable_progress_bar:
373+
disp_supercells = track_progress(
374+
disp_supercells, "Computing displacements..."
375+
)
376+
360377
phonon.forces = [
361378
self._calc_forces(supercell)
362379
for supercell in disp_supercells
@@ -490,13 +507,18 @@ def write_bands(
490507
bplt.savefig(plot_file)
491508

492509
def calc_thermal_props(
493-
self, write_thermal: Optional[bool] = None, **kwargs
510+
self,
511+
mesh: Optional[tuple[int, int, int]] = None,
512+
write_thermal: Optional[bool] = None,
513+
**kwargs,
494514
) -> None:
495515
"""
496516
Calculate thermal properties and optionally write results.
497517
498518
Parameters
499519
----------
520+
mesh : Optional[tuple[int, int, int]]
521+
Mesh for sampling. Default is self.mesh.
500522
write_thermal : Optional[bool]
501523
Whether to write out thermal properties to file. Default is
502524
self.write_results.
@@ -506,6 +528,9 @@ def calc_thermal_props(
506528
if write_thermal is None:
507529
write_thermal = self.write_results
508530

531+
if mesh is None:
532+
mesh = self.mesh
533+
509534
# Calculate phonons if not already in results
510535
if "phonon" not in self.results:
511536
# Use general (self.write_results) setting for writing force constants
@@ -515,7 +540,7 @@ def calc_thermal_props(
515540
self.logger.info("Starting thermal properties calculation")
516541
self.tracker.start_task("Thermal calculation")
517542

518-
self.results["phonon"].run_mesh()
543+
self.results["phonon"].run_mesh(mesh)
519544
self.results["phonon"].run_thermal_properties(
520545
t_step=self.temp_step, t_max=self.temp_max, t_min=self.temp_min
521546
)
@@ -563,7 +588,7 @@ def write_thermal_props(self, thermal_file: Optional[PathLike] = None) -> None:
563588
def calc_dos(
564589
self,
565590
*,
566-
mesh: MaybeList[float] = (10, 10, 10),
591+
mesh: Optional[tuple[int, int, int]] = None,
567592
write_dos: Optional[bool] = None,
568593
**kwargs,
569594
) -> None:
@@ -572,8 +597,8 @@ def calc_dos(
572597
573598
Parameters
574599
----------
575-
mesh : MaybeList[float]
576-
Mesh for sampling. Default is (10, 10, 10).
600+
mesh : Optional[tuple[int, int, int]]
601+
Mesh for sampling. Default is self.mesh.
577602
write_dos : Optional[bool]
578603
Whether to write out results to file. Default is True.
579604
**kwargs
@@ -582,6 +607,9 @@ def calc_dos(
582607
if write_dos is None:
583608
write_dos = self.write_results
584609

610+
if mesh is None:
611+
mesh = self.mesh
612+
585613
# Calculate phonons if not already in results
586614
if "phonon" not in self.results:
587615
# Use general (self.write_results) setting for writing force constants
@@ -665,7 +693,7 @@ def write_dos(
665693
def calc_pdos(
666694
self,
667695
*,
668-
mesh: MaybeList[float] = (10, 10, 10),
696+
mesh: Optional[tuple[int, int, int]] = None,
669697
write_pdos: Optional[bool] = None,
670698
**kwargs,
671699
) -> None:
@@ -674,8 +702,8 @@ def calc_pdos(
674702
675703
Parameters
676704
----------
677-
mesh : MaybeList[float]
678-
Mesh for sampling. Default is (10, 10, 10).
705+
mesh : Optional[tuple[int, int, int]]
706+
Mesh for sampling. Default is self.mesh.
679707
write_pdos : Optional[bool]
680708
Whether to write out results to file. Default is self.write_results.
681709
**kwargs
@@ -684,6 +712,9 @@ def calc_pdos(
684712
if write_pdos is None:
685713
write_pdos = self.write_results
686714

715+
if mesh is None:
716+
mesh = self.mesh
717+
687718
# Calculate phonons if not already in results
688719
if "phonon" not in self.results:
689720
# Use general (self.write_results) setting for writing force constants

0 commit comments

Comments
 (0)