File Coverage

blib/lib/Lab/Instrument/Vectormagnet.pm
Criterion Covered Total %
statement 20 285 7.0
branch 0 96 0.0
condition 0 27 0.0
subroutine 7 25 28.0
pod 0 17 0.0
total 27 450 6.0


line stmt bran cond sub pod time code
1             package Lab::Instrument::Vectormagnet;
2             #ABSTRACT: ???
3             $Lab::Instrument::Vectormagnet::VERSION = '3.881';
4 1     1   1768 use v5.20;
  1         4  
5              
6 1     1   7 use strict;
  1         3  
  1         28  
7 1     1   6 use Time::HiRes qw/usleep/, qw/time/;
  1         2  
  1         8  
8 1     1   718 use Math::Trig;
  1         14193  
  1         157  
9 1     1   12 use Lab::Instrument::IPS;
  1         3  
  1         34  
10 1     1   8 use Lab::XPRESS::hub;
  1         2  
  1         38  
11 1     1   10 use Lab::Generic;
  1         2  
  1         3504  
12              
13             our @ISA = ('Lab::Generic');
14              
15             sub new {
16              
17 0     0 0   my $proto = shift;
18 0   0       my $class = ref($proto) || $proto;
19 0           my $self = $class->SUPER::new(@_);
20 0           my @args = @_;
21              
22 0           my $hub = new Lab::XPRESS::hub();
23              
24             # init the three IPS-instuments for the 3d-vector magnet:
25 0 0         if ( $args[0] =~ /GPIB|gpib/ ) {
26 0           my $gpib_board = 0;
27 0           my $gpib_address_x = 1;
28 0           my $gpib_address_y = 2;
29 0           my $gpib_address_z = 3;
30              
31 0           $self->{IPS_x} = new Lab::Instrument::IPS(
32             {
33             'connection_type' => 'VISA_GPIB',
34             'gpib_address' => $gpib_address_x
35             }
36             );
37 0           $self->{IPS_y} = new Lab::Instrument::IPS(
38             {
39             'connection_type' => 'VISA_GPIB',
40             'gpib_address' => $gpib_address_y
41             }
42             );
43 0           $self->{IPS_z} = new Lab::Instrument::IPS(
44             {
45             'connection_type' => 'VISA_GPIB',
46             'gpib_address' => $gpib_address_z
47             }
48             );
49             }
50             else {
51              
52 0           $self->{IPS_x} = new Lab::Instrument::IPS(
53             {
54             'connection' => $hub->Connection(
55             'VISA_RS232', { 'rs232_address' => 7 }
56             )
57             }
58             );
59 0           $self->{IPS_y} = new Lab::Instrument::IPS(
60             {
61             'connection' => $hub->Connection(
62             'VISA_RS232', { 'rs232_address' => 8 }
63             )
64             }
65             );
66 0           $self->{IPS_z} = new Lab::Instrument::IPS(
67             {
68             'connection' => $hub->Connection(
69             'VISA_RS232', { 'rs232_address' => 9 }
70             )
71             }
72             );
73             }
74              
75             # set limits:
76             $self->{IPS_x}->{LIMITS} = {
77 0           'magneticfield' => 1,
78             'field_intervall_limits' => [ 0, 1 ],
79             'rate_intervall_limits' => [ 0.6, 0.01 ]
80             };
81             $self->{IPS_y}->{LIMITS} = {
82 0           'magneticfield' => 1,
83             'field_intervall_limits' => [ 0, 1 ],
84             'rate_intervall_limits' => [ 0.6, 0.01 ]
85             };
86             $self->{IPS_z}->{LIMITS} = {
87 0           'magneticfield' => 1,
88             'field_intervall_limits' => [ 0, 1 ],
89             'rate_intervall_limits' => [ 0.6, 0.01 ]
90             };
91              
92             # init magnets:
93 0           $self->{IPS_x}->_init_magnet();
94 0           $self->{IPS_y}->_init_magnet();
95 0           $self->{IPS_z}->_init_magnet();
96              
97             # set ID:
98 0           $self->{IPS_x}->{ID} = 'IPS_x';
99 0           $self->{IPS_y}->{ID} = 'IPS_Y';
100 0           $self->{IPS_z}->{ID} = 'IPS_Z';
101              
102             # set xy-plane as the initial sweeping plane:
103 0           $self->{KAPPA} = 0;
104 0           $self->{RHO} = 0;
105 0           $self->{STARTING_SPEED} = 0.6;
106              
107             # register instrument:
108 0           push( @{ ${Lab::Instrument::INSTRUMENTS} }, $self );
  0            
109 0           return $self
110              
111             }
112              
113       0 0   sub create_header { }
114              
115             sub get_config_data {
116 0     0 0   my $self = shift;
117 0           return $self;
118             }
119              
120             sub abort {
121 0     0 0   my $self = shift;
122              
123 0           $self->{IPS_x}->abort();
124 0           $self->{IPS_y}->abort();
125 0           $self->{IPS_z}->abort();
126              
127 0           return;
128              
129             }
130              
131             sub trg {
132 0     0 0   my $self = shift;
133              
134 0           $self->{IPS_x}->trg();
135 0           $self->{IPS_y}->trg();
136 0           $self->{IPS_z}->trg();
137              
138 0           return;
139             }
140              
141             sub active {
142              
143             # returns a value > 0 if MAGNET is SWEEPING. Else MAGNET is not sweeping.
144 0     0 0   my $self = shift;
145              
146 0           my $active_x = $self->{IPS_x}->active();
147 0           my $active_y = $self->{IPS_y}->active();
148 0           my $active_z = $self->{IPS_z}->active();
149              
150 0 0 0       if ( $active_x or $active_y or $active_z ) {
      0        
151 0           return 1;
152             }
153             else {
154 0           return 0;
155             }
156              
157             }
158              
159             sub wait {
160              
161             # waits while magnets are sweeping
162 0     0 0   my $self = shift;
163 0           my $seconds = shift;
164 0           my $min = 0.5;
165              
166 0           my $time_0 = time();
167              
168 0 0         if ( not defined $seconds ) {
169 0           while ( $self->active() ) {
170              
171             #wait ...
172             }
173 0           return 0;
174             }
175             else {
176 0           while ( $self->active() ) {
177 0           my $time_1 = time();
178 0 0         if ( ( $time_1 - $time_0 ) > ( $seconds - $min ) ) {
179 0 0         if ( ( $seconds - ( $time_1 - $time_0 ) ) < 0 ) {
180 0           last;
181             }
182 0           usleep( ( $seconds - ( $time_1 - $time_0 ) ) * 1e6 );
183 0           last;
184             }
185             }
186 0           return 0;
187             }
188              
189             }
190              
191             sub get_value {
192 0     0 0   my $self = shift;
193 0           return $self->get_field(@_);
194             }
195              
196             sub get_field {
197 0     0 0   my $self = shift;
198 0           my ( $mode, $tail ) = $self->_check_args( \@_, ['mode'] );
199 0           my @field;
200              
201 0 0         if ( $tail->{read_mode} eq 'request' ) {
202 0           $self->{IPS_x}->get_field( { read_mode => 'request' } );
203 0           $self->{IPS_y}->get_field( { read_mode => 'request' } );
204 0           $self->{IPS_z}->get_field( { read_mode => 'request' } );
205             }
206              
207 0           my $x = $self->{IPS_x}->get_field();
208 0           my $y = $self->{IPS_y}->get_field();
209 0           my $z = $self->{IPS_z}->get_field();
210              
211 0 0         if ( not defined $mode ) {
212 0           $mode = 's';
213             }
214              
215 0 0         if ( $mode =~ /^(spherical|SPHERICAL|s|s)$/ ) {
    0          
    0          
216              
217             # returns BR, PHI, THETA:
218 0           my ( $r, $phi, $theta ) = cartesian_to_spherical( $x, $y, $z );
219 0           $phi = ( $phi / pi ) * 180;
220 0           $theta = ( $theta / pi ) * 180;
221 0           $self->{value} = [ $r, $theta, $phi ];
222             }
223             elsif ( $mode =~ /^(cartesian|CARTESIAN|C|c)$/ ) {
224              
225             # returns X, Y, Z:
226 0           $self->{value} = [ $x, $y, $z ];
227             }
228             elsif ( $mode =~ /^(all|ALL|A|a)$/ ) {
229              
230             # returns BR, PHI, THETA, X, Y, Z:
231 0           my ( $r, $phi, $theta ) = cartesian_to_spherical( $x, $y, $z );
232 0           $phi = ( $phi * 180 ) / pi;
233 0           $theta = ( $theta * 180 ) / pi;
234 0 0 0       $r
235             = ( ( $theta <= 90 ) and ( $phi > -90 and $phi <= 90 ) )
236             ? $r
237             : -$r;
238 0           $self->{value} = [ $r, $theta, $phi, $x, $y, $z ];
239             }
240              
241 0 0         if ( wantarray() ) {
242 0           return @{ $self->{value} };
  0            
243             }
244             else {
245 0           return $self->{value};
246             }
247              
248             }
249              
250             sub change_plane {
251 0     0 0   my $self = shift;
252 0           my $kappa = shift;
253 0           my $rho = shift;
254 0           my $starting_speed = shift;
255 0 0         if ( defined $kappa ) {
256 0           $self->{KAPPA} = $kappa;
257             }
258 0 0         if ( defined $rho ) {
259 0           $self->{RHO} = $rho;
260             }
261 0 0         if ( defined $starting_speed ) {
262 0           $self->{STARTING_SPEED} = $starting_speed;
263             }
264             }
265              
266             sub config_CIRC_sweep {
267 0     0 0   my $self = shift;
268              
269 0           my ( $B_R, $phi_start, $phi_stop, $v_phi, $interval, $resolution )
270             = $self->_check_args(
271             \@_,
272             [ 'b_r', 'phi_start', 'phi_stop', 'rate', 'interval', 'resolution' ]
273             );
274              
275 0 0         if ( not defined $interval ) {
276 0           $interval = 1;
277             }
278 0 0         if ( not defined $v_phi ) {
279 0           $v_phi = 1;
280             }
281 0 0         if ( not defined $resolution ) {
282 0           $resolution = ( $v_phi / 60 ) * 2 * $interval;
283             }
284 0 0         if ( not defined $phi_start ) {
285 0           $phi_start = -180;
286             }
287 0 0         if ( not defined $phi_stop ) {
288 0           $phi_stop = +180;
289             }
290 0 0         if ( not defined $B_R ) {
291 0           die "B_R is mandatory value in sub config_CIRC_sweep\n";
292             }
293              
294 0           my ( $x, $y, $z, $vx, $vy, $vz ) = $self->create_basic_trace(
295             $B_R, $phi_start, $phi_stop, $v_phi,
296             $resolution
297             );
298 0           my @x = @$x;
299 0           my @y = @$y;
300 0           my @z = @$z;
301 0           my @vx = @$vx;
302 0           my @vy = @$vy;
303 0           my @vz = @$vz;
304              
305             # my $l = @x;
306              
307             # use Lab::XPRESS::Data::XPRESS_DataFile;
308              
309             # my $file = new Lab::XPRESS::Data::XPRESS_DataFile('test.dat');
310             # $file->add_column('X');
311             # $file->add_column('Y');
312             # $file->add_column('Z');
313             # $file->add_column('VX');
314             # $file->add_column('VY');
315             # $file->add_column('VZ');
316              
317             # for (my $i = 0; $i < $l; $i++) {
318              
319             # $file->LOG({
320             # 'X' => $x[$i],
321             # 'Y' => $y[$i],
322             # 'Z' => $z[$i],
323             # 'VX' => $vx[$i],
324             # 'VY' => $vy[$i],
325             # 'VZ' => $vz[$i],
326             # });
327             # }
328              
329             # exit;
330              
331 0           my ( $x_c, $y_c, $z_c ) = $self->get_field('C');
332 0 0         if ( ( $x_c, $y_c, $z_c ) != ( $x[0], $y[0], $z[0] ) ) {
333             $self->config_DIR_sweep(
334             $x[0], $y[0], $z[0], $self->{STARTING_SPEED},
335 0           1, 'C'
336             );
337 0           print "Goto starting point...";
338 0           $self->trg();
339 0           $self->wait();
340 0           print " done\n";
341             }
342              
343 0           my @X = $self->{IPS_x}->config_sweep( \@x, \@vx, $interval );
344 0           my @Y = $self->{IPS_y}->config_sweep( \@y, \@vy, $interval );
345 0           my @Z = $self->{IPS_z}->config_sweep( \@z, \@vz, $interval );
346              
347 0           my @r;
348             my @phi;
349 0           my @theta;
350 0           my @dphi;
351 0           my $len_x = @X;
352 0           my $len_y = @Y;
353 0           my $len_z = @Z;
354 0 0         my $len = ( $len_x >= $len_y ) ? $len_x : $len_y;
355 0 0         $len = ( $len >= $len_z ) ? $len : $len_z;
356              
357 0           for ( my $i = 0; $i < $len; $i++ ) {
358 0 0         if ( ( my $len_x = @X ) >= $i ) {
359 0           push( @X, $X[-1] );
360             }
361 0 0         if ( ( my $len_y = @Y ) >= $i ) {
362 0           push( @Y, $Y[-1] );
363             }
364 0 0         if ( ( my $len_z = @Z ) >= $i ) {
365 0           push( @Z, $Z[-1] );
366             }
367 0           ( $r[$i], $phi[$i], $theta[$i] )
368             = cartesian_to_spherical( $X[$i], $Y[$i], $Z[$i] );
369 0           $phi[$i] = ( $phi[$i] * 180 ) / pi;
370 0           $theta[$i] = ( $theta[$i] * 180 ) / pi;
371 0           $dphi[$i] = $phi[$i] - $phi[0];
372             }
373              
374 0           return \@r, \@theta, \@phi, \@dphi, \@X, \@Y, \@Z;
375              
376             }
377              
378             sub config_DIR_sweep {
379 0     0 0   my $self = shift;
380              
381 0           my ( $B_R, $theta, $phi, $rate, $interval, $mode ) = $self->_check_args(
382             \@_,
383             [ 'b_r', 'theta', 'phi', 'rate', 'interval', 'mode' ]
384             );
385              
386 0           my ( $x_1, $y_1, $z_1 );
387              
388 0 0 0       if ( $mode =~ /^(cartesian|CARTESIAN|c|C)$/ ) {
    0          
389 0           $x_1 = $B_R;
390 0           $y_1 = $theta;
391 0           $z_1 = $phi;
392             }
393             elsif ( not defined $mode or $mode =~ /^(spherical|SPHERICAL|s|S)$/ ) {
394 0           $mode = 'spherical';
395 0           $B_R = abs($B_R);
396 0           ( $x_1, $y_1, $z_1 ) = spherical_to_cartesian(
397             $B_R, pi * $phi / 180,
398             pi * $theta / 180
399             );
400             }
401             else {
402 0           die
403             "Give mode for magnetic field sweep in Vectormagnet is not supported. \n";
404             }
405              
406 0 0         if ( not defined $interval ) {
407 0           $interval = 1;
408             }
409              
410 0 0         if ( ( $x_1**2 + $y_1**2 + $z_1**2 ) > 1.01 ) {
411 0           die
412             "unexpected values in sub config_DIR_sweep. Magnetude of target magnetic field > 1 Tesla.";
413             }
414              
415 0 0         if ( $rate <= 0 ) {
416 0           die "unexpected value for RATE ($rate) in sub config_DIR_sweep.";
417             }
418              
419             # get current magnetic field:
420 0           my ( $x_0, $y_0, $z_0 ) = $self->get_field('C');
421              
422             #calculate sweep parameter:
423 0           my $trace_length
424             = ( ( $x_1 - $x_0 )**2 + ( $y_1 - $y_0 )**2 + ( $z_1 - $z_0 )**2 )
425             **0.5;
426 0           my $sweep_time = $trace_length / $rate;
427 0           my $rate_x;
428             my $rate_y;
429 0           my $rate_z;
430              
431 0 0         if ( $sweep_time == 0 ) {
432 0           $rate_x = 0.1;
433 0           $rate_y = 0.1;
434 0           $rate_z = 0.1;
435             }
436             else {
437 0           $rate_x = abs( ( $x_1 - $x_0 ) / $sweep_time );
438 0           $rate_y = abs( ( $y_1 - $y_0 ) / $sweep_time );
439 0           $rate_z = abs( ( $z_1 - $z_0 ) / $sweep_time );
440             }
441              
442             # config sweep:
443 0           my @X = $self->{IPS_x}->config_sweep( $x_1, $rate_x, $interval );
444 0           my @Y = $self->{IPS_y}->config_sweep( $y_1, $rate_y, $interval );
445 0           my @Z = $self->{IPS_z}->config_sweep( $z_1, $rate_z, $interval );
446              
447             # calculate trace:
448 0           my @r;
449             my @phi;
450 0           my @theta;
451 0           my @dphi;
452 0           my $len_x = @X;
453 0           my $len_y = @Y;
454 0           my $len_z = @Z;
455 0 0         my $len = ( $len_x >= $len_y ) ? $len_x : $len_y;
456 0 0         $len = ( $len >= $len_z ) ? $len : $len_z;
457              
458 0           for ( my $i = 0; $i < $len; $i++ ) {
459 0 0         if ( ( my $len_x = @X ) >= $i ) {
460 0           push( @X, $X[-1] );
461             }
462 0 0         if ( ( my $len_y = @Y ) >= $i ) {
463 0           push( @Y, $Y[-1] );
464             }
465 0 0         if ( ( my $len_z = @Z ) >= $i ) {
466 0           push( @Z, $Z[-1] );
467             }
468 0           ( $r[$i], $phi[$i], $theta[$i] )
469             = cartesian_to_spherical( $X[$i], $Y[$i], $Z[$i] );
470 0           $phi[$i] = ( $phi[$i] * 180 ) / pi;
471 0           $theta[$i] = ( $theta[$i] * 180 ) / pi;
472 0           $dphi[$i] = $phi[$i] - $phi[0];
473             }
474              
475 0           printf(
476             "Vectormagnet: estimate total duration for sweep: %dm %ds\n",
477             ( ( $len / 60 ) * $interval ),
478             ( ( ( $len / 60 ) * $interval ) % 1 ) * 60
479             );
480 0           return \@r, \@theta, \@phi, \@dphi, \@X, \@Y, \@Z;
481              
482             }
483              
484             sub cartesian_to_spherical {
485 0     0 0   my ( $x, $y, $z ) = @_;
486              
487 0           my $rho = sqrt( $x * $x + $y * $y + $z * $z );
488              
489 0 0         return ( $rho, atan2( $y, $x ), $rho ? acos_real( $z / $rho ) : 0 );
490             }
491              
492             sub spherical_to_cartesian {
493 0     0 0   my ( $rho, $theta, $phi ) = @_;
494              
495             return (
496 0           $rho * cos($theta) * sin($phi),
497             $rho * sin($theta) * sin($phi),
498             $rho * cos($phi)
499             );
500             }
501              
502             sub acos_real {
503 0 0   0     return 0 if $_[0] >= 1;
504 0 0         return pi if $_[0] <= -1;
505 0           return acos( $_[0] );
506             }
507              
508             sub Trafo_RHO {
509 0     0 0   my $self = shift;
510 0           my $x = shift;
511 0           my $y = shift;
512 0           my $z = shift;
513              
514             my $X = $x * cos( pi * $self->{RHO} / 180 )
515 0           + $y * cos( pi * ( $self->{RHO} + 90 ) / 180 );
516             my $Y = $x * cos( pi * ( $self->{RHO} - 90 ) / 180 )
517 0           + $y * cos( pi * $self->{RHO} / 180 );
518 0           my $Z = $z;
519              
520 0           return $X, $Y, $Z;
521              
522             }
523              
524             sub Trafo_KAPPA {
525 0     0 0   my $self = shift;
526 0           my $x = shift;
527 0           my $y = shift;
528 0           my $z = shift;
529              
530             my $X = $x * cos( pi * $self->{KAPPA} / 180 )
531 0           + $z * cos( pi * ( 90 - $self->{KAPPA} ) / 180 );
532 0           my $Y = $y;
533             my $Z = $x * cos( pi * ( $self->{KAPPA} - 90 ) / 180 )
534 0           + $z * cos( -1 * pi * $self->{KAPPA} / 180 );
535              
536 0           return $X, $Y, $Z;
537              
538             }
539              
540             sub create_basic_trace {
541 0     0 0   my $self = shift;
542 0           my $R = shift;
543 0           my $phi_start = shift;
544 0           my $phi_stop = shift;
545 0           my $v = shift;
546 0           my $resolution = shift;
547              
548             # resoltion:
549 0 0 0       if ( not defined $resolution
      0        
      0        
      0        
550             or not defined $v
551             or not defined $phi_start
552             or not defined $phi_stop
553             or not defined $R ) {
554 0           die
555             "ERROR in sub 'create_basic_trace'. Some of the parameters are not defined.";
556             }
557              
558             # calculate magnet sweep trace points:
559 0           my @x;
560             my @y;
561 0           my @z;
562 0           my $n = 0;
563 0           print "PHI STOP" . $phi_stop . "\n";
564 0           print "Res = " . $resolution . "\n";
565 0           for ( my $i = $phi_start; $i < $phi_stop; $i += $resolution ) {
566              
567 0           $x[$n] = $R * cos( pi * ($i) / 180 );
568 0           $y[$n] = $R * sin( pi * ($i) / 180 );
569 0           $z[$n] = 0;
570              
571             #print $x[$n]."\t".$y[$n]."\t".$z[$n]."\n";
572 0           ( $x[$n], $y[$n], $z[$n] )
573             = $self->Trafo_KAPPA( $x[$n], $y[$n], $z[$n] );
574 0           ( $x[$n], $y[$n], $z[$n] )
575             = $self->Trafo_RHO( $x[$n], $y[$n], $z[$n] );
576 0           $n++;
577             }
578              
579             # Add Phi_stop (final value) to points array
580 0           $x[$n] = $R * cos( pi * ($phi_stop) / 180 );
581 0           $y[$n] = $R * sin( pi * ($phi_stop) / 180 );
582 0           $z[$n] = 0;
583              
584             #print $x[$n]."\t".$y[$n]."\t".$z[$n]."\n";
585 0           ( $x[$n], $y[$n], $z[$n] ) = $self->Trafo_KAPPA( $x[$n], $y[$n], $z[$n] );
586 0           ( $x[$n], $y[$n], $z[$n] ) = $self->Trafo_RHO( $x[$n], $y[$n], $z[$n] );
587              
588             # calculate magnet sweep rate:
589 0           my @vx;
590             my @vy;
591 0           my @vz;
592 0           my $len = @x;
593 0           for ( my $i = 0; $i < $len; $i++ ) {
594 0 0         $vx[$i] = abs(
    0          
595             (
596             $x[ ( $i == $len - 1 ) ? $i : $i + 1 ]
597             - $x[ ( $i == $len - 1 ) ? $i - 1 : $i ]
598             ) / ( ( $resolution / $v ) )
599             );
600 0 0         $vy[$i] = abs(
    0          
601             (
602             $y[ ( $i == $len - 1 ) ? $i : $i + 1 ]
603             - $y[ ( $i == $len - 1 ) ? $i - 1 : $i ]
604             ) / ( ( $resolution / $v ) )
605             );
606 0 0         $vz[$i] = abs(
    0          
607             (
608             $z[ ( $i == $len - 1 ) ? $i : $i + 1 ]
609             - $z[ ( $i == $len - 1 ) ? $i - 1 : $i ]
610             ) / ( ( $resolution / $v ) )
611             );
612             }
613              
614             #open LOG2, ">test2.dat";
615             #my $len = @x;
616             #for ( my $i =0; $i < $len; $i++)
617             # {
618             # print LOG2 $x[$i]."\t".$y[$i]."\t".$z[$i]."\t".$vx[$i]."\t".$vy[$i]."\t".$vz[$i]."\n";
619             # }
620             #close LOG2;
621              
622 0           return \@x, \@y, \@z, \@vx, \@vy, \@vz;
623              
624             }
625              
626             1;
627              
628             __END__
629              
630             =pod
631              
632             =encoding UTF-8
633              
634             =head1 NAME
635              
636             Lab::Instrument::Vectormagnet - ???
637              
638             =head1 VERSION
639              
640             version 3.881
641              
642             =head1 COPYRIGHT AND LICENSE
643              
644             This software is copyright (c) 2023 by the Lab::Measurement team; in detail:
645              
646             Copyright 2013 Christian Butschkow
647             2014 Andreas K. Huettel
648             2015 Christian Butschkow
649             2016 Christian Butschkow, Simon Reinhardt
650             2017 Andreas K. Huettel
651             2020 Andreas K. Huettel
652              
653              
654             This is free software; you can redistribute it and/or modify it under
655             the same terms as the Perl 5 programming language system itself.
656              
657             =cut