From ac68d47d6298488f9b8529d0d4cdaa7beffd16a4 Mon Sep 17 00:00:00 2001
From: "Joachim Wuttke (o)" <j.wuttke@fz-juelich.de>
Date: Tue, 8 Mar 2022 17:36:21 +0100
Subject: [PATCH] Explain abbreviated vs expanded script

---
 Examples/specular/AlternatingLayers1.py |  52 ++++++++++++
 Tests/Examples/CMakeLists.txt           |  11 ++-
 Tests/Examples/PyPersistence.py.in      |   5 +-
 hugo/content/py/start/modify-script.md  |  59 +++++++++++++
 hugo/content/py/start/syntax.md         | 105 ++++++------------------
 5 files changed, 148 insertions(+), 84 deletions(-)
 create mode 100755 Examples/specular/AlternatingLayers1.py
 create mode 100644 hugo/content/py/start/modify-script.md

diff --git a/Examples/specular/AlternatingLayers1.py b/Examples/specular/AlternatingLayers1.py
new file mode 100755
index 00000000000..48df8feec18
--- /dev/null
+++ b/Examples/specular/AlternatingLayers1.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+"""
+Basic example of specular reflectometry simulation with BornAgain.
+The sample consists of 20 alternating Ti and Ni layers.
+Explicit variant without using std_samples and std_simulation.
+"""
+
+import bornagain as ba
+from bornagain import deg, angstrom
+
+def get_sample():
+    """
+    Sample consisting of 20 alternating Ti and Ni layers.
+    """
+
+    # Define materials
+    m_ambient = ba.MaterialBySLD("Vacuum", 0, 0)
+    m_ti = ba.MaterialBySLD("Ti", -1.9493e-06, 0)
+    m_ni = ba.MaterialBySLD("Ni", 9.4245e-06, 0)
+    m_substrate = ba.MaterialBySLD("SiSubstrate", 2.0704e-06, 0)
+
+    # Define layers
+    ambient_layer = ba.Layer(m_ambient)
+    ti_layer = ba.Layer(m_ti, 30*angstrom)
+    ni_layer = ba.Layer(m_ni, 70*angstrom)
+    substrate_layer = ba.Layer(m_substrate)
+
+    # Define sample
+    sample = ba.MultiLayer()
+    sample.addLayer(ambient_layer)
+    for _ in range(10):
+        sample.addLayer(ti_layer)
+        sample.addLayer(ni_layer)
+    sample.addLayer(substrate_layer)
+
+    return sample
+
+def get_simulation(sample, scan_size=500):
+    """
+    A standard specular simulation setup.
+    """
+    simulation = ba.SpecularSimulation()
+    scan = ba.AlphaScan(1.54*angstrom, scan_size, 0, 2*deg)
+    simulation.setScan(scan)
+    simulation.setSample(sample)
+    return simulation
+
+if __name__ == '__main__':
+    from bornagain import ba_plot
+    sample = get_sample()
+    simulation = get_simulation(sample)
+    ba_plot.run_and_plot(simulation)
diff --git a/Tests/Examples/CMakeLists.txt b/Tests/Examples/CMakeLists.txt
index 60badd7a436..f4215683a7a 100644
--- a/Tests/Examples/CMakeLists.txt
+++ b/Tests/Examples/CMakeLists.txt
@@ -39,11 +39,12 @@ function(run_noplot example)
         ${Python3_EXECUTABLE} ${script_path} ${TEST_OUTPUT_DIR_PY_EXAMPLES})
 endfunction()
 
-# Python persistence test: run modified example, and compare with reference data.
-function(test_example example tolerance)
+# Python equality test: run modified example, and compare with reference data of another example
+function(test_equality example reference tolerance)
     set(script_path ${EXAMPLES_DIR}/${example}.py)
     get_filename_component(EXAMPLE_NAME ${script_path} NAME_WE)
     get_filename_component(EXAMPLE_DIR ${script_path} DIRECTORY)
+    get_filename_component(REFERENCE_NAME ${reference} NAME_WE)
 
     set(test_name Example.persist.${EXAMPLE_NAME})
     set(PYPERSIST_TOLERANCE ${tolerance})
@@ -54,6 +55,11 @@ function(test_example example tolerance)
     add_test(NAME ${test_name} COMMAND ${Python3_EXECUTABLE} -B ${example_mod})
 endfunction()
 
+# Python persistence test: run modified example, and compare with reference data.
+function(test_example example tolerance)
+    test_equality(${example} ${example} ${tolerance})
+endfunction()
+
 ####################################################################################################
 #  Persistence tests
 ####################################################################################################
