#DistZilla: +PodWeaver
#PODNAME: Lab::Measurement::Tutorial
#ABSTRACT: Lab::Measurement tutorial

__END__

=pod

=encoding UTF-8

=head1 NAME

Lab::Measurement::Tutorial - Lab::Measurement tutorial

=head1 VERSION

version 3.930

=head1 How to read the documentation

The documentation of Lab::Measurement can be read both on MetaCPAN and by
using perldoc. The most important starting points are

=over

=item * L<Lab::Measurement::Installation>

Detailed installation instructions.

=item * L<Lab::Measurement::Backends>

Overview of available back-ends.

=item * L<Lab::Measurement::Manual>

The table of contents referencing all manual pages.

=item * L<Lab::Measurement::Tutorial>

This tutorial.

=back

=head1 Quickstart

In this quickstart section we demonstrate how to connect to instruments and do
simple communication to change instrument settings and perform data acquisition.
In the examples we use two comparatively simple instruments, the HP (later
Agilent, now Keysight) 34410A digital multimeter (DMM) and the Stanford Research
SR830 lock-in amplifier.

We cover the most important connection types and show how to use them on Linux
and Windows:

=over

=item * USB

This uses the USB-TMC protocol, which emulates IEEE 488.2 features over a USB
cable. Instruments are connected directly to the measurement PC or via a hub.
Depending on your USB hardware data transfer can be very fast.

=item * Ethernet / VXI11

This is the recommended protocol, if supported by the device; instruments are
connected via a local area ethernet network with the measurement PC. (Raw TCP
sockets are also supported by Lab::Measurement, but do not provide the
IEEE 488.2 style control commands of VXI11, such as device clear. This is why
VXI11 will be more seamless.) We recommend to create a private, firewalled or
non-routed LAN to separate you lab devices from the internet. Naturally then
you will have to take care of IP address assignment yourself.

=item * GPIB

This is the IEEE 488 bus, also called GPIB or HPIB. It is robust and often
the only option for older equipment in the lab.

=back

=head2 Connecting instruments on Linux

Let's use the Agilent DMM with USB on Linux. This requires the L<USB::TMC>
driver module and the libusb system library to be installed. libusb should be
available for every modern Linux distribution.

 use 5.010;
 use Lab::Moose;

 my $multimeter = instrument(
     type => 'Agilent34410A',
     connection_type => 'USB',
 );

Note that the connection type USB assumes that we have a USB Test & Measurement
class device. Devices that emulate a serial port or other USB classes (video,
audio, ...) are not supported (yet).

When connecting multiple devices of the same model via USB, we have to provide
serial numbers, which are unique for each device:

 my $multimeter1 = instrument(
     type => 'Agilent34410A',
     connection_type => 'USB',
     connection_options => {serial => '...'}
 );

 my $multimeter2 = instrument(
     type => 'Agilent34410A',
     connection_type => 'USB',
     connection_options => {serial => '...'}
 );

To use the LAN interface, set the C<connection_type> to I<VXI11> and
provide the instrument's IP address with the C<connection_options> hash:

 my $multimeter = instrument(
     type => 'Agilent34410A',
     connection_type => 'VXI11',
     connection_options => {host => '192.168.2.20'},
 );

The SR830 lock-in amplifier only has a GPIB interface. This means that our
measurement PC needs a GPIB host adaptor. The LinuxGPIB kernel driver and perl
bindings have to be installed and configured as described in
L<Lab::Measurement::Backends>. Note that the kernel driver consists of Linux
kernel modules, which may have to be compiled specific for the running kernel.

The GPIB address (primary address, short B<pad>) of our lock-in amplifier is
provided in the C<connection_options> hash:

 my $lia = instrument(
     type => 'SR830',
     connection_type => 'LinuxGPIB',
     connection_options => {pad => 1},
 );

=head2 Connecting instruments on Windows

