The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

$Lab::Moose::Sweep::Continuous::VERSION = '3.930';
#ABSTRACT: Base class for continuous sweeps (time, temperature, magnetic field)
use v5.20;
use Moose;
# Do not import all functions as they clash with the attribute methods.
use Lab::Moose 'linspace';
use Time::HiRes qw/time sleep/;
use Carp;
#
# Public attributes set by the user
#
has instrument =>
( is => 'ro', isa => 'Lab::Moose::Instrument', required => 1 );
has from => ( is => 'ro', isa => 'Num' );
has to => ( is => 'ro', isa => 'Num' );
has rate => ( is => 'ro', isa => 'Lab::Moose::PosNum' );
has start_rate => ( is => 'ro', isa => 'Lab::Moose::PosNum' );
has interval => ( is => 'ro', isa => 'Lab::Moose::PosNum' );
has points => (
is => 'ro', isa => 'ArrayRef[Num]', traits => ['Array'],
handles => {
get_point => 'get', num_points => 'count', points_array => 'elements'
},
writer => '_points',
);
has intervals => (
is => 'ro',
isa => 'ArrayRef[Num]',
traits => ['Array'],
handles => {
get_interval => 'get', num_intervals => 'count',
intervals_array => 'elements',
},
writer => '_intervals',
);
has rates => (
is => 'ro', isa => 'ArrayRef[Num]', traits => ['Array'],
handles => {
get_rate => 'get', num_rates => 'count', rates_array => 'elements'
},
writer => '_rates',
);
has backsweep => ( is => 'ro', isa => 'Bool', default => 0 );
has both_directions => ( is => 'ro', isa => 'Bool', default => 0 );
has direction_index => ( is => 'ro', isa => 'Int', default => 1 );
#
# Private attributes used internally
#
has points_index => (
is => 'ro', isa => 'Int', default => 0, init_arg => undef,
traits => ['Counter'],
handles => { inc_points_index => 'inc', reset_points_index => 'reset' },
);
# index for timing measurement sub
has index => (
is => 'ro', isa => 'Int', default => 0, init_arg => undef,
traits => ['Counter'],
handles => { inc_index => 'inc', reset_index => 'reset' }
);
has start_time =>
( is => 'ro', isa => 'Num', init_arg => undef, writer => '_start_time' );
# has in_backsweep => (
# is => 'ro', isa => 'Bool', init_arg => undef,
# writer => '_in_backsweep'
# );
sub _validate_points_attributes {
my $self = shift;
my $error_str
= "use either (points, rates, [intervals]) or (from, to, rate, [interval], [start_rate]) attributes";
if ( defined $self->points ) {
if ( not defined $self->rates ) {
croak "missing 'rates' attribute";
}
if ( defined $self->from
or defined $self->to
or defined $self->rate
or defined $self->start_rate
or $self->interval ) {
croak $error_str;
}
}
elsif ( defined $self->from ) {
if ( not defined $self->to ) {
croak "missing 'to' attribute";
}
if ( not defined $self->rate ) {
croak "missing 'rate' attribute";
}
if ( defined $self->points
or defined $self->rates
or defined $self->intervals ) {
croak $error_str;
}
}
}
sub BUILD {
my $self = shift;
$self->_validate_points_attributes();
# Time subclass uses neither points/rates nor from/to
if ( not defined $self->points and not defined $self->from ) {
return;
}
my @points;
my @rates;
my @intervals;
if ( defined $self->points ) {
my $num_points = $self->num_points;
my $num_rates = $self->num_rates;
my $num_intervals;
@points = $self->points_array;
@rates = $self->rates_array;
if ( $num_points < 2 ) {
croak "need at least two points";
}
if ( $num_rates > $num_points ) {
croak "rates array exceeds points array";
}
if ( $num_rates < 1 ) {
croak "need at least one element in rates array";
}
if ( $num_rates < $num_points ) {
push @rates, map { $rates[-1] } ( 1 .. $num_points - $num_rates );
}
if ( not defined $self->intervals ) {
@intervals = map {0} ( 1 .. $num_points - 1 );
}
else {
@intervals = $self->intervals_array;
$num_intervals = $self->num_intervals;
if ( $num_intervals > $num_points - 1 ) {
croak "intervals array exceeds points array";
}
if ( $num_intervals < 1 ) {
croak "need at least one element in intervals array";
}
if ( $num_intervals < $num_points - 1 ) {
push @intervals,
map { $intervals[-1] }
( 1 .. $num_points - 1 - $num_intervals );
}
}
}
elsif ( defined $self->from ) {
@points = ( $self->from, $self->to );
my $rate = $self->rate;
my $start_rate = $self->start_rate;
if ( not defined $self->start_rate ) {
$start_rate = $rate;
}
@rates = ( $start_rate, $rate );
my $interval = $self->interval;
if ( not defined $self->interval ) {
$interval = 0;
}
@intervals = ($interval);
}
if ( $self->backsweep ) {
my @bs_points = @points;
# Do not perform sweep of zero length in the middle
pop @bs_points;
push @points, reverse @bs_points;
my @bs_rates = @rates;
# Do not need start rate
shift @bs_rates;
push @rates, reverse @bs_rates;
push @intervals, reverse @intervals;
}
if ( $self->both_directions && $self->backsweep ) {
croak "Can't use backsweep and both_directions together."
}
$self->_points( \@points );
$self->_rates( \@rates );
$self->_intervals( \@intervals );
}
sub go_to_next_point {
my $self = shift;
my $index = $self->index;
my $interval = $self->get_interval( $self->points_index - 2 );
if ( $index == 0 or $interval == 0 ) {
# first point is special
# don't have to sleep until the level is reached
}
else {
my $t = time();
my $target_time = $self->start_time + $index * $interval;
if ( $t < $target_time ) {
sleep( $target_time - $t );
}
else {
my $prev_target_time
= $self->start_time + ( $index - 1 ) * $interval;
my $required = $t - $prev_target_time;
carp <<"EOF";
WARNING: Measurement function takes too much time:
required time: $required
interval: $interval
EOF
}
}
$self->inc_index();
}
sub go_to_sweep_start {
my $self = shift;
$self->reset_index();
$self->reset_points_index();
my $point = $self->get_point(0);
my $rate = $self->get_rate(0);
$self->inc_points_index;
carp <<"EOF";
Going to sweep start:
Setpoint: $point
Rate: $rate
EOF
my $instrument = $self->instrument();
$instrument->config_sweep(
point => $point,
rate => $rate
);
$instrument->trg();
$instrument->wait();
}
sub start_sweep {
my $self = shift;
my $instrument = $self->instrument();
my $to = $self->get_point( $self->points_index );
my $rate = $self->get_rate( $self->points_index );
$self->inc_points_index();
carp <<"EOF";
Starting sweep
Setpoint: $to
Rate: $rate
EOF
$instrument->config_sweep(
point => $to,
rate => $rate,
);
$instrument->trg();
$self->_start_time( time() );
$self->reset_index();
}
sub sweep_finished {
my $self = shift;
if ( $self->instrument->active() ) {
return 0;
}
# finished one segment of the sweep
if ( $self->points_index < $self->num_points ) {
# continue with next point
$self->start_sweep();
return 0;
}
else {
# finished all points!
if( $self->both_directions) {
my @points = $self->points_array;
my @rates = $self->rates_array;
my @intervals = $self->intervals_array;
@points = reverse @points;
@rates = reverse @rates;
@intervals = reverse @intervals;
$self->_points( \@points );
$self->_rates( \@rates );
$self->_intervals( \@intervals );
}
return 1;
}
}
# implement get_value in subclasses.
__PACKAGE__->meta->make_immutable();
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Lab::Moose::Sweep::Continuous - Base class for continuous sweeps (time, temperature, magnetic field)
=head1 VERSION
version 3.930
=head1 SYNOPSIS
use Lab::Moose;
#
# 1D sweep of magnetic field
#
my $ips = instrument(
type => 'OI_Mercury::Magnet'
connection_type => ...,
connection_options => {...}
);
my $multimeter = instrument(...);
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
);
# alternative: points/rates
# 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
# );
my $datafile = sweep_datafile(columns => ['B-field', 'current']);
$datafile->add_plot(x => 'B-field', y => 'current');
my $meas = sub {
my $sweep = shift;
my $field = $ips->get_field();
my $current = $multimeter->get_value();
$sweep->log('B-field' => $field, current => $current);
};
$sweep->start(
datafiles => [$datafile],
measurement => $meas,
);
=head1 DESCRIPTION
Continuous sweep constructure. The sweep can be configured with either
=over
=item * from/to
=item * rate
=item * interval (default: 0)
=back
or by providing the arrayrefs
=over
=item * points
=item * rates
=item * intervals (default: [0])
=item
=back
If an interval is C<0>, do as much measurements as possible.
Otherwise, warn if measurement requires more time than C<interval>.
Do backsweep if C<backsweep> attribute is set to 1.
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2025 by the Lab::Measurement team; in detail:
Copyright 2018 Simon Reinhardt
2020 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