File Coverage

blib/lib/Lab/Moose/Instrument/VNASweep.pm
Criterion Covered Total %
statement 84 88 95.4
branch 13 20 65.0
condition n/a
subroutine 14 14 100.0
pod 3 3 100.0
total 114 125 91.2


line stmt bran cond sub pod time code
1             package Lab::Moose::Instrument::VNASweep;
2             $Lab::Moose::Instrument::VNASweep::VERSION = '3.880';
3             #ABSTRACT: Role for network analyzer sweeps
4              
5 3     3   2246 use v5.20;
  3         24  
6              
7             # Some default exports like 'inner' would collide with PDL
8 3     3   17 use Moose::Role qw/with requires/;
  3         9  
  3         27  
9              
10 3     3   12681 use MooseX::Params::Validate 'validated_hash';
  3         9  
  3         24  
11 3     3   726 use Moose::Util::TypeConstraints 'enum';
  3         17  
  3         28  
12 3         233 use Lab::Moose::Instrument qw/
13             timeout_param getter_params precision_param validated_setter
14 3     3   1459 /;
  3         14  
15              
16 3     3   21 use Carp;
  3         10  
  3         189  
17              
18 3     3   30 use PDL;
  3         8  
  3         42  
19              
20 3     3   11505 use namespace::autoclean;
  3         7  
  3         21  