First, make sure that both National Instruments (NI) VISA and the Lab::VISA
package are installed, see L<Lab::VISA::Installation>. The I<VISA interactive
control>, part of the NI VISA installation, can be used to get a list of
connected equipment.

We first connect the DMM via USB. The serial number can be found using I<VISA interactive control> or from the utility menu of the instrument.

 use 5.010;
 use Lab::Moose;

 my $multimeter = instrument(
     type => 'Agilent34410A',
     connection_type => 'VISA::USB',
     connection_options => {serial => '00B50DAE'},
 );

To use the LAN interface, set the C<connection_type> to I<VISA::VXI11> and
provide the instrument's IP address with the C<connection_options> hash:

 my $multimeter = instrument(
     type => 'Agilent34410A',
     connection_type => 'VISA::VXI11',
     connection_options => {host => '192.168.2.20'},
 );

Finally, we connect the SR830 lock-in amplifier via GPIB. This requires that the
National Instruments NI-488 driver is installed in addition to VISA. The GPIB
address (primary address, short B<pad>), is provided in the
C<connection_options> hash:

 my $lia = instrument(
     type => 'SR830',
     connection_type => 'VISA::GPIB',
     connection_options => {pad => 1},
 );

=head2 Listening and talking to the devices

After initializing the multimeter with the C<instrument> function, let us
perform some basic operations

 # Perform *IDN? query, prints instrument manufacturer, model, serial number
 say $multimeter->idn();

 # Set range to 10 Volts (if multimeter is in voltage mode)
 $multimeter->sense_range(value => 10);

 # Perform voltage measurement (if multimeter is in voltage mode)
 my $voltage = $multimeter->get_value();

And for the SR830 lock-in amplifier:

 # Set reference frequeny to 10kHz
 $lia->set_frq(value => 10e3);

 # Set output voltage amplitude to 0.5 V
 $lia->set_amplitude(value => 0.5);

 # Set sensitivity to 1mV
 $lia->set_sens(value => 1e-3);

 # Set filter slope to 18dB/oct
 $lia->set_filter_slope(value => 18);

 # Read x/y measurement data
 my $xy = $lia->get_xy();
 # Print contents of $xy hashref
 say "x = $xy->{x}, y = $xy->{y}";

=head2 Connection logging

We can monitor the commands sent to the instrument by specifying a log file in
the constructor:

 my $lia = instrument(
     type => 'SR830',
     connection_type => 'VISA::GPIB',
     connection_options => {pad => 1},
     log_file => 'some_file.yml'
 );

All communication will be logged in the human readable file F<some_file.yml>.

=head1 Types of instrument drivers

Here we show some examples of more advanced types of instruments.

Note that when using sources (voltage, magnetic field, temperature) we often do
not call the instrument object directly. Instead, we use the high-level sweep
interfaces as described below. These provide a common API for creating both
discrete and continuous sweeps of output parameters.

=head2 Voltage / current source drivers

For a voltage or current source, the instrument initialization requires several
additional parameters which enforce step size and rate limits. The intention
here is that often sensitive devices can be destroyed by large and sudden jumps
in input voltage; by providing limits at initialization, the later script
follows these limits automatically without further programming effort.

 my $yoko = instrument(
     type => 'YokogawaGS200',
     connection_type => 'USB',
     max_units_per_step => 0.001,
     max_units_per_second => 0.01,
     min_units => -10,
     max_units => 10,
 );

We assume that the Yokogawa GS200 source is in voltage output mode. Now we use
C<set_level> to set output level to 9V. The source will sweep with step size
and speed given by the max_units_per_step / max_units_per_second parameters;
unit is Volt (the corresponding SI unit).

 $yoko->set_level(value => 9);

Once this has finished, we can read the new level from the cache that is
automatically kept by Lab::Measurement:

 my $level = $yoko->cached_level();

=head2 Superconducting magnet power supplies

 my $ips = instrument(
     type => 'OI_Mercury::Magnet',
     connection_type => 'Socket',
     connection_options => {host => '192.168.3.15'},
 );

