BornAgain  1.19.79
Open-source research software to simulate and fit neutron and x-ray reflectometry and grazing-incidence small-angle scattering
ReSample.cpp
Go to the documentation of this file.
1 // ************************************************************************************************
2 //
3 // BornAgain: simulate and fit reflection and scattering
4 //
5 //! @file Resample/Processed/ReSample.cpp
6 //! @brief Implements class reSample.
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 
16 #include "Base/Util/Assert.h"
19 #include "Resample/Flux/IFlux.h"
37 
38 namespace {
39 
40 //! Returns the average of an intensive property.
41 //! Used for SLD (T=complex) and magnetization (T=vector).
42 
43 template <class T>
44 T averageData(const Material& layer_mat, const Admixtures& admixtures,
45  std::function<T(const Material&)> average)
46 {
47  const T layer_data = average(layer_mat);
48  T averaged_data = layer_data;
49  for (const OneAdmixture& admix : admixtures)
50  averaged_data += admix.fraction * (average(admix.material) - layer_data);
51  return averaged_data;
52 }
53 
54 // Tested by Tests/Unit/Sim/Sample/MaterialTest.cpp
55 
56 Material createAveragedMaterial(const Material& layer_mat, const Admixtures& admixtures)
57 {
58  const std::string avge_name = layer_mat.materialName() + "_avg";
59 
60  const R3 avge_magnetization = averageData<R3>(
61  layer_mat, admixtures, [](const Material& mat) { return mat.magnetization(); });
62 
63  // Is there a uniform material type (refractive or SLD)?
64  std::vector<const Material*> materials;
65  materials.push_back(&layer_mat);
66  for (const OneAdmixture& admix : admixtures)
67  materials.push_back(&admix.material);
68 
69  const MATERIAL_TYPES common_type = MaterialUtils::checkMaterialTypes(materials);
70  if (common_type == MATERIAL_TYPES::InvalidMaterialType)
71  throw std::runtime_error("Error in createAveragedMaterial(): non-default materials of "
72  "different types used simultaneously.");
73 
74  if (common_type == MATERIAL_TYPES::RefractiveMaterial) {
75  // avrData returns (1 - mdc)^2 - 1, where mdc is material data conjugate
76  auto avrData = [](const Material& mat) -> complex_t {
77  const complex_t mdc = std::conj(mat.materialData());
78  return mdc * mdc - 2.0 * mdc;
79  };
80  const complex_t avr_mat_data = std::conj(
81  1.0 - std::sqrt(1.0 + averageData<complex_t>(layer_mat, admixtures, avrData)));
82  return RefractiveMaterial(avge_name, avr_mat_data.real(), avr_mat_data.imag(),
83  avge_magnetization);
84  }
85  if (common_type == MATERIAL_TYPES::MaterialBySLD) {
86  complex_t (*avrData)(const Material&) = [](const Material& mat) {
87  return mat.materialData();
88  };
89  const auto avr_mat_data = averageData<complex_t>(layer_mat, admixtures, avrData);
90  return MaterialBySLD(avge_name, avr_mat_data.real(), avr_mat_data.imag(),
91  avge_magnetization);
92  }
93  ASSERT(0);
94 }
95 
96 void checkVolumeFractions(const Admixtures& admixtures)
97 {
98  double total_fraction = 0.0;
99  for (const auto& admix : admixtures)
100  total_fraction += admix.fraction;
101  ASSERT(total_fraction >= 0);
102  if (total_fraction > 1)
103  throw std::runtime_error("average slices: "
104  "total volumetric fraction of particles exceeds 1!");
105 }
106 
107 SliceStack refineStack(const SliceStack& stack, const std::vector<reLayout>& layouts)
108 {
109  SliceStack result = stack;
110  for (const reLayout& layout : layouts) {
111  for (const auto& entry : layout.regionMap()) {
112  const size_t i_slice = entry.first;
113  if (i_slice == 0 || i_slice == result.size() - 1)
114  continue; // skip semi-infinite layers
115  const auto slice_mat = result[i_slice].material();
116  checkVolumeFractions(entry.second);
117  result[i_slice].setMaterial(createAveragedMaterial(slice_mat, entry.second));
118  }
119  }
120  ASSERT(result.size() == stack.size());
121  return result;
122 }
123 
124 //! Returns a SliceStack that refines the layer structure of sample,
125 //! taking into account the average SLD of layer material and particle decoration.
126 //!
127 //! For use in reSample constructor.
128 
129 SliceStack slicify(const MultiLayer& sample, const SimulationOptions& options)
130 {
131  SliceStack result(sample.roughnessModel());
132  if (sample.numberOfLayers() == 0)
133  return result;
134  const bool use_slicing = options.useAvgMaterials() && sample.numberOfLayers() > 1;
135  const auto layer_limits = Compute::Slicing::particleRegions(sample, use_slicing);
136  for (size_t i = 0; i < sample.numberOfLayers(); ++i) {
137  const Layer* const layer = sample.layer(i);
138  const size_t N = layer->numberOfSlices();
139  if (N == 0)
140  throw std::runtime_error("Cannot process layer with numberOfSlices=0");
141  const ZLimits& slice_limits = layer_limits[i];
142  double tl = layer->thickness();
143  const Material* const material = layer->material();
144  const LayerRoughness* roughness = SampleUtils::Multilayer::LayerTopRoughness(sample, i);
145  if (roughness && roughness->sigma() <= 0)
146  roughness = nullptr;
147  // if no slicing is needed, create single slice for the layer
148  if (!slice_limits.isFinite() || N == 0) {
149  if (i == sample.numberOfLayers() - 1)
150  tl = 0.0;
151  if (i == 0)
152  result.addTopSlice(tl, *material);
153  else
154  result.addSlice(tl, *material, roughness);
155  continue;
156  }
157  const double top = slice_limits.zTop();
158  const double bottom = slice_limits.zBottom();
159  // top layer
160  if (i == 0) {
161  if (top <= 0)
162  throw std::runtime_error("reSample::reSample: "
163  "top limit for top layer must be > 0.");
164  result.addTopSlice(top, *material); // semiinfinite top slice
165  result.addNSlices(N, top - bottom, *material);
166  if (bottom > 0)
167  result.addSlice(bottom, *material);
168  }
169  // middle or bottom layer
170  else {
171  if (top < 0) {
172  result.addSlice(-top, *material, roughness);
173  result.addNSlices(N, top - bottom, *material);
174  } else {
175  result.addNSlices(N, top - bottom, *material, roughness);
176  }
177  // middle layer
178  if (i < sample.numberOfLayers() - 1 && bottom > -tl)
179  result.addSlice(bottom + tl, *material);
180  // bottom layer
181  if (i == sample.numberOfLayers() - 1)
182  result.addSlice(0.0, *material); // semiinfinite bottom slice
183  }
184  }
185  return result;
186 }
187 
188 size_t zToSliceIndex(double z, const SliceStack& slices)
189 {
190  auto n_layers = slices.size();
191  if (n_layers < 2 || z > 0.0)
192  return 0;
193 
194  double z0 = slices[0].zBottom();
195  size_t result = 0;
196  while (result < n_layers - 1) {
197  ++result;
198  if (slices[result].zBottom() - z0 < z)
199  break;
200  }
201  return result;
202 }
203 
204 std::pair<size_t, size_t> SliceIndexSpan(const IParticle& particle, const SliceStack& slices,
205  double z_ref)
206 {
207  const ZLimits zSpan = Compute::Slicing::zSpan(&particle);
208  const double zbottom = zSpan.zBottom();
209  const double ztop = zSpan.zTop();
210  const double eps =
211  (ztop - zbottom) * 1e-6; // allow for relatively small crossing
212  // due to numerical approximations (like rotation over 180 degrees)
213  const double zmax = ztop + z_ref - eps;
214  const double zmin = zbottom + z_ref + eps;
215  size_t top_index = zToSliceIndex(zmax, slices);
216  const size_t bottom_index = zToSliceIndex(zmin, slices);
217  if (top_index > bottom_index) // happens for zero size particles
218  top_index = bottom_index;
219  return {top_index, bottom_index};
220 }
221 
222 reLayout makereLayout(const ParticleLayout& layout, const SliceStack& slices, double z_ref,
223  bool polarized)
224 {
225  const double layout_abundance = layout.totalAbundance();
226  ASSERT(layout_abundance > 0);
227  const double surface_density = layout.weightedParticleSurfaceDensity();
228 
229  std::vector<std::unique_ptr<const CoherentFFSum>> formfactors;
230  std::map<size_t, Admixtures> slice2admixtures;
231 
232  for (const auto& particle : layout.particles()) {
233 
234  std::vector<std::shared_ptr<const SumDWBA>> terms;
235 
236  for (const auto& subparticle : particle->decompose()) {
237  const auto slice_indices = SliceIndexSpan(*subparticle, slices, z_ref);
238  bool single_layer = (slice_indices.first == slice_indices.second);
239  double z0 = slices.at(0).zBottom();
240  for (size_t iSlice = slice_indices.first; iSlice < slice_indices.second + 1; ++iSlice) {
241  const Slice& slice = slices[iSlice];
242  double z_top_i = iSlice == 0 ? 0 : slice.zTop() - z0;
243  R3 translation(0.0, 0.0, z_ref - z_top_i);
244  z_ref = z_top_i; // subparticle now has coordinates relative to z_top_i
245  subparticle->translate(translation);
246  // if subparticle is contained in this layer, set limits to infinite:
247  ZLimits limits;
248  if (!single_layer) {
249  double z1 = slice.zTopOr0();
250  limits = {slice.zBottom() - z1, slice.zTop() - z1};
251  }
252  ParticleInSlice pis =
253  Compute::Slicing::createParticleInSlice(subparticle.get(), limits);
254  pis.particleSlice->setAmbientMaterial(slices.at(iSlice).material());
255 
256  std::unique_ptr<SumDWBA> computer;
257  if (slices.size() > 1)
258  computer = std::make_unique<SumDWBA>(*pis.particleSlice, iSlice);
259  else
260  computer = std::make_unique<SumDWBA>(*pis.particleSlice);
261  terms.emplace_back(computer.release());
262 
263  double factor = particle->abundance() / layout_abundance * surface_density;
264  if (double thickness = slice.thicknessOr0(); thickness > 0.0)
265  factor /= thickness;
266  for (auto& admixture : pis.admixtures)
267  admixture.fraction *= factor;
268 
269  slice2admixtures[iSlice].insert(slice2admixtures[iSlice].begin(),
270  pis.admixtures.begin(), pis.admixtures.end());
271  }
272  }
273 
274  formfactors.emplace_back(
275  std::make_unique<CoherentFFSum>(particle->abundance() / layout_abundance, terms));
276  }
277 
278  const auto* iff = layout.interferenceFunction();
279 
280  return reLayout(polarized, surface_density, std::move(formfactors),
281  iff ? iff->clone() : nullptr, std::move(slice2admixtures));
282 }
283 
284 //! Returns collection of all reLayout%s, for use in reSample constructor.
285 
286 std::vector<reLayout> collectLayouts(const MultiLayer& sample, const SliceStack& slices,
287  bool polarized)
288 {
289  std::vector<reLayout> result;
290 
291  double z_ref = -slices.front().zBottom();
292 
293  for (size_t i = 0; i < sample.numberOfLayers(); ++i) {
294  if (i > 1)
295  z_ref -= sample.layer(i - 1)->thickness();
296  for (const auto* layout : sample.layer(i)->layouts())
297  result.emplace_back(makereLayout(*layout, slices, z_ref, polarized));
298  }
299  return result;
300 }
301 
302 } // namespace
303 
304 
305 // ************************************************************************************************
306 // class implementation
307 // ************************************************************************************************
308 
309 reSample reSample::make(const MultiLayer& sample, const SimulationOptions& options,
310  bool forcePolarized)
311 {
312  const bool polarized = forcePolarized || sample.isMagnetic() || sample.externalField() != R3();
313 
314  // slices1: accounting for roughness, but not yet for particles
315  const SliceStack slices1 = slicify(sample, options).setBField(sample.externalField());
316 
317  std::vector<reLayout> layouts = collectLayouts(sample, slices1, polarized);
318 
319  const SliceStack refined_stack =
320  options.useAvgMaterials() ? refineStack(slices1, layouts) : slices1;
321 
322  return reSample(sample, polarized, std::move(layouts), refined_stack);
323 }
324 
325 reSample::reSample(const MultiLayer& sample, bool polarized, std::vector<reLayout>&& layouts,
326  const SliceStack& refined_stack)
327  : m_sample(sample)
328  , m_polarized(polarized)
329  , m_layouts(std::move(layouts))
330  , m_stack(refined_stack)
331 {
332 }
333 
334 reSample::~reSample() = default;
335 
337 {
338  return m_stack.size();
339 }
340 
342 {
343  return m_stack;
344 }
345 
346 const Slice& reSample::avgeSlice(size_t i) const
347 {
348  return m_stack.at(i);
349 }
350 
351 const std::vector<reLayout>& reSample::layouts() const
352 {
353  return m_layouts;
354 }
355 
356 double reSample::sliceTopZ(size_t i) const
357 {
358  return m_stack.at(i).zTop();
359 }
360 
361 double reSample::sliceBottomZ(size_t i) const
362 {
363  return m_stack.at(i).zBottom();
364 }
365 
367 {
368  return m_polarized;
369 }
370 
372 {
373  return containsMagneticMaterial() || m_sample.externalField() != R3{};
374 }
375 
377 {
378  for (const auto& slice : m_stack)
379  if (slice.topRoughness())
380  return true;
381  return false;
382 }
383 
384 Fluxes reSample::fluxesIn(const R3& k) const
385 {
386  if (m_polarized)
389 }
390 
391 Fluxes reSample::fluxesOut(const R3& k) const
392 {
393  if (m_polarized)
394  return Compute::SpecularMagnetic::fluxes(m_stack, -k, false);
396 }
397 
398 double reSample::crossCorrSpectralFun(const R3 kvec, size_t j, size_t k) const
399 {
400  const double xCorrLength = m_sample.crossCorrLength();
401  if (xCorrLength <= 0.0)
402  return 0.0;
403  const double z_j = sliceBottomZ(j);
404  const double z_k = sliceBottomZ(k);
405  const LayerRoughness* rough_j = m_stack.at(j + 1).topRoughness();
406  const LayerRoughness* rough_k = m_stack.at(k + 1).topRoughness();
407  if (!rough_j || !rough_k)
408  return 0.0;
409  const double sigma_j = rough_j->sigma();
410  const double sigma_k = rough_k->sigma();
411  if (sigma_j <= 0 || sigma_k <= 0)
412  return 0.0;
413  return 0.5
414  * ((sigma_k / sigma_j) * rough_j->spectralFunction(kvec)
415  + (sigma_j / sigma_k) * rough_k->spectralFunction(kvec))
416  * std::exp(-1 * std::abs(z_j - z_k) / xCorrLength);
417 }
Defines the macro ASSERT.
#define ASSERT(condition)
Definition: Assert.h:45
MATERIAL_TYPES
Defines class CoherentFFSum.
Defines namespace Compute::SpecularMagnetic.
Defines namespace Compute::SpecularScalar.
std::vector< std::unique_ptr< const IFlux > > Fluxes
Defines and implements class IFlux.
Defines and implements the interface class IInterference.
Defines interface IParticle.
Defines class LayerRoughness.
Defines class Layer.
Defines class MaterialBySLDImpl.
Declares functions in namespace MaterialUtils.
Defines class MultiLayer.
Defines namespace SampleUtils::Multilayer.
Defines struct ParticleInSlice; there are no member functions to implement.
Defines class ParticleLayout.
Defines function Compute::Slicing::particleRegions.
Defines class reLayout.
Defines class reSample.
Defines class RefractiveMaterialImpl.
Defines class SimulationOptions.
Defines function Compute::Slicing::sliceFormFactor.
Defines class SumDWBA.
The list of material admixtures to a slice.
Definition: Admixtures.h:39
Abstract base class for Particle, ParticleComposition, ParticleCoreShell, MesoCrystal....
Definition: IParticle.h:31
double abundance() const
Definition: IParticle.h:38
virtual std::vector< std::unique_ptr< IParticle > > decompose() const
Decompose in constituent IParticle objects.
Definition: IParticle.cpp:52
bool isMagnetic() const
Returns true if there is any magnetic material in this ISampleNode.
Definition: ISampleNode.cpp:39
A roughness of interface between two layers.
double spectralFunction(R3 kvec) const
Returns power spectral density of the surface roughness.
double sigma() const
Returns rms of roughness.
A layer in a MultiLayer sample.
Definition: Layer.h:26
double thickness() const
Definition: Layer.h:41
std::vector< const ParticleLayout * > layouts() const
Definition: Layer.cpp:54
unsigned int numberOfSlices() const
Definition: Layer.h:48
const Material * material() const override
Returns nullptr, unless overwritten to return a specific material.
Definition: Layer.h:39
A wrapper for underlying material implementation.
Definition: Material.h:35
R3 magnetization() const
Get the magnetization (in A/m)
Definition: Material.cpp:78
std::string materialName() const
Returns the name of material.
Definition: Material.cpp:68
complex_t materialData() const
Returns delta + i beta.
Definition: Material.cpp:83
Our sample model: a stack of layers one below the other.
Definition: MultiLayer.h:43
const Layer * layer(size_t i_layer) const
Returns layer with given index.
Definition: MultiLayer.cpp:91
R3 externalField() const
Returns the external field applied to the sample (units: A/m)
Definition: MultiLayer.h:71
RoughnessModel roughnessModel() const
Definition: MultiLayer.h:67
size_t numberOfLayers() const
Definition: MultiLayer.h:51
double crossCorrLength() const
Returns cross correlation length of roughnesses between interfaces.
Definition: MultiLayer.h:69
Decorator class that adds particles to ISampleNode objects.
double totalAbundance() const
const IInterference * interferenceFunction() const
std::vector< const IParticle * > particles() const
double weightedParticleSurfaceDensity() const
Collect the different options for simulation.SimulationOptions.
bool useAvgMaterials() const
A stack of Slices.
Definition: SliceStack.h:38
SliceStack setBField(const R3 &externalField)
Definition: SliceStack.cpp:57
void addTopSlice(double zbottom, const Material &material)
Definition: SliceStack.cpp:25
void addNSlices(size_t n, double thickness, const Material &material, const LayerRoughness *roughness=nullptr)
Adds n times the same slice to the stack.
Definition: SliceStack.cpp:46
void addSlice(double thickness, const Material &material, const LayerRoughness *roughness=nullptr)
Definition: SliceStack.cpp:30
Data structure containing the data of a single slice, for calculating the Fresnel coefficients.
Definition: Slice.h:31
double zBottom() const
Definition: Slice.cpp:56
double thicknessOr0() const
Definition: Slice.cpp:71
double zTop() const
Definition: Slice.cpp:61
const LayerRoughness * topRoughness() const
Definition: Slice.cpp:76
double zTopOr0() const
Definition: Slice.cpp:66
An interval. Limits are of type double, and may be infinite. Used for the z-coordinate,...
Definition: ZLimits.h:32
double zBottom() const
Definition: ZLimits.h:39
bool isFinite() const
Definition: ZLimits.cpp:32
double zTop() const
Definition: ZLimits.h:40
Data structure that contains preprocessed data for a single layout.
Definition: ReLayout.h:36
Data structure that contains all the necessary data for scattering calculations.
Definition: ReSample.h:41
const Slice & avgeSlice(size_t i) const
Definition: ReSample.cpp:346
size_t numberOfSlices() const
Definition: ReSample.cpp:336
double sliceTopZ(size_t i) const
Definition: ReSample.cpp:356
static reSample make(const MultiLayer &sample, const SimulationOptions &options, bool forcePolarized=false)
Factory method that wraps the private constructor.
Definition: ReSample.cpp:309
double crossCorrSpectralFun(R3 kvec, size_t j, size_t k) const
Fourier transform of the correlation function of roughnesses between the interfaces.
Definition: ReSample.cpp:398
bool hasRoughness() const
Definition: ReSample.cpp:376
bool polarizing() const
Contains magnetic material, or nonzero magnetic field.
Definition: ReSample.cpp:371
const MultiLayer & m_sample
Definition: ReSample.h:69
std::vector< reLayout > m_layouts
Definition: ReSample.h:71
const bool m_polarized
Definition: ReSample.h:70
const std::vector< reLayout > & layouts() const
Definition: ReSample.cpp:351
bool containsMagneticMaterial() const
Definition: ReSample.cpp:366
const SliceStack m_stack
Definition: ReSample.h:72
Fluxes fluxesIn(const R3 &k) const
Definition: ReSample.cpp:384
double sliceBottomZ(size_t i) const
Definition: ReSample.cpp:361
Fluxes fluxesOut(const R3 &k) const
Definition: ReSample.cpp:391
reSample(const reSample &)=delete
const MultiLayer & sample() const
Definition: ReSample.h:58
const SliceStack & averageSlices() const
Definition: ReSample.cpp:341
Material RefractiveMaterial(const std::string &name, complex_t refractive_index, R3 magnetization)
Material MaterialBySLD()
#define N
Definition: mixmax.h:31
ParticleInSlice createParticleInSlice(const IParticle *particle, const ZLimits &)
Definition: Slicer.cpp:251
ZLimits zSpan(const IParticle *particle)
Definition: Slicer.cpp:351
std::vector< ZLimits > particleRegions(const MultiLayer &sample, bool use_slicing)
Calculate z-admixtures occupied by particles.
Fluxes fluxes(const SliceStack &slices, const R3 &k, bool forward)
Computes refraction angle reflection/transmission coefficients for given sliced sample and wavevector...
Fluxes fluxes(const SliceStack &slices, const R3 &k)
Computes refraction angles and transmission/reflection coefficients for given coherent wave propagati...
MATERIAL_TYPES checkMaterialTypes(const std::vector< const Material * > &materials)
Checks if all non-default materials in materials are of the same type and returns this type....
const LayerRoughness * LayerTopRoughness(const MultiLayer &sample, size_t i)
Returns top roughness of layer.
An admixture to a slice, consisting of a certain volume fraction of a homogeneous material.
Definition: Admixtures.h:30
Struct that contains information on a sliced particle. This information is needed for evaluating the ...
Admixtures admixtures
std::unique_ptr< IReParticle > particleSlice