Quick Start

In this section a list of examples is presented, covering the most interesting features of the module.

Super quick example

In this example the basic functionality PyScatman is shown, in a step-by-step manner.

So, as a first step, let’s open the Python iterpreter and import the scatman module:

Python 3.8.2 (default, Apr 27 2020, 15:53:34)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import scatman

Then, the first thing to set is the experiment, by using the following function

scatman.set_experiment(wavelength=45., angle=30, resolution=512);

This function sets the relevant parameters for the experiment simulation. The first is the radiation wavelength, the second is the maximum scattering angle and the latter is the size in pixels of the detector. For further information see the documentation.

Once that the experiment has been set up, it is necessary to define the detector that will be used. In this case, we decide to use the MSFT detector, which is actually an abstraction of a detector that provides the exact result of the MSFT.

mydetector = scatman.Detectors.MSFT()

Once that the detector is defined, it is time to define the shape of the sample on which we want to perform the MSFT. In this case, we go for an ellipsoid, where the first three quantities provided to the constructor are the lengths of its three main axes, while the other parameters define the orientation in space.

myshape = scatman.Shapes.Ellipsoid(400,250,250, latitude=60, longitude=40, rotation=0)

Now the time to perform the actual simulation has come. The data acquisition is performed by calling the acquire method of the detector, and providing a shape:

mypattern = mydetector.acquire(myshape)

That’s all! If you want to have a look at the result, you can directly plot the mypattern object via, for example, matplotlib:

import matplotlib.pyplot as plt
import numpy as np

plt.imshow(np.log(mypattern)) #plot in logarithmic scale
plt.show()
_images/quick_1.png

Less quick example

Here, instead, we go through the module functionalities in a more detailed way.

First, once PyScatman is imported, it is possible to have a look at the default module settings:

>>> import scatman
>>> scatman.info()
*Experiment:
    - wavelength:              60.0
    - angle:                   30.0°
    - resolution:              1024x1024
*MSFT:
    - rendering oversampling:  3
    - fourier oversampling:    2.0
*Hardware:
    - Available threads:       8
    - Selected threads:        8
    - GPU computing not enabled

In this case, the scatman configuration is set up such that the simulation is performed with a 60 nm wavelength, at a maximum scattering angle of 30 degrees, and with a pixel resolution of 1024x1024. For the MSFT settings, see the documentation of scatman.info().

Note

The wavelength value is actually dimensionless in the module. Due to the fact that the MSFT computation depends only on the ratio between the sample sizes and the wavelength, the user only needs to be consistent with the dimensions. If, for example, the wavelength refers to nanometers, all the other lengths has to be intended in nanometers and must be expressed in a consistent way.

The hardware configuration

Concerning the hardware configuration, the scatman uses all available CPU threads by default. In this case, the module wasn’t compiled with GPU support (and/or the PC doesn’t have GPUs installed). It is possible to modify the number of threads, by calling:

>>> scatman.set_threads(2)
>>> scatman.info()
*Experiment:
    - wavelength:              60.0
    - angle:                   30.0°
    - resolution:              1024x1024
    - photon density:          1.00e+06
*MSFT:
    - rendering oversampling:  3
    - fourier oversampling:    2.0
*Hardware:
    - Available threads:       8
    - Selected threads:        2
    - GPU computing not enabled

If we move to a GPU machine, and the code was compiled with GPU support, then a possible output of the info() function could be the following:

>>> scatman.info()
*Experiment:
    - wavelength:              60.0
    - angle:                   30.0°
    - resolution:              1024x1024
    - photon density:          1.00e+06
*MSFT:
    - rendering oversampling:  3
    - fourier oversampling:    2.0
*Hardware:
    - Available threads:       32
    - Selected threads:        32
    - Available GPUs:          GPU-0 GPU-1 GPU-2 GPU-3 GPU-4 GPU-5 GPU-6
    - Selected GPUs:           GPU-0

The module lists all the available GPUs by their ID. The default one is ID 0. It is possible to change the gpu by calling the appropriate function:

>>> scatman.set_gpu_ids(2)
>>> scatman.info()
*Experiment:
    - wavelength:              60.0
    - angle:                   30.0°
    - resolution:              1024x1024
    - photon density:          1.00e+06
