Mantid
Loading...
Searching...
No Matches
CrystalElectricField.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
7//----------------------------------------------------------------------
8// Includes
9//----------------------------------------------------------------------
12
13#include <algorithm>
14#include <array>
15#include <cmath>
16
18
19namespace {
20
21// The missing braces warning is a false positive -
22// https://llvm.org/bugs/show_bug.cgi?id=21629
23GNU_DIAG_OFF("missing-braces")
24
25// number of rare earth ions (trivalent rare earths with unfilled f-shell)
26const int maxNre = 13;
27
28// define some rare earth constants (ggj is the Lande g-factor, ddimj=2J+1)
29const std::array<double, maxNre> ggj = {6.0 / 7., 4.0 / 5., 8.0 / 11., 3.0 / 5., 2.0 / 7., 0.0, 2.0,
30 3.0 / 2., 4.0 / 3., 5.0 / 4., 6.0 / 5., 7.0 / 6., 8.0 / 7.};
31
32const std::array<double, maxNre> ddimj = {6.0, 9.0, 10.0, 9.0, 6.0, 1.0, 8.0, 13.0, 16.0, 17.0, 16.0, 13.0, 8.0};
33//---------------------------
34// set some natural constants
35//---------------------------
36const double pi = 4.0 * atan(1.0);
37const double kb = 1.38062; // x 10**(-23) J/K, Boltzmann constant k_B
38const double hh = 6.626075540; // x 10**(-34) J*sec, Planks constant h
39const double hq = hh / 2 / pi;
40const double ee = 1.6021773349; // x 10**(-19) Coulomb, electric charge
41const double me = 9.109389754; // x 10**(-31) kg, electron mass
42// within the above choose, the factor which connects
43// 1meV and 1 Kelvin is fixed and given by:
44// 10*ee/kb =: fmevkelvin = 11.6047...
45// this means 1 meV is nearly 11.6 K
46const double c_fmevkelvin = 10 * ee / kb;
47// magneton of Bohr in meV per tesla
48const double c_myb = hq / me / 2;
49
50//--------------------------------
51// define the delta function
52//--------------------------------
53double delta(double mj, double nj, double j) {
54 double res = 0.0;
55 if (mj == nj && fabs(mj) <= j && fabs(nj) <= j) {
56 res = 1.0;
57 }
58 return res;
59}
60
61//--------------------
62// jp(n) = <n+1|j+|n>
63//--------------------
64double jp(double nj, double j) {
65 if (fabs(nj) <= j) {
66 return sqrt(j * (j + 1) - nj * (nj + 1));
67 } else {
68 return 0.0;
69 }
70}
71
72//--------------------
73// jm(n) = <n-1|j-|n>
74//--------------------
75double jm(double nj, double j) {
76 if (fabs(nj) <= j) {
77 return sqrt(j * (j + 1) - nj * (nj - 1));
78 } else {
79 return 0.0;
80 }
81}
82
83//-----------------------
84// jp2(n) = <n+2|j+^2|n>
85//-----------------------
86double jp2(double nj, double j) { return jp(nj + 1, j) * jp(nj, j); }
87
88//-----------------------
89// jm2(n) = <n-2|j-^2|n>
90//-----------------------
91double jm2(double nj, double j) { return jm(nj - 1, j) * jm(nj, j); }
92
93//-----------------------
94// jp3(n) = <n+3|j+^3|n>
95//-----------------------
96double jp3(double nj, double j) { return jp(nj + 2, j) * jp(nj + 1, j) * jp(nj, j); }
97
98//-----------------------
99// jm3(n) = <n-3|j-^3|n>
100//-----------------------
101double jm3(double nj, double j) { return jm(nj - 2, j) * jm(nj - 1, j) * jm(nj, j); }
102
103//-----------------------
104// jp4(n) = <n+4|j+^4|n>
105//-----------------------
106double jp4(double nj, double j) { return jp(nj + 3, j) * jp(nj + 2, j) * jp(nj + 1, j) * jp(nj, j); }
107
108//-----------------------
109// jm4(n) = <n-4|j-^4|n>
110//-----------------------
111double jm4(double nj, double j) { return jm(nj - 3, j) * jm(nj - 2, j) * jm(nj - 1, j) * jm(nj, j); }
112
113//-----------------------
114// jp5(n) = <n+5|j+^5|n>
115//-----------------------
116double jp5(double nj, double j) { return jp(nj + 4, j) * jp(nj + 3, j) * jp(nj + 2, j) * jp(nj + 1, j) * jp(nj, j); }
117
118//-----------------------
119// jm5(n) = <n-5|j-^5|n>
120//-----------------------
121double jm5(double nj, double j) { return jm(nj - 4, j) * jm(nj - 3, j) * jm(nj - 2, j) * jm(nj - 1, j) * jm(nj, j); }
122
123//-----------------------
124// jp6(n) = <n+6|j+^6|n>
125//-----------------------
126double jp6(double nj, double j) {
127 return jp(nj + 5, j) * jp(nj + 4, j) * jp(nj + 3, j) * jp(nj + 2, j) * jp(nj + 1, j) * jp(nj, j);
128}
129
130//-----------------------
131// jm6(n) = <n-6|j-^6|n>
132//-----------------------
133double jm6(double nj, double j) {
134 return jm(nj - 5, j) * jm(nj - 4, j) * jm(nj - 3, j) * jm(nj - 2, j) * jm(nj - 1, j) * jm(nj, j);
135}
136
137//-----------------------------
138double f20(double nj, double j) { return 3 * pow(nj, 2) - j * (j + 1); }
139
140//------------------------------
141double f21(double nj, double /*unused*/) { return nj; }
142
143//------------------------------
144double f22(double /*unused*/, double /*unused*/) { return 1.0; }
145
146//------------------------------
147double f40(double nj, double j) {
148 return 35 * pow(nj, 4) - 30 * j * (j + 1) * pow(nj, 2) + 25 * pow(nj, 2) - 6 * j * (j + 1) +
149 3 * pow(j, 2) * pow((j + 1), 2);
150}
151
152//------------------------------
153double f41(double nj, double j) { return 7 * pow(nj, 3) - 3 * j * (j + 1) * nj - nj; }
154
155//------------------------------
156double f42(double nj, double j) { return 7 * pow(nj, 2) - j * (j + 1) - 5; }
157
158//------------------------------
159double f43(double nj, double /*unused*/) { return nj; }
160
161//------------------------------
162double f44(double /*unused*/, double /*unused*/) { return 1.0; }
163
164//------------------------------
165double f60(double nj, double j) {
166 return 231 * pow(nj, 6) - 315 * j * (j + 1) * pow(nj, 4) + 735 * pow(nj, 4) +
167 105 * pow(j, 2) * pow((j + 1), 2) * pow(nj, 2) - 525 * j * (j + 1) * pow(nj, 2) + 294 * pow(nj, 2) -
168 5 * pow(j, 3) * pow((j + 1), 3) + 40 * pow(j, 2) * pow((j + 1), 2) - 60 * j * (j + 1);
169}
170
171//------------------------------
172double f61(double nj, double j) {
173 return 33 * pow(nj, 5) - (30 * j * (j + 1) - 15) * pow(nj, 3) - 10 * j * (j + 1) * nj +
174 5 * pow(j, 2) * pow((j + 1), 2) * nj + 12 * nj;
175}
176
177//------------------------------
178double f62(double nj, double j) {
179 return 33 * pow(nj, 4) - 18 * j * (j + 1) * pow(nj, 2) - 123 * pow(nj, 2) + pow(j, 2) * pow(j + 1, 2) +
180 10 * j * (j + 1) + 102;
181}
182
183//------------------------------
184double f63(double nj, double j) { return 11 * pow(nj, 3) - 3 * j * (j + 1) * nj - 59 * nj; }
185
186//------------------------------
187double f64(double nj, double j) { return 11 * pow(nj, 2) - j * (j + 1) - 38; }
188
189//------------------------------
190double f65(double nj, double /*unused*/) { return nj; }
191
192//------------------------------
193double f66(double /*unused*/, double /*unused*/) { return 1.0; }
194
195//-------------------------------
196// ff(k,q,nj,j) := fkq(nj,j)
197//-------------------------------
198double ff(int k, int q, double nj, double j) {
199 int qq = abs(q);
200 if (k == 2 && qq == 0) {
201 return f20(nj, j);
202 } else if (k == 2 && qq == 1) {
203 return f21(nj, j);
204 } else if (k == 2 && qq == 2) {
205 return f22(nj, j);
206 } else if (k == 4 && qq == 0) {
207 return f40(nj, j);
208 } else if (k == 4 && qq == 1) {
209 return f41(nj, j);
210 } else if (k == 4 && qq == 2) {
211 return f42(nj, j);
212 } else if (k == 4 && qq == 3) {
213 return f43(nj, j);
214 } else if (k == 4 && qq == 4) {
215 return f44(nj, j);
216 } else if (k == 6 && qq == 0) {
217 return f60(nj, j);
218 } else if (k == 6 && qq == 1) {
219 return f61(nj, j);
220 } else if (k == 6 && qq == 2) {
221 return f62(nj, j);
222 } else if (k == 6 && qq == 3) {
223 return f63(nj, j);
224 } else if (k == 6 && qq == 4) {
225 return f64(nj, j);
226 } else if (k == 6 && qq == 5) {
227 return f65(nj, j);
228 } else if (k == 6 && qq == 6) {
229 return f66(nj, j);
230 }
231 throw std::runtime_error("Unknown case in ff function.");
232}
233
234// c----------------------------------
235// c testing if the real number r is 0
236// c----------------------------------
237void ifnull(double &r) {
238 const double macheps = 1.0e-14;
239 if (fabs(r) <= macheps)
240 r = 0.0;
241}
242
243//---------------------------------------------
244// calculates the neutron scattering radius r0
245//---------------------------------------------
246double c_r0() {
247 return -1.91 * pow(ee, 2) / me; // * 10**(-12) cm
248}
249
250// c---------------------------------------
251double epsilon(int k, int q) {
252 const std::array<double, 49> eps = {
253 1.00000000000000, 0.707106781186547, 1.00000000000000, -0.707106781186547, 0.612372435695795,
254 1.22474487139159, 0.500000000000000, -1.22474487139159, 0.612372435695795, 0.559016994374947,
255 1.36930639376292, 0.433012701892219, 0.500000000000000, -0.433012701892219, 1.36930639376292,
256 -0.559016994374947, 0.522912516583797, 1.47901994577490, 0.395284707521047, 0.559016994374947,
257 0.125000000000000, -0.559016994374947, 0.395284707521047, -1.47901994577490, 0.522912516583797,
258 0.496078370824611, 1.56873754975139, 0.369754986443726, 0.603807364424560, 0.114108866146910,
259 0.125000000000000, -0.114108866146910, 0.603807364424560, -0.369754986443726, 1.56873754975139,
260 -0.496078370824611, 0.474958879799083, 1.64530582263602, 0.350780380010057, 0.640434422872475,
261 0.106739070478746, 0.135015431216830, 0.062500000000000, -0.135015431216830, 0.106739070478746,
262 -0.640434422872475, 0.350780380010057, -1.64530582263602, 0.474958879799083};
263 return eps[k * (k + 1) + q];
264}
265
266// c---------------------------------------
267double omega(int k, int q) {
268 const std::array<double, 49> oma = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
269 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
270 1.0, 1.0, 3.0, 3.0, 1.0, 3.0, 3.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
271 1.0, 3.0, 3.0, 1.0, 3.0, 3.0, 1.0, 1.0, 1.0, 1.0};
272
273 return oma[k * (k + 1) + q];
274}
275
276//--------------------------------------------------------
277// Function to calculate factorial
278//--------------------------------------------------------
279double fac(double n) {
280 if (n < 0.0)
281 return 0.0;
282 if (n == 0.0)
283 return 1.0;
284 double f = 1.0;
285 auto m = static_cast<int>(std::floor(n));
286 for (int i = 1; i <= m; ++i) {
287 f *= i;
288 }
289 return f;
290}
291
292//--------------------------------------------------------
293// binom (n over k)
294//--------------------------------------------------------
295double binom(int n, int k) { return fac(double(n)) / fac(double(k)) / fac(double(n - k)); }
296
297//--------------------------------------------------------
298// (k)
299// calculates D (a,b,c) [ The Wigner D-matrix ]
300// ms m
301//
302// see Lindner A, 'Drehimpulse in der Quantenmechanik',
303// ISBN 3-519-03061-6 (j)
304// Stuttgart: Teubner, 1984 page 86 (d (beta)=...)
305// for equation (1) m ms
306//
307// see Buckmaster phys. stat. sol. (a) 13 (1972) 9
308// for equation (2)
309//
310//--------------------------------------------------------
311ComplexType ddrot(int j, int m, int ms, double a, double b, double c) {
312 // c equation (1)
313 double d = delta(double(ms), double(m), double(j));
314 ifnull(b);
315 if (b != 0.0) {
316 d = 0.0;
317 for (int n = std::max(0, -(m + ms)); n <= std::min(j - m, j - ms); ++n) { // do n=max(0,-(m+ms)),min(j-m,j-ms)
318 d = d + pow(-1.0, (j - ms - n)) * binom(j - ms, n) * binom(j + ms, j - m - n) *
319 pow(cos(0.5 * b), (2 * n + m + ms)) * pow(sin(0.5 * b), (2 * j - 2 * n - m - ms));
320 }
321 d = d * sqrt(fac(double(j + m)) / fac(double(j + ms)) * fac(double(j - m)) / fac(double(j - ms)));
322 }
323 // c equation (2)
324 return ComplexType(cos(m * a), -sin(m * a)) * d * ComplexType(cos(ms * c), -sin(ms * c));
325}
326//--------------------------------------------------
327// <jm| jp^|q| or jm^|q| |nj>
328//--------------------------------------------------
329double jop(int q, double mj, double nj, double j) {
330 switch (q) {
331 case 0:
332 return delta(mj, nj, j);
333 case 1:
334 return delta(mj, nj + 1, j) * jp(nj, j);
335 case 2:
336 return delta(mj, nj + 2, j) * jp2(nj, j);
337 case 3:
338 return delta(mj, nj + 3, j) * jp3(nj, j);
339 case 4:
340 return delta(mj, nj + 4, j) * jp4(nj, j);
341 case 5:
342 return delta(mj, nj + 5, j) * jp5(nj, j);
343 case 6:
344 return delta(mj, nj + 6, j) * jp6(nj, j);
345 case -1:
346 return delta(mj, nj - 1, j) * jm(nj, j);
347 case -2:
348 return delta(mj, nj - 2, j) * jm2(nj, j);
349 case -3:
350 return delta(mj, nj - 3, j) * jm3(nj, j);
351 case -4:
352 return delta(mj, nj - 4, j) * jm4(nj, j);
353 case -5:
354 return delta(mj, nj - 5, j) * jm5(nj, j);
355 case -6:
356 return delta(mj, nj - 6, j) * jm6(nj, j);
357 default:
358 throw std::runtime_error("Cannot calculate jop with this q value.");
359 }
360}
361
362//--------------------------------------------------
363// calculate the full stevens operators
364//--------------------------------------------------
365double full_okq(int k, int q, double mj, double nj, double j) {
366 return 0.5 * jop(q, mj, nj, j) * (ff(k, q, mj, j) + ff(k, q, nj, j));
367}
368
369//-------------------------------
370// calculates <i|j+|k>
371//-------------------------------
372ComplexType matjp(const ComplexFortranMatrix &ev, int i, int k, int dim) {
373 ComplexFortranVector v(1, dim);
374 auto j = 0.5 * (double(dim) - 1.0);
375 for (int s = dim; s >= 2; --s) { // do 10 s=dim,2,-1
376 auto sj = double(s) - j - 1.0;
377 v(s) = ev(s - 1, k) * jp(sj - 1, j);
378 }
379 v(1) = 0.0;
380 ComplexType res = 0.0;
381 for (int s = 1; s <= dim; ++s) { // do 20 s=1,dim
382 res += std::conj(ev(s, i)) * v(s);
383 }
384 return res;
385}
386//--------------------
387// calculates <i|j-|k>
388//--------------------
389ComplexType matjm(const ComplexFortranMatrix &ev, int i, int k, int dim) {
390 ComplexFortranVector v(1, dim);
391 auto j = 0.5 * (double(dim) - 1.0);
392 for (int s = 1; s <= dim - 1; ++s) { // do 10 s=1,dim-1
393 auto sj = double(s) - j - 1.0;
394 v(s) = ev(s + 1, k) * jm(sj + 1, j);
395 }
396 v(dim) = 0.0;
397 ComplexType res = 0.0;
398 for (int s = 1; s <= dim; ++s) { // do 20 s=1,dim
399 res += std::conj(ev(s, i)) * v(s);
400 }
401 return res;
402}
403//--------------------
404// calculates <i|jx|k>
405//--------------------
406ComplexType matjx(const ComplexFortranMatrix &ev, int i, int k, int dim) {
407 return 0.5 * (matjm(ev, i, k, dim) + matjp(ev, i, k, dim));
408}
409//-------------------------
410// calculates |<i|jx|k>|**2
411//-------------------------
412double matjx2(const ComplexFortranMatrix &ev, int i, int k, int dim) { return std::norm(matjx(ev, i, k, dim)); }
413//--------------------
414// calculates <i|jy|k>
415//--------------------
416ComplexType matjy(const ComplexFortranMatrix &ev, int i, int k, int dim) {
417 auto ci = ComplexType(0.0, 1.0);
418 return 0.5 * ci * (matjm(ev, i, k, dim) - matjp(ev, i, k, dim));
419}
420//-------------------------
421// calculates |<i|jy|k>|**2
422//-------------------------
423double matjy2(const ComplexFortranMatrix &ev, int i, int k, int dim) { return std::norm(matjy(ev, i, k, dim)); }
424//--------------------
425// calculates <i|jz|k>
426//--------------------
427ComplexType matjz(const ComplexFortranMatrix &ev, int i, int k, int dim) {
428 ComplexFortranVector v(1, dim);
429 auto j = 0.5 * (double(dim) - 1.0);
430 for (int s = 1; s <= dim; ++s) { // do 10 s=1,dim
431 auto sj = double(s) - j - 1.0;
432 v(s) = ev(s, k) * sj;
433 }
434 ComplexType res = 0.0;
435 for (int s = 1; s <= dim; ++s) { // do 20 s=1,dim
436 res += std::conj(ev(s, i)) * v(s);
437 }
438 return res;
439}
440//-------------------------
441// calculates |<i|jz|k>|**2
442//-------------------------
443double matjz2(const ComplexFortranMatrix &ev, int i, int k, int dim) { return std::norm(matjz(ev, i, k, dim)); }
444
445//---------------------------------------------------------------
446// calculates all transition matrix elements for a single crystal
447// and a polycrystalline sample (powder)
448//---------------------------------------------------------------
449void matcalc(const ComplexFortranMatrix &ev, const int dim,
450 DoubleFortranMatrix &jx2, // cppcheck-suppress constParameterReference
451 DoubleFortranMatrix &jy2, // cppcheck-suppress constParameterReference
452 DoubleFortranMatrix &jz2, DoubleFortranMatrix &jt2) { // cppcheck-suppress constParameterReference
453 for (int i = 1; i <= dim; ++i) { // do 10 i=1,dim
454 for (int k = 1; k <= dim; ++k) { // do 20 k=1,dim
455 jx2(i, k) = matjx2(ev, i, k, dim);
456 jy2(i, k) = matjy2(ev, i, k, dim);
457 jz2(i, k) = matjz2(ev, i, k, dim);
458 jt2(i, k) = 2.0 / 3 * (jx2(i, k) + jy2(i, k) + jz2(i, k));
459 }
460 }
461}
462
463//------------------------------------------------------------
464// make sure that no underflow and overflow appears within exp
465//------------------------------------------------------------
466double exp_(double z) {
467 const double zmax = 71.0;
468 if (z < -zmax) {
469 return 0.0;
470 }
471 if (z < zmax) {
472 return exp(z);
473 }
474 return exp(zmax);
475}
476
477//------------------------------------------
478// calculates all transition intensities for
479// a polycrystalline sample (powder)
480//------------------------------------------
481void intcalc(const double r0, const double gj, const double z, const DoubleFortranMatrix &jt2,
482 const DoubleFortranVector &e, DoubleFortranMatrix &inten, // cppcheck-suppress constParameterReference
483 const int dim, double temp) {
484 // Original code from FOCUS calculated integrated intensity in barn
485 // auto constant = 4.0 * pi * pow(0.5 * r0 * gj, 2);
486 // ISIS normalised data is in milibarn/steradian - need to multiply
487 // by 1000 / 4 / PI
488 auto constant = pow(0.5 * r0 * gj, 2) * 1000.;
489 if (temp == 0.0) {
490 temp = 1.0;
491 }
492 // convert temperature to meV
493 temp /= c_fmevkelvin;
494
495 for (int i = 1; i <= dim; ++i) { // do 10 i=1,dim
496 auto coeff = exp_(-e(i) / temp) / z * constant;
497 for (int k = 1; k <= dim; ++k) { // do 20 k=1,dim
498 inten(i, k) = coeff * jt2(i, k);
499 }
500 }
501}
502//-------------------------------------
503// calculation of the occupation factor
504//-------------------------------------
505double c_occupation_factor(const DoubleFortranVector &energy, double dimj, double temp) {
506 auto dim = static_cast<int>(dimj);
507 double occupation_factor = 0.0;
508 if (temp == 0.0) {
509 temp = 1.0;
510 }
511 // convert temperature to meV
512 temp /= c_fmevkelvin;
513 for (int s = 1; s <= dim; ++s) { // do 10 s=1,dim
514 occupation_factor += exp_(-energy(s) / temp);
515 }
516 return occupation_factor;
517}
518
519//--------------------------------------
520// calculation of the zeeman hamiltonian
521//--------------------------------------
522void zeeman(ComplexFortranMatrix &hamiltonian, const int nre, const DoubleFortranVector &bext,
523 const DoubleFortranVector &bmol) {
524 auto i = ComplexType(0.0, 1.0);
525 auto bmolp = bmol(1) + i * bmol(2);
526 auto bmolm = bmol(1) - i * bmol(2);
527 auto bmolz = bmol(3);
528 auto bextp = bext(1) + i * bext(2);
529 auto bextm = bext(1) - i * bext(2);
530 ComplexType bextz = bext(3);
531 auto gj = (nre > 0) ? ggj[nre - 1] : 2.;
532 auto facmol = 2 * (gj - 1) * c_myb;
533 auto facext = gj * c_myb;
534 // Negative nre means arbitrary J, with abs(nre) = 2J. dimj=2J+1
535 auto dimj = (nre > 0) ? ddimj[nre - 1] : (abs(nre) + 1);
536 auto j = 0.5 * (dimj - 1.0);
537 auto dim = static_cast<int>(dimj);
538 hamiltonian.allocate(1, dim, 1, dim);
539 hamiltonian.zero();
540 //-------------------------------------------------------------------
541 // define only the lower triangle of h(m,n)
542 //-------------------------------------------------------------------
543 for (int m = 1; m <= dim; ++m) { // do 10 m=1,dim
544 auto mj = double(m) - j - 1.0;
545 for (int n = 1; n <= m; ++n) { // do 20 n=1,m
546 auto nj = double(n) - j - 1.0;
547 // add the molecular field
548 // f*J*B = f*( 1/2*(J+ * B- + J- * B+) + Jz*Bz )
549 hamiltonian(m, n) =
550 hamiltonian(m, n) + 0.5 * facmol * bmolm * delta(mj, nj + 1, j) * jp(nj, j) +
551 0.5 * facmol * bmolp * delta(mj, nj - 1, j) * jm(nj, j) + facmol * bmolz * delta(mj, nj, j) * nj +
552 // c add an external magnetic field
553 0.5 * facext * bextm * delta(mj, nj + 1, j) * jp(nj, j) +
554 0.5 * facext * bextp * delta(mj, nj - 1, j) * jm(nj, j) + facext * bextz * delta(mj, nj, j) * nj;
555 hamiltonian(n, m) = std::conj(hamiltonian(m, n));
556 }
557 }
558}
559
560//---------------------------------------
561// Calculation of the eigenvalues/vectors
562//---------------------------------------
563void diagonalise(const ComplexFortranMatrix &hamiltonian, DoubleFortranVector &eigenvalues,
564 ComplexFortranMatrix &eigenvectors) {
565 // Diagonalisation of the hamiltonian
566 auto dim = hamiltonian.len1();
567 eigenvalues.allocate(1, dim);
568 eigenvectors.allocate(1, dim, 1, dim);
569 ComplexFortranMatrix h = hamiltonian;
570
571 h.eigenSystemHermitian(eigenvalues, eigenvectors);
572
573 // Get the indicies of the eigenvalues sorted in ascending order
574 auto sortedIndices = eigenvalues.sortIndices();
575
576 // Shift the lowest energy level to 0
577 auto indexMin = static_cast<int>(sortedIndices[0] + 1); // default fortran matrix has base 1.
578 auto eshift = eigenvalues[indexMin];
579 eigenvalues += -eshift;
580
581 eigenvalues.sort(sortedIndices);
582 // Eigenvectors are in columns. Sort the columns
583 // to match the sorted eigenvalues.
584 eigenvectors.sortColumns(sortedIndices);
585}
586
587GNU_DIAG_ON("missing-braces")
588
589} // anonymous namespace
590
604 const ComplexFortranMatrix &hamiltonian, int nre, const DoubleFortranVector &bext) {
605 ComplexFortranMatrix h = hamiltonian;
606 DoubleFortranVector bmol(1, 3);
607 bmol.zero();
608 // Adds the external and molecular fields
610 zeeman(hz, nre, bext, bmol);
611 h -= hz;
612 // Now run the actual diagonalisation
613 diagonalise(h, eigenvalues, eigenvectors);
614}
615
634 ComplexFortranMatrix &hamiltonian, ComplexFortranMatrix &hzeeman, int nre,
635 const DoubleFortranVector &bmol, const DoubleFortranVector &bext,
636 const ComplexFortranMatrix &bkq, double alpha_euler, double beta_euler, double gamma_euler) {
637 if (nre > maxNre) {
638 throw std::out_of_range("nre is out of range");
639 }
640
641 // initialize some rare earth constants
642 auto dimj = (nre > 0) ? ddimj[nre - 1] : (abs(nre) + 1);
643
644 //------------------------------------------------------------
645 // transform the Bkq with
646 // H = sum_k=0 Bk0 Ok0 + sum_k>0_q>0 ReBkq ReOkq + ImBkq ImOkq
647 // to a representation with
648 // *
649 // H = sum_kq dkq Okq
650 //
651 // one finds: dk0 = Bk0 and for q<>0: dkq = Bkq/2
652 //-------------------------------------------------------
653 ComplexFortranMatrix dkq_star(1, 6, -6, 6);
654 dkq_star.zero();
655 auto i = ComplexType(0.0, 1.0);
656 for (int k = 2; k <= 6; k += 2) { // do k=2,6,2
657 for (int q = 0; q <= k; ++q) { // do q=0,k
658 ComplexType b_kq = bkq(k, q);
659 dkq_star(k, q) = b_kq;
660 if (q != 0) {
661 dkq_star(k, q) = dkq_star(k, q) / 2.0;
662 }
663 dkq_star(k, -q) = std::conj(dkq_star(k, q));
664 }
665 }
666 //-------------------------------------------------------------------
667 // the parameters are conjg(D_kq)
668 //-------------------------------------------------------------------
669 for (int k = 2; k <= 6; k += 2) { // do k=2,6,2
670 for (int q = -k; q <= k; ++q) { // do q=-k,k
671 dkq_star(k, q) = std::conj(dkq_star(k, q));
672 }
673 }
674 //-------------------------------------------------------------------
675 // Rotate the crystal field quantisation axis by the specified
676 // Euler angles. In some cases the number of CF parameters can be
677 // reduced by chosing the quantisation axis along a high symmetry
678 // rotation axis, rather than along a crystallographic axis.
679 // The eigenvalues should remain the same, but the eigenvectors will
680 // change.
681 // The rotation is done using a Wigner D-matrix. As noted by
682 // Buckmaster, the Stevens operators cannot be rotated as is, because
683 // the have an inconsistent normalisation between differen k, q terms
684 // Thus they have to be converted to and from the "Wybourne"
685 // normalisation using the epsilon / omega values.
686 // There was a bug in the original FOCUS code. Multiplying by
687 // omega*epsilon converts Wybourne parameters to Stevens parameters.
688 // Thus to convert the original dkq_star(k,qs) from Steven to Wybourn
689 // we should divide by epsilon(k,qs)*omega(k,qs) and then multiply by
690 // the new dkq_star(k,q) by epsilon(k,q)*omega(k,q).
691 //-------------------------------------------------------------------
692 ComplexFortranMatrix rdkq_star(1, 6, -6, 6);
693 for (int k = 2; k <= 6; k += 2) { // do k=2,6,2
694 for (int q = -k; q <= k; ++q) { // do q=-k,k
695 rdkq_star(k, q) = ComplexType(0.0, 0.0);
696 for (int qs = -k; qs <= k; ++qs) { // do qs=-k,k
697 rdkq_star(k, q) = rdkq_star(k, q) + dkq_star(k, qs) * epsilon(k, q) / epsilon(k, qs) * omega(k, q) /
698 omega(k, qs) * ddrot(k, q, qs, alpha_euler, beta_euler, gamma_euler);
699 }
700 }
701 }
702 // -------------------------------------------------------------------
703 // rotate the external magnetic field
704 // -------------------------------------------------------------------
705 // aj = sqrt(3/j/(j+1)/(2*j+1))
706 //
707 // the following hamiltonian will be rotated
708 //
709 // cry ----- cry cry
710 // h = f * > Bex V with <j|| V1 ||j> = sqrt(3)
711 // mag ----- 1q 1q
712 // q
713 // -------------------------------------------------------------------
714
715 ComplexFortranMatrix bex(1, 1, -1, 1);
716 bex(1, 1) = -(bext(1) - i * bext(2)) * M_SQRT1_2;
717 bex(1, -1) = (bext(1) + i * bext(2)) * M_SQRT1_2;
718 bex(1, 0) = bext(3);
719
720 // calculates Bex(1,q) for a canted moment
721 //
722 // moment cry cry cry +
723 // h = R (abc) h R (abc)
724 // mag mag
725 //
726 ComplexFortranMatrix rbex(1, 1, -1, 1);
727 for (int q = -1; q <= 1; ++q) { // do q=-1,1
728 rbex(1, q) = ComplexType(0.0, 0.0);
729 for (int qs = -1; qs <= 1; ++qs) { // do qs=-1,1
730 rbex(1, q) = rbex(1, q) + bex(1, qs) * ddrot(1, q, qs, alpha_euler, beta_euler, gamma_euler);
731 }
732 }
733 DoubleFortranVector rbext(1, 3);
734 rbext(1) = real(static_cast<ComplexType>(rbex(1, -1))) * M_SQRT2;
735 rbext(2) = imag(static_cast<ComplexType>(rbex(1, -1))) * M_SQRT2;
736 rbext(3) = real(static_cast<ComplexType>(rbex(1, 0)));
737
738 auto dim = static_cast<int>(dimj);
739 hamiltonian.allocate(1, dim, 1, dim);
740 hamiltonian.zero();
741 //-------------------------------------------------------------------
742 // calculate the crystal field hamiltonian
743 // define only the lower triangle of h(m,n)
744 //-------------------------------------------------------------------
745 // total momentum J of R3+
746 auto j = 0.5 * (dimj - 1.0);
747 for (int m = 1; m <= dim; ++m) { // do m=1,dim
748 auto mj = double(m) - j - 1.0;
749 for (int n = 1; n <= m; ++n) { // do n=1,m
750 auto nj = double(n) - j - 1.0;
751 for (int k = 2; k <= 6; k += 2) { // do k=2,6,2
752 for (int q = -k; q <= k; ++q) { // do q=-k,k
753 hamiltonian(m, n) = hamiltonian(m, n) + rdkq_star(k, q) * full_okq(k, q, mj, nj, j);
754 }
755 }
756 hamiltonian(n, m) = std::conj(hamiltonian(m, n));
757 }
758 }
759
760 // Adds the external and molecular fields
761 zeeman(hzeeman, nre, rbext, bmol);
762 hamiltonian -= hzeeman;
763
764 // Now run the actual diagonalisation
765 diagonalise(hamiltonian, eigenvalues, eigenvectors);
766}
767
768//-------------------------
769// transforms the indices
770//-------------------------
771int no(int i, const IntFortranVector &d, int n) {
772 // implicit none
773 // integer i,d(17*17),n,up,lo
774 int up = 0;
775 for (int nno = 1; nno <= n; ++nno) { // do no=1,n
776 int lo = up + 1;
777 up = lo + d(nno) - 1;
778 if (i >= lo && i <= up) {
779 return nno;
780 }
781 }
782 return n;
783}
784
796 DoubleFortranVector &e_energies, DoubleFortranMatrix &i_energies, double de) {
797 // real*8 energy(17) ! already defined in CF_FABI.INC
798 // real*8 mat(17,17)
799 // integer degeneration(17*17) ! stores the degeneration of a level
800 // integer n_energies ! no. of degenerated energy levels
801 // real*8 e_energies(17*17) ! energy values of the degenerated energy
802 // levels
803 // real*8 i_energies(17,17) ! intensities of the degenerated energy
804 // levels
805 // real*8 de,di
806
807 // energy levels which are closer than de are assumed to be degenerated
808 // only those excitations are taken into account whose intensities are
809 // greater equal than di
810
811 const auto dim = static_cast<int>(energy.size());
812 IntFortranVector tempDegeneration(dim);
813 DoubleFortranVector tempEnergies(dim);
814
815 // find out how many degenerated energy levels exists
816 tempEnergies(1) = 0.0;
817 tempDegeneration(1) = 1;
818 int n_energies = 1;
819 for (int i = 2; i <= dim; ++i) {
820 if (std::fabs(tempEnergies(n_energies) - energy(i)) >= de) {
821 n_energies += 1;
822 tempEnergies(n_energies) = energy(i);
823 tempDegeneration(n_energies) = 1;
824 } else {
825 tempDegeneration(n_energies) = tempDegeneration(n_energies) + 1;
826 }
827 }
828
829 // Resize the output arrays
830 degeneration.allocate(n_energies);
831 e_energies.allocate(n_energies);
832 i_energies.allocate(n_energies, n_energies);
833
834 // store the intensities of the degenarated levels
835 i_energies.zero();
836 for (int i = 1; i <= dim; ++i) {
837 int ii = no(i, tempDegeneration, n_energies);
838 degeneration(ii) = tempDegeneration(ii);
839 e_energies(ii) = tempEnergies(ii);
840 for (int k = 1; k <= dim; ++k) {
841 int kk = no(k, tempDegeneration, n_energies);
842 i_energies(ii, kk) = i_energies(ii, kk) + mat(i, k);
843 }
844 }
845}
846
857void calculateIntensities(int nre, const DoubleFortranVector &energies, const ComplexFortranMatrix &wavefunctions,
858 double temperature, double de, IntFortranVector &degeneration,
859 DoubleFortranVector &e_energies, DoubleFortranMatrix &i_energies) {
860 auto dim = static_cast<int>(energies.size());
861 auto dimj = (nre > 0) ? ddimj[nre - 1] : (abs(nre) + 1);
862 if (static_cast<double>(dim) != dimj) {
863 throw std::runtime_error("calculateIntensities was called for a wrong ion");
864 }
865
866 // calculates the transition matrixelements for a single crystal and
867 // a powdered sample
868 DoubleFortranMatrix jx2mat(1, dim, 1, dim);
869 DoubleFortranMatrix jy2mat(1, dim, 1, dim);
870 DoubleFortranMatrix jz2mat(1, dim, 1, dim);
871 DoubleFortranMatrix jt2mat(1, dim, 1, dim);
872 matcalc(wavefunctions, dim, jx2mat, jy2mat, jz2mat, jt2mat);
873
874 // calculates the sum over all occupation_factor
875 auto occupation_factor = c_occupation_factor(energies, dimj, temperature);
876
877 // calculates the transition intensities for a powdered sample
878 auto r0 = c_r0();
879 auto gj = (nre > 0) ? ggj[nre - 1] : 2.;
880 DoubleFortranMatrix mat(1, dim, 1, dim);
881 intcalc(r0, gj, occupation_factor, jt2mat, energies, mat, dim, temperature);
882
883 deg_on(energies, mat, degeneration, e_energies, i_energies, de);
884}
885
896void calculateExcitations(const DoubleFortranVector &e_energies, const DoubleFortranMatrix &i_energies, double de,
897 double di, DoubleFortranVector &e_excitations, DoubleFortranVector &i_excitations) {
898 auto n_energies = static_cast<int>(e_energies.size());
899 auto dimj = n_energies;
900 // Calculate transition energies (excitations) and corresponding
901 // intensities.
902 DoubleFortranVector eex(n_energies * n_energies);
903 DoubleFortranVector iex(n_energies * n_energies);
904 int nex = 0;
905 for (int i = 1; i <= n_energies; ++i) { // do i=1,n_energies
906 for (int k = 1; k <= n_energies; ++k) { // do k=1,n_energies
907 nex = nex + 1;
908 eex(nex) = e_energies(k) - e_energies(i);
909 iex(nex) = i_energies(i, k); // !I(i->k)
910 ifnull(eex(nex));
911 }
912 }
913 // nex at this point is the largest possible number
914 // of transitions
915
916 // Sort excitations in ascending order.
917 std::vector<size_t> ind = eex.sortIndices();
918 eex.sort(ind);
919
920 // Define lambda that transforms C++ indices to fortran indices
921 auto index = [&ind](int i) { return static_cast<int>(ind[i - 1] + 1); };
922
923 DoubleFortranVector tempEex(nex);
924 IntFortranVector degeneration(nex);
925 tempEex(1) = eex(1);
926 degeneration(1) = 1;
927
928 int k = 1;
929 // Check if there are any overlapping (degenerate) excitations
930 for (int i = 2; i <= nex; ++i) { // do i=2,nex
931 if (std::fabs(tempEex(k) - eex(i)) >= de) {
932 k = k + 1;
933 tempEex(k) = eex(i);
934 degeneration(k) = 1;
935 } else {
936 degeneration(k) = degeneration(k) + 1;
937 }
938 }
939 // Number of non-overlapping transitions
940 int n_excitation = k;
941
942 DoubleFortranVector tempIex(n_excitation);
943 for (int i = 1; i <= nex; ++i) { // do i=1,nex
944 auto ii = no(i, degeneration, n_excitation);
945 tempIex(ii) = tempIex(ii) + iex(index(i));
946 }
947
948 ind = tempIex.sortIndices(false);
949 tempIex.sort(ind);
950
951 // Keep only transitions that are strong enough
952 // i >= di
953 e_excitations.allocate(n_excitation);
954 i_excitations.allocate(n_excitation);
955 k = 0;
956 for (int i = 1; i <= n_excitation; ++i) { // do i=1,n_excitation
957 if (tempIex(i) >= di || dimj == 1) {
958 k = k + 1;
959 i_excitations(k) = tempIex(i);
960 e_excitations(k) = tempEex(index(i));
961 }
962 }
963 // nex now is the actual number of excitations that will
964 // be outputted.
965 nex = k;
966 if (nex != n_excitation) {
967 e_excitations.allocate(nex);
968 i_excitations.allocate(nex);
969 }
970}
971
978void calculateMagneticMoment(const ComplexFortranMatrix &ev, const DoubleFortranVector &Hdir, const int nre,
979 DoubleFortranVector &moment) {
980 int dim = (nre > 0) ? (int)ddimj[nre - 1] : (abs(nre) + 1);
981 auto gj = (nre > 0) ? ggj[nre - 1] : 2.;
982 moment.allocate(dim);
983 for (auto i = 1; i <= dim; ++i) {
984 moment(i) = real(matjx(ev, i, i, dim)) * Hdir(1) + // <ev|jx|ev>
985 real(matjy(ev, i, i, dim)) * Hdir(2) + // <ev|jy|ev>
986 real(matjz(ev, i, i, dim)) * Hdir(3); // <ev|jz|ev>
987 }
988 moment *= gj;
989}
990
996void calculateMagneticMomentMatrix(const ComplexFortranMatrix &ev, const std::vector<double> &Hdir, const int nre,
997 ComplexFortranMatrix &mumat) {
998 int dim = (nre > 0) ? (int)ddimj[nre - 1] : (abs(nre) + 1);
999 auto gj = (nre > 0) ? ggj[nre - 1] : 2.;
1000 mumat.allocate(1, dim, 1, dim);
1001 for (auto i = 1; i <= dim; ++i) {
1002 for (auto j = 1; j <= dim; ++j) {
1003 mumat(i, j) = matjx(ev, i, j, dim) * Hdir[0] + // <ev|jx|ev'>
1004 matjy(ev, i, j, dim) * Hdir[1] + // <ev|jy|ev'>
1005 matjz(ev, i, j, dim) * Hdir[2]; // <ev|jz|ev'>
1006 }
1007 }
1008 mumat *= gj;
1009}
1010
1011} // namespace Mantid::CurveFitting::Functions
double energy
Definition GetAllEi.cpp:157
std::map< DeltaEMode::Type, std::string > index
#define fabs(x)
Definition Matrix.cpp:22
#define GNU_DIAG_ON(x)
#define GNU_DIAG_OFF(x)
This is a collection of macros for turning compiler warnings off in a controlled manner.
void zero()
Set all elements to zero.
void eigenSystemHermitian(EigenVector &eigenValues, ComplexMatrix &eigenVectors)
Calculate the eigensystem of a Hermitian matrix.
void zero()
Set all elements to zero.
void sort(const std::vector< size_t > &indices)
Sort this vector in order defined by an index array.
std::vector< size_t > sortIndices(bool ascending=true) const
Create an index array that would sort this vector.
size_t size() const
Size of the vector.
void allocate(const int iFrom, const int iTo, const int jFrom, const int jTo)
Resize the matrix.
void allocate(int firstIndex, int lastIndex)
Resize the vector.
void MANTID_CURVEFITTING_DLL calculateMagneticMoment(const ComplexFortranMatrix &ev, const DoubleFortranVector &Hmag, const int nre, DoubleFortranVector &moment)
Calculate the diagonal matrix elements of the magnetic moment operator in a particular eigenvector ba...
void MANTID_CURVEFITTING_DLL calculateEigensystem(DoubleFortranVector &eigenvalues, ComplexFortranMatrix &eigenvectors, ComplexFortranMatrix &hamiltonian, ComplexFortranMatrix &hzeeman, int nre, const DoubleFortranVector &bmol, const DoubleFortranVector &bext, const ComplexFortranMatrix &bkq, double alpha_euler=0.0, double beta_euler=0.0, double gamma_euler=0.0)
Calculate eigenvalues and eigenvectors of the crystal field hamiltonian.
void deg_on(const DoubleFortranVector &energy, const DoubleFortranMatrix &mat, IntFortranVector &degeneration, DoubleFortranVector &e_energies, DoubleFortranMatrix &i_energies, double de)
Find out how many degenerated energy levels exists.
void MANTID_CURVEFITTING_DLL calculateExcitations(const DoubleFortranVector &e_energies, const DoubleFortranMatrix &i_energies, double de, double di, DoubleFortranVector &e_excitations, DoubleFortranVector &i_excitations)
Calculate the excitations (transition energies) and their intensities.
void MANTID_CURVEFITTING_DLL calculateZeemanEigensystem(DoubleFortranVector &eigenvalues, ComplexFortranMatrix &eigenvectors, const ComplexFortranMatrix &hamiltonian, int nre, const DoubleFortranVector &bext)
Calculates the eigenvalues/vectors of a crystal field Hamiltonian in a specified external magnetic fi...
void MANTID_CURVEFITTING_DLL calculateMagneticMomentMatrix(const ComplexFortranMatrix &ev, const std::vector< double > &Hdir, const int nre, ComplexFortranMatrix &mumat)
Calculate the full magnetic moment matrix in a particular eigenvector basis.
int no(int i, const IntFortranVector &d, int n)
void MANTID_CURVEFITTING_DLL calculateIntensities(int nre, const DoubleFortranVector &energies, const ComplexFortranMatrix &wavefunctions, double temperature, double de, IntFortranVector &degeneration, DoubleFortranVector &e_energies, DoubleFortranMatrix &i_energies)
Calculate the intensities of transitions.
FortranMatrix< EigenMatrix > DoubleFortranMatrix
FortranVector< ComplexVector > ComplexFortranVector
std::complex< double > ComplexType
FortranMatrix< ComplexMatrix > ComplexFortranMatrix
FortranVector< EigenVector > DoubleFortranVector
STL namespace.