From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

$Lab::Moose::Sweep::Step::VERSION = '3.930';
#ABSTRACT: Base class for step/list sweeps
use v5.20;
use Moose;
# Do not import all functions as they clash with the attribute methods.
use Lab::Moose 'linspace';
use Carp;
#
# Public attributes set by the user
#
has from => ( is => 'ro', isa => 'Num', predicate => 'has_from' );
has to => ( is => 'ro', isa => 'Num', predicate => 'has_to' );
has step =>
( is => 'ro', isa => 'Lab::Moose::PosNum', predicate => 'has_step' );
has points =>
( is => 'ro', isa => 'ArrayRef[Num]', predicate => 'has_points' );
has steps => ( is => 'ro', isa => 'ArrayRef[Num]', predicate => 'has_steps' );
has list => (
is => 'ro', isa => 'ArrayRef[Num]', predicate => 'has_list',
writer => '_list'
);
has backsweep => ( is => 'ro', isa => 'Bool', default => 0 );
has both_directions => ( is => 'ro', isa => 'Bool', default => 0 );
has setter => ( is => 'ro', isa => 'CodeRef', required => 1 );
#
# Private attributes used internally
#
has _points => (
is => 'ro', isa => 'ArrayRef[Num]', lazy => 1, init_arg => undef,
builder => '_build_points', traits => ['Array'],
handles => { get_point => 'get', num_points => 'count', points_array => 'elements' },
writer => 'write_points',
);
has index => (
is => 'ro', isa => 'Int', default => 0, init_arg => undef,
writer => '_index'
);
has current_value => (
is => 'ro', isa => 'Num', init_arg => undef,
writer => '_current_value'
);
my $error_msg = <<"EOF";
give either (from => ..., to => ..., step => ...)
or (list => [...])
or (points => [...], steps => [....])
or (points => [...], step => )
EOF
sub _build_points {
my $self = shift;
my $has_from_to_step
= $self->has_from && $self->has_to && $self->has_step;
my $has_list = $self->has_list;
my $has_points_steps = $self->has_points && $self->has_steps;
my $has_points_step = $self->has_points && $self->has_step;
if ( $has_from_to_step + $has_list + $has_points_steps + $has_points_step
!= 1 ) {
croak $error_msg;
}
my @points;
if ($has_list) {
@points = @{ $self->list() };
if ( @points < 1 ) {
croak "list needs at least 1 point";
}
}
elsif ($has_from_to_step) {
@points = linspace(
from => $self->from,
to => $self->to,
step => $self->step
);
}
else {
# points_steps or points_step
my @steps;
my @corner_points = @{ $self->points };
if ( @corner_points < 2 ) {
croak "points array needs at least two elements";
}
if ($has_points_steps) {
@steps = @{ $self->steps };
}
else {
@steps = ( $self->step );
}
if ( @steps >= @corner_points ) {
croak "steps array exceeds points array";
}
push @steps, map { $steps[-1] } ( 1 .. @corner_points - @steps - 1 );
my ( $p1, $p2 );
$p1 = shift @corner_points;
while (@corner_points) {
my $p2 = shift @corner_points;
my $step = shift @steps;
push @points, linspace( from => $p1, to => $p2, step => $step );
$p1 = $p2;
}
}
if ( $self->backsweep ) {
my @backsweep_points = reverse @points;
push @points, @backsweep_points;
}
if ( $self->backsweep && $self->both_directions ) {
croak "Can't use backsweep and both_directions together."
}
return \@points;
}
sub start_sweep {
# do nothing
}
sub go_to_next_point {
my $self = shift;
my $index = $self->index();
my $point = $self->get_point($index);
my $setter = $self->setter();
$self->$setter($point);
$self->_current_value($point);
$self->_index( ++$index );
}
sub go_to_sweep_start {
my $self = shift;
my $point = $self->get_point(0);
my $setter = $self->setter();
$self->$setter($point);
$self->_current_value($point);
$self->_index(0);
}
sub sweep_finished {
my $self = shift;
my $index = $self->index();
if ( $index >= $self->num_points ) {
if ( $self->both_directions ) {
my @points = $self->points_array;
@points = reverse @points;
$self->write_points( \@points );
}
return 1;
}
return 0;
}
sub _in_backsweep {
my $self = shift;
if ( $self->backsweep ) {
if ( $self->index > $self->num_points / 2 ) {
return 1;
}
}
else {
return 0;
}
}
sub get_value {
my $self = shift;
if ( not defined $self->current_value() ) {
croak "sweep not yet started";
}
my $value = sprintf( "%.14g", $self->current_value );
if ( $self->_in_backsweep ) {
$value .= '_backsweep';
}
return $value;
}
__PACKAGE__->meta->make_immutable();
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Lab::Moose::Sweep::Step - Base class for step/list sweeps
=head1 VERSION
version 3.930
=head1 SYNOPSIS
use Lab::Moose;
#
# basic 1D sweep (e.g. IV curve)
#
my $source = instrument(
type => ...,
connection_type => ...,
connection_options => {...}
);
my $multimeter = instrument(...);
my $sweep = sweep(
type => 'Step::Voltage',
instrument => $instrument,
from => -1,
to => 1,
step => 0.1,
backsweep => 1, # points: -1, -0.9, ..., 0.9, 1, 0.9, ..., -1
);
my $datafile = sweep_datafile(columns => ['volt', 'current']);
$datafile->add_plot(x => 'volt', y => 'current');
my $meas = sub {
my $sweep = shift;
my $volt = $source->cached_level();
my $current = $multimeter->get_value();
$sweep->log(volt => $volt, current => $current);
};
$sweep->start(
datafiles => [$datafile],
measurement => $meas,
);
#
# 2D sweep (quantum dot stability diagram)
#
my $gate = instrument(...);
my $bias = instrument(...);
my $multimeter = instrument(...);
# master sweep
my $gate_sweep = sweep(
type => 'Step::Voltage',
instrument => $gate,
from => -5,
to => 5,
step => 0.01
);
# slave sweep
my $bias_sweep = sweep(
type => 'Step::Voltage',
instrument => $bias,
from => -1,
to => 1,
step => 0.01
);
my $datafile = sweep_datafile(columns => [qw/gate bias current/]);
$datafile->add_plot(
type => 'pm3d',
x => 'gate',
y => 'bias',
z => 'current'
);
my $meas = sub {
my $sweep = shift;
my $gate_v = $gate->cached_level();
my $bias_v = $bias->cached_level();
my $current = $multimeter->get_value();
$sweep->log(gate => $gate_v, bias => $bias_v, current => $current);
};
$gate_sweep->start(
slaves => [$bias_sweep],
datafiles => [$datafile],
measurement => $meas
);
=head1 DESCRIPTION
This C<sweep> constructor defines the following arguments
=over
=item * from/to/step
define a linear range of points.
=item * list
alternative to from/to/step, give an arbitrary arrayref of points.
=item * points/steps
alternative to from/to/step. Lets define multiple segments with different steps, e.g.
points => [0,1,2],
steps => [0.5, 0.2],
is equivalent to
list => [0, 0.5, 1, 1, 1.2, 1.4, 1.6, 1.8, 2]
If C<steps> has fewer elements than segments provided in C<points>, reuse the last value in C<steps>.
=item * points/step
points => [...],
step => $x, # equivalent to steps => [$x]
=item * backsweep
Include a backsweep: After finishing the sweep, go through all points in
reverse.
=item * setter
A coderef which will be called to change the source level.
Use this if you do some arcane type of sweep which does not justify its own
sweep subclass.
Sweep subclasses like L<Lab::Moose::Sweep::Step::Voltage> will
define defaults for this. E.g. for the Voltage sweep:
sub {
my $sweep = shift;
my $value = shift;
$sweep->instrument->set_level(value => $value);
};
=back
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2025 by the Lab::Measurement team; in detail:
Copyright 2017-2018 Simon Reinhardt
2023 Mia Schambeck
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