ottBeams Example : Creating and visualising beams

This example demonstrates how to create and use different kinds of optical tweezers toolbox (OTT) beams. The code for this example can be found in the examples directory, if OTT has been added to the path, run:

open examples/ottBeams.m

A live script is also available for this example:

open examples/liveScripts/beams.mlx

This example assumes the toolbox has been added to the Matlab path, for information, see adding-ott-to-matlabs-path.

Creating a beam

OTT provides two main methods for creating beams: using the classes in the ott.beam package and using the classes in the ott.bsc package. The ott.beam package is intended to provide a easy to use, high-level interface for interacting with beams. Internally, the ott.beam classes use the functions declared in ott.bsc, and in some cases, using the ott.bsc classes directly can give better run-times. However, for most use cases, the ott.beam classes should be adequate. In this example, we will focus on the ott.beam package.

The ott.beam package provides several classes representing common beams, for example, to create a Gaussian beam, call:

beam = ott.beam.Gaussian();

This create the default Gaussian beam (see the ott.beam.Gaussian documentation for the exact specification). We can change the beam properties such as the medium speed and power using the beam’s properties or methods, for example:

beam.power = 0.1;           % Set power [Watts]
beam.speed = 3e8/1.33;      % Set speed [meters/second]

Some properties, such as wavelength, need to be set using beam methods. Beams are not handle classes, as such, it is important to store the resulting beam object. The following sets the wavelength by keeping the speed fixed:

beam = beam.setWavelength(532e-9, 'fixedSpeed'); % Set wavelength [meters]

Alternatively, we can create the beam with the desired properties at the start by passing a list of named arguments to the constructor:

beam = ott.beam.Gaussian('power', 0.1, ...
    'index_medium', 1.33, 'omega', 2*pi*3e8/532e-9);

Many of the ott.beam classes provide alternative construction methods for convenience, for example, the Gaussian and Laguerre–Gaussian beams provide a FromNa method that accepts a numerical aperture value as the first argument.

To view our beam, we can use one of the beam visualisation methods, for example, the following creates a XY slice through the beam focus:

beam.visNearfield();

This creates a new plot in the current figure window (or creates a new figure if required). Alternatively, we can request the resulting image data and plot the results ourselves (this is usually done in conjunction with specifying the image range):

xrange = linspace(-1, 1, 80)*1e-6;   % Range in meters
yrange = xrange;
im = beam.visNearfield('range', {xrange, yrange});
figure();
contour(xrange, yrange, im);
xlabel('X Position [m]');
ylabel('Y Position [m]');

Translations and rotations

Beams have a position and rotation property. These properties are applied to the beam whenever the beam is used (for example, when a visualisation method is called or when getData is called).

The position property is a 3x1 numeric vector. To shift the beam by 1 wavelength in the x direction, we can directly set the position property:

beam.position = [1;0;0]*beam.wavelength;

Alternatively, we can use the ott.utils.TranslateHelper.translateXyz() method. The translation method applies the translation on top of any existing displacement and returns a new copy of the beam, for example, to translate our previous beam along the Y-direction, we could use:

tbeam = beam.translateXyz([0;1;0]*beam.wavelength);

Rotations are stored as 3x3 rotation matrices. As with the position property, we can also directly set the rotation property, however it is often easier to use the rotate* methods from ott.utils.RotateHelper. The following rotates the beam pi/2 radians about the Y axis. When the beam is used, the rotation is applied before the translation:

rbeam = beam.rotateY(pi/2);

Arrays of beams

The toolbox supports three kinds of arrays: Coherent arrays, Incoherent arrays, and generic arrays. Incoherent/Coherent arrays represent beams which can be represented by a finite set of sub-beams. Generic arrays are simply collections of multiple beams.

To create a generic array, simply use Matlab’s array syntax, for example:

beams = [ott.beam.Gaussian(), ...
    ott.beam.LaguerreGaussian('lmode', 10)];

Most operations can be applied to generic arrays. The result is the same as applying the operation to each element of the array. For example, to translate the array of beams:

tbeams = beams.translateXyz([1;0;0]*beam.wavelength);