21              
22             with qw(
23             Lab::Moose::Instrument::Common
24              
25             Lab::Moose::Instrument::SCPI::Format
26              
27             Lab::Moose::Instrument::SCPI::Instrument
28              
29             Lab::Moose::Instrument::SCPI::Sense::Average
30             Lab::Moose::Instrument::SCPI::Sense::Bandwidth
31             Lab::Moose::Instrument::SCPI::Sense::Frequency
32             Lab::Moose::Instrument::SCPI::Sense::Sweep
33              
34             Lab::Moose::Instrument::SCPI::Source::Power
35              
36             Lab::Moose::Instrument::SCPI::Initiate
37              
38             Lab::Moose::Instrument::SCPIBlock
39             );
40              
41             requires qw/sparam_sweep_data sparam_catalog/;
42              
43             sub _get_data_columns {
44 6     6   17 my ( $self, $catalog, $freq_array, $points ) = @_;
45              
46 6         24 $freq_array = pdl($freq_array);
47 6         431 $points = pdl($points);
48              
49 6         305 my $num_rows = $freq_array->nelem();
50 6 50       25 if ( $num_rows != $self->cached_sense_sweep_points() ) {
51 0         0 croak
52             "length of frequency array not equal to number of configured points";
53             }
54              
55 6         10 my $num_columns = @{$catalog};
  6         15  
56              
57 6         24 my $num_points = $points->nelem();
58              
59 6 50       37 if ( $num_points != $num_columns * $num_rows ) {
60 0         0 croak "$num_points != $num_columns * $num_rows";
61             }
62              
63             # One pdl for each column. Will cat these before we return.
64 6         9 my @data_columns;
65              
66 6         29 for my $col_index ( 0 .. $num_columns / 2 - 1 ) {
67              
68 6         15 my $start = $col_index * $num_rows * 2;
69 6         16 my $stop = $start + 2 * ( $num_rows - 1 );
70              
71 6         31 my $real = $points->slice( [ $start, $stop, 2 ] );
72 6         214 my $im = $points->slice( [ $start + 1, $stop + 1, 2 ] );
73              
74 6         256 my $amplitude = 10 * log10( $real**2 + $im**2 );
75 6         1000 my $phase = atan2( $im, $real );
76              
77 6         143 push @data_columns, $real, $im, $amplitude, $phase;
78             }
79              
80 6         80 return cat( $freq_array, @data_columns );
81             }
82              
83              
84             sub sparam_sweep {
85 6     6 1 17539 my ( $self, %args ) = validated_hash(
86             \@_,
87             timeout_param(),
88             type => { isa => enum( ['frequency'] ), default => 'frequency' },
89             average => { isa => 'Int', default => 1 },
90             precision_param()
91             );
92              
93 6         11425 my $average_count = delete $args{average};
94 6         18 my $precision = delete $args{precision};
95              
96             # Not used so far.
97 6         25 my $sweep_type = delete $args{type};
98              
99 6         30 my $catalog = $self->sparam_catalog();
100              
101 6         32 my $freq_array = $self->sense_frequency_linear_array();
102              
103             # Ensure single sweep mode.
104 6 100       25 if ( $self->cached_initiate_continuous() ) {
105 2         27 $self->initiate_continuous( value => 0 );
106             }
107              
108             # Set average and sweep count.
109              
110 6 100       45 if ( $self->cached_sense_average_count() != $average_count ) {
111 2         16 $self->sense_average_count( value => $average_count );
112             }
113              
114 6 100       29 if ( $self->cached_sense_sweep_count() != $average_count ) {
115 1         6 $self->sense_sweep_count( value => $average_count );
116             }
117              
118             # Ensure correct data format
119 6         30 $self->set_data_format_precision( precision => $precision );
120              
121             # Query measured traces.
122              
123             # Get data.
124             $args{read_length} = $self->block_length(
125 6         15 num_points => @{$catalog} * @{$freq_array},
  6         14  
  6         25  
126             precision => $precision
127             );
128              
129 6         31 my $binary = $self->sparam_sweep_data(%args);
130              
131 6         33 my $points_ref = $self->block_to_array(
132             binary => $binary,
133             precision => $precision
134             );
135              
136 6         24 return $self->_get_data_columns( $catalog, $freq_array, $points_ref );
137             }
138              
139             sub _ensure_single_point_mode {
140 2     2   5 my $self = shift;
141 2         6 my $points = $self->cached_sense_sweep_points();
142 2 50       13 if ( $points != 1 ) {
143 0         0 croak "not in single point mode (have $points points)";
144             }
145 2         6 my $start = $self->cached_sense_frequency_start();
146 2         13 my $stop = $self->cached_sense_frequency_stop();
147 2 50       21 if ( $start != $stop ) {
148 0         0 croak <<"EOF";
149             not in single point mode:
150             start frequency: $start
151             stop frequency: $stop
152             EOF
153             }
154             }
155              
156             sub _rel_error {
157 2     2   6 my $a = shift;
158 2         3 my $b = shift;
159 2         20 return ( abs( ( $a - $b ) / $b ) );
160             }
161              
162              
163             sub set_frq {
164 1     1 1 2937 my ( $self, $value, %args ) = validated_setter( \@_ );
165 1         14 my $points = $self->cached_sense_sweep_points();
166 1 50       7 if ( $points != 1 ) {
167 1         14 $self->sense_sweep_points( value => 1 );
168             }
169 1         11 my $start = $self->cached_sense_frequency_start();
170 1         13 my $stop = $self->cached_sense_frequency_stop();
171              
172 1 50       10 if ( _rel_error( $start, $value ) > 1e-14 ) {
173 1         6 $self->sense_frequency_start( value => $value );
174             }
175 1 50       6 if ( _rel_error( $stop, $value ) > 1e-14 ) {
176 1         8 $self->sense_frequency_stop( value => $value );
177             }
178              
179 1         8 $self->_ensure_single_point_mode();
180             }
181              
182              
183             sub get_frq {
184              
185             # ensure single point mode
186 1     1 1 8 my $self = shift;
187 1         4 $self->_ensure_single_point_mode();
188 1         7 return $self->cached_sense_frequency_start();
189             }
190              
191              
192             1;
193              
194             __END__
195              
196             =pod
197              
198             =encoding UTF-8
199              
200             =head1 NAME
201              
202             Lab::Moose::Instrument::VNASweep - Role for network analyzer sweeps
203              
204             =head1 VERSION
205              
206             version 3.880
207              
208             =head1 METHODS
209              
210             =head2 sparam_sweep
211              
212             my $data = $vna->sparam_sweep(timeout => 10, average => 10, precision => 'double');
213              
214             Perform a single sweep, and return the resulting data as a 2D PDL. The first
215             dimension runs over the sweep points. E.g. if only the S11 parameter is
216             measured, the resulting PDL has dimensions N x 5:
217              
218             [
219             [freq1 , freq2 , ..., freqN ],
220             [Re(S11)_1, Re(S11)_2, ..., Re(S11)_N],
221             [Im(S11)_1, Im(S11)_2, ..., Im(S11)_N],
222             [Amp_1 , Amp_2 , ..., Amp_N ],
223             [phase_1 , phase_2 , ..., phase_N ],
224             ]
225              
226             The row with the amplitudes (power in units of dB) is calculated from the
227             S-params as
228              
229             10 * log10(Re(S11)**2 + Im(S11)**2)
230              
231             The row with the phases is calculated as from the S-params as
232              
233             atan2(Im(S11), Re(S11))
234              
235             Thus, each recorded S-param will create 4 subsequent rows in the output PDL.
236              
237             This method accepts a hash with the following options:
238              
239             =over
240              
241             =item B<timeout>
242              
243             timeout for the sweep operation. If this is not given, use the connection's
244             default timeout.
245              
246             =item B<average>
247              
248             Setting this to C<$N>, the method will perform C<$N> sweeps and the
249             returned data will consist of the average values.
250              
251             =item B<precision>
252              
253             floating point type. Has to be 'single' or 'double'. Defaults to 'single'.
254              
255             =back
256              
257             =head2 set_frq
258              
259             # Prepare VNA for single point measurement at frequency 4GHz:
260             $vna->set_frq(value => 4e9);
261              
262             Set VNA to single point mode. That is only a single frequency is measured and
263             one point of data is returned per measurement.
264              
265             This high-level function make the VNA usable with L<Lab::Moose::Sweep::Step::Frequency>.
266              
267             Will croak if the VNA does not support single point mode.
268              
269             =head2 get_frq
270              
271             my $frq = $vna->get_frq();
272              
273             Get frequency of VNA in single point mode. Croak if the VNA is not configured
274             for single point measurement.
275              
276             =head1 REQUIRED METHODS
277              
278             The following methods are required for role consumption.
279              
280             =head2 sparam_catalog
281              
282             my $array_ref = $vna->sparam_catalog();
283              
284             Return an arrayref of available S-parameter names. Example result:
285             C<['Re(s11)', 'Im(s11)', 'Re(s21)', 'Im(s21)']>.
286              
287             =head2 sparam_sweep_data
288              
289             my $binary_string = $vna->sparam_sweep_data(timeout => $timeout)
290              
291             Return binary SCPI data block of S-parameter values. This string contains
292             the C<sparam_catalog> values of each frequency point. The floats must be in
293             native byte order.
294              
295             =head1 CONSUMED ROLES
296              
297             =over
298              
299             =item L<Lab::Moose::Instrument::Common>
300              
301             =item L<Lab::Moose::Instrument::SCPI::Format>
302              
303             =item L<Lab::Moose::Instrument::SCPI::Instrument>
304              
305             =item L<Lab::Moose::Instrument::SCPI::Sense::Average>
306              
307             =item L<Lab::Moose::Instrument::SCPI::Sense::Bandwidth>
308              
309             =item L<Lab::Moose::Instrument::SCPI::Sense::Frequency>
310              
311             =item L<Lab::Moose::Instrument::SCPI::Sense::Sweep>
312              
313             =item L<Lab::Moose::Instrument::SCPI::Source::Power>
314              
315             =item L<Lab::Moose::Instrument::SCPI::Initiate>
316              
317             =item L<Lab::Moose::Instrument::SCPIBlock>
318              
319             =back
320              
321             =head1 COPYRIGHT AND LICENSE
322              
323             This software is copyright (c) 2023 by the Lab::Measurement team; in detail:
324              
325             Copyright 2016 Simon Reinhardt
326             2017 Andreas K. Huettel, Simon Reinhardt
327             2018 Simon Reinhardt
328             2020 Andreas K. Huettel
329              
330              
331             This is free software; you can redistribute it and/or modify it under
332             the same terms as the Perl 5 programming language system itself.
333              
334             =cut