The following commands perform a continuous sweep of the magnetic field
from 0T to 0.5T with a rate of 0.1T/min:

 # Set field setpoint and rate
 $ips->config_sweep(point => 0.5, rate => 0.1);

 # Start (trigger) sweep
 $ips->trg();

 # Show progress until sweep is finished
 $ips->wait();

A high-level interface for creating continuous sweeps and measuring while the
sweep is running is described below.

=head2 Spectrum analyzers

Typically, a spectrum analyzer performs a frequency sweep in hardware. The
resulting spectrum data, i.e., power as function of frequency, is returned as a
2D L<PDL> object. Here is the corresponding code at the example of a Rohde &
Schwarz FSV spectrum analyzer:

 my $analyzer = instrument(
     type => 'RS_FSV',
     connection_type => 'VXI11',
     connection_options => {host => '...'},
 );

 # Set sweep start/stop frequencies
 $analyzer->sense_frequency_start(value => 1e9);
 $analyzer->sense_frequency_stop(value => 1e9);

 # Perform sweep, get data as PDL
 my $data = $analyzer->get_spectrum(timeout => 100);
 # Print data
 say $data;

You can always convert a PDL into an ordinary nested arrayref with C<unpdl>:

 my $arrayref_2D = $data->unpdl();

=head1 Sweeps, datafiles, and datafolders

=head2 Quick start: Measuring an IV-curve

As a basic example of a one-dimensional sweep, we measure an I(V) curve. A
Yokogawa voltage source is combined with an Agilent multimeter.

 # file: IV.pl
 use Lab::Moose; # you get 'use warnings; use strict;' for free

 my $source = instrument(
     type            => 'YokogawaGS200',
     connection_type => 'USB',
     # Safety limits:
     max_units => 10, min_units => -10,
     max_units_per_step => 0.1, max_units_per_second => 1
 );

 my $dmm = instrument(type => 'Agilent34410A', connection_type => 'USB');

 my $sweep = sweep(
     type       => 'Step::Voltage',
     instrument => $source,
     from => -5, to => 5, step => 0.01
 );

 my $datafile = sweep_datafile(columns => [qw/voltage current/]);

 my $meas = sub {
     my $sweep = shift;
     $sweep->log(
         voltage => $source->cached_level(),
         current => $dmm->get_value(),
     );
 };

 $sweep->start(
     measurement => $meas,
     datafile    => $datafile,
 );

Running this script repeatedly creates output folders F<MEAS_000>, F<MEAS_001>,
... Each of these folders contains the following files:

=over

=item * F<IV.pl>

A copy of the measurement script.

=item * F<META.yml>

A YAML file with various automatically collected metadata (the time of
the script run, the user name, the host name, the used command line, the
Lab::Measurement version, ...).

=item * F<data.dat>

A Gnuplot-style datafile:

 # voltage        current
 -5               42
 -4.99            43
 ...

=back

=head3 Backsweeps

To also measure the IV in the reverse direction from -5 to 5 volts, we add the
C<backsweep> option:

 my $sweep = sweep(
     type       => 'Step::Voltage',
     instrument => $source,
     from => -5, to => 5, step => 0.01,
     backsweep  => 1,
 );

=head2 The data folder

You can change the name of the data folder by providing a C<folder> argument to
the C<start> method:

 $sweep->start(
     measurement => $meas,
     datafile    => $datafile,
     folder      => 'IV_curve'
 );

This will create output folders with names F<YYYY-MM-DD_HH-MM-SS_IV_curve_xxx>
If you do not want to use the date/time prefixes use

 $sweep->start(
     measurement => $meas,
     datafile    => $datafile,
     folder      => 'IV_curve',
     date_prefix => 0,
     time_prefix => 0
 );

=head2 Multiple datafiles