*MSFT:
    - rendering oversampling:  3
    - fourier oversampling:    2.0
*Hardware:
    - Available threads:       32
    - Selected threads:        32
    - Available GPUs:          GPU-0 GPU-1 GPU-2 GPU-3 GPU-4 GPU-5 GPU-6
    - Selected GPUs:           GPU-2

The scatman module is, moreover, capable of exploiting multi-GPU systems. To select more than one GPU, it is required to call the set_gpu_ids([...]) function, providing a list of GPU IDs to be used:

>>> scatman.set_gpu_ids([1,2,3,6])
>>> scatman.info()
*Experiment:
    - wavelength:              60.0
    - angle:                   30.0°
    - resolution:              1024x1024
    - photon density:          1.00e+06
*MSFT:
    - rendering oversampling:  3
    - fourier oversampling:    2.0
*Hardware:
    - Available threads:       32
    - Selected threads:        32
    - Available GPUs:          GPU-0 GPU-1 GPU-2 GPU-3 GPU-4 GPU-5 GPU-6
    - Selected GPUs:           GPU-1 GPU-2 GPU-3 GPU-6

Once that the hardware configuration is ready, the user can decide which hardware will be used for the computation, by calling the following functions:

>>> scatman.set_verbose(1)      # Not necessary, used just to print log messages

>>> scatman.use_cpu()           # Move the MSFT computation to the CPU (default)
Switching computation on CPU.

>>> scatman.use_gpu()           # Move the MSFT computation on the GPU(s)
Switching computation on GPU.

Finally, if the user tries to use the GPUs, or set GPU-related settings, and CUDA support was not enabled at compile time, the module will warn about it and the functions won’t have any effect:

>>> scatman.set_gpu(3)
WARNING: GPUs not enabled. Please recompile module with option -DWITH_CUDA=On

>>> scatman.use_gpu()
WARNING: Cannot use GPUs. Please recompile module with option -DWITH_CUDA=On

Simulation of a dataset

Even if, especially when GPU computing is enables, the speedup of a single pattern simulation is relevant, the full potential of the module is exploited when the simulation is performed on a set of samples (let’s call it dataset).

When more than one simulation has to be performed, i.e. when the user wants to simulate a dataset, the following happens:

  • When the computation is on the CPU, instead of splitting one simulation to be performed on different CPU cores, each CPU core takes care of one full simulation. In this case, the parallelism is more coarse grained, and the parallel efficiency of the code tends to increase
  • When the computation is on the GPU, multiple MSFT computations can overlap, such that the GPU can compute one simulation while transferring the data of an other one
  • When the computation is on multiple GPUs, the single simulations are dispatched, providing an almost perfect scaling. When only a single pattern simulation is performed, only one GPU is used, even in the case that more than one were selected.

For this reason, special functions are implemented to directly work on datasets, instead of single samples.

In the first quick start example, a shape, that identifies a sample, was defined, and then passed to the acquire method of the detector, that is:

mydetector = scatman.Detectors.MSFT()
myshape = scatman.Shapes.Ellipsoid(400,250,250, latitude=60, longitude=40, rotation=0)
mypattern = mydetector.acquire(myshape)

Simulating a dataset means that, instead of using just one shape, the simulation acts on a list of shapes:

mydetector = scatman.Detectors.MSFT()

myshapes = []

myshapes.append(scatman.Shapes.Ellipsoid(400,250,250, latitude=60, longitude=40, rotation=0))
myshapes.append(scatman.Shapes.Ellipsoid(300,350,350, latitude=150, longitude=90, rotation=0))
myshapes.append(scatman.Shapes.Dumbbell(400,300,250,250, 150, latitude=20, longitude=170, rotation=30))

mypatterns = mydetector.acquire_dataset(myshapes)
type(mypatterns)
    <class 'list'>

As it can be seen in the example, if a list of shapes is provided to the acquire_dataset method, then a list of patterns is returned.

If the verbosity of the module is set to 1, additional information are displayed, i.e. the level of completion (continuously updated) and the execution time of the routine:

>>> scatman.set_verbose(1)
>>> mypatterns = mydetector.acquire_dataset(myshapes)

100.0%
Total time:       11.718 s
Time per pattern: 3905.8 ms
>>>