Or to set the position of each beam with deal:

[tbeams.position] = deal([1;0;0]*beam.wavelength);

Or directly with element access:

tbeams(1).position = [1;2;3]*beam.wavelength;

Coherent and Incoherent arrays can be created using ott.beam.Array, for example:

cbeams = ott.beam.Array(beams, 'arrayType', 'coherent');

Calculating the change in momentum between two beams

The ott.beam.Beam class provides methods for calculating the change in momentum between two beams. Although it is more common to calculate the force acting on a particle (see the ottForce.m example), the following shows how to calculate the change in momentum between two beams:

beam1 = ott.beam.Gaussian();
beam2 = beam1.rotateY(pi/2);
force = beam1.force(beam2)

Creating a custom beam

Although the toolbox has several different beams commonly used in optical trapping (for a complete list, see the beam package reference section), it is sometimes necessary to create a custom beam. The most common scenario is when modelling an SLM or the experimentally measured field at the back aperture of the focussing objective. For this task we can use the PmParaxial class (for more control over the fields we could also use the ott.bsc classes). The following example shows how we could model a phase-only SLM illuminated by a Gaussian-like beam:

% Generate coordinates for pattern
x = linspace(-1, 1, 20);
y = linspace(-1, 1, 20);
[X, Y] = ndgrid(x, y);

% Calculate incident field
E0 = exp(-(X.^2 + Y.^2)./4);

% Calculate SLM-like pattern
kx = 2;
phi = 2*pi*kx*x;

% Calculate field at back aperture
E = E0 .* exp(1i*phi);

% Calculate beam
beam = ott.beam.PmParaxial.InterpProfile(X, Y, E);

Improving runtime

Toolbox beams are represented using a vector spherical wave function expansion. Depending on how many terms are included in the expansion, visualisation and translation functions can take quite a while. One method to improve the runtime is to reduce the number of terms in the expansion. By defaut, finite beams (such as Gaussians) are calcualted with ~10,000 terms and then truncated such that the resulting beam power doesnt drop bellow 2% of the original beam power. This threshold can be changed using, for example, to change it to 10% use:

ott.beam.BscFinite.getSetShrinkNmaxRelTol(0.01);

Next time we create a beam, it will use this new tolerance:

beam = ott.beam.Gaussian();
disp(beam.data.Nmax);
tic
beam.visNearfield();
toc

% Change back to 2%
ott.beam.BscFinite.getSetShrinkNmaxRelTol(0.02);

For repeated field calculation at the same locations, there is a lot of data that can be re-used in the field calculation functions. Both the visNearfield and Gaussian beam generation functions require calculating fields. To speed up these methods, we can store the field data from a previous run. The following creates a plot with 2 different beams:

figure();
range = [1,1]*2e-6;

% Generage first beam with no prior data.  We use the recalculate
% method explicitly so we can get the returned data for repeated
% calculations.  For visualisation, we also get the returned data,
% but we also need to specify the plot axes explicitly to show the plot.
subplot(1, 2, 1);
tic
beam = ott.beam.LaguerreGaussian('lmode', 9, 'calculate', false);
[beam, dataBm] = beam.recalculate([]);
[~, ~, dataVs] = beam.visNearfield('range', range, 'plot_axes', gca());
toc

% Generate second beam with prior data for both beam and visNearfield.
subplot(1, 2, 2);
tic
beam = ott.beam.LaguerreGaussian('lmode', 7, 'calculate', false);
beam = beam.recalculate([], 'data', dataBm);
beam.visNearfield('range', range, 'data', dataVs);
toc

Different beams/methods support different optional parameters. It is not always faster to pass the data structure as an input. See the documentation for notes on improving speed and the supported arguments these methods support.

Further reading

For the full range of beams currently inculded in the toolbox, refer to the beams-package part of the reference section. The example code used to generate the overview figure in the reference section can be found in the examples/packageOverview/ directory. More advanced beam functionality can be implemented by directly using the beam shape coefficient classes (the ott.bsc package). For a example which uses both ott.beam.Beam and ott.bsc.Bsc, see examples/ottLandscape.m.