ASE (Atomic Simulation Environment) est équipé d'une fonction de recherche de structure stable utilisant un algorithme génétique (GA). Les algorithmes génétiques peuvent être utilisés pour rechercher efficacement des structures stables en vrac et en nanoclusters.
Cherchons la structure des nanoclusters métalliques en nous référant au Official Document Tutorial. Pour plus de simplicité, EMT (Effective Medium Theory) est utilisé pour le calcul de l'énergie.
Le calcul suivant a été effectué sur Jupyter Lab.
Créez une structure aléatoire en spécifiant le rapport chimique. Ici, 10 clusters de Pt 15 </ sub> Au 15 </ sub> sont créés dans une boîte de simulation 25 × 25 × 25A.
import numpy as np
from ase.atoms import Atoms
from ase.ga.startgenerator import StartGenerator
from ase.ga.utilities import closest_distances_generator
L = 25.
population_size = 10
atom_numbers = Atoms('Pt15Au15').get_atomic_numbers()
slab = Atoms(cell=np.array([L, L, L]))
cd = closest_distances_generator(atom_numbers=atom_numbers,
ratio_of_covalent_radii=0.7)
sg = StartGenerator(slab=slab,
atom_numbers=atom_numbers,
closest_allowed_distances=cd,
box_to_place_in=[np.zeros(3), np.eye(3)*L/2.])
initial_population = [sg.get_new_candidate() for _ in range(population_size)]
Utilisez la fonction view
pour vérifier la structure initiale.
from ase.visualize import view
view(initial_population)
Une telle population initiale a été générée. Puisqu'il est généré avec des nombres aléatoires, ce résultat changera d'un essai à l'autre.
Enregistrez le groupe initial créé dans un fichier de base de données externe. Le module ʻase.ga.data` vous permet de gérer efficacement de grandes quantités de données de calcul.
from ase.ga.data import PrepareDB
db_file = 'ga.db'
db = PrepareDB(db_file_name=db_file,
simulation_cell=slab,
stoichiometry=atom_numbers,
population_size=population_size)
for atoms in initial_population:
db.add_unrelaxed_candidate(atoms)
Optimiser une structure créée aléatoirement.
from ase.ga.data import DataConnection
from ase.optimize import BFGS
from ase.calculators.emt import EMT
db = DataConnection(db_file)
while db.get_number_of_unrelaxed_candidates() > 0:
atoms = db.get_an_unrelaxed_candidate()
atoms.set_calculator(EMT())
BFGS(atoms, trajectory=None, logfile=None).run(fmax=0.05, steps=100)
atoms.info['key_value_pairs']['raw_score'] = -atoms.get_potential_energy()
db.add_relaxed_step(atoms)
Définissez les algorithmes de croisement et de mutation.
from random import random
from ase.ga.data import DataConnection
from ase.ga.population import Population
from ase.ga.standard_comparators import InteratomicDistanceComparator
from ase.ga.cutandsplicepairing import CutAndSplicePairing
from ase.ga.offspring_creator import OperationSelector
from ase.ga.standardmutations import (MirrorMutation, RattleMutation)
mutation_probability = 0.3
n_generation = 10
#Algorithme de détermination d'identité de structure atomique:Utilisé pour assurer la diversité de la population
comperator = InteratomicDistanceComparator(n_top=len(atom_numbers),
pair_cor_cum_diff=0.015,
pair_cor_max=0.7,
dE=0.02,
mic=False)
#Traversée
pairing = CutAndSplicePairing(slab, len(atom_numbers), cd)
#mutation:Choisissez au hasard l'un des deux algorithmes de mutation
mutations = OperationSelector([1., 1.], #Ratio de probabilité pour adopter chaque algorithme
[MirrorMutation(cd, len(atom_numbers)),
RattleMutation(cd, len(atom_numbers))])
population = Population(data_connection=db,
population_size=population_size,
comparator=comperator)
Exécute un algorithme génétique. Cela prendra quelques minutes. Si vous utilisez DFT au lieu d'EMT, cela prendra encore plus de temps.
from random import random
for i in range(population_size * n_generation):
#Traversée
atoms1, atoms2 = population.get_two_candidates()
atoms3, desc = pairing.get_new_individual([atoms1, atoms2])
if atoms3 is None: continue
db.add_unrelaxed_candidate(atoms3, description=desc)
#mutation
if random() < mutation_probability:
atoms3_mut, desc = mutations.get_new_individual([atoms3])
if atoms3_mut is not None:
db.add_unrelaxed_step(atoms3_mut, description=desc)
atoms3 = atoms3_mut
#Relaxation structurelle de la progéniture
atoms3.set_calculator(EMT())
BFGS(atoms3, trajectory=None, logfile=None).run(fmax=0.05, steps=100)
atoms3.info['key_value_pairs']['raw_score'] = -atoms3.get_potential_energy()
db.add_relaxed_step(atoms3)
population.update()
print(f'{i}th structure relaxation completed')
Tracez la structure la plus stable pour chaque génération en utilisant matplotlib.
%matplotlib inline
from tempfile import NamedTemporaryFile
from ase.io import write
import matplotlib.pyplot as plt
from matplotlib.offsetbox import (OffsetImage, AnnotationBbox)
#Convertir l'objet Atoms en Offset Image de matplotlib
def atoms_to_img(atoms):
pngfile = NamedTemporaryFile(suffix='.png').name
write(pngfile, atoms, scale=10, show_unit_cell=False)
return OffsetImage(plt.imread(pngfile, format='png'), zoom=1.0)
plt.rcParams["font.size"] = 24
fig, ax = plt.subplots(figsize=(12,8))
#Obtenez un objet Atoms pour chaque génération
X, Y = [], []
for i_gen in range(n_generation):
atoms = db.get_all_relaxed_candidates_after_generation(i_gen)[0] #Trié par énergie
e = atoms.get_potential_energy()
X += [i_gen-0.5, i_gen+0.5]
Y += [e, e]
if i_gen % 3 == 0:
abb = AnnotationBbox(atoms_to_img(atoms), [i_gen+0.5, e+2.], frameon=False)
ax.add_artist(abb)
ax.plot(X, Y, color='darkblue', linewidth=3.)
ax.set_ylim((min(Y)-1, max(Y)+5))
ax.set_title('Optimal Structure of Pt$_{15}$Au$_{15}$ by Generation')
ax.set_xlabel('GA Generation')
ax.set_ylabel('Total Energy (eV)')
plt.show()
Recommended Posts