CASM
AClustersApproachtoStatisticalMechanics
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules
ChemicalReference.cc
Go to the documentation of this file.
2 #include "casm/clex/ConfigIO.hh"
5 #include "casm/clex/PrimClex.hh"
7 
8 #include "casm/misc/algorithm.hh"
9 
10 namespace CASM {
11 
17  const Configuration &config,
18  std::function<Eigen::VectorXd(Configuration)> n,
19  std::function<double(Configuration)> e) {
20 
21  auto names = config.get_prim().get_struc_molecule_name();
22  auto vec = n(config);
23 
24  if(vec.size() != names.size()) {
25  std::cerr << "Error constructing ChemicalReferenceState: Number of species != vec.size()\n";
26  std::cerr << " get_struc_molecule_name: " << names << "\n";
27  std::cerr << " n(config): " << vec << std::endl;
28  throw std::runtime_error("Error constructing ChemicalReferenceState: Number of species != vec.size()");
29  }
30 
31  for(int i = 0; i < names.size(); ++i) {
32  if(!is_vacancy(names[i])) {
33  species_num[names[i]] = vec(i);
34  }
35  }
36  energy_per_species = e(config);
37  }
38 
39 
40  // --- ChemicalReference implementations -----------
41 
42  const std::string ChemicalReference::Name = "chem_ref";
43 
44  const std::string ChemicalReference::Desc = "Returns a reference energy as interpolated via a composition-energy hyperplane.";
45 
46  namespace {
47 
48  Eigen::MatrixXd _species_frac_matrix(const PrimClex &primclex,
49  const std::vector<std::string> &ref_config) {
50  Eigen::MatrixXd _N(primclex.composition_axes().components().size(), ref_config.size());
51  for(int i = 0; i < ref_config.size(); ++i) {
52  _N.col(i) = species_frac(primclex.configuration(ref_config[i]));
53  }
54  return _N;
55  }
56 
57  Eigen::MatrixXd _species_frac_space(const Eigen::MatrixXd &_N) {
58  // The input space
59  Eigen::MatrixXd N(_N.rows(), _N.cols() - 1);
60  for(int i = 0; i < N.cols(); i++) {
61  N.col(i) = _N.col(i + 1) - _N.col(0);
62  }
63  return N;
64  }
65 
66  int _rank(const Eigen::MatrixXd &N,
67  double lin_alg_tol) {
68  auto Qr = N.transpose().fullPivHouseholderQr();
69  Qr.setThreshold(lin_alg_tol);
70  return Qr.rank();
71  }
72 
73  // Eliminate ref config until the rank of the defined species_frac space
74  // is the same as the number of ref config.
75  void _prune_ref_config(const PrimClex &primclex,
76  std::vector<std::string> &ref_config,
77  double lin_alg_tol) {
78  // Each column of _N is the species_frac of the corresponding ref config
79  Eigen::MatrixXd _N = _species_frac_matrix(primclex, ref_config);
80 
81  // Contains vectors spanning the space defined by the ref config
82  // N has _N.cols() - 1 columns, with N.col(i) being _N.col(i) - _N.col(0)
83  Eigen::MatrixXd N = _species_frac_space(_N);
84 
85  while(_rank(N, lin_alg_tol) != N.cols()) {
86 
87  // remove ref_config whose summed distance in species_frac space to all
88  // others is the minimum
89  double min_dist = std::numeric_limits<double>::max();
90  double min_dist_ref = -1;
91  for(int i = 0; i < ref_config.size(); ++i) {
92  double tot_dist = 0.0;
93  for(int j = 0; j < ref_config.size(); ++j) {
94  if(i != j) {
95  tot_dist += (_N.col(i) - _N.col(j)).norm();
96  }
97  }
98  if(tot_dist < min_dist) {
99  min_dist_ref = i;
100  min_dist = tot_dist;
101  }
102  }
103 
104  ref_config.erase(ref_config.begin() + min_dist_ref);
105  _N = _species_frac_matrix(primclex, ref_config);
106  N = _species_frac_space(_N);
107  }
108 
109  }
110 
111  }
112 
115  const Structure &prim,
116  const std::vector<std::string> &struc_mol_name,
117  Eigen::MatrixXd _N,
118  Eigen::VectorXd E,
119  double tol) {
120 
121  // --- convert input compositions to atom_frac
122 
123  Index Va_index = find_index_if(struc_mol_name, [ = ](const std::string & str) {
124  return is_vacancy(str);
125  });
126  bool has_Va = (Va_index != struc_mol_name.size());
127 
128  if(has_Va) {
129  _N.row(Va_index) = Eigen::VectorXd::Zero(_N.cols());
130  }
131  for(int i = 0; i < _N.cols(); i++) {
132  _N.col(i) /= _N.col(i).sum();
133  }
134 
135  // --- check that the input space is full rank (excluding Va) --------------
136 
137  // The input space
138  Eigen::MatrixXd N = _species_frac_space(_N);
139 
140  int r = _rank(N, tol);
141 
142  if(r != N.cols()) {
143  std::cerr << "Error in ChemicalReference::hyperplane " << std::endl;
144  std::cerr << "Input space (column vectors of atom_frac):\n" << N << std::endl;
145  std::cerr << "Rows correspond to: " << jsonParser(struc_mol_name) << std::endl;
146  std::cerr << "Input space rank: " << r << std::endl;
147  throw std::runtime_error("Error in ChemicalReference::hyperplane: Too many reference states specified");
148  }
149 
150  // --- check that the input space spans the prim space --------------------
151 
152  // get the prim composition space (column vectors are comp_n)
153  Eigen::MatrixXd C = composition_space(prim, tol);
154 
155  // get Molecule allowed in prim, and how many there are
156  std::vector<Molecule> struc_mol = prim.get_struc_molecule();
157  for(int i = 0; i < struc_mol.size(); i++) {
158  if(!is_molecule_name(struc_mol[i], struc_mol_name[i])) {
159  std::cerr << "Error in ChemicalReference::hyperplane " << std::endl;
160  std::cerr << "Initial struc_mol_name must be in same order as prim.get_struc_molecule()" << std::endl;
161  throw std::runtime_error("Error in ChemicalReference::hyperplane: Molecule name mismatch");
162  }
163  }
164  Index prim_N_mol = struc_mol.size();
165 
166  // ensure that there is a solution X for:
167  // C = N.topRows(prim_N_mol) * X
168  // (prim_space = input_space involving prim species * X)
169  Eigen::MatrixXd X = N.topRows(prim_N_mol).fullPivHouseholderQr().solve(C);
170 
171  double relative_error = (N.topRows(prim_N_mol) * X - C).norm() / C.norm();
172 
173  if(relative_error > tol) {
174  std::cerr << "Error in ChemicalReference::hyperplane " << std::endl;
175  std::cerr << "Input space does not span the composition space of your prim." << std::endl;
176 
177  std::cerr << "Input space (column vectors in atom_frac space):\n" << N.topRows(prim_N_mol) << std::endl;
178  std::cerr << "End members:\n" << end_members(prim) << std::endl;
179  std::cerr << "Prim space (column vectors in atom_frac space):\n" << C << std::endl;
180  std::cerr << "Rows correspond to: " << jsonParser(struc_mol_name) << std::endl;
181  std::cerr << "X, prim_space = input_space*X: \n" << X << std::endl;
182  std::cerr << "input_space*X: \n" << N.topRows(prim_N_mol)*X << std::endl;
183  std::cerr << "relative_error: " << relative_error << std::endl;
184  std::cerr << "tol: " << tol << std::endl;
185 
186  throw std::runtime_error("Error in ChemicalReference::hyperplane: Input space does not span prim space");
187  }
188 
189 
190  // --- solve N.transpose() * P = E, for P, the hyperplane reference --------
191 
192  Eigen::VectorXd P = _N.transpose().fullPivHouseholderQr().solve(E);
193 
194  relative_error = (_N.transpose() * P - E).norm() / E.norm();
195 
196  if(relative_error > tol) {
197  std::cerr << "Error in ChemicalReference::hyperplane " << std::endl;
198  std::cerr << "Could not solve for hyperplane reference." << std::endl;
199 
200  std::cerr << "Input space (column vectors in atom_frac space), N:\n" << N << std::endl;
201  std::cerr << "Rows correspond to: " << jsonParser(struc_mol_name) << std::endl;
202  std::cerr << "Solve: _N.transpose()*P = E" << std::endl;
203  std::cerr << "_N.transpose():\n" << _N.transpose() << std::endl;
204  std::cerr << "Reference state energies, E:\n" << E << std::endl;
205  std::cerr << "P:\n" << P.transpose() << std::endl;
206  std::cerr << "relative_err: " << relative_error << std::endl;
207  std::cerr << "tol: " << tol << std::endl;
208 
209  throw std::runtime_error("Error in ChemicalReference::hyperplane: Could not solve for hyperplane reference");
210  }
211 
212  if(has_Va && !almost_zero(P(Va_index))) {
213  std::cerr << "Error in ChemicalReference::hyperplane " << std::endl;
214  std::cerr << "Non-zero pure Va reference: " << P.transpose() << std::endl;
215  std::cerr << "Elements correspond to: " << jsonParser(struc_mol_name) << std::endl;
216 
217  throw std::runtime_error("Error in ChemicalReference::hyperplane: Input space does not span prim space");
218  }
219 
220  return P.head(prim_N_mol);
221  }
222 
225  ChemicalReference auto_chemical_reference(const PrimClex &primclex, double lin_alg_tol) {
226 
227  auto closest_calculated_config = [&](const Eigen::VectorXd & target) {
228 
229  // return name of Configuration with param_comp closest to target_param_comp
230  // tie break goes to first Configuration with fewest atoms
231  //
232  // must be Configurations for which the relaxed_energy has been calculated
233  auto begin = primclex.config_begin();
234  auto end = primclex.config_end();
235  auto res = end;
236  double close_dist = std::numeric_limits<double>::max();
237  for(auto it = begin; it != end; ++it) {
238  if(!it->calc_properties().contains("relaxed_energy")) {
239  continue;
240  }
241  double curr_dist = (target - comp(*it)).norm();
242  if((res == end) ||
243  (almost_equal(curr_dist, close_dist, TOL) && it->size() < res->size()) ||
244  (curr_dist < close_dist)) {
245  res = it;
246  close_dist = curr_dist;
247  }
248  }
249 
250  if(res == end) {
251  std::cerr << "Error in auto_chemical_reference: Could not find any configurations\n";
252  throw std::runtime_error("Error in auto_chemical_reference: Could not find any configurations");
253  }
254  return res->name();
255  };
256 
257 
258 
259  // get number of reference states needed minus one... we'll also look for the 'origin'
260  int Naxes = primclex.composition_axes().independent_compositions();
261  Eigen::VectorXd target = Eigen::VectorXd::Zero(Naxes);
262 
263  // get 'origin' configuration
264  std::vector<std::string> ref_config;
265  ref_config.push_back(closest_calculated_config(target));
266 
267  for(int i = 0; i < Naxes; i++) {
268 
269  target(i) = 1.0;
270 
271  // get end member configurations
272  std::string configname = closest_calculated_config(target);
273 
274  // ensure no repeats
275  if(std::find(ref_config.begin(), ref_config.end(), configname) != ref_config.end()) {
276  std::cerr << "Error in auto_chemical_reference: Could not find enough calculated configurations\n";
277  throw std::runtime_error("Error in auto_chemical_reference: Could not find enough calculated configurations");
278  }
279 
280  ref_config.push_back(configname);
281 
282  target(i) = 0.0;
283  }
284 
285  // make sure there are the right number of references in species_frac space
286  // (may be 1 extra if pure Va configuration is possible)
287  _prune_ref_config(primclex, ref_config, lin_alg_tol);
288 
289  // construct ChemicalReferenceState
290  std::vector<ChemicalReferenceState> ref_states;
291  for(auto it = ref_config.begin(); it != ref_config.end(); ++it) {
292  ref_states.emplace_back(primclex.configuration(*it),
295  }
296 
297  return ChemicalReference(primclex.get_prim(), ref_states.begin(), ref_states.end(), lin_alg_tol);
298  }
299 
300 }
Eigen::MatrixXd MatrixXd
AtomFrac SpeciesFrac
In the future, AtomFrac will actually be atoms only.
Definition: ConfigIO.hh:233
bool almost_zero(const T &val, double tol=TOL)
If T is not integral, use std::abs(val) < tol;.
Definition: CASM_math.hh:41
Eigen::MatrixXd composition_space(const Structure &prim, double tol=1e-14)
Return the composition space of a Structure.
double energy_per_species
Energy in this reference state.
ChemicalReference auto_chemical_reference(const PrimClex &primclex, double lin_alg_tol)
Automatically set ChemicalReference using calculated Configurations with 'extreme' compositions...
bool is_vacancy(const std::string &name)
A vacancy is any Specie/Molecule with (name == "VA" || name == "va" || name == "Va") ...
Definition: Molecule.hh:27
Structure specifies the lattice and atomic basis of a crystal.
Definition: Structure.hh:29
PrimClex * primclex
Definition: settings.cc:101
std::vector< Molecule > get_struc_molecule() const
Returns an Array of each possible Molecule in this Structure.
Definition: Structure.cc:146
bool is_molecule_name(const Molecule &mol, std::string name)
Return true if Molecule name matches 'name', including Va checks.
Definition: Molecule.cc:298
static const std::string Name
Main CASM namespace.
Definition: complete.cpp:8
std::map< std::string, double > species_num
Map of Molecule name : number of each species in reference state.
Eigen::VectorXd comp(const Configuration &config)
Returns parametric composition, as calculated using PrimClex::param_comp.
const double TOL
static const std::string Desc
config_iterator config_begin()
Configuration iterator: begin.
Definition: PrimClex.cc:371
config_iterator config_end()
Configuration iterator: end.
Definition: PrimClex.cc:379
Index find_index_if(Iterator begin, Iterator end, UnaryPredicate p)
Equivalent to std::distance(begin, std::find_if(begin, end, p))
Definition: algorithm.hh:28
std::vector< std::string > get_struc_molecule_name() const
Returns an Array of each possible Molecule in this Structure.
Definition: Structure.cc:166
const Structure & get_prim() const
Get the primitive Structure for this Configuration.
std::vector< std::string > components() const
The order of components in mol composition vectors.
double tol
static Eigen::VectorXd _calc_hyperplane(const Structure &prim, const std::vector< std::string > &struc_mol_name, Eigen::MatrixXd N, Eigen::VectorXd E, double tol)
Convert a set of ChemicalReferenceState to a hyperplane, including checks.
T norm(const Tensor< T > &ttens)
Definition: Tensor.hh:968
EigenIndex Index
For long integer indexing:
Eigen::VectorXd VectorXd
Iterator find(Iterator begin, Iterator end, const T &value, BinaryCompare q)
Equivalent to std::find(begin, end, value), but with custom comparison.
Definition: algorithm.hh:10
PrimClex is the top-level data structure for a CASM project.
Definition: PrimClex.hh:52
size_type independent_compositions() const
The dimensionality of the composition space.
const CompositionConverter & composition_axes() const
const Access CompositionConverter object
Definition: PrimClex.cc:237
T max(const T &A, const T &B)
Definition: CASM_math.hh:32
const Configuration & configuration(const std::string &configname) const
access configuration by name (of the form "scellname/[NUMBER]", e.g., ("SCEL1_1_1_1_0_0_0/0") ...
Definition: PrimClex.cc:347
ConfigIO::GenericConfigFormatter< std::string > configname()
Constructs DataFormmaterDictionary containing all Configuration DatumFormatters.
Definition: ConfigIO.cc:340
Eigen::MatrixXd end_members(const Structure &prim)
Generate a column matrix containing all the possible molecular end members.
ConfigIO::GenericConfigFormatter< double > relaxed_energy_per_species()
Definition: ConfigIO.cc:415
bool almost_equal(const GenericCluster< CoordType > &LHS, const GenericCluster< CoordType > &RHS, double tol)
Eigen::VectorXd species_frac(const Configuration &config)
Returns the composition as species fraction, with [Va] = 0.0, in the order of Structure::get_struc_mo...
A Configuration represents the values of all degrees of freedom in a Supercell.
const Structure & get_prim() const
const Access to primitive Structure
Definition: PrimClex.cc:260