The following example creates multiple data files:

 my $datafile1 = sweep_datafile(
     filename => 'data1',
     columns  => [qw/voltage current/]
 );
 my $datafile2 = sweep_datafile(
     filename => 'data2',
     columns  => [qw/voltage current/]
 );

 $sweep->start(
     measurement => $meas,
     datafiles   => [$datafile1, $datafile2],
     folder      => 'IV_curve'
 );

In the C<$meas> subroutine, we now call the C<log> method for both
data files:

 my $meas = sub {
     my $sweep = shift;
     my $voltage = $source->cached_level();
     $sweep->log(
         datafile => $datafile1,
         voltage  => $voltage,
         current  => $dmm1->get_value(),
     );
     $sweep->log(
         datafile => $datafile2,
         voltage  => $voltage,
         current  => $dmm2->get_value()
    );
 };

=head2 Multiple instruments executing the same sweep

If you are working with multiple Lock-In amplifiers for example and you want to
sweep the lock-in-frequency, it is of course necessary to change the frequency
of every involved Lock-In amplifier to keep them synchronous. To do that, you
simply replace the single instrument with an array referrence of multiple
devices when defining the sweep:

    my $frq_sweep = sweep(
      type       => 'Step::Frequency',
      instrument => [$LOCKIN_REF, $LOCKIN_DUT, $LOCKIN_OUT],
      from => 105, to => 2005, step => 100
    );

=head2 Multi-dimensional sweeps: Data file dimensions and file name extensions

=head3 2D sweeps

We start with a simple 2D sweep: we sweep a gate voltage (outer sweep) and
a bias voltage (inner sweep) and again measure a current:

 use Lab::Moose;

 # As we use two Yokogawas, we need to provide serial numbers to identify them
 # on the USB bus.
 my $gate_source = instrument(
     type               => 'YokogawaGS200',
     connection_type    => 'USB',
     connection_options => {serial => '...'},
     # Safety limits:
     max_units => 10, min_units => -10,
     max_units_per_step => 0.1, max_units_per_second => 1
 );

 my $bias_source = instrument(
     type               => 'YokogawaGS200',
     connection_type    => 'USB',
     connection_options => {serial => '...'},
     # Safety limits:
     max_units => 10, min_units => -10,
     max_units_per_step => 0.1, max_units_per_second => 1
 );

 my $dmm = instrument(type => 'Agilent34410A', connection_type => 'USB');

 my $gate_sweep = sweep(
     type       => 'Step::Voltage',
     instrument => $gate_source,
     from => 0, to => 1, step => 0.1
 );

 my $bias_sweep = sweep(
     type       => 'Step::Voltage',
     instrument => $bias_source,
     from => 0, to => 1, step => 0.1
 );

 my $datafile = sweep_datafile(columns => [qw/gate bias current/]);

 # We use cached_level since that speeds up measurement a LOT.
 my $meas = sub {
     my $sweep = shift;
     my $v_gate = $gate_source->cached_level();
     my $v_bias = $bias_source->cached_level();
     $sweep->log(
         gate    => $v_gate,
         bias    => $v_bias,
         current => $dmm->get_value(),
     );
 };

 $gate_sweep->start(
     slave       => $bias_sweep,
     measurement => $meas,
     datafile    => $datafile,
 );

By default, this will create a 2D block data file in the typical gnuplot format,
i.e., with each bias sweep followed by a blank line as separator.

 # gate    bias    current
 0         0       x
 0         0.1     x
 0         0.2     x
 ...
 0         1       x

 0.1       0       x
 0.1       0.1     x
 0.1       0.2     x
 ...
 ...

 1         0       x
 ...
 1         1       x

Alternatively, we can create multiple 1D data files, one for each value of the
gate voltage. We do this by setting the C<datafile_dim> parameter to 1:

 $gate_sweep->start(
     slave        => $bias_sweep,
     measurement  => $meas,
     datafile     => $datafile,
     datafile_dim => 1
 );

