1+ """Script to run QHA with FLARE calculator."""
2+ import numpy as np
3+
4+ from ase import units , Atoms
5+ from ase .io import read
6+ from ase .optimize import BFGS
7+ from ase .constraints import ExpCellFilter
8+
9+ from phonopy import Phonopy
10+ from phonopy .structure .atoms import PhonopyAtoms
11+
12+ from flare .bffs .sgp .calculator import SGP_Calculator
13+ from flare .atoms import FLARE_Atoms
14+
15+
16+ class QHA :
17+ """Run QHA with FLARE calculator."""
18+
19+ def __init__ (
20+ self ,
21+ structure_filepath = 'Si.pwi' ,
22+ model_filepath = 'model.json' ,
23+ initial_vcrelax = False ,
24+ fmax = 0.001 ,
25+ unitcell = None ,
26+ scale_i = 0.94 ,
27+ scale_f = 1.06 ,
28+ scale_step = 12 ,
29+ supercell_matrix = [2 , 2 , 2 ],
30+ distance = 0.01 ,
31+ t_min = 0 ,
32+ t_max = 300 ,
33+ symmetrize = False ,
34+ primitive_matrix = None ,
35+ mesh = 100 ,
36+ ):
37+ """Constructor of the class."""
38+ self .initial_vcrelax = initial_vcrelax
39+ self .fmax = fmax
40+ self .unitcell = FLARE_Atoms .from_ase_atoms (read (structure_filepath ))
41+ self .scale_i = scale_i
42+ self .scale_f = scale_f
43+ self .scale_step = scale_step
44+ self .supercell_matrix = supercell_matrix
45+ self .distance = distance
46+ self .t_min = t_min
47+ self .t_max = t_max
48+ self .symmetrize = symmetrize
49+ self .primitive_matrix = primitive_matrix
50+ self .mesh = mesh
51+
52+ flare_calc , _ = SGP_Calculator .from_file (model_filepath )
53+ self .unitcell .calc = flare_calc
54+
55+ def run (self ) -> None :
56+ """Run QHA calculation."""
57+ print (self .unitcell .get_potential_energy (), flush = True )
58+ # Initial optimization
59+ print ("Running initial geometry optimization..." , flush = True )
60+ if self .initial_vcrelax :
61+ print ("Initial volume: " , self .unitcell .get_volume (), flush = True )
62+ self .unitcell = geometry_optimization (
63+ self .unitcell ,
64+ vcrelax = self .initial_vcrelax ,
65+ fmax = self .fmax ,
66+ )
67+ if self .initial_vcrelax :
68+ print ("Final volume: " , self .unitcell .get_volume (), flush = True )
69+
70+ # Run equation of state
71+ print ("Running equation of state..." , flush = True )
72+ all_scaled_atoms , energies = self .run_eos ()
73+
74+ print ("Running phonons for each volume..." )
75+ start_index = - energies .index (min (energies ))
76+ for scaled_atoms in all_scaled_atoms :
77+ self .phonons (scaled_atoms , filename = f'./thermal_properties.yaml-{ start_index } ' )
78+ start_index += 1
79+
80+ print ("Run completed" )
81+
82+ def run_eos (self ) -> tuple [tuple [Atoms ], list ]:
83+ """Run equation of states."""
84+ energies , volumes = [], []
85+ all_scaled_atoms = []
86+
87+ scale_factors = np .linspace (self .scale_i , self .scale_f , self .scale_step )
88+
89+ for scale_factor in scale_factors :
90+ scaled_atoms = scale_and_relax (self .unitcell , scale_factor , fmax = self .fmax )
91+ all_scaled_atoms .append (scaled_atoms )
92+ energies .append (scaled_atoms .get_potential_energy ())
93+ volumes .append (scaled_atoms .get_volume ())
94+
95+ np .savetxt ('./e-v.dat' , np .array ([volumes , energies ]).T )
96+
97+ return all_scaled_atoms , energies
98+
99+ def phonons (self , atoms : Atoms , filename : str = None ) -> None :
100+ """Run phonons using Phonopy."""
101+ unitcell = PhonopyAtoms (
102+ symbols = atoms .get_chemical_symbols (),
103+ numbers = atoms .get_atomic_numbers (),
104+ scaled_positions = atoms .get_scaled_positions (),
105+ cell = atoms .get_cell (),
106+ )
107+
108+ ph = Phonopy (
109+ unitcell = unitcell ,
110+ primitive_matrix = self .primitive_matrix ,
111+ supercell_matrix = self .supercell_matrix ,
112+ )
113+
114+ ph .generate_displacements (distance = self .distance )
115+ supercells = ph .supercells_with_displacements
116+
117+ sets_of_forces = []
118+ for supercell in supercells :
119+ cell , scaled_positions , numbers = supercell .totuple ()
120+ supercell_atoms = Atoms (
121+ cell = cell ,
122+ scaled_positions = scaled_positions ,
123+ numbers = numbers ,
124+ calculator = self .flare_calc ,
125+ )
126+ sets_of_forces .append (supercell_atoms .get_forces ())
127+
128+ ph .forces = sets_of_forces
129+ ph .produce_force_constants ()
130+
131+ if self .symmetrize :
132+ ph .symmetrize_force_constants ()
133+ ph .symmetrize_force_constants_by_space_group ()
134+
135+ ph .run_mesh (mesh = self .mesh )
136+ ph .run_thermal_properties (t_min = self .t_min , t_max = self .t_max )
137+ ph .thermal_properties .write_yaml (filename )
138+
139+
140+ def geometry_optimization (
141+ atoms : Atoms ,
142+ vcrelax : bool = False ,
143+ fmax : float = 0.001 ,
144+ ) -> Atoms :
145+ """Optimize geometry of the given atoms."""
146+ print ("I am in opt" , flush = True )
147+ print (atoms .calc )
148+ if vcrelax :
149+ ecf = ExpCellFilter (atoms , scalar_pressure = 0 )
150+ optimizer = BFGS (ecf )
151+ else :
152+ optimizer = BFGS (atoms = atoms )
153+ print ("Running opt" , flush = True )
154+ optimizer .run (fmax = fmax )
155+ print ("I almost finished in opt" , flush = True )
156+
157+ if vcrelax :
158+ return optimizer .atoms .atoms
159+ return optimizer .atoms
160+
161+
162+ def scale_and_relax (atoms : Atoms , scale_factor : float , fmax : float = 0.001 ) -> Atoms :
163+ """Scale and relax the atoms of an ASE Atoms object."""
164+ ase = atoms .copy ()
165+ ase .calc = atoms .calc
166+ ase .set_cell (ase .get_cell () * float (scale_factor ) ** (1 / 3 ), scale_atoms = True )
167+
168+ return geometry_optimization (ase , vcrelax = False , fmax = fmax )
169+
170+
171+ QHA ().run ()
172+
0 commit comments