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
|
import random
from pathlib import Path
from ase.build import fcc111
from ase.calculators.emt import EMT
from ase.data import atomic_numbers, reference_states
from ase.ga import set_raw_score
from ase.ga.data import PrepareDB
def get_avg_lattice_constant(syms):
a = 0.0
for m in set(syms):
a += syms.count(m) * lattice_constants[m]
return a / len(syms)
metals = ['Cu', 'Pt']
# Use experimental lattice constants
lattice_constants = {
m: reference_states[atomic_numbers[m]]['a'] for m in metals
}
# Create the references (pure slabs) manually
pure_slabs = []
refs = {}
print('Reference energies:')
for m in metals:
slab = fcc111(
m, size=(2, 4, 3), a=lattice_constants[m], vacuum=5, orthogonal=True
)
slab.calc = EMT()
# We save the reference energy as E_A / N
e = slab.get_potential_energy()
e_per_atom = e / len(slab)
refs[m] = e_per_atom
print(f'{m} = {e_per_atom:.3f} eV/atom')
# The mixing energy for the pure slab is 0 by definition
set_raw_score(slab, 0.0)
pure_slabs.append(slab)
# The population size should be at least the number of different compositions
pop_size = 2 * len(slab)
# We prepare the db and write a few constants that we are going to use later
target = Path('hull.db')
if target.exists():
target.unlink()
db = PrepareDB(
target,
population_size=pop_size,
reference_energies=refs,
metals=metals,
lattice_constants=lattice_constants,
)
# We add the pure slabs to the database as relaxed because we have already
# set the raw_score
for slab in pure_slabs:
db.add_relaxed_candidate(
slab, atoms_string=''.join(slab.get_chemical_symbols())
)
# Now we create the rest of the candidates for the initial population
for i in range(pop_size - 2):
# How many of each metal is picked at random, making sure that
# we do not pick pure slabs
nA = random.randint(0, len(slab) - 2)
nB = len(slab) - 2 - nA
symbols = [metals[0]] * nA + [metals[1]] * nB + metals
# Making a generic slab with the correct lattice constant
slab = fcc111(
'X',
size=(2, 4, 3),
a=get_avg_lattice_constant(symbols),
vacuum=5,
orthogonal=True,
)
# Setting the symbols and randomizing the order
slab.set_chemical_symbols(symbols)
random.shuffle(slab.numbers)
# Add these candidates as unrelaxed, we will relax them later
atoms_string = ''.join(slab.get_chemical_symbols())
db.add_unrelaxed_candidate(slab, atoms_string=atoms_string)
|