BornAgain  1.19.0
Simulate and fit neutron and x-ray scattering at grazing incidence
ProcessedSample.cpp
Go to the documentation of this file.
1 // ************************************************************************************************
2 //
3 // BornAgain: simulate and fit reflection and scattering
4 //
5 //! @file Sample/Processed/ProcessedSample.cpp
6 //! @brief Implements class ProcessedSample.
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 
28 
29 namespace {
30 
31 std::unique_ptr<IFresnelMap> createFresnelMap(const MultiLayer& sample,
32  const std::vector<Slice>& slices,
33  const SimulationOptions& options)
34 {
35  std::unique_ptr<IFresnelMap> result;
36  const bool magnetic = std::any_of(slices.cbegin(), slices.cend(), [](const Slice& slice) {
37  return slice.material().isMagneticMaterial();
38  });
39  if (magnetic)
40  result = std::make_unique<MatrixFresnelMap>(SpecularStrategyBuilder::build(sample, true));
41  else
42  result = std::make_unique<ScalarFresnelMap>(SpecularStrategyBuilder::build(sample, false));
43  if (options.isIntegrate())
44  result->disableCaching();
45  return result;
46 }
47 
48 bool checkRegions(const std::vector<HomogeneousRegion>& regions)
49 {
50  double total_fraction = 0.0;
51  for (const auto& region : regions)
52  total_fraction += region.m_volume;
53  return (total_fraction >= 0 && total_fraction <= 1);
54 }
55 
56 std::vector<Slice>
57 createAverageMaterialSlices(const std::vector<Slice>& slices,
58  const std::map<size_t, std::vector<HomogeneousRegion>>& region_map)
59 {
60  std::vector<Slice> result = slices;
61  const auto last_slice_index = slices.size() - 1;
62  for (const auto& entry : region_map) {
63  const auto i_slice = entry.first;
64  if (i_slice == 0 || i_slice == last_slice_index)
65  continue; // skip semi-infinite layers
66  const auto slice_mat = slices[i_slice].material();
67  if (!checkRegions(entry.second))
68  throw std::runtime_error("createAverageMaterialSlices: "
69  "total volumetric fraction of particles exceeds 1!");
70  const auto new_material = createAveragedMaterial(slice_mat, entry.second);
71  result[i_slice].setMaterial(new_material);
72  }
73  return result;
74 }
75 
76 std::vector<double> bottomLayerCoordinates(const MultiLayer& multilayer)
77 {
78  auto n_layers = multilayer.numberOfLayers();
79  if (n_layers < 2)
80  return {};
81  std::vector<double> result(n_layers - 1);
82  result[0] = 0.0;
83  for (size_t i = 1; i < n_layers - 1; ++i)
84  result[i] = result[i - 1] - multilayer.layer(i)->thickness();
85  return result;
86 }
87 
88 //! Calculate z-regions occupied by particles
89 std::vector<ZLimits> particleRegions(const MultiLayer& multilayer, bool use_slicing)
90 {
91  const std::vector<double> bottom_coords = bottomLayerCoordinates(multilayer);
92  LayerFillLimits layer_fill_limits(bottom_coords);
93  if (use_slicing) {
94  for (size_t i = 0; i < multilayer.numberOfLayers(); ++i) {
95  auto layer = multilayer.layer(i);
96  double offset = (i == 0) ? 0 : bottom_coords[i - 1];
97  for (const auto* layout : layer->layouts())
98  for (const auto* particle : layout->particles())
99  layer_fill_limits.update(particle->bottomTopZ(), offset);
100  }
101  }
102  return layer_fill_limits.layerZLimits();
103 }
104 
105 } // namespace
106 
107 // ************************************************************************************************
108 // class ProcessedSample
109 // ************************************************************************************************
110 
112  bool forcePolarized)
113  : m_slices{}
114  , m_top_z{0.0}
115  , m_polarized{forcePolarized}
116  , m_crossCorrLength{sample.crossCorrLength()}
117  , m_ext_field{sample.externalField()}
118 {
119  initSlices(sample, options);
120  m_fresnel_map = createFresnelMap(sample, m_slices, options);
121  initBFields();
122  initLayouts(sample);
123  initFresnelMap(options);
124 }
125 
127 
129 {
130  return m_slices.size();
131 }
132 
133 const std::vector<Slice>& ProcessedSample::slices() const
134 {
135  return m_slices;
136 }
137 
138 const std::vector<Slice>& ProcessedSample::averageSlices() const
139 {
140  return m_fresnel_map->slices();
141 }
142 
143 const std::vector<ProcessedLayout>& ProcessedSample::layouts() const
144 {
145  return m_layouts;
146 }
147 
149 {
150  return m_fresnel_map.get();
151 }
152 
154 {
155  return m_crossCorrLength;
156 }
157 
159 {
160  return m_ext_field;
161 }
162 
164 {
165  if (i + 2 > m_slices.size())
166  throw std::runtime_error("ProcessedSample::bottomRoughness: "
167  "index out of bounds.");
168  return m_slices[i + 1].topRoughness();
169 }
170 
171 double ProcessedSample::sliceTopZ(size_t i) const
172 {
173  if (i == 0)
174  return m_top_z;
175  return sliceBottomZ(i - 1);
176 }
177 
178 double ProcessedSample::sliceBottomZ(size_t i) const
179 {
180  if (numberOfSlices() < 2)
181  return m_top_z;
182  // Last slice has no bottom:
183  if (i + 2 > numberOfSlices())
184  i = numberOfSlices() - 2;
185  double z = m_top_z;
186  for (size_t j = 1; j <= i; ++j)
187  z -= m_slices[j].thickness();
188  return z;
189 }
190 
192 {
193  return m_polarized;
194 }
195 
197 {
198  for (const auto& slice : m_slices)
199  if (slice.topRoughness())
200  return true;
201  return false;
202 }
203 
204 double ProcessedSample::crossCorrSpectralFun(const kvector_t kvec, size_t j, size_t k) const
205 {
206  if (m_crossCorrLength <= 0.0)
207  return 0.0;
208  const double z_j = sliceBottomZ(j);
209  const double z_k = sliceBottomZ(k);
210  const LayerRoughness* rough_j = bottomRoughness(j);
211  const LayerRoughness* rough_k = bottomRoughness(k);
212  if (!rough_j || !rough_k)
213  return 0.0;
214  const double sigma_j = rough_j->getSigma();
215  const double sigma_k = rough_k->getSigma();
216  if (sigma_j <= 0 || sigma_k <= 0)
217  return 0.0;
218  return 0.5
219  * ((sigma_k / sigma_j) * rough_j->getSpectralFun(kvec)
220  + (sigma_j / sigma_k) * rough_k->getSpectralFun(kvec))
221  * std::exp(-1 * std::abs(z_j - z_k) / m_crossCorrLength);
222 }
223 
224 // Creates a array of slices with the correct thickness, roughness and material
225 void ProcessedSample::initSlices(const MultiLayer& sample, const SimulationOptions& options)
226 {
227  if (sample.numberOfLayers() == 0)
228  return;
229  bool use_slicing = options.useAvgMaterials() && sample.numberOfLayers() > 1;
230  const auto layer_limits = particleRegions(sample, use_slicing);
231  for (size_t i = 0; i < sample.numberOfLayers(); ++i) {
232  const auto layer = sample.layer(i);
233  const auto n_slices = layer->numberOfSlices();
234  const ZLimits& slice_limits = layer_limits[i];
235  double tl = layer->thickness();
236  const Material* material = layer->material();
237  auto roughness = MultiLayerUtils::LayerTopRoughness(sample, i);
238  if (roughness && roughness->getSigma() <= 0)
239  roughness = nullptr;
240  // if no slicing is needed, create single slice for the layer
241  if (!slice_limits.isFinite() || n_slices == 0) {
242  if (i == sample.numberOfLayers() - 1)
243  tl = 0.0;
244  addSlice(tl, *material, roughness);
245  continue;
246  }
247  const double top = slice_limits.upperLimit().m_value;
248  const double bottom = slice_limits.lowerLimit().m_value;
249  // top layer
250  if (i == 0) {
251  if (top <= 0)
252  throw std::runtime_error("ProcessedSample::ProcessedSample: "
253  "top limit for top layer must be > 0.");
254  addSlice(0.0, *material);
255  addNSlices(n_slices, top - bottom, *material);
256  if (bottom > 0)
257  addSlice(bottom, *material);
258  m_top_z = top;
259  }
260  // middle or bottom layer
261  else {
262  if (top < 0) {
263  addSlice(-top, *material, roughness);
264  addNSlices(n_slices, top - bottom, *material);
265  } else {
266  addNSlices(n_slices, top - bottom, *material, roughness);
267  }
268  // middle layer
269  if (i < sample.numberOfLayers() - 1 && bottom > -tl)
270  addSlice(bottom + tl, *material);
271  // bottom layer
272  if (i == sample.numberOfLayers() - 1)
273  addSlice(0.0, *material);
274  }
275  }
276 }
277 
279 {
280  double z_ref = -m_top_z;
281  m_polarized = m_polarized || sample.isMagnetic();
282 
283  for (size_t i = 0; i < sample.numberOfLayers(); ++i) {
284  if (i > 1)
285  z_ref -= sample.layer(i - 1)->thickness();
286  auto layer = sample.layer(i);
287  for (const auto* layout : layer->layouts()) {
288  m_layouts.emplace_back(*layout, m_slices, z_ref, m_fresnel_map.get(), m_polarized);
289  mergeRegionMap(m_layouts.back().regionMap());
290  }
291  }
292 }
293 
294 void ProcessedSample::addSlice(double thickness, const Material& material,
295  const LayerRoughness* roughness)
296 {
297  if (roughness)
298  m_slices.emplace_back(thickness, material, *roughness);
299  else
300  m_slices.emplace_back(thickness, material);
301 }
302 
303 void ProcessedSample::addNSlices(size_t n, double thickness, const Material& material,
304  const LayerRoughness* roughness)
305 {
306  if (thickness <= 0.0)
307  return;
308  if (n == 0)
309  throw std::runtime_error("ProcessedSample::addNSlices: number of slices should be "
310  "bigger than zero.");
311  const double slice_thickness = thickness / n;
312  addSlice(slice_thickness, material, roughness);
313  for (size_t i = 1; i < n; ++i)
314  addSlice(slice_thickness, material);
315 }
316 
318 {
319  if (m_slices.empty())
320  return;
321  const double m_z0 = m_slices[0].material().magnetization().z();
322  const double b_z = Slice::Magnetic_Permeability * (m_ext_field.z() + m_z0);
323  for (size_t i = 0; i < m_slices.size(); ++i) {
324  m_slices[i].initBField(m_ext_field, b_z);
325  }
326 }
327 
329  const std::map<size_t, std::vector<HomogeneousRegion>>& region_map)
330 {
331  for (const auto& entry : region_map) {
332  size_t i = entry.first;
333  auto& regions = entry.second;
334  m_region_map[i].insert(m_region_map[i].begin(), regions.begin(), regions.end());
335  }
336 }
337 
339 {
340  if (sim_options.useAvgMaterials()) {
341  m_fresnel_map->setSlices(createAverageMaterialSlices(m_slices, m_region_map));
342  } else {
343  m_fresnel_map->setSlices(m_slices);
344  }
345 }
Defines struct HomogeneousRegion, and declares fct createAveragedMaterial.
Defines interface IParticle.
Defines class LayerFillLimits.
Defines class LayerRoughness.
Defines class Layer.
Defines class MatrixFresnelMap.
Defines helper functions for MultiLayer objects.
Defines class ParticleLayout.
Defines class ProcessedLayout.
Defines class ProcessedSample.
Defines class ScalarFresnelMap.
Defines class SimulationOptions.
Defines class SpecularStrategyBuilder.
T z() const
Returns z-component in cartesian coordinate system.
Definition: BasicVector3D.h:67
Holds the necessary information to calculate the radiation wavefunction in every layer for different ...
Definition: IFresnelMap.h:34
bool isMagnetic() const
Returns true if there is any magnetic material in this ISampleNode.
Definition: ISampleNode.cpp:40
Helper class for the graded layer approximation.
A roughness of interface between two layers.
double getSigma() const
Returns rms of roughness.
double getSpectralFun(const kvector_t kvec) const
Returns power spectral density of the surface roughness.
double thickness() const
Definition: Layer.h:38
unsigned int numberOfSlices() const
Definition: Layer.h:52
A wrapper for underlying material implementation.
Definition: Material.h:29
Our sample model: a stack of layers one below the other.
Definition: MultiLayer.h:41
const Layer * layer(size_t i_layer) const
Returns layer with given index.
Definition: MultiLayer.cpp:91
size_t numberOfLayers() const
Definition: MultiLayer.h:50
void initLayouts(const MultiLayer &sample)
const std::vector< Slice > & slices() const
std::vector< Slice > m_slices
std::map< size_t, std::vector< HomogeneousRegion > > m_region_map
void initSlices(const MultiLayer &sample, const SimulationOptions &options)
bool hasRoughness() const
double sliceTopZ(size_t i) const
kvector_t externalField() const
ProcessedSample(const MultiLayer &sample, const SimulationOptions &options, bool forcePolarized=false)
void addSlice(double thickness, const Material &material, const LayerRoughness *p_roughness=nullptr)
void addNSlices(size_t n, double thickness, const Material &material, const LayerRoughness *p_roughness=nullptr)
void mergeRegionMap(const std::map< size_t, std::vector< HomogeneousRegion >> &region_map)
double sliceBottomZ(size_t i) const
void initFresnelMap(const SimulationOptions &sim_options)
bool containsMagneticMaterial() const
std::unique_ptr< IFresnelMap > m_fresnel_map
const IFresnelMap * fresnelMap() const
double m_crossCorrLength
kvector_t m_ext_field
size_t numberOfSlices() const
double crossCorrSpectralFun(const kvector_t kvec, size_t j, size_t k) const
Fourier transform of the correlation function of roughnesses between the interfaces.
const std::vector< Slice > & averageSlices() const
const std::vector< ProcessedLayout > & layouts() const
double crossCorrelationLength() const
const LayerRoughness * bottomRoughness(size_t i) const
std::vector< ProcessedLayout > m_layouts
Collect the different options for simulation.
bool isIntegrate() const
bool useAvgMaterials() const
Data structure containing the data of a single slice, for calculating the Fresnel coefficients.
Definition: Slice.h:32
static constexpr double Magnetic_Permeability
Definition: Slice.h:62
static std::unique_ptr< ISpecularStrategy > build(const MultiLayer &sample, const bool magnetic)
Class that contains upper and lower limits of the z-coordinate for the slicing of form factors.
Definition: ZLimits.h:45
OneSidedLimit lowerLimit() const
Definition: ZLimits.cpp:39
OneSidedLimit upperLimit() const
Definition: ZLimits.cpp:44
bool isFinite() const
Definition: ZLimits.cpp:32
Material createAveragedMaterial(const Material &layer_mat, const std::vector< HomogeneousRegion > &regions)
Creates averaged material.
const LayerRoughness * LayerTopRoughness(const MultiLayer &multilayer, size_t i)
Returns top roughness of layer.
double m_value
Definition: ZLimits.h:37