1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
|
from ase.calculators.calculator import Calculator, all_changes
from ase.calculators.calculator import PropertyNotImplementedError
class LinearCombinationCalculator(Calculator):
"""LinearCombinationCalculator for weighted summation of multiple calculators.
"""
def __init__(self, calcs, weights, atoms=None):
"""Implementation of sum of calculators.
calcs: list
List of an arbitrary number of :mod:`ase.calculators` objects.
weights: list of float
Weights for each calculator in the list.
atoms: Atoms object
Optional :class:`~ase.Atoms` object to which the calculator will be attached.
"""
super().__init__(atoms=atoms)
if len(calcs) == 0:
raise ValueError('The value of the calcs must be a list of Calculators')
for calc in calcs:
if not isinstance(calc, Calculator):
raise ValueError('All the calculators should be inherited form the ase\'s Calculator class')
common_properties = set.intersection(*(set(calc.implemented_properties) for calc in calcs))
self.implemented_properties = list(common_properties)
if not self.implemented_properties:
raise PropertyNotImplementedError('There are no common property implemented for the potentials!')
if len(weights) != len(calcs):
raise ValueError('The length of the weights must be the same as the number of calculators!')
self.calcs = calcs
self.weights = weights
def calculate(self, atoms=None, properties=['energy'], system_changes=all_changes):
""" Calculates all the specific property for each calculator and returns with the summed value.
"""
super().calculate(atoms, properties, system_changes)
if not set(properties).issubset(self.implemented_properties):
raise PropertyNotImplementedError('Some of the requested property is not in the '
'list of supported properties ({})'.format(self.implemented_properties))
for w, calc in zip(self.weights, self.calcs):
if calc.calculation_required(atoms, properties):
calc.calculate(atoms, properties, system_changes)
for k in properties:
if k not in self.results:
self.results[k] = w * calc.results[k]
else:
self.results[k] += w * calc.results[k]
def reset(self):
"""Clear all previous results recursively from all fo the calculators."""
super().reset()
for calc in self.calcs:
calc.reset()
def __str__(self):
calculators = ', '.join(calc.__class__.__name__ for calc in self.calcs)
return '{}({})'.format(self.__class__.__name__, calculators)
class MixedCalculator(LinearCombinationCalculator):
"""
Mixing of two calculators with different weights
H = weight1 * H1 + weight2 * H2
Has functionality to get the energy contributions from each calculator
Parameters
----------
calc1 : ASE-calculator
calc2 : ASE-calculator
weight1 : float
weight for calculator 1
weight2 : float
weight for calculator 2
"""
def __init__(self, calc1, calc2, weight1, weight2):
super().__init__([calc1, calc2], [weight1, weight2])
def set_weights(self, w1, w2):
self.weights[0] = w1
self.weights[1] = w2
def calculate(self, atoms=None, properties=['energy'], system_changes=all_changes):
""" Calculates all the specific property for each calculator and returns with the summed value.
"""
super().calculate(atoms, properties, system_changes)
if 'energy' in properties:
energy1 = self.calcs[0].get_property('energy', atoms)
energy2 = self.calcs[1].get_property('energy', atoms)
self.results['energy_contributions'] = (energy1, energy2)
def get_energy_contributions(self, atoms=None):
""" Return the potential energy from calc1 and calc2 respectively """
self.calculate(properties=['energy'], atoms=atoms)
return self.results['energy_contributions']
class SumCalculator(LinearCombinationCalculator):
"""SumCalculator for combining multiple calculators.
This calculator can be used when there are different calculators for the different chemical environment or
for example during delta leaning. It works with a list of arbitrary calculators and evaluates them in sequence
when it is required.
The supported properties are the intersection of the implemented properties in each calculator.
"""
def __init__(self, calcs, atoms=None):
"""Implementation of sum of calculators.
calcs: list
List of an arbitrary number of :mod:`ase.calculators` objects.
atoms: Atoms object
Optional :class:`~ase.Atoms` object to which the calculator will be attached.
"""
weights = [1.] * len(calcs)
super().__init__(calcs, weights, atoms)
class AverageCalculator(LinearCombinationCalculator):
"""AverageCalculator for equal summation of multiple calculators (for thermodynamic purposes)..
"""
def __init__(self, calcs, atoms=None):
"""Implementation of average of calculators.
calcs: list
List of an arbitrary number of :mod:`ase.calculators` objects.
atoms: Atoms object
Optional :class:`~ase.Atoms` object to which the calculator will be attached.
"""
n = len(calcs)
if n == 0:
raise ValueError('The value of the calcs must be a list of Calculators')
weights = [1 / n] * n
super().__init__(calcs, weights, atoms)
|