File Coverage

blib/lib/Lab/Moose/Sweep/Step.pm
Criterion Covered Total %
statement 85 94 90.4
branch 23 30 76.6
condition 10 12 83.3
subroutine 14 14 100.0
pod 0 5 0.0
total 132 155 85.1


line stmt bran cond sub pod time code
1             package Lab::Moose::Sweep::Step;
2             $Lab::Moose::Sweep::Step::VERSION = '3.880';
3             #ABSTRACT: Base class for step/list sweeps
4              
5 2     2   4950 use v5.20;
  2         7  
6              
7              
8 2     2   16 use Moose;
  2         6  
  2         17  
9 2     2   16997 use Moose::Util::TypeConstraints 'enum';
  2         6  
  2         19  
10 2     2   1283 use MooseX::Params::Validate;
  2         6  
  2         20  
11 2     2   1151 use Data::Dumper;
  2         4  
  2         150  
12              
13             # Do not import all functions as they clash with the attribute methods.
14 2     2   16 use Lab::Moose 'linspace';
  2         7  
  2         31  
15              
16 2     2   15 use Carp;
  2         5  
  2         2600  
17              
18             extends 'Lab::Moose::Sweep';
19              
20             #
21             # Public attributes set by the user
22             #
23              
24             has from => ( is => 'ro', isa => 'Num', predicate => 'has_from' );
25             has to => ( is => 'ro', isa => 'Num', predicate => 'has_to' );
26             has step =>
27             ( is => 'ro', isa => 'Lab::Moose::PosNum', predicate => 'has_step' );
28              
29             has points =>
30             ( is => 'ro', isa => 'ArrayRef[Num]', predicate => 'has_points' );
31             has steps => ( is => 'ro', isa => 'ArrayRef[Num]', predicate => 'has_steps' );
32              
33             has list => (
34             is => 'ro', isa => 'ArrayRef[Num]', predicate => 'has_list',
35             writer => '_list'
36             );
37             has backsweep => ( is => 'ro', isa => 'Bool', default => 0 );
38              
39             has both_directions => ( is => 'ro', isa => 'Bool', default => 0 );
40              
41             has setter => ( is => 'ro', isa => 'CodeRef', required => 1 );
42              
43             #
44             # Private attributes used internally
45             #
46              
47             has _points => (
48             is => 'ro', isa => 'ArrayRef[Num]', lazy => 1, init_arg => undef,
49             builder => '_build_points', traits => ['Array'],
50             handles => { get_point => 'get', num_points => 'count', points_array => 'elements' },
51             writer => 'write_points',
52             );
53              
54             has index => (
55             is => 'ro', isa => 'Int', default => 0, init_arg => undef,
56             writer => '_index'
57             );
58              
59             has current_value => (
60             is => 'ro', isa => 'Num', init_arg => undef,
61             writer => '_current_value'
62             );
63              
64             my $error_msg = <<"EOF";
65             give either (from => ..., to => ..., step => ...)
66             or (list => [...])
67             or (points => [...], steps => [....])
68             or (points => [...], step => )
69              
70             EOF
71              
72             sub _build_points {
73 18     18   52 my $self = shift;
74 18   66     619 my $has_from_to_step
75             = $self->has_from && $self->has_to && $self->has_step;
76 18         555 my $has_list = $self->has_list;
77 18   100     559 my $has_points_steps = $self->has_points && $self->has_steps;
78 18   100     527 my $has_points_step = $self->has_points && $self->has_step;
79 18 50       90 if ( $has_from_to_step + $has_list + $has_points_steps + $has_points_step
80             != 1 ) {
81 0         0 croak $error_msg;
82             }
83              
84 18         38 my @points;
85              
86 18 100       49 if ($has_list) {
    100          
87 2         4 @points = @{ $self->list() };
  2         67  
88 2 50       8 if ( @points < 1 ) {
89 0         0 croak "list needs at least 1 point";
90             }
91             }
92             elsif ($has_from_to_step) {
93 14         395 @points = linspace(
94             from => $self->from,
95             to => $self->to,
96             step => $self->step
97             );
98             }
99             else {
100             # points_steps or points_step
101 2         4 my @steps;
102 2         5 my @corner_points = @{ $self->points };
  2         65  
103 2 50       8 if ( @corner_points < 2 ) {
104 0         0 croak "points array needs at least two elements";
105             }
106              
107 2 100       12 if ($has_points_steps) {
108 1         2 @steps = @{ $self->steps };
  1         39  
109             }
110             else {
111 1         30 @steps = ( $self->step );
112             }
113 2 50       7 if ( @steps >= @corner_points ) {
114 0         0 croak "steps array exceeds points array";
115             }
116 2         11 push @steps, map { $steps[-1] } ( 1 .. @corner_points - @steps - 1 );
  1         4  
117 2         16 my ( $p1, $p2 );
118 2         6 $p1 = shift @corner_points;
119 2         6 while (@corner_points) {
120 4         9 my $p2 = shift @corner_points;
121 4         5 my $step = shift @steps;
122 4         16 push @points, linspace( from => $p1, to => $p2, step => $step );
123 4         12 $p1 = $p2;
124             }
125             }
126              
127 18 100       612 if ( $self->backsweep ) {
128 1         5 my @backsweep_points = reverse @points;
129 1         3 push @points, @backsweep_points;
130             }
131 18 50 66     495 if ( $self->backsweep && $self->both_directions ) {
132 0         0 croak "Can't use backsweep and both_directions together."
133             }
134              
135 18         653 return \@points;
136             }
137              
138       36 0   sub start_sweep {
139              
140             # do nothing
141             }
142              
143             sub go_to_next_point {
144 142     142 0 257 my $self = shift;
145 142         3652 my $index = $self->index();
146 142         4814 my $point = $self->get_point($index);
147 142         4460 my $setter = $self->setter();
148 142         442 $self->$setter($point);
149 142         5049 $self->_current_value($point);
150 142         4340 $self->_index( ++$index );
151             }
152              
153             sub go_to_sweep_start {
154 36     36 0 67 my $self = shift;
155 36         1237 my $point = $self->get_point(0);
156 36         1219 my $setter = $self->setter();
157 36         166 $self->$setter($point);
158 36         1326 $self->_current_value($point);
159 36         1154 $self->_index(0);
160             }
161              
162             sub sweep_finished {
163 178     178 0 289 my $self = shift;
164 178         4833 my $index = $self->index();
165 178 100       6282 if ( $index >= $self->num_points ) {
166 36 50       1095 if ( $self->both_directions ) {
167 0         0 my @points = $self->points_array;
168 0         0 @points = reverse @points;
169 0         0 $self->write_points( \@points );
170             }
171 36         130 return 1;
172             }
173 142         382 return 0;
174             }
175              
176             sub _in_backsweep {
177 36     36   58 my $self = shift;
178 36 100       1007 if ( $self->backsweep ) {
179 6 100       158 if ( $self->index > $self->num_points / 2 ) {
180 3         11 return 1;
181             }
182             }
183             else {
184 30         89 return 0;
185             }
186             }
187              
188             sub get_value {
189 36     36 0 93 my $self = shift;
190 36 50       1027 if ( not defined $self->current_value() ) {
191 0         0 croak "sweep not yet started";
192             }
193 36         968 my $value = sprintf( "%.14g", $self->current_value );
194 36 100       101 if ( $self->_in_backsweep ) {
195 3         9 $value .= '_backsweep';
196             }
197 36         113 return $value;
198             }
199              
200             __PACKAGE__->meta->make_immutable();
201             1;
202              
203             __END__
204              
205             =pod
206              
207             =encoding UTF-8
208              
209             =head1 NAME
210              
211             Lab::Moose::Sweep::Step - Base class for step/list sweeps
212              
213             =head1 VERSION
214              
215             version 3.880
216              
217             =head1 SYNOPSIS
218              
219             use Lab::Moose;
220              
221             #
222             # basic 1D sweep (e.g. IV curve)
223             #
224            
225             my $source = instrument(
226             type => ...,
227             connection_type => ...,
228             connection_options => {...}
229             );
230             my $multimeter = instrument(...);
231            
232             my $sweep = sweep(
233             type => 'Step::Voltage',
234             instrument => $instrument,
235             from => -1,
236             to => 1,
237             step => 0.1,
238             backsweep => 1, # points: -1, -0.9, ..., 0.9, 1, 0.9, ..., -1
239             );
240              
241             my $datafile = sweep_datafile(columns => ['volt', 'current']);
242             $datafile->add_plot(x => 'volt', y => 'current');
243            
244             my $meas = sub {
245             my $sweep = shift;
246             my $volt = $source->cached_level();
247             my $current = $multimeter->get_value();
248             $sweep->log(volt => $volt, current => $current);
249             };
250              
251             $sweep->start(
252             datafiles => [$datafile],
253             measurement => $meas,
254             );
255              
256             #
257             # 2D sweep (quantum dot stability diagram)
258             #
259            
260             my $gate = instrument(...);
261             my $bias = instrument(...);
262             my $multimeter = instrument(...);
263              
264             # master sweep
265             my $gate_sweep = sweep(
266             type => 'Step::Voltage',
267             instrument => $gate,
268             from => -5,
269             to => 5,
270             step => 0.01
271             );
272              
273             # slave sweep
274             my $bias_sweep = sweep(
275             type => 'Step::Voltage',
276             instrument => $bias,
277             from => -1,
278             to => 1,
279             step => 0.01
280             );
281              
282             my $datafile = sweep_datafile(columns => [qw/gate bias current/]);
283             $datafile->add_plot(
284             type => 'pm3d',
285             x => 'gate',
286             y => 'bias',
287             z => 'current'
288             );
289            
290             my $meas = sub {
291             my $sweep = shift;
292             my $gate_v = $gate->cached_level();
293             my $bias_v = $bias->cached_level();
294             my $current = $multimeter->get_value();
295             $sweep->log(gate => $gate_v, bias => $bias_v, current => $current);
296             };
297              
298             $gate_sweep->start(
299             slaves => [$bias_sweep],
300             datafiles => [$datafile],
301             measurement => $meas
302             );
303              
304             =head1 DESCRIPTION
305              
306             This C<sweep> constructor defines the following arguments
307              
308             =over
309              
310             =item * from/to/step
311              
312             define a linear range of points.
313              
314             =item * list
315              
316             alternative to from/to/step, give an arbitrary arrayref of points.
317              
318             =item * points/steps
319              
320             alternative to from/to/step. Lets define multiple segments with different steps, e.g.
321              
322             points => [0,1,2],
323             steps => [0.5, 0.2],
324              
325             is equivalent to
326              
327             list => [0, 0.5, 1, 1, 1.2, 1.4, 1.6, 1.8, 2]
328              
329             If C<steps> has fewer elements than segments provided in C<points>, reuse the last value in C<steps>.
330              
331             =item * points/step
332              
333             points => [...],
334             step => $x, # equivalent to steps => [$x]
335              
336             =item * backsweep
337              
338             Include a backsweep: After finishing the sweep, go through all points in
339             reverse.
340              
341             =item * setter
342              
343             A coderef which will be called to change the source level.
344             Use this if you do some arcane type of sweep which does not justify its own
345             sweep subclass.
346             Sweep subclasses like L<Lab::Moose::Sweep::Step::Voltage> will
347             define defaults for this. E.g. for the Voltage sweep:
348              
349             sub {
350             my $sweep = shift;
351             my $value = shift;
352             $sweep->instrument->set_level(value => $value);
353             };
354              
355             =back
356              
357             =head1 COPYRIGHT AND LICENSE
358              
359             This software is copyright (c) 2023 by the Lab::Measurement team; in detail:
360              
361             Copyright 2017-2018 Simon Reinhardt
362             2020 Andreas K. Huettel
363             2023 Mia Schambeck
364              
365              
366             This is free software; you can redistribute it and/or modify it under
367             the same terms as the Perl 5 programming language system itself.
368              
369             =cut