Skip to content

Commit 943574d

Browse files
Merge pull request #1167 from esoteric-ephemera/run_stats
Correct typing of TaskDoc.run_stats, add number of electronic steps per ionic step to TaskDoc
2 parents 7683b00 + 84d4b22 commit 943574d

File tree

14 files changed

+109
-63
lines changed

14 files changed

+109
-63
lines changed

.github/workflows/testing.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,13 @@ jobs:
8181

8282
- name: Install Python dependencies
8383
shell: bash -l {0}
84+
env:
85+
CC: gcc-14
86+
CXX: g++-14
8487
run: |
8588
python${{ matrix.python-version }} -m pip install --upgrade pip pip-tools
8689
python${{ matrix.python-version }} -m pip install --user `grep numpy ${{ matrix.package }}/requirements/${{ matrix.os }}_py${{ matrix.python-version }}_extras.txt`
8790
python${{ matrix.python-version }} -m pip install --user -r ${{ matrix.package }}/requirements/${{ matrix.os }}_py${{ matrix.python-version }}_extras.txt
88-
env:
89-
CC: gcc-10
90-
CXX: g++-10
9191
9292
- name: Install editable emmet-core if needed
9393
shell: bash -l {0}

emmet-core/emmet/core/defect.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
from pydantic import Field
44

55
from emmet.core.tasks import TaskDoc, _VOLUMETRIC_FILES
6-
from typing import TYPE_CHECKING
6+
from typing import TYPE_CHECKING, Optional
77
from pymatgen.analysis.defects.core import Defect
88
from monty.json import MontyDecoder
99
from pydantic import BaseModel
1010

1111
if TYPE_CHECKING:
12-
from typing import Any, Dict, Optional, Tuple, Union
12+
from typing import Any, Dict, Tuple, Union
1313
from pathlib import Path
1414

1515
mdecoder = MontyDecoder().process_decoded
@@ -32,13 +32,13 @@ class DefectInfo(BaseModel):
3232
description="Unit cell representation of the defect object.",
3333
)
3434

