ParallelEnergyAndForces¶
- ParallelEnergyAndForces(configurations, processes_per_configuration, filenames=None, master_only=True)¶
Evaluates the energy and forces for a list of configurations in parallel.
- Parameters:
configurations (list of configurations (
MoleculeConfiguration
|BulkConfiguration
|SurfaceConfiguration
|DeviceConfiguration
)) – A list of configurations with attached calculators.processes_per_configuration (int) – The number of MPI processes to use for calculating the energy and forces on each configuration.
log_filenames (list of strings) – A list of filenames to log each calculation to. It must be the same length as the configurations argument. If None is given, then logging will be performed to stdout. Default:
All calculations are logged to stdout
master_only (bool) – Controls if the master process should be the only rank allowed to write to the log. Default:
True
- Returns:
A tuple of energies and forces as PhysicalQuantity arrays.
- Return type:
tuple
Usage Examples¶
Calculate the potential energy curve and forces for a hydrogen molecule in parallel. At the end of the script, a table of internuclear distances, energies, and the force is printed to the screen.
# Make a list to hold the configurations.
configurations = []
# Loop over a list of distances between 0.3 and 5.0 Angstrom.
distances = numpy.linspace(0.3, 5.0, 20)
for distance in distances:
# Define elements
elements = [Hydrogen, Hydrogen]
# Define coordinates
cartesian_coordinates = [[ 0.0, 0.0, 0.0 ],
[ distance, 0.0, 0.0 ]]*Angstrom
# Set up configuration
molecule_configuration = MoleculeConfiguration(
elements=elements,
cartesian_coordinates=cartesian_coordinates
)
# Define a calculator
molecule_configuration.setCalculator(LCAOCalculator())
# Add the configuration to the list of configurations.
configurations.append(molecule_configuration)
def task_function(configuration, index):
# Compute the total energy.
total_energy = TotalEnergy(configuration)
# Save the result to a file.
nlsave('total_energy_%i.hdf5'%index, configuration)
# Return the calculated total energy.
return total_energy.evaluate()
# Define a list of filenames to save the logging output from each calculation to.
filenames = [ 'total_energy_%i.log' % i for i in range(len(configurations)) ]
# Calculate the energy of each configuration. Each calculation will use 2 MPI processes.
energies = ParallelMapConfigurations(
task_function,
configurations,
processes_per_configuration=2,
filenames=filenames,
)
# Only print on the master process. This prevents the table from being printed multiple times.
if processIsMaster():
print('%10s %12s' % ('distance', 'energy'))
for i in range(len(configurations)):
print('%10.4f %12.3e' % (distances[i], energies[i].inUnitsOf(eV)))
Notes¶
It is important to properly coordinate the total number of MPI processes, the processes_per_configuration
argument, and the number of configurations. When ParallelEnergyAndForces
is called, the MPI processes are divided up into NLEngine.numberOfProcesses()
/processes_per_configuration
sized groups. For example, if there are 8 MPI processes and processes_per_configuration=2
, then 4 groups will be made. However, if there are only 2 configurations in the configurations
list then 2 of those groups will be idle.
Ideally, one would pick processes_per_configuration
to be the largest number of processes that a single DFT calculation runs efficiently on. This generally depends on a number of variables including the number of atoms, basis set size, computer hardware, etc. Then, the total number of MPI processes should be an integer multiple of processes_per_configuration
.
This function can be used with ATK-ForceFiled calculators as well.
However, ATK-ForceField does not currently make use of MPI.
This means that processes_per_configuration
should always be set to 1 in order not to
have idle processes.
See also, Notes.