The output files will be F<<data_Voltage=0.dat, data_Voltage=0.1.dat, ...,
data_Voltage=1.dat>> We can customize the F<Voltage=> part in the data file
names by providing a B<filename extension> in the gate sweep:

 my $gate_sweep = sweep(
     type               => 'Step::Voltage',
     instrument         => $gate_source,
     from => 0, to => 1, step => 0.1,
     filename_extension => 'Gate=',
 );

=head3 Higher dimensional sweeps

Sweeps setups with dimension > 2 can be created by using a C<slaves> array
parameter in C<sweep_start> instead of C<slave>:

 $outer_sweep->start(
     slaves => [$middle_sweep, $inner_sweep],
     datafile => ...
 );

The maximum data file dimension
remains 2. E.g. if we create a 3D sweep [Temperature, Gate, Bias], a 2D
data file will be created for each value of the temperature sweep.
If we set C<datafile_dim> to 1, a subfolder will be created for each value of
the temperature and the subfolders contain 1D data files for each gate voltage
value.

=head2 Live plotting

=head3 Line plots

Let us add a simple line plot to our IV measurement:

 my $datafile = sweep_datafile(columns => [qw/voltage current/]);

 $datafile->add_plot(
     x => 'voltage',
     y => 'current',
 );

This will create a live line plot, which will be updated for each new data
point. A copy of the plot will be saved in the output folder in png format with
file name C<"$datafile.png">. You can change this file name with the
C<hard_copy> option:

 $datafile->add_plot(
     x         => 'voltage',
     y         => 'current',
     hard_copy => 'data.png',
 );

The C<hard_copy> attribute is mandatory if you add multiple plots to one datafile.

=head3 Connection problems associated with persistent plot windows

Some gnuplot terminals like B<qt> are always persistent, i.e. the live
plot windows persist after the measurement script is finished. Some connection
types like B<LinuxGPIB> only work after closing all plot windows. This might
be caused by file locks which are inherited by the forked gnuplot processes even when
the main perl process is finished. As an alternative to close the plot
windows yourself, one can use terminals like B<x11> where one can set
C<< persist => 0 >> in the C<terminal_options> attribute (see below).

=head3 Labeling lines for different blocks

Assume the following 2d sweep setup:

 # outer sweep
 my $gate_sweep = sweep(
     type               => 'Step::Voltage',
     instrument         => $gate_source,
     list => [10, 20, 30],
 );

 # inner (fast) sweep
 my $bias_sweep = sweep(
     type                => 'Step::Voltage',
     from                => -1,
     to                  => 1,
     step                => 0.01
 );

 my $datafile = datafile(columns => [qw/gate bias current/]);

We create a current vs. bias plot with one curve for each value of the gate:

 $datafile->add_plot(
     x => 'bias',
     y => 'current',
     legend => 'gate',
 );

Setting the C<'legend'> attribute will create a key where each curve is labeled
by its value of the gate parameter.

Conversely, if we do not need different plotting styles for each block, we can set

 $datafile->add_plot(
     x => 'bias',
     y => 'current',
     curve_options => {linetype => 1},
 );

=head3 Multiple curves per plot

We can draw multiple curves in a single plot window:

 $datafile->add_plot(
     curves => [{x => 'voltage', y => 'current1'}, {x => 'voltage', y =>
     'current2'}],
     hard_copy_ => 'data.png'
 );

=head3 Color maps (3D plots)

Let us add a color plot to the gate/bias 2D sweep:

 my $datafile = sweep_datafile(columns => [qw/gate bias current/]);

 $datafile->add_plot(
     type => 'pm3d',
     x    => 'gate',
     y    => 'bias',
     z    => 'current'
 );

The designation 'pm3d' comes from the gnuplot plot type of this name.
By default, the live plot will be updated after each bias sweep is completed.

=head3 Terminal options