35-
charge_state: int = Field(
35+
charge_state: Optional[int] = Field(
3636
None,
3737
title="Charge State",
3838
description="Charge state of the defect.",
3939
)
4040

41-
supercell_matrix: list = Field(
41+
supercell_matrix: Optional[list] = Field(
4242
None,
4343
title="Supercell Matrix",
4444
description="Supercell matrix used to construct the defect supercell.",

emmet-core/emmet/core/elasticity.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,10 @@ class ElasticityDoc(PropertyDoc):
195195
thermal_conductivity: Optional[ThermalConductivity] = Field(
196196
None, description="Thermal conductivity"
197197
)
198-
young_modulus: float = Field(
198+
young_modulus: Optional[float] = Field(
199199
None, description="Young's modulus (SI units)", alias="youngs_modulus"
200200
)
201-
universal_anisotropy: float = Field(
201+
universal_anisotropy: Optional[float] = Field(
202202
None, description="Universal elastic anisotropy"
203203
)
204204
homogeneous_poisson: Optional[float] = Field(

emmet-core/emmet/core/feff/task.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ def xas_spectrum(self) -> XAS:
6969
structure = self.structure
7070
absorbing_index = self.absorbing_atom
7171
absorbing_element = self.absorbing_element
72+
if isinstance(absorbing_element, Species):
73+
absorbing_element = absorbing_element.element
74+
7275
edge = self.edge
7376
spectrum_type = str(self.spectrum_type)
7477

emmet-core/emmet/core/molecules/trajectory.py

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ class ForcesDoc(PropertyDoc):
2323

2424
forces: List[List[float]] = Field(..., description="Atomic forces (units: Ha/Bohr)")
2525

26-
precise_forces: Optional[List[List[float]]] = Field(
27-
None, description="High-precision atomic forces (units: Ha/Bohr)"
26+
precise_forces: List[Optional[List[float]]] = Field(
27+
default_factory=list,
28+
description="High-precision atomic forces (units: Ha/Bohr)",
2829
)
2930

3031
pcm_forces: Optional[List[List[float]]] = Field(
@@ -159,44 +160,44 @@ class TrajectoryDoc(PropertyDoc):
159160
)
160161

161162
pcm_forces: List[Optional[List[List[List[float]]]]] = Field(
162-
None,
163+
default_factory=list,
163164
description="Electrostatic atomic forces from polarizable continuum model (PCM) implicit solvation "
164165
"for each optimization step for each optimization trajectory (units: Ha/Bohr).",
165166
)
166167

167168
cds_forces: List[Optional[List[List[List[float]]]]] = Field(
168-
None,
169+
default_factory=list,
169170
description="Atomic force contributions from cavitation, dispersion, and structural rearrangement in the SMx "
170171
"family of implicit solvent models, for each optimization step for each optimization trajectory "
171172
"(units: Ha/Bohr)",
172173
)
173174

174175
mulliken_partial_charges: List[Optional[List[List[float]]]] = Field(
175-
None,
176+
default_factory=list,
176177
description="Partial charges of each atom for each optimization step for each optimization trajectory, using "
177178
"the Mulliken method",
178179
)
179180

180181
mulliken_partial_spins: List[Optional[List[List[float]]]] = Field(
181-
None,
182+
default_factory=list,
182183
description="Partial spins of each atom for each optimization step for each optimization trajectory, using "
183184
"the Mulliken method",
184185
)
185186

186187
resp_partial_charges: List[Optional[List[List[float]]]] = Field(
187-
None,
188+
default_factory=list,
188189
description="Partial charges of each atom for each optimization step for each optimization trajectory, using "
189190
"the restrained electrostatic potential (RESP) method",
190191
)
191192

192193
dipole_moments: List[Optional[List[List[float]]]] = Field(
193-
None,
194+
default_factory=list,
194195
description="Molecular dipole moment for each optimization step for each optimization trajectory, "
195196
"(units: Debye)",
196197
)
197198

198199
resp_dipole_moments: List[Optional[List[List[float]]]] = Field(
199-
None,
200+
default_factory=list,
200201
description="Molecular dipole moment for each optimization step for each optimization trajectory, "
201202
"using the restrainted electrostatic potential (RESP) method (units: Debye)",
202203
)
@@ -247,21 +248,27 @@ def as_trajectories(self) -> List[Trajectory]:
247248
num_steps = len(mols)
248249

249250
# Frame (structure) properties
250-
frame_props = {
251-
"energies": self.energies[ii],
252-
"dipole_moments": self.dipole_moments[ii],
253-
"resp_dipole_moments": self.resp_dipole_moments[ii],
254-
}
251+
frame_props = {"energies": self.energies[ii]}
252+
for prop in (
253+
"dipole_moments",
254+
"resp_dipole_moments",
255+
):
256+
frame_props[prop] = []
257+
if (vals := getattr(self, prop, None)) is not None:
258+
frame_props[prop] = vals[ii]
255259

256260
# Site (atomic) properties
257-
site_props = {
258-
"forces": self.forces[ii],
259-
"pcm_forces": self.pcm_forces[ii],
260-
"cds_forces": self.cds_forces[ii],
261-
"mulliken_partial_charges": self.mulliken_partial_charges[ii],
262-
"mulliken_partial_spins": self.mulliken_partial_spins[ii],
263-
"resp_partial_charges": self.resp_partial_charges[ii],
264-
}
261+
site_props = {"forces": self.forces[ii]}
262+
for prop in (
263+
"pcm_forces",
264+
"cds_forces",
265+
"mulliken_partial_charges",
266+
"mulliken_partial_spins",
267+
"resp_partial_charges",
268+
):
269+
site_props[prop] = []
270+
if (vals := getattr(self, prop, None)) is not None:
271+
site_props[prop] = vals[ii]
265272

266273
# Convert into a Trajectory object
267274
traj_frame_props = list()

emmet-core/emmet/core/openmm/calculations.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,31 @@ class CalculationsDoc(BaseModel):
1313
In each field, calculations are listed sequentially, in the order they were run.
1414
"""
1515

16-
task_names: List[str] = Field(None, description="Names of tasks.")
16+
task_names: Optional[List[str]] = Field(None, description="Names of tasks.")
1717

18-
calc_types: List[str] = Field(None, description="Types of calculations.")
18+
calc_types: Optional[List[str]] = Field(None, description="Types of calculations.")
1919

20-
elapsed_times: List[Union[float, None]] = Field(
20+
elapsed_times: Optional[List[Union[float, None]]] = Field(
2121
None, description="Elapsed time for calculations."
2222
)
2323

24-
steps: List[Union[float, None]] = Field(
24+
steps: Optional[List[Union[float, None]]] = Field(
2525
None, description="n_steps for calculations."
2626
)
2727

28-
step_sizes: List[Union[float, None]] = Field(
28+
step_sizes: Optional[List[Union[float, None]]] = Field(
2929
None, description="Step sizes for each calculations."
3030
)
3131

32-
temperatures: List[Union[float, None]] = Field(
32+
temperatures: Optional[List[Union[float, None]]] = Field(
3333
None, description="Temperature for each calculations."
3434
)
3535

36-
pressures: List[Union[float, None]] = Field(
36+
pressures: Optional[List[Union[float, None]]] = Field(
3737
None, description="Pressure for each calculations."
3838
)
3939

40-
friction_coefficients: List[Union[float, None]] = Field(
40+
friction_coefficients: Optional[List[Union[float, None]]] = Field(
4141
None,
4242
description="Friction coefficients for each calculations.",
4343
)

emmet-core/emmet/core/openmm/tasks.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,12 +249,14 @@ class OpenMMInterchange(BaseModel):
249249
"""An object to sit in the place of the Interchance object
250250
and serialize the OpenMM system, topology, and state."""
251251

252-
system: str = Field(None, description="An XML file representing the OpenMM system.")
253-
state: str = Field(
252+
system: Optional[str] = Field(
253+
None, description="An XML file representing the OpenMM system."
254+
)
255+
state: Optional[str] = Field(
254256
None,
255257
description="An XML file representing the OpenMM state.",
256258
)
257-
topology: str = Field(
259+
topology: Optional[str] = Field(
258260
None,
259261
description="An XML file representing an OpenMM topology object."
260262
"This must correspond to the atom ordering in the system.",

emmet-core/emmet/core/qchem/molecule.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ class MoleculeDoc(CoreMoleculeDoc):
161161
None, description="Standardized hash of the InChI for this molecule"
162162
)
163163

164-
calc_types: Mapping[str, CalcType] = Field( # type: ignore
164+
calc_types: Optional[Mapping[str, CalcType]] = Field( # type: ignore
165165
None,
166166
description="Calculation types for all the calculations that make up this molecule",
167167
)

emmet-core/emmet/core/tasks.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from collections import OrderedDict
66
from datetime import datetime
77
from pathlib import Path
8-
from typing import Any, Dict, List, Optional, Tuple, Type, TypeVar, Union
8+
from typing import Any, Dict, List, Mapping, Optional, Tuple, Type, TypeVar, Union
99

1010
import numpy as np
1111
from monty.json import MontyDecoder
@@ -99,7 +99,7 @@ class OutputDoc(BaseModel):
9999
density: Optional[float] = Field(None, description="Density of in units of g/cc.")
100100
energy: Optional[float] = Field(None, description="Total Energy in units of eV.")
101101
forces: Optional[List[List[float]]] = Field(
102-
None, description="The force on each atom in units of eV/A^2."
102+
None, description="The force on each atom in units of eV/A."
103103
)
104104
stress: Optional[List[List[float]]] = Field(
105105
None, description="The stress on the cell in units of kB."
@@ -147,10 +147,10 @@ def from_vasp_calc_doc(
147147
OutputDoc
148148
The calculation output summary.
149149
"""
150-
if calc_doc.output.ionic_steps is not None:
150+
if calc_doc.output.ionic_steps:
151151
forces = calc_doc.output.ionic_steps[-1].forces
152152
stress = calc_doc.output.ionic_steps[-1].stress
153-
elif trajectory is not None:
153+
elif trajectory:
154154
ionic_steps = trajectory.frame_properties
155155
forces = ionic_steps[-1]["forces"]
156156
stress = ionic_steps[-1]["stress"]
@@ -434,7 +434,7 @@ class TaskDoc(StructureMetadata, extra="allow"):
434434
description="Identifier for this calculation; should provide rough information about the calculation origin and purpose.",
435435
)
436436

437-
run_stats: Optional[RunStatistics] = Field(
437+
run_stats: Optional[Mapping[str, RunStatistics]] = Field(
438438
None,
439439
description="Summary of runtime statistics for each calculation in this task",
440440
)
@@ -982,9 +982,10 @@ def _parse_additional_json(dir_name: Path) -> Dict[str, Any]:
982982
def _get_max_force(calc_doc: Calculation) -> Optional[float]:
983983
"""Get max force acting on atoms from a calculation document."""
984984
if calc_doc.output.ionic_steps:
985-
forces: Optional[Union[np.ndarray, List]] = calc_doc.output.ionic_steps[
986-
-1
987-
].forces
985+
forces: Optional[Union[np.ndarray, List]] = None
986+
if calc_doc.output.ionic_steps:
987+
forces = calc_doc.output.ionic_steps[-1].forces
988+
988989
structure = calc_doc.output.structure
989990
if forces:
990991
forces = np.array(forces)

emmet-core/emmet/core/vasp/calculation.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from typing import Any, Dict, List, Optional, Tuple, Union
1010

1111
import numpy as np
12-
from pydantic import BaseModel, ConfigDict, Field
12+
from pydantic import BaseModel, ConfigDict, Field, model_validator
1313
from pymatgen.command_line.bader_caller import bader_analysis_from_path
1414
from pymatgen.command_line.chargemol_caller import ChargemolAnalysis
1515
from pymatgen.core.lattice import Lattice
@@ -341,12 +341,21 @@ class IonicStep(BaseModel): # type: ignore
341341
electronic_steps: Optional[List[ElectronicStep]] = Field(
342342
None, description="The electronic convergence steps."
343343
)
344+
num_electronic_steps: Optional[int] = Field(
345+
None, description="The number of electronic steps needed to reach convergence."
346+
)
344347
structure: Optional[Structure] = Field(
345348
None, description="The structure at this step."
346349
)
347350

348351
model_config = ConfigDict(extra="allow")
349352

353+
@model_validator(mode="after")
354+
def set_elec_step_count(self):
355+
if self.electronic_steps is not None:
356+
self.num_electronic_steps = len(self.electronic_steps)
357+
return self
358+
350359

351360
class CalculationOutput(BaseModel):
352361
"""Document defining VASP calculation outputs."""
@@ -413,6 +422,9 @@ class CalculationOutput(BaseModel):
413422
ionic_steps: Optional[List[IonicStep]] = Field(
414423
None, description="Energy, forces, structure, etc. for each ionic step"
415424
)
425+
num_electronic_steps: Optional[List[int]] = Field(
426+
None, description="The number of electronic steps in each ionic step."
427+
)
416428
locpot: Optional[Dict[int, List[float]]] = Field(
417429
None, description="Average of the local potential along the crystal axes"
418430
)
@@ -576,6 +588,19 @@ def from_vasp_outputs(
576588
temp = str(elph_poscar.name).replace("POSCAR.T=", "").replace(".gz", "")
577589
elph_structures["temperatures"].append(temp)
578590
elph_structures["structures"].append(Structure.from_file(elph_poscar))
591+
592+
ionic_steps = (
593+
vasprun.ionic_steps
594+
if store_trajectory == StoreTrajectoryOption.NO
595+
else None
596+
)
597+
num_elec_steps = None
598+
if ionic_steps is not None:
599+
num_elec_steps = [
600+
len(ionic_step.get("electronic_steps", []) or [])
601+
for ionic_step in ionic_steps
602+
]
603+
579604
return cls(
580605
structure=structure,
581606
energy=vasprun.final_energy,
@@ -587,11 +612,8 @@ def from_vasp_outputs(
587612
frequency_dependent_dielectric=freq_dependent_diel,
588613
elph_displaced_structures=elph_structures,
589614
dos_properties=dosprop_dict,
590-
ionic_steps=(
591-
vasprun.ionic_steps
592-
if store_trajectory == StoreTrajectoryOption.NO
593-
else None
594-
),
615+
ionic_steps=ionic_steps,
616+
num_electronic_steps=num_elec_steps,
595617
locpot=locpot_avg,
596618
outcar=outcar_dict,
597619
run_stats=RunStatistics.from_outcar(outcar) if outcar else None,

0 commit comments

Comments
 (0)