package Lab::Moose::Sweep::Continuous; $Lab::Moose::Sweep::Continuous::VERSION = '3.931'; #ABSTRACT: Base class for continuous sweeps (time, temperature, magnetic field) use v5.20; use Moose; use MooseX::Params::Validate; # Do not import all functions as they clash with the attribute methods. use Lab::Moose 'linspace'; use Time::HiRes qw/time sleep/; use Carp; extends 'Lab::Moose::Sweep'; # # 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.931 =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. Do backsweep if C 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