-
Notifications
You must be signed in to change notification settings - Fork 122
/
CrystalFieldSusceptibility.cpp
189 lines (179 loc) · 6.57 KB
/
CrystalFieldSusceptibility.cpp
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#include "MantidCurveFitting/Functions/CrystalFieldSusceptibility.h"
#include "MantidCurveFitting/Functions/CrystalFieldPeaksBase.h"
#include "MantidCurveFitting/Functions/CrystalElectricField.h"
#include "MantidCurveFitting/FortranDefs.h"
#include "MantidAPI/FunctionFactory.h"
#include "MantidAPI/FunctionValues.h"
#include "MantidAPI/FunctionDomain.h"
#include "MantidAPI/FunctionDomain1D.h"
#include "MantidAPI/IFunction1D.h"
#include "MantidAPI/Jacobian.h"
#include "MantidKernel/Exception.h"
#include "MantidKernel/PhysicalConstants.h"
#include <cmath>
#include <boost/algorithm/string/predicate.hpp>
namespace Mantid {
namespace CurveFitting {
namespace Functions {
namespace {
// Get a complex conjugate of the value returned by
// ComplexMatrix::operator(i,j)
ComplexType conj(const ComplexMatrixValueConverter &conv) {
return std::conj(static_cast<ComplexType>(conv));
}
// Does the actual calculation of the susceptibility
void calculate(double *out, const double *xValues, const size_t nData,
const DoubleFortranVector &en, const ComplexFortranMatrix &wf,
const int nre, const std::vector<double> &H,
const double convfact) {
// Some constants
const double k_B = PhysicalConstants::BoltzmannConstant; // in meV/K
const double eps = 1.e-6; // for degeneracy calculations
// First calculates the matrix elements of the magnetic moment operator
ComplexFortranMatrix mumat;
calculateMagneticMomentMatrix(wf, H, nre, mumat);
int nlevels = en.len();
DoubleFortranVector mu(nlevels);
DoubleFortranVector mu2(nlevels);
mu.zero();
mu2.zero();
for (auto i = 1; i <= nlevels; i++) {
for (auto j = 1; j <= nlevels; j++) {
const double den = en(i) - en(j);
if (fabs(den) < eps) { // First order term
mu(i) += real(mumat(i, j) * conj(mumat(i, j)));
} else { // Second order term
mu2(i) += real(mumat(i, j) * conj(mumat(i, j))) / den;
}
}
}
// Now calculates the temperature dependence.
mu *= convfact;
mu2 *= convfact;
for (size_t iT = 0; iT < nData; iT++) {
double Z = 0.;
double U = 0.;
const double beta = 1 / (k_B * xValues[iT]);
for (auto i = 1; i <= nlevels; i++) {
double expfact = exp(-beta * en(i));
Z += expfact;
U += ((mu(i) * beta) - (2 * mu2(i))) * expfact;
}
out[iT] = U / Z;
}
}
// Calculate powder average - Mpowder = (Mx + My + Mz)/3
void calculate_powder(double *out, const double *xValues, const size_t nData,
const DoubleFortranVector &en,
const ComplexFortranMatrix &wf, const int nre,
const double convfact) {
for (size_t j = 0; j < nData; j++) {
out[j] = 0.;
}
// Loop over the x, y, z directions
std::vector<double> H;
std::vector<double> tmp(nData, 0.);
for (int i = 0; i < 3; i++) {
H.assign(3, 0.);
H[i] = 1.;
calculate(&tmp[0], xValues, nData, en, wf, nre, H, convfact);
for (size_t j = 0; j < nData; j++) {
out[j] += tmp[j];
}
}
for (size_t j = 0; j < nData; j++) {
out[j] /= 3.;
}
}
}
DECLARE_FUNCTION(CrystalFieldSusceptibility)
CrystalFieldSusceptibility::CrystalFieldSusceptibility()
: CrystalFieldPeaksBase(), API::IFunction1D(), m_nre(0),
m_setDirect(false) {
declareAttribute("Hdir", Attribute(std::vector<double>{0., 0., 1.}));
declareAttribute("Unit", Attribute("cgs"));
declareAttribute("inverse", Attribute(false));
declareAttribute("powder", Attribute(false));
declareAttribute("ScaleFactor", Attribute(1.0)); // Only for multi-site use
declareParameter("Lambda", 0.0, "Effective exchange interaction");
}
// Sets the eigenvectors / values directly
void CrystalFieldSusceptibility::setEigensystem(const DoubleFortranVector &en,
const ComplexFortranMatrix &wf,
const int nre) {
m_setDirect = true;
m_en = en;
m_wf = wf;
m_nre = nre;
}
void CrystalFieldSusceptibility::function1D(double *out, const double *xValues,
const size_t nData) const {
auto H = getAttribute("Hdir").asVector();
if (H.size() != 3) {
throw std::invalid_argument("Hdir must be a three-element vector.");
}
auto powder = getAttribute("powder").asBool();
double Hnorm = sqrt(H[0] * H[0] + H[1] * H[1] + H[2] * H[2]);
if (fabs(Hnorm) > 1.e-6) {
for (auto i = 0; i < 3; i++) {
H[i] /= Hnorm;
}
}
// Get the unit conversion factor.
auto unit = getAttribute("Unit").asString();
const double NAMUB2cgs = 0.03232776; // N_A * muB(erg/G) * muB(meV/G)
// The constant above is in strange units because we need the output
// to be in erg/G^2/mol==cm^3/mol, but in the calculation all energies
// are in meV and the expression for chi has a energy denominator.
const double NAMUB2si = 4.062426e-7; // N_A * muB(J/T) * muB(meV/T) * mu0
// Again, for SI units, we need to have one of the muB in meV not J.
// The additional factor of mu0 is due to the different definitions of
// the magnetisation, B- and H-fields in the SI and cgs systems.
double convfact = boost::iequals(unit, "bohr")
? 0.057883818
: (boost::iequals(unit, "SI") ? NAMUB2si : NAMUB2cgs);
// Note the constant for "bohr" is the bohr magneton in meV/T, this will
// give the susceptibility in "atomic" units of uB/T/ion.
// Note that chi_SI = (4pi*10^-6)chi_cgs
// Default unit is cgs (cm^3/mol).
if (!m_setDirect) {
// Because this method is const, we can't change the stored en / wf
// Use temporary variables instead.
DoubleFortranVector en;
ComplexFortranMatrix wf;
int nre = 0;
calculateEigenSystem(en, wf, nre);
if (powder) {
calculate_powder(out, xValues, nData, en, wf, nre, convfact);
} else {
calculate(out, xValues, nData, en, wf, nre, H, convfact);
}
} else {
// Use stored values
if (powder) {
calculate_powder(out, xValues, nData, m_en, m_wf, m_nre, convfact);
} else {
calculate(out, xValues, nData, m_en, m_wf, m_nre, H, convfact);
}
}
if (getAttribute("inverse").asBool()) {
for (size_t i = 0; i < nData; i++) {
out[i] = 1. / out[i];
}
}
auto fact = getAttribute("ScaleFactor").asDouble();
if (fact != 1.0) {
for (size_t i = 0; i < nData; i++) {
out[i] *= fact;
}
}
const double lambda = getParameter("Lambda");
if (fabs(lambda) > 1.e-6) {
for (size_t i = 0; i < nData; i++) {
out[i] /= (1. - lambda * out[i]); // chi = chi0/(1 - lambda.chi0)
}
}
}
} // namespace Functions
} // namespace CurveFitting
} // namespace Mantid