Source code for scine_puffin.jobs.scine_afir

# -*- coding: utf-8 -*-
from __future__ import annotations
__copyright__ = """ This code is licensed under the 3-clause BSD license.
Copyright ETH Zurich, Department of Chemistry and Applied Biosciences, Reiher Group.
See LICENSE.txt for details.
"""

from typing import TYPE_CHECKING

from scine_puffin.config import Configuration
from .templates.job import calculation_context, job_configuration_wrapper
from .templates.scine_react_job import ReactJob
from scine_puffin.utilities.imports import module_exists, MissingDependency

if module_exists("scine_database") or TYPE_CHECKING:
    import scine_database as db
else:
    db = MissingDependency("scine_database")


[docs]class ScineAfir(ReactJob): """A job optimizing a structure while applying an artificial force. **Order Name** ``scine_afir`` **Optional Settings** Optional settings are read from the ``settings`` field, which is part of any ``Calculation`` stored in a SCINE Database. Possible settings for this job are: All settings recognized by ReaDuct's AFIR task. For a complete list see the `ReaDuct manual <https://scine.ethz.ch/download/readuct>`_ The most common AFIR related ones being: afir_afir_weak_forces : bool This activates an additional, weakly attractiveforce applied to all atom pairs. By default set to ``False``. afir_afir_attractive : bool Specifies whether the artificial force is attractive or repulsive. By default set to ``True``, which means that the force is attractive. afir_afir_rhs_list : List[int] This specifies list of indices of atoms to be artificially forced onto or away from those in the LHS list (see below). By default, this list is empty. Note that the first atom has the index zero. afir_afir_lhs_list : List[int] This specifies list of indices of atoms to be artificially forced onto or away from those in the RHS list (see above). By default, this list is empty. Note that the first atom has the index zero. afir_afir_energy_allowance : float The maximum amount of energy to be added by the artificial force, in kJ/mol. By default set to 1000 kJ/mol. afir_afir_phase_in : int The number of steps over which the full force is gradually applied. By default set to 100. All settings that are recognized by the SCF program chosen. Common examples are: max_scf_iterations : int The number of allowed SCF cycles until convergence. **Required Packages** - SCINE: Database (present by default) - SCINE: Readuct (present by default) - SCINE: Utils (present by default) - A program implementing the SCINE Calculator interface, e.g. Sparrow **Generated Data** If successful the following data will be generated and added to the database: Structures A new structure, the optimized (inc. artificial force) structure. Properties The ``electronic_energy`` associated with the new structure. """ def __init__(self) -> None: super().__init__() self.name = "Scine AFIR Job" afir_defaults = { "output": ["afir"], "convergence_max_iterations": 500, } opt_defaults = { "output": ["opt"], "convergence_max_iterations": 500, } self.settings = { **self.settings, "afir": afir_defaults, self.opt_key: opt_defaults, }
[docs] @job_configuration_wrapper def run(self, manager: db.Manager, calculation: db.Calculation, config: Configuration) -> bool: import scine_readuct as readuct structure = db.Structure(calculation.get_structures()[0], self._structures) settings_manager, program_helper = self.create_helpers(structure) with calculation_context(self): """ preparation """ if len(calculation.get_structures()) > 1: raise RuntimeError(self.name + " is only meant for a single structure!") settings_manager.separate_settings(self._calculation.get_settings()) self.sort_settings(settings_manager.task_settings) self.systems, keys = settings_manager.prepare_readuct_task( structure, calculation, calculation.get_settings(), config["resources"] ) if program_helper is not None: program_helper.calculation_preprocessing(self.get_system(keys[0]), calculation.get_settings()) """ AFIR Optimization """ print("Afir Settings:") print(self.settings["afir"], "\n") self.systems, success = readuct.run_afir_task(self.systems, keys, **self.settings['afir']) self.throw_if_not_successful(success, self.systems, keys, ["energy"], "AFIR optimization failed:\n") """ Endpoint Optimization """ product_names, self.systems = self.optimize_structures( "product", self.systems, [self.get_system(self.output('afir')[0]).structure], [structure.get_charge()], [structure.get_multiplicity()], settings_manager.calculator_settings ) if len(product_names) != 1: self.raise_named_exception("Optimization of the product yielded multiple structures, " "which is not expected") lowest_name, names_within_range = self._get_propensity_names_within_range( product_names[0], self.systems, self.settings[self.propensity_key]["energy_range_to_optimize"] ) if lowest_name is None: self.raise_named_exception("Product optimization was not successful") raise RuntimeError("Unreachable") old_label = structure.get_label() db_results = calculation.get_results() for product in [lowest_name] + names_within_range: graph, self.systems = self.make_graph_from_calc(self.systems, product) new_label = self.determine_new_label(old_label, graph, structure.has_property("surface_atom_indices")) new_structure = self.optimization_postprocessing(success, self.systems, [product], structure, new_label, program_helper, ['energy', 'bond_orders']) bond_orders = self.get_system(product_names[0]).get_results().bond_orders assert bond_orders is not None self.store_property( self._properties, "bond_orders", "SparseMatrixProperty", bond_orders.matrix, self._calculation.get_model(), self._calculation, new_structure, ) self.add_graph(new_structure, bond_orders) db_results += calculation.get_results() calculation.set_results(db_results) return self.postprocess_calculation_context()