@@ -100,6 +106,7 @@ test_example(scatter2d/TriangularRipple 2e-10)
 # test_example(scatter2d/TwoTypesOfCylindersWithSizeDistribution 2e-10)
 
 test_example(specular/AlternatingLayers 2e-10)
+test_equality(specular/AlternatingLayers1 specular/AlternatingLayers 2e-10)
 test_example(specular/BeamAngularDivergence 2e-10)
 test_example(specular/BeamFullDivergence 2e-10)
 test_example(specular/TOFRWithResolution 2e-10)
diff --git a/Tests/Examples/PyPersistence.py.in b/Tests/Examples/PyPersistence.py.in
index edbefe43a7a..a0834b27ff5 100644
--- a/Tests/Examples/PyPersistence.py.in
+++ b/Tests/Examples/PyPersistence.py.in
@@ -14,6 +14,7 @@ import bornagain as ba
 REFERENCE_DIR = "@TEST_REFERENCE_DIR_EXAMPLES_MINI@"
 EXAMPLE_DIR = "@EXAMPLE_DIR@"
 EXAMPLE_NAME = "@EXAMPLE_NAME@"
+REFERENCE_NAME = "@REFERENCE_NAME@"
 OUTPUT_DIR = "@TEST_OUTPUT_DIR_PY_PERSIST@"
 TOLERANCE = @PYPERSIST_TOLERANCE@
 
@@ -147,9 +148,9 @@ def process_example():
     nfailures = 0
     if type(result) is dict:
         for dict_key, subresult in result.items():
-            nfailures += check_result(subresult, EXAMPLE_NAME + "." + str(dict_key))
+            nfailures += check_result(subresult, REFERENCE_NAME + "." + str(dict_key))
     else:
-        nfailures += check_result(result, EXAMPLE_NAME)
+        nfailures += check_result(result, REFERENCE_NAME)
 
     return nfailures
 
diff --git a/hugo/content/py/start/modify-script.md b/hugo/content/py/start/modify-script.md
new file mode 100644
index 00000000000..51958068850
--- /dev/null
+++ b/hugo/content/py/start/modify-script.md
@@ -0,0 +1,59 @@
++++
+title = "Modify the script"
+weight = 40
++++
+
+## Expanded simulation script
+
+As a first step towards writing sample and simulation specifications
+of your own, let us expand the simulation script
+[AlternatingLayers.py]({{% ref-src "Examples/specular/AlternatingLayers.py" %}})
+from the preceding pages.
+Instead of the shorthand calls to modules
+[std_samples]({{% ref-src "Wrap/Python/std_samples.py" %}}) and
+[std_simulations]({{% ref-src "Wrap/Python/std_simulations.py" %}}),
+we provide explicit code for the functions `get_sample` and `get_simulation`:
+{{< highlightfile file="Examples/specular/AlternatingLayers1.py">}}
+<p>
+
+### Sample
+
+`get_sample` is a function without arguments.
+It returns an object of type [MultiLayer]({{% ref-api "classMultiLayer" %}}).
+
+The return statement is preceded by three stances.
+Each stance starts with a comment line,
+{{< highlight python >}}
+    # comment extends from hash character to end of line
+{{< /highlight >}}
+
+BornAgain functions that start with a capital letter,
+like `MaterialBySLD` or `Layer` are _constructors_ or
+constructor-like global functions.
+They return new _objects_. An object is an instance of a _class_.
+The function `MaterialBySLD` instantiates an object of type
+[Material]({{% ref-api "classMaterial" %}})
+the function `Layer` an object of type
+[Layer]({{% ref-api "classLayer" %}}).
+
+Function like `addLayer` is a member function of class
+[MultiLayer]({{% ref-api "classMultiLayer" %}}).
+This can be seen from the two lines
+{{< highlight python >}}
+    sample = ba.MultiLayer()
+    sample.addLayer(ambient_layer)
+{{< /highlight >}}
+where `sample` is created as a new instance of class `MultiLayer`.
+
+### Simulation
+
+`get_simulation(sample, scan_size=500)` is a function with one
+required argument (`sample`) and one optional keyword argument
+(`scan_size`). If the function is called with only one argument,
+then `scan_size` is assigned the default value 500.
+
+`angstrom` and `deg` are numeric constants. They are used to
+convert physical quantities to internal units nanometer and radian.
+
+The function returns an object of type
+[SpecularSimulation]({{% ref-api "classSpecularSimulation" %}}).
diff --git a/hugo/content/py/start/syntax.md b/hugo/content/py/start/syntax.md
index caf150481ae..5eba7b1516c 100644
--- a/hugo/content/py/start/syntax.md
+++ b/hugo/content/py/start/syntax.md
@@ -1,5 +1,5 @@
 +++
-title = "Syntax"
+title = "Understand the syntax"
 weight = 30
 +++
 
