-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement new standard outputs for atomistic models #654
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -136,7 +136,9 @@ ModelOutput ModelOutputHolder::from_json(std::string_view json) { | |
/******************************************************************************/ | ||
|
||
std::unordered_set<std::string> KNOWN_OUTPUTS = { | ||
"energy" | ||
"energy", | ||
"dipole", | ||
"energy_ensemble" | ||
}; | ||
|
||
void ModelCapabilitiesHolder::set_outputs(torch::Dict<std::string, ModelOutput> outputs) { | ||
|
@@ -1032,6 +1034,29 @@ static std::unordered_map<std::string, Quantity> KNOWN_QUANTITIES = { | |
{"J", "Joule"}, | ||
{"Ry", "Rydberg"}, | ||
}}}, | ||
{"energy_ensemble", Quantity{/* name */ "energy", /* baseline */ "eV", { | ||
{"eV", 1.0}, | ||
{"meV", 1000.0}, | ||
{"Hartree", 0.03674932247495664}, | ||
{"kcal/mol", 23.060548012069496}, | ||
{"kJ/mol", 96.48533288249877}, | ||
{"Joule", 1.60218e-19}, | ||
{"Rydberg", 0.07349864435130857}, | ||
}, { | ||
// alternative names | ||
{"J", "Joule"}, | ||
{"Ry", "Rydberg"}, | ||
}}}, | ||
{"dipole", Quantity{/* name */ "dipole", /* baseline */ "D", { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we want to call this |
||
{"Debye", 1.0}, | ||
{"Coulomb-meter", 1000.0}, | ||
{"atomic units", 0.03674932247495664}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I love having a space inside the unit =/ |
||
}, { | ||
// alternative names | ||
{"D", "Debye"}, | ||
{"C-m", "Coulomb-meter"}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be |
||
{"a.u.", "atomic units"}, | ||
}}}, | ||
}; | ||
|
||
bool metatensor_torch::valid_quantity(const std::string& quantity) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,6 +62,7 @@ def __init__( | |
extensions_directory=None, | ||
check_consistency=False, | ||
device=None, | ||
properties_to_store: Optional[List[str]] = None, | ||
): | ||
""" | ||
:param model: model to use for the calculation. This can be a file path, a | ||
|
@@ -73,6 +74,12 @@ def __init__( | |
running, defaults to False. | ||
:param device: torch device to use for the calculation. If ``None``, we will try | ||
the options in the model's ``supported_device`` in order. | ||
:param properties_to_store: list of model outputs to store as results of the ASE | ||
calculator at every step. This is useful when you want to store properties | ||
that are not used in the propagation of the dynamics and/or are not standard | ||
ASE properties ('energy', 'forces', 'stress', 'stresses', 'dipole', | ||
'charges', 'magmom', 'magmoms', 'free_energy', 'energies'). These properties | ||
will be available as ``atoms.calc.results['<stored_property>']``. | ||
Comment on lines
+77
to
+82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this typical in ASE? The idea for properties not part of the standard was to use the In general, this does not seems to be required for dipole, since ASE already supports them as property. |
||
""" | ||
super().__init__() | ||
|
||
|
@@ -147,6 +154,9 @@ def __init__( | |
# We do our own check to verify if a property is implemented in `calculate()`, | ||
# so we pretend to be able to compute all properties ASE knows about. | ||
self.implemented_properties = ALL_ASE_PROPERTIES | ||
self.properties_to_store = ( | ||
properties_to_store if properties_to_store is not None else [] | ||
) | ||
|
||
def todict(self): | ||
if "model_path" not in self.parameters: | ||
|
@@ -243,7 +253,9 @@ def calculate( | |
) | ||
|
||
with record_function("ASECalculator::prepare_inputs"): | ||
outputs = _ase_properties_to_metatensor_outputs(properties) | ||
outputs = _ase_properties_to_metatensor_outputs( | ||
properties + self.properties_to_store | ||
) | ||
capabilities = self._model.capabilities() | ||
for name in outputs.keys(): | ||
if name not in capabilities.outputs: | ||
|
@@ -257,11 +269,11 @@ def calculate( | |
) | ||
|
||
do_backward = False | ||
if "forces" in properties: | ||
if "forces" in properties + self.properties_to_store: | ||
do_backward = True | ||
positions.requires_grad_(True) | ||
|
||
if "stress" in properties: | ||
if "stress" in properties + self.properties_to_store: | ||
do_backward = True | ||
|
||
scaling = torch.eye(3, requires_grad=True, dtype=self._dtype) | ||
|
@@ -271,7 +283,7 @@ def calculate( | |
|
||
cell = cell @ scaling | ||
|
||
if "stresses" in properties: | ||
if "stresses" in properties + self.properties_to_store: | ||
raise NotImplementedError("'stresses' are not implemented yet") | ||
|
||
run_options = ModelEvaluationOptions( | ||
|
@@ -322,14 +334,14 @@ def calculate( | |
|
||
self.results = {} | ||
|
||
if "energies" in properties: | ||
if "energies" in properties + self.properties_to_store: | ||
energies_values = energies.detach().reshape(-1) | ||
energies_values = energies_values.to(device="cpu").to( | ||
dtype=torch.float64 | ||
) | ||
self.results["energies"] = energies_values.numpy() | ||
|
||
if "energy" in properties: | ||
if "energy" in properties + self.properties_to_store: | ||
energy_values = energy.detach() | ||
energy_values = energy_values.to(device="cpu").to(dtype=torch.float64) | ||
self.results["energy"] = energy_values.numpy()[0, 0] | ||
|
@@ -339,18 +351,32 @@ def calculate( | |
energy.backward(-torch.ones_like(energy)) | ||
|
||
with record_function("ASECalculator::convert_outputs"): | ||
if "forces" in properties: | ||
if "forces" in properties + self.properties_to_store: | ||
forces_values = system.positions.grad.reshape(-1, 3) | ||
forces_values = forces_values.to(device="cpu").to(dtype=torch.float64) | ||
self.results["forces"] = forces_values.numpy() | ||
|
||
if "stress" in properties: | ||
if "stress" in properties + self.properties_to_store: | ||
stress_values = -scaling.grad.reshape(3, 3) / atoms.cell.volume | ||
stress_values = stress_values.to(device="cpu").to(dtype=torch.float64) | ||
self.results["stress"] = _full_3x3_to_voigt_6_stress( | ||
stress_values.numpy() | ||
) | ||
|
||
if "dipole" in properties + self.properties_to_store: | ||
dipole_values = outputs["dipole"].block().values.detach().reshape(3) | ||
dipole_values = dipole_values.to(device="cpu").to(dtype=torch.float64) | ||
self.results["dipole"] = dipole_values.numpy() | ||
|
||
if "energy_ensemble" in properties + self.properties_to_store: | ||
energy_ensemble_values = ( | ||
outputs["energy_ensemble"].block().values.detach().flatten() | ||
) | ||
energy_ensemble_values = energy_ensemble_values.to(device="cpu").to( | ||
dtype=torch.float64 | ||
) | ||
self.results["energy_ensemble"] = energy_ensemble_values.numpy() | ||
|
||
|
||
def _find_best_device(devices: List[str]) -> torch.device: | ||
""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be needed,
energy_ensemble
should be able to re-use theenergy
quantity and associated units