If we don't want to use gnuplot's default terminal for the live plot or
hardcopy, we use the C<terminal>, C<hard_copy_terminal>, C<terminal_options> and
C<hard_copy_terminal_options> options:

 $datafile->add_plot(
     type                       => 'pm3d',
     x                          => 'gate',
     y                          => 'bias',
     z                          => 'current',
     terminal                   => 'x11',
     terminal_options           => {linewidth => 3},
     hard_copy                  => 'data.jpg',
     hard_copy_terminal         => 'jpeg',
     hard_copy_terminal_options => {linewidth => 0.5}
 );

=head3 Plot and curve options

L<PDL::Graphics::Gnuplot> separates between plot options and curve options.

 $datafile->add_plot(
     type => 'pm3d',
     x    => 'gate',
     y    => 'bias',
     z    => 'current',
     plot_options => {
         title   => 'x - y plot',
         xlabel  => 'x (V)',
         ylabel  => 'y (V)',
         cblabel => 'current (A)', # label for color box
         format  => {x => "'%.2e'", y => "'%.2e'"},
         clut    => 'sepia',
         grid    => 0, # disable grid
     },
     curve_options => {
         with      => 'lines', # default is 'points'
         linetype  => 2, # color
         linewidth => 2,
     },
 );

More plot and curve options are documented in L<PDL::Graphics::Gnuplot>.

=head3 Live plots in higher dimensional sweeps

Live plotting is also supported for higher dimensional sweeps. When the sweep
creates multiple subfolders with datafiles, C<add_plot> will be called each
time a new datafile is created. In this case, the C<hard_copy> argument to
C<add_plot> cannot be used, as the hard copies of each datafile plot need to
have unique names. Instead we use C<hard_copy_suffix>:

 # plot current versus voltage
 $datafile->add_plot(
     x         => 'voltage',
     y         => 'current',
     hard_copy_suffix => '_IV',
 );

 # plot voltage versus current
 $datafile->add_plot(
     x         => 'current',
     y         => 'voltage',
     hard_copy_suffix => '_VI',
 );

=head3 Refresh type 'block' for 2D plots

By default, 2D plots will be refreshed after every new point that is appended
to the datafile. For C<pm3d> plots, the default is to refresh after every new
block/line of data. When using C<datafile_dim = 2> with a 2D plot, as shown
above, it is often useful to refresh only after each finished block:

 $datafile->add_plot(
    ...
    refresh => 'block',
 );

As more and more data points are contained in a plot, refreshing the plot
becomes slower. Without the C<< refresh => 'block' >> this would lead to a
increasing delay of the measurement.

=head3 Refresh interval

We can set a mininum time between redraws of the live plot. This becomes handy
for large plot, where the redrawing the plot is slow.

 # Wait at least 10 minutes before redraw:
 $datafile->add_plot(
     ...
     refresh_interval => 600,
 );

To ensure that the whole data is plotted at the end of the measurement, we force a
redraw of all plots:

 $sweep->refresh_plots(force => 1);

=head2 Block data

There are types of instruments which return more than a single data. Examples
are spectrum and network analyzers, which perform a frequency sweep and return
an array of data after each sweep.

FIXME: We need to link to a good introduction to PDL here.

The C<sparam_sweep> method provided, e.g., by the
L<Lab::Moose::Instrument::RS_ZVA> returns a 2D PDL with the following format:

 [
  [freq1    , freq2    , ..., freqN    ],
  [Re(S11)_1, Re(S11)_2, ..., Re(S11)_N],
  [Im(S11)_1, Im(S11)_2, ..., Im(S11)_N],
  [Amp_1    , Amp_2    , ..., Amp_N    ],
  [phase_1  , phase_2  , ..., phase_N  ],
 ]