@@ -14,9 +14,7 @@ For easy reference, here again the full script:
 {{< highlightfile file="Examples/specular/AlternatingLayers.py">}}
 <p>
 
-We shall now explain the code section by section.
-
-### Front matter
+We shall now explain the code.
 
 The [shebang](https://en.wikipedia.org/wiki/Shebang_(Unix)) line
 {{< highlight python >}}
@@ -34,92 +32,39 @@ Text, text, text
 {{< /highlight >}}
 are comments.
 
-The line
-{{< highlight python >}}
-import bornagain as ba
-{{< /highlight >}}
-imports the Python module `bornagain` (small caps, see chapter on
- [how to find]({{% ref-py "start/find.md" %}}) BornAgain),
-and assigns it the alias `ba` for brevity.
-Classes and functions from the BornAgain API are
-now available with the prefix `ba.`.
-
-The line
-{{< highlight python >}}
-from bornagain import deg, angstrom
-{{< /highlight >}}
-makes a few frequently used symbols, namely the unit multipliers for
-degree and angstrom, available without prefix.
-
-### Sample
-
-`get_sample` is a function without arguments.
-It returns an object of type [MultiLayer]({{% ref-api "classMultiLayer" %}}).
-
-The return statement is preceded by three stances.
-Each stance starts with a comment line,
+The block
 {{< highlight python >}}
-    # comment extends from hash character to end of line
+def get_sample():
+    from bornagain import std_samples
+    return std_samples.alternating_layers()
 {{< /highlight >}}
+defines the function `get_sample` that
+imports the BornAgain submodule [std_samples]({{% ref-src "Wrap/Python/std_samples.py" %}}),
+and returns the alternating layers sample model.
 
-BornAgain functions that start with a capital letter,
-like `MaterialBySLD` or `Layer` are _constructors_ or
-constructor-like global functions.
-They return new _objects_. An object is an instance of a _class_.
-The function `MaterialBySLD` instantiates an object of type
-[Material]({{% ref-api "classMaterial" %}})
-the function `Layer` an object of type
-[Layer]({{% ref-api "classLayer" %}}).
-
-Function like `addLayer` is a member function of class
-[MultiLayer]({{% ref-api "classMultiLayer" %}}).
-This can be seen from the two lines
+Similarly, the next block
 {{< highlight python >}}
-    sample = ba.MultiLayer()
-    sample.addLayer(ambient_layer)
+def get_simulation(sample, scan_size=500):
+    from bornagain import std_simulations
+    return std_simulations.specular(sample, scan_size)
 {{< /highlight >}}
-where `sample` is created as a new instance of class `MultiLayer`.
-
-### Simulation
-
-`get_simulation(sample, scan_size=500)` is a function with one
-required argument (`sample`) and one optional keyword argument
-(`scan_size`). If the function is called with only one argument,
-then `scan_size` is assigned the default value 500.
-
-`angstrom` and `deg` are numeric constants. They are used to
-convert physical quantities to internal units nanometer and radian.
-
-The function returns an object of type
-[SpecularSimulation]({{% ref-api "classSpecularSimulation" %}}).
+defines the function `get_simulation` that)
+imports the BornAgain submodule [std_simulations]({{% ref-src "Wrap/Python/std_simulations.py" %}}),
+and returns a standard setup to simulate a specular reflectivity scan.
 
-### Main program
-
-The line
+The clause
 {{< highlight python >}}
 if __name__ == '__main__':
 {{< /highlight >}}
-is standard Python idiom.
-It ensures that the following is executed if and only if this script
-is executed directly, as a _main program_,
-but not if this script is included as a submodule in some other code.
-
-The lines
-{{< highlight python >}}
-    sample = get_sample()
-    simulation = get_simulation(sample)
-{{< /highlight >}}
-call the two functions defined above
-in order to obtain the sample model
-and then the full simulation setup, comprising sample and instrument model.
+ensures that the following statements are only executed
+if the script is called directly, as a "main" program.
+This precaution is required by the GUI, where scripts can be imported without
+being immediately executed.
 
-Finally, the single line
+In the main block, the statement
 {{< highlight python >}}
     ba_plot.run_and_plot(simulation)
 {{< /highlight >}}
-runs the simulation and plots the result, using MatPlotLib.
-It uses the module
-[ba_plot]({{% ref-src "Wrap/Python/ba_plot.py" %}}),
-imported a few lines before.
-See section [Plot and export]({{% ref-py "result/_index.md" %}})
-for other ways of running simulations and plotting or exporting results.
+runs the previously defined simulation, and calls MatPlotLib to display the results.
+The function `run_and_plot` is implemented in the module
+[ba_plot]({{% ref-src "Wrap/Python/ba_plot.py" %}}).
-- 
GitLab