File Coverage

blib/lib/Lab/Moose/Sweep/Continuous.pm
Criterion Covered Total %
statement 87 136 63.9
branch 25 54 46.3
condition 9 27 33.3
subroutine 12 12 100.0
pod 0 5 0.0
total 133 234 56.8


line stmt bran cond sub pod time code
1             package Lab::Moose::Sweep::Continuous;
2             $Lab::Moose::Sweep::Continuous::VERSION = '3.900';
3             #ABSTRACT: Base class for continuous sweeps (time, temperature, magnetic field)
4              
5 3     3   3836 use v5.20;
  3         11  
6              
7              
8 3     3   19 use Moose;
  3         8  
  3         21  
9 3     3   19881 use MooseX::Params::Validate;
  3         8  
  3         43  
10              
11             # Do not import all functions as they clash with the attribute methods.
12 3     3   1478 use Lab::Moose 'linspace';
  3         11  
  3         26  
13 3     3   24 use Time::HiRes qw/time sleep/;
  3         23  
  3         28  
14              
15 3     3   286 use Carp;
  3         7  
  3         5048  
16              
17             extends 'Lab::Moose::Sweep';
18              
19             #
20             # Public attributes set by the user
21             #
22              
23             has instrument =>
24             ( is => 'ro', isa => 'Lab::Moose::Instrument', required => 1 );
25              
26             has from => ( is => 'ro', isa => 'Num' );
27             has to => ( is => 'ro', isa => 'Num' );
28             has rate => ( is => 'ro', isa => 'Lab::Moose::PosNum' );
29             has start_rate => ( is => 'ro', isa => 'Lab::Moose::PosNum' );
30             has interval => ( is => 'ro', isa => 'Lab::Moose::PosNum' );
31              
32             has points => (
33             is => 'ro', isa => 'ArrayRef[Num]', traits => ['Array'],
34             handles => {
35             get_point => 'get', num_points => 'count', points_array => 'elements'
36             },
37             writer => '_points',
38             );
39              
40             has intervals => (
41             is => 'ro',
42             isa => 'ArrayRef[Num]',
43             traits => ['Array'],
44             handles => {
45             get_interval => 'get', num_intervals => 'count',
46             intervals_array => 'elements',
47             },
48             writer => '_intervals',
49             );
50              
51             has rates => (
52             is => 'ro', isa => 'ArrayRef[Num]', traits => ['Array'],
53             handles => {
54             get_rate => 'get', num_rates => 'count', rates_array => 'elements'
55             },
56             writer => '_rates',
57             );
58              
59             has backsweep => ( is => 'ro', isa => 'Bool', default => 0 );
60              
61             has both_directions => ( is => 'ro', isa => 'Bool', default => 0 );
62             has direction_index => ( is => 'ro', isa => 'Int', default => 1 );
63             #
64             # Private attributes used internally
65             #
66              
67             has points_index => (
68             is => 'ro', isa => 'Int', default => 0, init_arg => undef,
69             traits => ['Counter'],
70             handles => { inc_points_index => 'inc', reset_points_index => 'reset' },
71              
72             );
73              
74             # index for timing measurement sub
75             has index => (
76             is => 'ro', isa => 'Int', default => 0, init_arg => undef,
77             traits => ['Counter'],
78             handles => { inc_index => 'inc', reset_index => 'reset' }
79             );
80              
81             has start_time =>
82             ( is => 'ro', isa => 'Num', init_arg => undef, writer => '_start_time' );
83              
84             # has in_backsweep => (
85             # is => 'ro', isa => 'Bool', init_arg => undef,
86             # writer => '_in_backsweep'
87             # );
88              
89             sub _validate_points_attributes {
90 2     2   5 my $self = shift;
91              
92 2         7 my $error_str
93             = "use either (points, rates, [intervals]) or (from, to, rate, [interval], [start_rate]) attributes";
94 2 100       99 if ( defined $self->points ) {
    50          
95 1 50       35 if ( not defined $self->rates ) {
96 0         0 croak "missing 'rates' attribute";
97             }
98              
99 1 50 33     36 if ( defined $self->from
      33        
      33        
      33        
100             or defined $self->to
101             or defined $self->rate
102             or defined $self->start_rate
103             or $self->interval ) {
104 0         0 croak $error_str;
105             }
106              
107             }
108             elsif ( defined $self->from ) {
109 0 0       0 if ( not defined $self->to ) {
110 0         0 croak "missing 'to' attribute";
111             }
112 0 0       0 if ( not defined $self->rate ) {
113 0         0 croak "missing 'rate' attribute";
114             }
115 0 0 0     0 if ( defined $self->points
      0        
116             or defined $self->rates
117             or defined $self->intervals ) {
118 0         0 croak $error_str;
119             }
120             }
121             }
122              
123             sub BUILD {
124 2     2 0 7 my $self = shift;
125              
126 2         23 $self->_validate_points_attributes();
127              
128             # Time subclass uses neither points/rates nor from/to
129 2 50 66     68 if ( not defined $self->points and not defined $self->from ) {
130 1         33 return;
131             }
132              
133 1         7 my @points;
134             my @rates;
135 1         0 my @intervals;
136              
137 1 50       31 if ( defined $self->points ) {
    0          
138 1         46 my $num_points = $self->num_points;
139 1         40 my $num_rates = $self->num_rates;
140 1         2 my $num_intervals;
141 1         41 @points = $self->points_array;
142 1         45 @rates = $self->rates_array;
143              
144 1 50       4 if ( $num_points < 2 ) {
145 0         0 croak "need at least two points";
146             }
147              
148 1 50       3 if ( $num_rates > $num_points ) {
149 0         0 croak "rates array exceeds points array";
150             }
151 1 50       5 if ( $num_rates < 1 ) {
152 0         0 croak "need at least one element in rates array";
153             }
154 1 50       3 if ( $num_rates < $num_points ) {
155 0         0 push @rates, map { $rates[-1] } ( 1 .. $num_points - $num_rates );
  0         0  
156             }
157              
158 1 50       34 if ( not defined $self->intervals ) {
159 0         0 @intervals = map {0} ( 1 .. $num_points - 1 );
  0         0  
160             }
161             else {
162 1         41 @intervals = $self->intervals_array;
163 1         40 $num_intervals = $self->num_intervals;
164 1 50       6 if ( $num_intervals > $num_points - 1 ) {
165 0         0 croak "intervals array exceeds points array";
166             }
167 1 50       6 if ( $num_intervals < 1 ) {
168 0         0 croak "need at least one element in intervals array";
169             }
170 1 50       6 if ( $num_intervals < $num_points - 1 ) {
171             push @intervals,
172 0         0 map { $intervals[-1] }
  0         0  
173             ( 1 .. $num_points - 1 - $num_intervals );
174             }
175             }
176              
177             }
178             elsif ( defined $self->from ) {
179 0         0 @points = ( $self->from, $self->to );
180 0         0 my $rate = $self->rate;
181 0         0 my $start_rate = $self->start_rate;
182              
183 0 0       0 if ( not defined $self->start_rate ) {
184 0         0 $start_rate = $rate;
185             }
186              
187 0         0 @rates = ( $start_rate, $rate );
188              
189 0         0 my $interval = $self->interval;
190              
191 0 0       0 if ( not defined $self->interval ) {
192 0         0 $interval = 0;
193             }
194 0         0 @intervals = ($interval);
195             }
196              
197 1 50       34 if ( $self->backsweep ) {
198 0         0 my @bs_points = @points;
199              
200             # Do not perform sweep of zero length in the middle
201 0         0 pop @bs_points;
202 0         0 push @points, reverse @bs_points;
203              
204 0         0 my @bs_rates = @rates;
205              
206             # Do not need start rate
207 0         0 shift @bs_rates;
208 0         0 push @rates, reverse @bs_rates;
209              
210 0         0 push @intervals, reverse @intervals;
211             }
212              
213 1 50 33     37 if ( $self->both_directions && $self->backsweep ) {
214 0         0 croak "Can't use backsweep and both_directions together."
215             }
216              
217 1         40 $self->_points( \@points );
218 1         46 $self->_rates( \@rates );
219 1         41 $self->_intervals( \@intervals );
220              
221             }
222              
223             sub go_to_next_point {
224 16     16 0 52 my $self = shift;
225 16         641 my $index = $self->index;
226              
227 16         567 my $interval = $self->get_interval( $self->points_index - 2 );
228 16 100 66     125 if ( $index == 0 or $interval == 0 ) {
229              
230             # first point is special
231             # don't have to sleep until the level is reached
232             }
233             else {
234 13         64 my $t = time();
235 13         522 my $target_time = $self->start_time + $index * $interval;
236 13 50       58 if ( $t < $target_time ) {
237 13         14941079 sleep( $target_time - $t );
238             }
239             else {
240 0         0 my $prev_target_time
241             = $self->start_time + ( $index - 1 ) * $interval;
242 0         0 my $required = $t - $prev_target_time;
243 0         0 carp <<"EOF";
244             WARNING: Measurement function takes too much time:
245             required time: $required
246             interval: $interval
247             EOF
248             }
249              
250             }
251 16         1931 $self->inc_index();
252             }
253              
254             sub go_to_sweep_start {
255 1     1 0 2 my $self = shift;
256 1         54 $self->reset_index();
257 1         44 $self->reset_points_index();
258              
259 1         47 my $point = $self->get_point(0);
260 1         41 my $rate = $self->get_rate(0);
261 1         51 $self->inc_points_index;
262              
263 1         289 carp <<"EOF";
264             Going to sweep start:
265             Setpoint: $point
266             Rate: $rate
267             EOF
268 1         284 my $instrument = $self->instrument();
269 1         8 $instrument->config_sweep(
270             point => $point,
271             rate => $rate
272             );
273 1         6 $instrument->trg();
274 1         6 $instrument->wait();
275            
276             }
277              
278             sub start_sweep {
279 2     2 0 6 my $self = shift;
280 2         77 my $instrument = $self->instrument();
281              
282 2         68 my $to = $self->get_point( $self->points_index );
283 2         59 my $rate = $self->get_rate( $self->points_index );
284 2         80 $self->inc_points_index();
285              
286 2         421 carp <<"EOF";
287             Starting sweep
288             Setpoint: $to
289             Rate: $rate
290             EOF
291 2         349 $instrument->config_sweep(
292             point => $to,
293             rate => $rate,
294             );
295 2         11 $instrument->trg();
296 2         85 $self->_start_time( time() );
297 2         79 $self->reset_index();
298             }
299              
300             sub sweep_finished {
301 11     11 0 39 my $self = shift;
302 11 100       392 if ( $self->instrument->active() ) {
303 9         42 return 0;
304             }
305              
306             # finished one segment of the sweep
307              
308 2 100       67 if ( $self->points_index < $self->num_points ) {
309              
310             # continue with next point
311 1         5 $self->start_sweep();
312 1         7 return 0;
313             }
314             else {
315             # finished all points!
316 1 50       36 if( $self->both_directions) {
317 0         0 my @points = $self->points_array;
318 0         0 my @rates = $self->rates_array;
319 0         0 my @intervals = $self->intervals_array;
320            
321 0         0 @points = reverse @points;
322 0         0 @rates = reverse @rates;
323 0         0 @intervals = reverse @intervals;
324            
325 0         0 $self->_points( \@points );
326 0         0 $self->_rates( \@rates );
327 0         0 $self->_intervals( \@intervals );
328             }
329 1         6 return 1;
330             }
331             }
332              
333             # implement get_value in subclasses.
334              
335             __PACKAGE__->meta->make_immutable();
336             1;
337              
338             __END__
339              
340             =pod
341              
342             =encoding UTF-8
343              
344             =head1 NAME
345              
346             Lab::Moose::Sweep::Continuous - Base class for continuous sweeps (time, temperature, magnetic field)
347              
348             =head1 VERSION
349              
350             version 3.900
351              
352             =head1 SYNOPSIS
353              
354             use Lab::Moose;
355              
356             #
357             # 1D sweep of magnetic field
358             #
359            
360             my $ips = instrument(
361             type => 'OI_Mercury::Magnet'
362             connection_type => ...,
363             connection_options => {...}
364             );
365              
366             my $multimeter = instrument(...);
367            
368             my $sweep = sweep(
369             type => 'Continuous::Magnet',
370             instrument => $ips,
371             from => -1, # Tesla
372             to => 1,
373             rate => 0.1, (Tesla/min, always positive)
374             start_rate => 1, (optional) rate to approach start point
375             interval => 0.5, # one measurement every 0.5 seconds
376             );
377              
378              
379             # alternative: points/rates
380             # my $sweep = sweep(
381             # type => 'Continuous::Magnet',
382             # instrument => $ips,
383             # points => [-1, -0.1, 0.1, 1],
384             # # start rate: 1
385             # # use slow rate 0.01 between points -0.1 and 0.1
386             # rates => [1, 0.1, 0.01, 0.1],
387             # intervals => [0.5], # one measurement every 0.5 seconds
388             # );
389            
390              
391             my $datafile = sweep_datafile(columns => ['B-field', 'current']);
392             $datafile->add_plot(x => 'B-field', y => 'current');
393            
394             my $meas = sub {
395             my $sweep = shift;
396             my $field = $ips->get_field();
397             my $current = $multimeter->get_value();
398             $sweep->log('B-field' => $field, current => $current);
399             };
400              
401             $sweep->start(
402             datafiles => [$datafile],
403             measurement => $meas,
404             );
405              
406             =head1 DESCRIPTION
407              
408             Continuous sweep constructure. The sweep can be configured with either
409              
410             =over
411              
412             =item * from/to
413              
414             =item * rate
415              
416             =item * interval (default: 0)
417              
418             =back
419              
420             or by providing the arrayrefs
421              
422             =over
423              
424             =item * points
425              
426             =item * rates
427              
428             =item * intervals (default: [0])
429              
430             =item
431              
432             =back
433              
434             If an interval is C<0>, do as much measurements as possible.
435             Otherwise, warn if measurement requires more time than C<interval>.
436              
437             Do backsweep if C<backsweep> attribute is set to 1.
438              
439             =head1 COPYRIGHT AND LICENSE
440              
441             This software is copyright (c) 2023 by the Lab::Measurement team; in detail:
442              
443             Copyright 2018 Simon Reinhardt
444             2020 Andreas K. Huettel, Simon Reinhardt
445             2023 Mia Schambeck
446              
447              
448             This is free software; you can redistribute it and/or modify it under
449             the same terms as the Perl 5 programming language system itself.
450              
451             =cut