From bca3da13985837a9a3deca6b8eefa1021d25d16c Mon Sep 17 00:00:00 2001
From: "Joachim Wuttke (h)" <j.wuttke@fz-juelich.de>
Date: Thu, 3 Dec 2020 12:21:25 +0100
Subject: [PATCH] start analysis of parameter distribution

---
 .../architecture/ParameterDistribution.md     | 117 ++++++++++++++++++
 devtools/architecture/Scattering.md           |   3 +-
 2 files changed, 119 insertions(+), 1 deletion(-)
 create mode 100644 devtools/architecture/ParameterDistribution.md

diff --git a/devtools/architecture/ParameterDistribution.md b/devtools/architecture/ParameterDistribution.md
new file mode 100644
index 00000000000..dd175699a18
--- /dev/null
+++ b/devtools/architecture/ParameterDistribution.md
@@ -0,0 +1,117 @@
+## Parameter distributions
+
+Analysis of BornAgain develop, per 3dec20.
+
+It seems there are two different ways for handling parameter distributions,
+the one specifically for particle distributions, the other one more generic.
+
+### Particle distribution
+
+#### Typical use
+
+Typical use of parameter distributions is described in
+`Examples/Python/sim01_Particles/CylindersWithSizeDistribution.py`:
+```
+def get_sample():
+    # Define materials and form factor ...
+
+    # Define particles
+    particle = ba.Particle(material_Particle, ff)
+
+    # Define particles with parameter following a distribution
+    distr_1 = ba.DistributionGaussian(5.0*nm, 1.0*nm)
+    par_distr_1 = ba.ParameterDistribution(
+        "/Particle/Cylinder/Radius", distr_1, 100, 2.0)
+    particle_distrib = ba.ParticleDistribution(particle, par_distr_1)
+
+    # Define particle layouts
+    layout = ba.ParticleLayout()
+    layout.addParticle(particle_distrib, 1.0)
+
+    # Define layers
+    layer = ba.Layer(material_Vacuum)
+    layer.addLayout(layout)
+
+    # Define sample
+    sample = ba.MultiLayer()
+    sample.addLayer(layer)
+
+    return sample
+
+
+def get_simulation():
+    simulation = ba.GISASSimulation()
+    simulation.setDetectorParameters(200, 0.0*deg, 2.0*deg, 200, 0.0*deg, 2.0*deg)
+    simulation.setBeamParameters(1.0*angstrom, 0.2*deg, 0.0*deg)
+    return simulation
+
+
+def run_simulation():
+    simulation = get_simulation()
+    simulation.setSample(get_sample())
+    simulation.runSimulation()
+    return simulation.result()
+```
+
+#### What goes on behind the scenes?
+
+Class hierarchy:
+```
+IParametricComponent
+- INode
+  - ISampleNode
+    - IAbstractParticle
+      - ParticleDistribution (final)
+      - IParticle
+        - Particle (final)
+        - ParticleCoreShell (final)
+        ... (all final)
+- ISampleBuilder
+- DistributionHandler (final)
+- ParameterDistribution (final)
+```
+
+As we see from the `CylindersWithSizeDistribution` example,
+a `ParticleDistribution` is bound to a `ParticleLayout`.
+The drawing of `IParticle`s from the distribution happens here:
+
+```
+SafePointerVector<IParticle> ParticleLayout::particles() const {
+    SafePointerVector<IParticle> particle_vector;
+    for (const auto* particle : m_particles) {
+        if (const auto* p_part_distr = dynamic_cast<const ParticleDistribution*>(particle)) {
+            SafePointerVector<IParticle> generated_particles = p_part_distr->generateParticles();
+            for (const IParticle* particle : generated_particles)
+                particle_vector.push_back(particle->clone());
+        } else if (const auto* p_iparticle = dynamic_cast<const IParticle*>(particle)) {
+            particle_vector.push_back(p_iparticle->clone());
+        }
+    }
+    return particle_vector;
+}
+```
+
+### Generic distribution handler
+
+This mechanism acts almost on top of the simulation model hierarchy.
+
+`ISimulation` has member `DistributionHandler m_distribution_handler`.
+It is modified through `ISimulation::addParameterDistribution`.
+This function is called explicitly by some standard simulations and Python scripts
+(e.g. `sim03_Structures/Interference2DLatticeSumOfRotated.py`),
+but not in the above simple example.
+
+
+```
+void ISimulation::runSimulation() {
+    ...
+    size_t param_combinations = m_distribution_handler.getTotalNumberOfSamples();
+    std::unique_ptr<ParameterPool> param_pool(createParameterTree());
+    for (size_t index = 0; index < param_combinations; ++index) {
+        double weight = m_distribution_handler.setParameterValues(param_pool.get(), index);
+        runSingleSimulation(batch_start, batch_size, weight);
+    }
+    m_distribution_handler.setParameterToMeans(param_pool.get());
+    ...
+}
+```
diff --git a/devtools/architecture/Scattering.md b/devtools/architecture/Scattering.md
index 93b1972538c..c352a07d71a 100644
--- a/devtools/architecture/Scattering.md
+++ b/devtools/architecture/Scattering.md
@@ -1,6 +1,7 @@
 ## How does BornAgain compute scattering?
 
-Code analysis per 18nov, after merge of https://github.com/scgmlz/BornAgain/pull/1105.
+Analysis of BornAgain develop, per 18nov,
+after merge of https://github.com/scgmlz/BornAgain/pull/1105.
 
 ### Simulation computes an incoherent sum
 
-- 
GitLab