The following script sweeps a voltage source and performs a frequency sweep
with the VNA for each level of the voltage source. Each VNA sweep is logged
into a separate datafile which contains one line of data for each frequency
point.

 use Lab::Moose;

 my $source = instrument(
     type            => 'YokogawaGS200',
     connection_type => 'USB',
     # Safety limits:
     max_units => 10, min_units => -10,
     max_units_per_step => 0.1, max_units_per_second => 1
 );

 my $vna = instrument(
     type               => 'RS_ZVA',
     connection_type    => 'VXI11',
     connection_options => {host => '192.168.x.x'},
 );

 my $sweep = sweep(
     type => 'Step::Voltage',
     instrument => $source,
     from => -5, to => 5, step => 0.01
 );

 my $datafile = sweep_datafile(
     columns => [qw/voltage freq Re_S21 Im_S21 amplitude phase/]);

 my $meas = sub {
     my $sweep = shift;
     my $voltage = $source->cached_level();
     my $block = $vna->sparam_sweep(timeout => 10, average => 100);

     $sweep->log_block(
         prefix => {voltage => $voltage},
         block => $block
     );
 };

 $sweep->start(
     measurement => $meas,
     datafile   => $datafile,
     datafile_dim => 1, # each VNA trace in a separate file
     point_dim => 1, # the measurement sub logs blocks, not points
 );

Without the C<< point_dim => 1 >> setting, only one datafile would be
generated. One could also log all blocks into a single 2D datafile by setting
C<< datafile_dim => 2 >>.

=head3 log_block with single-point sweeps

Assume that we use the VNA to measure transmission at a single fixed
frequency. In this case C<sparam_sweep> returns a 2D PDL where the first
dimension has length one. One can still use C<log_block> for logging. By
default C<log_block> does not trigger a refresh of live plots. This can be
changed by adding C<< refresh_plots => 1 >> to the C<log_block> arguments.

=head2 Continuous sweeps

With continuous sweeps, the sweep parameter is ramped in the background while
data is recorded. This is in constrast with step/list sweeps where the sweep
parameter is kept constant during data acquisition.
The rate of measurement points taken is controlled by the C<interval> sweep
attribute.

For example, the following time sweep records data every 0.5 seconds and
finishes after 60 seconds:

 use Lab::Moose;

 my $sweep = sweep(
     type => 'Continuous::Time',
     interval => 0.5,
     duration => 60
 );

=head3 Configuration of continuous sweeps

In this example we sweep a magnet field with the C<Continuous::Magnet> sweep
class. All subclasses of C<Continuous> work like this.

Note that the rate is given in Tesla/min.

 my $sweep = sweep(
     type => 'Continuous::Magnet',
     instrument => $ips,
     from => -1, # Tesla
     to => 1,
     rate => 0.1, # (Tesla/min, always positive)
     start_rate => 1, # (optional, rate to approach start point)
     interval => 0.5, # one measurement every 0.5 seconds
 );

If the sweep should use different rates in different sections, use the
C<points>, C<rates>, and C<intervals> arguments:

 my $sweep = sweep(
     type => 'Continuous::Magnet',
     instrument => $ips,
     points => [-1, -0.1, 0.1, 1],
     # start rate: 1
     # use slow rate 0.01 between points -0.1 and 0.1
     rates => [1, 0.1, 0.01, 0.1],
     intervals => [0.5], # one measurement every 0.5 seconds
 );

If the C<rates> array contains fewer elements than the C<points> array, it will
be filled with the last value.

If no C<interval> or C<intervals> parameter is provided a default of 0 is used.
With an interval of 0, as many data points as possible are recorded without any
delay between the measurement points.

=head2 Further sweep customizations

=head3 The delay_before_loop, delay_in_loop, and delay_after_loop attributes

These attributes can be used to introduce delays into a sweep:

 my $sweep = sweep(
     type       => 'Step::Voltage',
     instrument => $source,
     from => -5, to => 5, step => 0.01,
     delay_before_loop => 1.5,
     delay_in_loop => 0.1,
     delay_after_loop => 2.5,
 );

With C<delay_before_loop> set, the sweep will sleep 1.5 seconds before
starting the sweep (after going to the start point of the sweep). With
C<delay_in_loop> set, there is a sleep between going to the setpoint and
calling the measurement subroutine. The C<delay_after_loop> causes a delay
between finishing the sweep and going back to the start point.

