BornAgain  1.18.0
Simulate and fit neutron and x-ray scattering at grazing incidence
SpecularMagneticStrategy.cpp
Go to the documentation of this file.
1 // ************************************************************************** //
2 //
3 // BornAgain: simulate and fit scattering at grazing incidence
4 //
5 //! @file Sample/Specular/SpecularMagneticStrategy.cpp
6 //! @brief Implements class SpecularMagneticStrategy.
7 //!
8 //! @homepage http://www.bornagainproject.org
9 //! @license GNU General Public License v3 or higher (see COPYING)
10 //! @copyright Forschungszentrum Jülich GmbH 2018
11 //! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS)
12 //
13 // ************************************************************************** //
14 
18 #include "Sample/Slice/Slice.h"
19 
20 namespace
21 {
23 Eigen::Vector2cd eigenvalues(complex_t kz, double b_mag);
24 Eigen::Vector2cd checkForUnderflow(const Eigen::Vector2cd& eigenvs);
26 
27 // The factor 1e-18 is here to have unit: 1/T*nm^-2
30 } // namespace
31 
33  const kvector_t& k) const
34 {
35  return Execute(slices, KzComputation::computeReducedKz(slices, k));
36 }
37 
39 SpecularMagneticStrategy::Execute(const std::vector<Slice>& slices,
40  const std::vector<complex_t>& kz) const
41 {
42  if (slices.size() != kz.size())
43  throw std::runtime_error("Number of slices does not match the size of the kz-vector");
44 
46  for (auto& coeff : computeTR(slices, kz))
47  result.push_back(std::make_unique<MatrixRTCoefficients_v2>(coeff));
48 
49  return result;
50 }
51 
52 std::vector<MatrixRTCoefficients_v2>
53 SpecularMagneticStrategy::computeTR(const std::vector<Slice>& slices,
54  const std::vector<complex_t>& kzs)
55 {
56  if (kzs[0] == 0.)
57  throw std::runtime_error("Edge case k_z = 0 not implemented");
58 
59  if (slices.size() != kzs.size())
60  throw std::runtime_error(
61  "Error in SpecularMagnetic_::execute: kz vector and slices size shall coinside.");
62  if (slices.empty())
63  return {};
64 
65  std::vector<MatrixRTCoefficients_v2> result;
66  result.reserve(slices.size());
67 
68  const double kz_sign = kzs.front().real() > 0.0 ? 1.0 : -1.0; // save sign to restore it later
69  const kvector_t b_0 = magneticImpact(slices.front().bField());
70  result.emplace_back(kz_sign, eigenvalues(kzs.front(), 0.0), kvector_t{0.0, 0.0, 0.0});
71  for (size_t i = 1, size = slices.size(); i < size; ++i) {
72  kvector_t b = magneticImpact(slices[i].bField()) - b_0;
73  result.emplace_back(kz_sign, checkForUnderflow(eigenvalues(kzs[i], b.mag())), b);
74  }
75 
76  if (result.front().m_lambda == Eigen::Vector2cd::Zero()) {
77  std::for_each(result.begin(), result.end(), [](auto& coeff) { setNoTransmission(coeff); });
78  return result;
79  }
80 
81  std::for_each(result.begin(), result.end(), [](auto& coeff) { calculateTR(coeff); });
82  nullifyBottomReflection(result.back());
83  propagateBackwardsForwards(result, slices);
84 
85  return result;
86 }
87 
89 {
90  const double b = coeff.m_b.mag();
91  if (b == 0.0) {
92  calculateZeroFieldTR(coeff);
93  return;
94  }
95 
96  const double bpbz = b + coeff.m_b.z();
97  const double bmbz = b - coeff.m_b.z();
98  const complex_t bxmby = coeff.m_b.x() - I * coeff.m_b.y();
99  const complex_t bxpby = coeff.m_b.x() + I * coeff.m_b.y();
100  const complex_t l_1 = coeff.m_lambda(0);
101  const complex_t l_2 = coeff.m_lambda(1);
102 
103  auto& T1 = coeff.T1;
104  T1 << bmbz, -bxmby, -bmbz * l_1, bxmby * l_1, -bxpby, bpbz, bxpby * l_1, -bpbz * l_1,
105  -bmbz / l_1, bxmby / l_1, bmbz, -bxmby, bxpby / l_1, -bpbz / l_1, -bxpby, bpbz;
106  T1 /= 4.0 * b;
107 
108  auto& R1 = coeff.R1;
109  R1 << T1(0, 0), T1(0, 1), -T1(0, 2), -T1(0, 3), T1(1, 0), T1(1, 1), -T1(1, 2), -T1(1, 3),
110  -T1(2, 0), -T1(2, 1), T1(2, 2), T1(2, 3), -T1(3, 0), -T1(3, 1), T1(3, 2), T1(3, 3);
111 
112  auto& T2 = coeff.T2;
113  T2 << bpbz, bxmby, -bpbz * l_2, -bxmby * l_2, bxpby, bmbz, -bxpby * l_2, -bmbz * l_2,
114  -bpbz / l_2, -bxmby / l_2, bpbz, bxmby, -bxpby / l_2, -bmbz / l_2, bxpby, bmbz;
115  T2 /= 4.0 * b;
116 
117  auto& R2 = coeff.R2;
118  R2 << T2(0, 0), T2(0, 1), -T2(0, 2), -T2(0, 3), T2(1, 0), T2(1, 1), -T2(1, 2), -T2(1, 3),
119  -T2(2, 0), -T2(2, 1), T2(2, 2), T2(2, 3), -T2(3, 0), -T2(3, 1), T2(3, 2), T2(3, 3);
120 }
121 
123 {
124  coeff.T1 = Eigen::Matrix4cd::Zero();
125  coeff.R1 = Eigen::Matrix4cd::Zero();
126  coeff.T2 = Eigen::Matrix4cd::Zero();
127  coeff.R2 = Eigen::Matrix4cd::Zero();
128 
129  // lambda_1 == lambda_2, no difference which one to use
130  const complex_t eigen_value = coeff.m_lambda(0);
131 
132  Eigen::Matrix3cd Tblock;
133  Tblock << 0.5, 0.0, -0.5 * eigen_value, 0.0, 0.0, 0.0, -0.5 / eigen_value, 0.0, 0.5;
134 
135  Eigen::Matrix3cd Rblock;
136  Rblock << 0.5, 0.0, 0.5 * eigen_value, 0.0, 0.0, 0.0, 0.5 / eigen_value, 0.0, 0.5;
137 
138  coeff.T1.block<3, 3>(1, 1) = Tblock;
139  coeff.R1.block<3, 3>(1, 1) = Rblock;
140  coeff.T2.block<3, 3>(0, 0) = Tblock;
141  coeff.R2.block<3, 3>(0, 0) = Rblock;
142 }
143 
145 {
146  coeff.m_w_plus = Eigen::Vector4cd::Zero();
147  coeff.m_w_min = Eigen::Vector4cd::Zero();
148  coeff.T1 = Eigen::Matrix4cd::Identity() / 4.0;
149  coeff.R1 = coeff.T1;
150  coeff.T2 = coeff.T1;
151  coeff.R2 = coeff.T1;
152 }
153 
155 {
156  const complex_t l_1 = coeff.m_lambda(0);
157  const complex_t l_2 = coeff.m_lambda(1);
158  const double b_mag = coeff.m_b.mag();
159  const kvector_t& b = coeff.m_b;
160 
161  if (b_mag == 0.0) {
162  // both eigenvalues are the same, no difference which one to take
163  coeff.m_w_plus << -l_1, 0.0, 1.0, 0.0;
164  coeff.m_w_min << 0.0, -l_1, 0.0, 1.0;
165  return;
166  }
167 
168  // First basis vector that has no upward going wave amplitude
169  coeff.m_w_min(0) = (b.x() - I * b.y()) * (l_1 - l_2) / 2.0 / b_mag;
170  coeff.m_w_min(1) = b.z() * (l_2 - l_1) / 2.0 / b_mag - (l_1 + l_2) / 2.0;
171  coeff.m_w_min(2) = 0.0;
172  coeff.m_w_min(3) = 1.0;
173 
174  // Second basis vector that has no upward going wave amplitude
175  coeff.m_w_plus(0) = -(l_1 + l_2) / 2.0 - b.z() / (l_1 + l_2);
176  coeff.m_w_plus(1) = (b.x() + I * b.y()) * (l_1 - l_2) / 2.0 / b_mag;
177  coeff.m_w_plus(2) = 1.0;
178  coeff.m_w_plus(3) = 0.0;
179 }
180 
182  std::vector<MatrixRTCoefficients_v2>& coeff, const std::vector<Slice>& slices)
183 {
184  const int size = static_cast<int>(coeff.size());
185  std::vector<Eigen::Matrix2cd> SMatrices(coeff.size());
186  std::vector<complex_t> Normalization(coeff.size());
187 
188  for (int index = size - 2; index >= 0; --index) {
189  const size_t i = static_cast<size_t>(index);
190  const double t = slices[i].thickness();
191  const auto kz = coeff[i].getKz();
192  const Eigen::Matrix4cd l = coeff[i].R1 * GetImExponential(kz(0) * t)
193  + coeff[i].T1 * GetImExponential(-kz(0) * t)
194  + coeff[i].R2 * GetImExponential(kz(1) * t)
195  + coeff[i].T2 * GetImExponential(-kz(1) * t);
196  coeff[i].m_w_plus = l * coeff[i + 1].m_w_plus;
197  coeff[i].m_w_min = l * coeff[i + 1].m_w_min;
198 
199  // rotate and normalize polarization
200  const auto Snorm = findNormalizationCoefficients(coeff[i]);
201  auto S = Snorm.first;
202  auto norm = Snorm.second;
203 
204  SMatrices[i] = S;
205  Normalization[i] = norm;
206 
207  const complex_t a_plus = S(0, 0) / norm;
208  const complex_t b_plus = S(1, 0) / norm;
209  const complex_t a_min = S(0, 1) / norm;
210  const complex_t b_min = S(1, 1) / norm;
211 
212  const Eigen::Vector4cd w_plus = a_plus * coeff[i].m_w_plus + b_plus * coeff[i].m_w_min;
213  const Eigen::Vector4cd w_min = a_min * coeff[i].m_w_plus + b_min * coeff[i].m_w_min;
214 
215  coeff[i].m_w_plus = std::move(w_plus);
216  coeff[i].m_w_min = std::move(w_min);
217  }
218 
219  complex_t dumpingFactor = 1;
220  Eigen::Matrix2cd S = Eigen::Matrix2cd::Identity();
221  for (size_t i = 1; i < coeff.size(); ++i) {
222  dumpingFactor = dumpingFactor * Normalization[i - 1];
223  S = SMatrices[i - 1] * S;
224 
225  if (std::isinf(std::norm(dumpingFactor))) {
226  // not entirely sure, whether this is the correct edge case
227  std::for_each(coeff.begin() + i, coeff.end(),
228  [](auto& coeff) { setNoTransmission(coeff); });
229  break;
230  }
231 
232  const complex_t a_plus = S(0, 0) / dumpingFactor;
233  const complex_t b_plus = S(1, 0) / dumpingFactor;
234  const complex_t a_min = S(0, 1) / dumpingFactor;
235  const complex_t b_min = S(1, 1) / dumpingFactor;
236 
237  Eigen::Vector4cd w_plus = a_plus * coeff[i].m_w_plus + b_plus * coeff[i].m_w_min;
238  Eigen::Vector4cd w_min = a_min * coeff[i].m_w_plus + b_min * coeff[i].m_w_min;
239 
240  coeff[i].m_w_plus = std::move(w_plus);
241  coeff[i].m_w_min = std::move(w_min);
242  }
243 }
244 
245 std::pair<Eigen::Matrix2cd, complex_t>
247 {
248  const Eigen::Vector2cd Ta = coeff.T1plus() + coeff.T2plus();
249  const Eigen::Vector2cd Tb = coeff.T1min() + coeff.T2min();
250 
251  Eigen::Matrix2cd S;
252  S << Ta(0), Tb(0), Ta(1), Tb(1);
253 
254  Eigen::Matrix2cd SInverse;
255  SInverse << S(1, 1), -S(0, 1), -S(1, 0), S(0, 0);
256  const complex_t d1 = S(1, 1) - S(0, 1);
257  const complex_t d2 = S(1, 0) - S(0, 0);
258  const complex_t denominator = S(0, 0) * d1 - d2 * S(0, 1);
259 
260  return {SInverse, denominator};
261 }
262 
263 namespace
264 {
266 {
267  return -magnetic_prefactor * B_field;
268 }
269 
270 Eigen::Vector2cd eigenvalues(complex_t kz, double b_mag)
271 {
272  const complex_t a = kz * kz;
273  return {I * std::sqrt(a + b_mag), I * std::sqrt(a - b_mag)};
274 }
275 
276 Eigen::Vector2cd checkForUnderflow(const Eigen::Vector2cd& eigenvs)
277 {
278  auto lambda = [](complex_t value) { return std::abs(value) < 1e-40 ? 1e-40 : value; };
279  return {lambda(eigenvs(0)), lambda(eigenvs(1))};
280 }
281 
283 {
284  if (exponent.imag() > -std::log(std::numeric_limits<double>::min()))
285  return 0.0;
286  return std::exp(I * exponent);
287 }
288 } // namespace
constexpr complex_t I
Definition: Complex.h:21
std::complex< double > complex_t
Definition: Complex.h:20
Declares functions in namespace KzComputation.
constexpr double magnetic_prefactor
Defines the values of physical constants (SI)
Defines class Slice.
Defines class SpecularMagneticStrategy.
double mag() const
Returns magnitude of the vector.
T z() const
Returns z-component in cartesian coordinate system.
Definition: BasicVector3D.h:68
T y() const
Returns y-component in cartesian coordinate system.
Definition: BasicVector3D.h:66
T x() const
Returns x-component in cartesian coordinate system.
Definition: BasicVector3D.h:64
std::vector< std::unique_ptr< const ILayerRTCoefficients > > coeffs_t
Specular reflection and transmission coefficients in a layer in case of magnetic interactions between...
Eigen::Vector2cd T2plus() const override
Eigen::Vector2cd m_lambda
wave propagation direction (-1 for direct one, 1 for time reverse)
Eigen::Matrix4cd T2
matrix selecting the transmitted part of the second eigenmode
Eigen::Vector2cd T2min() const override
Eigen::Matrix4cd R1
matrix selecting the reflected part of the first eigenmode
Eigen::Vector2cd T1plus() const override
The following functions return the transmitted and reflected amplitudes for different incoming beam p...
Eigen::Vector4cd m_w_plus
boundary values for up-polarization
Eigen::Vector4cd m_w_min
boundary values for down-polarization
Eigen::Vector2cd T1min() const override
Eigen::Matrix4cd T1
matrix selecting the transmitted part of the first eigenmode
kvector_t m_b
normalized magnetic field impact (with correction for external mag. field)
Eigen::Matrix4cd R2
matrix selecting the reflected part of the second eigenmode
static void calculateZeroFieldTR(MatrixRTCoefficients_v2 &coeff)
static std::vector< MatrixRTCoefficients_v2 > computeTR(const std::vector< Slice > &slices, const std::vector< complex_t > &kzs)
static void nullifyBottomReflection(MatrixRTCoefficients_v2 &coeff)
initializes reflectionless bottom boundary condition.
static void setNoTransmission(MatrixRTCoefficients_v2 &coeff)
ISpecularStrategy::coeffs_t Execute(const std::vector< Slice > &slices, const kvector_t &k) const
Computes refraction angle reflection/transmission coefficients for given sliced multilayer and waveve...
static void calculateTR(MatrixRTCoefficients_v2 &coeff)
Computes frobenius matrices for multilayer solution.
static void propagateBackwardsForwards(std::vector< MatrixRTCoefficients_v2 > &coeff, const std::vector< Slice > &slices)
Propagates boundary conditions from the bottom to the top of the layer stack.
static std::pair< Eigen::Matrix2cd, complex_t > findNormalizationCoefficients(const MatrixRTCoefficients_v2 &coeff)
finds linear coefficients for normalizing transmitted wave to unity.
std::vector< complex_t > computeReducedKz(const std::vector< Slice > &slices, kvector_t k)
constexpr double g_factor_n
neutron g-factor
constexpr double h_bar
Reduced Plank constant, J s.
constexpr double m_n
Neutron mass, kg.
constexpr double mu_N
Nuclear magneton ( ), J/T.
Eigen::Vector2cd eigenvalues(complex_t kz, double b_mag)
Eigen::Vector2cd checkForUnderflow(const Eigen::Vector2cd &eigenvs)