1+ """Utility functions for setting up FLARE models."""
2+ from __future__ import annotations
3+
4+ import sys , json , yaml
5+ import numpy as np
6+ from ase .symbols import symbols2numbers
7+
8+
9+ def get_flare_calc (flare_config ):
10+ """Set up ASE flare calculator."""
11+ gp_name = flare_config .get ("gp" )
12+ if gp_name == "GaussianProcess" :
13+ return get_gp_calc (flare_config )
14+ elif gp_name == "SGP_Wrapper" :
15+ return get_sgp_calc (flare_config )
16+ else :
17+ raise NotImplementedError (f"{ gp_name } is not implemented" )
18+
19+
20+ def get_gp_calc (flare_config ):
21+ """Return a FLARE_Calculator with gp from GaussianProcess."""
22+ from flare .bffs .gp import GaussianProcess
23+ from flare .bffs .mgp import MappedGaussianProcess
24+ from flare .bffs .gp .calculator import FLARE_Calculator
25+ from flare .utils .parameter_helper import ParameterHelper
26+
27+ gp_file = flare_config .get ("file" , None )
28+
29+ # Load GP from file
30+ if gp_file is not None :
31+ with open (gp_file , "r" ) as f :
32+ gp_dct = json .loads (f .readline ())
33+ if gp_dct .get ("class" , None ) == "FLARE_Calculator" :
34+ flare_calc = FLARE_Calculator .from_file (gp_file )
35+ else :
36+ gp , _ = GaussianProcess .from_file (gp_file )
37+ flare_calc = FLARE_Calculator (gp )
38+ return flare_calc
39+
40+ # Create gaussian process model
41+ kernels = flare_config .get ("kernels" )
42+ hyps = flare_config .get ("hyps" , "random" )
43+ opt_algorithm = flare_config .get ("opt_algorithm" , "BFGS" )
44+ max_iterations = flare_config .get ("max_iterations" , 20 )
45+ bounds = flare_config .get ("bounds" , None )
46+
47+ gp_parameters = flare_config .get ("gp_parameters" )
48+ n_cpus = flare_config .get ("n_cpus" , 1 )
49+ use_mapping = flare_config .get ("use_mapping" , False )
50+
51+ # set up GP hyperparameters
52+ pm = ParameterHelper (
53+ kernels = kernels ,
54+ random = True ,
55+ parameters = gp_parameters ,
56+ )
57+ hm = pm .as_dict ()
58+ if hyps == "random" :
59+ hyps = hm ["hyps" ]
60+
61+ gp_model = GaussianProcess (
62+ kernels = kernels ,
63+ component = "mc" ,
64+ hyps = hyps ,
65+ cutoffs = hm ["cutoffs" ],
66+ hyps_mask = None ,
67+ hyp_labels = hm ["hyp_labels" ],
68+ opt_algorithm = opt_algorithm ,
69+ maxiter = max_iterations ,
70+ parallel = n_cpus > 1 ,
71+ per_atom_par = flare_config .get ("per_atom_par" , True ),
72+ n_cpus = n_cpus ,
73+ n_sample = flare_config .get ("n_sample" , 100 ),
74+ output = None ,
75+ name = flare_config .get ("name" , "default_gp" ),
76+ energy_noise = flare_config .get ("energy_noise" , 0.01 ),
77+ )
78+
79+ # create mapped gaussian process
80+ if use_mapping :
81+ grid_params = flare_config .get ("grid_params" )
82+ var_map = flare_config .get ("var_map" , "pca" )
83+ unique_species = flare_config .get ("unique_species" )
84+ coded_unique_species = symbols2numbers (unique_species )
85+ mgp_model = MappedGaussianProcess (
86+ grid_params = grid_params ,
87+ unique_species = coded_unique_species ,
88+ n_cpus = n_cpus ,
89+ var_map = var_map ,
90+ )
91+ else :
92+ mgp_model = None
93+
94+ flare_calc = FLARE_Calculator (
95+ gp_model = gp_model ,
96+ mgp_model = mgp_model ,
97+ par = n_cpus > 1 ,
98+ use_mapping = use_mapping ,
99+ )
100+ return flare_calc , kernels
101+
102+
103+ def get_sgp_calc (flare_config ):
104+ """Return a SGP_Calculator with sgp from SparseGP."""
105+ from flare .bffs .sgp ._C_flare import NormalizedDotProduct , SquaredExponential
106+ from flare .bffs .sgp ._C_flare import B2 , B3 , TwoBody , ThreeBody , FourBody
107+ from flare .bffs .sgp import SGP_Wrapper
108+ from flare .bffs .sgp .calculator import SGP_Calculator
109+
110+ sgp_file = flare_config .get ("file" , None )
111+
112+ # Load sparse GP from file
113+ if sgp_file is not None :
114+ with open (sgp_file , "r" ) as f :
115+ gp_dct = json .loads (f .readline ())
116+ if gp_dct .get ("class" , None ) == "SGP_Calculator" :
117+ flare_calc , kernels = SGP_Calculator .from_file (sgp_file )
118+ else :
119+ sgp , kernels = SGP_Wrapper .from_file (sgp_file )
120+ flare_calc = SGP_Calculator (sgp )
121+ return flare_calc , kernels
122+
123+ kernels = flare_config .get ("kernels" )
124+ opt_algorithm = flare_config .get ("opt_algorithm" , "BFGS" )
125+ max_iterations = flare_config .get ("max_iterations" , 20 )
126+ bounds = flare_config .get ("bounds" , None )
127+ use_mapping = flare_config .get ("use_mapping" , False )
128+
129+ # Define kernels.
130+ kernels = []
131+ for k in flare_config ["kernels" ]:
132+ if k ["name" ] == "NormalizedDotProduct" :
133+ kernels .append (NormalizedDotProduct (k ["sigma" ], k ["power" ]))
134+ elif k ["name" ] == "SquaredExponential" :
135+ kernels .append (SquaredExponential (k ["sigma" ], k ["ls" ]))
136+ else :
137+ raise NotImplementedError (f"{ k ['name' ]} kernel is not implemented" )
138+
139+ # Define descriptor calculators.
140+ n_species = len (flare_config ["species" ])
141+ cutoff = flare_config ["cutoff" ]
142+ descriptors = []
143+ for d in flare_config ["descriptors" ]:
144+ if "cutoff_matrix" in d : # multiple cutoffs
145+ assert np .allclose (np .array (d ["cutoff_matrix" ]).shape , (n_species , n_species )),\
146+ "cutoff_matrix needs to be of shape (n_species, n_species)"
147+
148+ if d ["name" ] == "B2" :
149+ radial_hyps = [0.0 , cutoff ]
150+ cutoff_hyps = []
151+ descriptor_settings = [n_species , d ["nmax" ], d ["lmax" ]]
152+ if "cutoff_matrix" in d : # multiple cutoffs
153+ desc_calc = B2 (
154+ d ["radial_basis" ],
155+ d ["cutoff_function" ],
156+ radial_hyps ,
157+ cutoff_hyps ,
158+ descriptor_settings ,
159+ d ["cutoff_matrix" ],
160+ )
161+ else :
162+ desc_calc = B2 (
163+ d ["radial_basis" ],
164+ d ["cutoff_function" ],
165+ radial_hyps ,
166+ cutoff_hyps ,
167+ descriptor_settings ,
168+ )
169+
170+ elif d ["name" ] == "B3" :
171+ radial_hyps = [0.0 , cutoff ]
172+ cutoff_hyps = []
173+ descriptor_settings = [n_species , d ["nmax" ], d ["lmax" ]]
174+ desc_calc = B3 (
175+ d ["radial_basis" ],
176+ d ["cutoff_function" ],
177+ radial_hyps ,
178+ cutoff_hyps ,
179+ descriptor_settings ,
180+ )
181+
182+ elif d ["name" ] == "TwoBody" :
183+ desc_calc = TwoBody (cutoff , n_species , d ["cutoff_function" ], cutoff_hyps )
184+
185+ elif d ["name" ] == "ThreeBody" :
186+ desc_calc = ThreeBody (cutoff , n_species , d ["cutoff_function" ], cutoff_hyps )
187+
188+ elif d ["name" ] == "FourBody" :
189+ desc_calc = FourBody (cutoff , n_species , d ["cutoff_function" ], cutoff_hyps )
190+
191+ else :
192+ raise NotImplementedError (f"{ d ['name' ]} descriptor is not supported" )
193+
194+ descriptors .append (desc_calc )
195+
196+ # Define remaining parameters for the SGP wrapper.
197+ species_map = {flare_config .get ("species" )[i ]: i for i in range (n_species )}
198+ sae_dct = flare_config .get ("single_atom_energies" , None )
199+ if sae_dct is not None :
200+ assert n_species == len (
201+ sae_dct
202+ ), "'single_atom_energies' should be the same length as 'species'"
203+ single_atom_energies = {i : sae_dct [i ] for i in range (n_species )}
204+ else :
205+ single_atom_energies = {i : 0 for i in range (n_species )}
206+
207+ sgp = SGP_Wrapper (
208+ kernels = kernels ,
209+ descriptor_calculators = descriptors ,
210+ cutoff = cutoff ,
211+ sigma_e = flare_config .get ("energy_noise" ),
212+ sigma_f = flare_config .get ("forces_noise" ),
213+ sigma_s = flare_config .get ("stress_noise" ),
214+ species_map = species_map ,
215+ variance_type = flare_config .get ("variance_type" , "local" ),
216+ single_atom_energies = single_atom_energies ,
217+ energy_training = flare_config .get ("energy_training" , True ),
218+ force_training = flare_config .get ("force_training" , True ),
219+ stress_training = flare_config .get ("stress_training" , True ),
220+ max_iterations = max_iterations ,
221+ opt_method = opt_algorithm ,
222+ bounds = bounds ,
223+ )
224+
225+ flare_calc = SGP_Calculator (sgp , use_mapping )
226+ return flare_calc , kernels
227+
228+
229+ def get_model (file : str ):
230+ """Main method of the script."""
231+ with open (file , "r" ) as f :
232+ config = yaml .safe_load (f )
233+
234+ return get_flare_calc (config )
235+
236+
237+ if __name__ == "__main__" :
238+ """Launch script from cmd."""
239+ model , kernels = get_model (sys .argv [1 ])
240+ print (model , kernels )
0 commit comments