=head3 The before_loop coderef

The C<before_loop> coderef is used to execute arbitrary code at the start of a sweep:

 my $before_loop = sub {
     print("will start loop now\n");
 };

 my $sweep = sweep(
     type       => 'Step::Voltage',
     instrument => $source,
     from => -5, to => 5, step => 0.01,
     before_loop => $before_loop,
 );

The C<$before_loop> code is called after a possible C<delay_before_loop> delay.

=head3 Adding entries to META.yml

An arbitray hash of metadata can be added to F<META.yml> my providing a
C<meta_data> attribute to the C<start> method:

 $sweep->start(
     measurement => $meas,
     datafile   => $datafile,
     meta_data => {foo_string => "123", bar_array => [1, 2, 3]},
 );

=head1 Custom measurement control without the Sweep layer

Here we describe measurement control without the Sweep API. One can still use
the Datafolder, Datafile, and live plotting features, as they are implemented
independently of the Sweep layer.
For example, the 2D sweep example from above could be rewritten like this:

 use Lab::Moose; # get instrument, datafolder, datafile, linspace
 use Lab::Moose::Countdown; # get countdown

 my $gate_source = instrument(
     type            => 'DummySource',
     connection_type => 'Debug',
     connection_options => {verbose => 0},

     # Safety limits:
     max_units => 10, min_units => -10,
     max_units_per_step => 0.11, max_units_per_second => 10
 );

 my $bias_source = instrument(
     type               => 'DummySource',
     connection_type => 'Debug',
     connection_options => {verbose => 0},

     # Safety limits:
     max_units => 10, min_units => -10,
     max_units_per_step => 0.11, max_units_per_second => 10
 );

 my $folder = datafolder();
 my $datafile = datafile(folder => $folder, filename => 'data.dat',
                         columns => [qw/gate bias current/]);

 $datafile->add_plot(
     type => 'pm3d',
     x => 'gate',
     y => 'bias',
     z => 'current'
     );

 my @gate_values = linspace(from => 0, to => 1, step => 0.1);
 my @bias_values = linspace(from => -1, to => 1, step => 0.1);

 for my $gate_value (@gate_values) {
     $gate_source->set_level(value => $gate_value);

     # go to bias sweep start point and wait 5 sec
     $bias_source->set_level(value => $bias_values[0]);
     countdown(5);

     for my $bias_value (@bias_values) {
         $bias_source->set_level(value => $bias_value);
         $datafile->log(
             gate => $gate_value,
             bias => $bias_value,
             current => $bias_value + $gate_value,
             );
     }
     $datafile->new_block();
 }

 $bias_source->set_level(value => 0);

One can also use the stabilization routines used by some sweep types like the
Step::Temperature sweep:

 use Lab::Moose;
 use Lab::Moose::Stabilizer;
 my $instrument = instrument(type => 'OI_ITC503', ...);

 # Set temp and stabilize
 $instrument->set_T(value => $temp);
 stabilize(
     instrument => $instrument,
     setpoint   => $temp,
     getter     => 'get_T',
     tolerance_setpoint   => 0.1,
     tolerance_std_dev    => 0.1,
     measurement_interval => 10,
     observation_time     => 100,
 );

=head1 Writing new instrument drivers

Detailed instructions for writing new instrument drivers are provided in
L<Lab::Measurement::Developer>.

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2025 by the Lab::Measurement team; in detail:

  Copyright 2006       Daniel Schroeer
            2010       Daniel Schroeer
            2011-2012  Andreas K. Huettel
            2016       Andreas K. Huettel, Simon Reinhardt
            2017       Andreas K. Huettel
            2018       Andreas K. Huettel, Simon Reinhardt
            2019-2020  Simon Reinhardt
            2021       Fabian Weinelt, Simon Reinhardt
            2023       Andreas K. Huettel


This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
