ojagan has asked for the wisdom of the Perl Monks concerning the following question:

Hi everyone, I am writing a code in perl to combine two euler matrices. As part of the code, I have a sub which converts the given euler angles to its corresponding rotation matrix. However, I seem to be having an issue returning the array in the sub. I have pasted the code of the sub below -
sub eul2mat { my $phi_rad=deg2rad($_[0]); my $theta_rad=deg2rad($_[1]); my $psi_rad=deg2rad($_[2]); print "$phi_rad $theta_rad $psi_rad\n"; my @rot_mat; $rot_mat[0][0]=cos($phi_rad)*cos($theta_rad)*cos($psi_rad)-sin +($phi_rad)*sin($psi_rad); $rot_mat[0][1]=cos($phi_rad)*cos($theta_rad)*sin($psi_rad)+sin +($phi_rad)*cos($psi_rad); $rot_mat[0][2]=-cos($phi_rad)*sin($theta_rad); $rot_mat[1][0]=-sin($phi_rad)*cos($theta_rad)*cos($psi_rad)-si +n($psi_rad)*cos($phi_rad); $rot_mat[1][1]=-sin($phi_rad)*cos($theta_rad)*sin($psi_rad)+co +s($phi_rad)*cos($psi_rad); $rot_mat[1][2]=sin($phi_rad)*sin($theta_rad); $rot_mat[2][0]=sin($theta_rad)*cos($psi_rad); $rot_mat[2][1]=sin($theta_rad)*sin($psi_rad); $rot_mat[2][2]=cos($theta_rad); print "@rot_mat"; print "\n"; return @rot_mat; }
I was able to take the three angles into the sub but when I compute the matrix and return it, I get the reference to the array instead of the array itself. I am not sure why that is happening. Here is what the output looks like -
0 0 0 ARRAY(0xad80a0) ARRAY(0xad8190) ARRAY(0xb4f618) 2.10905464637947 0.019391480654283 -2.12295165601555 ARRAY(0xad80a0) ARRAY(0xad8190) ARRAY(0xb4f618)
Am I making a silly mistake? Did I miss something?

Update: Thank you everyone. Here is the final code which works perfectly.

#!/usr/bin/perl -w use strict; use warnings; use Data::Dumper; use feature qw/ say /; BEGIN { use constant PI => 3.14159265358979; sub deg2rad { my $degrees = shift; return ($degrees / 180) * PI; } sub rad2deg { my $radians = shift; return ($radians / PI) * 180; } } my $num_args=$#ARGV+1; if($num_args < 3) { print "Usage: combine_doc.pl <doc_file_1> <doc_file_2> <combin +ed_doc>\n"; exit; } chomp(my $indoc_file1_name=$ARGV[0]); chomp(my $indoc_file2_name=$ARGV[1]); chomp(my $outdoc_file_name=$ARGV[2]); open INDOC1,"<$indoc_file1_name" || die "Can't open file: $!"; my $line=0; my @data; my @data_doc1; my $i=0; foreach (<INDOC1>) { if($line==0) { $line++; } elsif($line%2!=0) { @data=split(" ",$_); $data_doc1[$i]=$data[1]; $i++; $line++; } else { @data=split(" ",$_); $data_doc1[$i]=$data[2]; $data_doc1[$i+1]=$data[3]; $data_doc1[$i+2]=$data[4]; $data_doc1[$i+3]=$data[5]; $data_doc1[$i+4]=$data[6]; $data_doc1[$i+5]=$data[7]; $i+=6; $line++; } } print scalar(@data_doc1); print "\n"; #print "@data_doc1"; #print "\n"; open INDOC2,"<$indoc_file2_name" || die "Can't open file: $!"; $line=0; my @data_doc2; $i=0; foreach (<INDOC2>) { if($line==0) { $line++; } elsif($line%2!=0) { @data=split(" ",$_); $data_doc2[$i]=$data[1]; $i++; $line++; } else { @data=split(" ",$_); $data_doc2[$i]=$data[2]; $data_doc2[$i+1]=$data[3]; $data_doc2[$i+2]=$data[4]; $data_doc2[$i+3]=$data[5]; $data_doc2[$i+4]=$data[6]; $data_doc2[$i+5]=$data[7]; $i+=6; $line++; } } #print scalar(@data_doc2); #print "\n"; #print "@data_doc2"; #print "\n"; open OUTDOC,">$outdoc_file_name" || die "Can't open file: $!"; print OUTDOC " ; Headerinfo columns: rot (1), tilt (2), psi (3), Xoff +(4), Yoff (5), Zoff (6), Ref (7), Wedge (8), Pmax/sumP (9), LL (10)\n +"; my $rot_mat1; my $rot_mat2; my $part_id; my $cbn_mat; my @cbn_angles; my @cbn_shifts; for($i=1;$i<scalar(@data_doc1);$i+=7) { #print "$i $data_doc1[$i] $data_doc1[$i+1] $data_doc1[$i+2]\n" +; #print "$i $data_doc2[$i] $data_doc2[$i+1] $data_doc2[$i+2]\n" +; $rot_mat1=eul2mat($data_doc1[$i],$data_doc1[$i+1],$data_doc1[$ +i+2]); #Catch the reference to the first matrix $rot_mat2=eul2mat($data_doc2[$i],$data_doc2[$i+1],$data_doc2[$ +i+2]); #Catch the reference to the second matrix $cbn_mat=mat_multiply($rot_mat1,$rot_mat2); #Multiply the two +matrices @cbn_angles=mat2eul($cbn_mat); #Convert the matrix back to eul +er print "@cbn_angles"; print "\n"; $cbn_shifts[0]=$data_doc1[$i+3]+$data_doc2[$i+3]; $cbn_shifts[1]=$data_doc1[$i+4]+$data_doc2[$i+4]; $cbn_shifts[2]=$data_doc1[$i+5]+$data_doc2[$i+5]; print OUTDOC " ; $data_doc1[$i-1]\n"; $part_id=(($i-1)/7)+1; printf OUTDOC "%6d%3d%11.5f%11.5f%11.5f%11.5f%11.5f%11.5f%11.5 +f%11.5f%11.5f%11.5f\n",$part_id,10,$cbn_angles[0],$cbn_angles[1],$cbn +_angles[2],$cbn_shifts[0],$cbn_shifts[1],$cbn_shifts[2],1,1,0,0; } sub eul2mat { my $phi_rad=deg2rad($_[0]); my $theta_rad=deg2rad($_[1]); my $psi_rad=deg2rad($_[2]); #print "$phi_rad $theta_rad $psi_rad\n"; my @rot_mat; $rot_mat[0][0]=cos($phi_rad)*cos($theta_rad)*cos($psi_rad)-sin +($phi_rad)*sin($psi_rad); $rot_mat[0][1]=cos($phi_rad)*cos($theta_rad)*sin($psi_rad)+sin +($phi_rad)*cos($psi_rad); $rot_mat[0][2]=-cos($phi_rad)*sin($theta_rad); $rot_mat[1][0]=-sin($phi_rad)*cos($theta_rad)*cos($psi_rad)-si +n($psi_rad)*cos($phi_rad); $rot_mat[1][1]=-sin($phi_rad)*cos($theta_rad)*sin($psi_rad)+co +s($phi_rad)*cos($psi_rad); $rot_mat[1][2]=sin($phi_rad)*sin($theta_rad); $rot_mat[2][0]=sin($theta_rad)*cos($psi_rad); $rot_mat[2][1]=sin($theta_rad)*sin($psi_rad); $rot_mat[2][2]=cos($theta_rad); #say Dumper \@rot_mat; return \@rot_mat; } sub mat_multiply { my ($mat1,$mat2)=@_; my ($i,$j,$k); my $result_mat=[]; for $i (0..2) { for $j (0..2) { for $k (0..2) { $result_mat->[$i][$j]+=$mat1->[$i][$k] + * $mat2->[$k][$j]; } } } return $result_mat; } sub mat2eul { my @cbn_mat=@{$_[0]}; my $theta_deg=rad2deg(atan2((sqrt($cbn_mat[2][0]**2+$cbn_mat[2 +][1]**2)),$cbn_mat[2][2])); my $phi_deg=rad2deg(atan2(($cbn_mat[1][2]/sin(deg2rad($theta_d +eg))),(-$cbn_mat[0][2]/sin(deg2rad($theta_deg))))); my $psi_deg=rad2deg(atan2(($cbn_mat[2][1]/sin(deg2rad($theta_d +eg))),($cbn_mat[2][0]/sin(deg2rad($theta_deg))))); return ($theta_deg,$phi_deg,$psi_deg); }
The program reads two files and pulls out euler angles from them which need to be combined and writes the combined output to another file. Please let me know if you have any other suggestions on the way it is written (i.e. if you think some things can be written in a different way). I would definitely like to improve the way I write my programs. Thank you all once again.

Replies are listed 'Best First'.
Re: Question about returning array from sub
by hippo (Archbishop) on Jul 10, 2015 at 12:43 UTC

    @rot_mat is an array of array references, that's why you see three fields in the output from your print statement. If you wanted to print all values of $rot_mat[0] then you would need to do

    print @{$rot_mat[0]};

    or just use Data::Dumper on \@rot_mat to get the full tree. See perldsc for more on these nested structures and the section "Access and Printing of an ARRAY OF ARRAYS" for this particular question.

      Thanks hippo. That indeed did work. But it is still not clear to me. I did declare the array and assign values to it and not references. So when I want to print the array why does it print the array with references.
        Perl does multidimensional arrays by storing elements of the sub-array in the parent array. So
        $x[0][1] = 12;

        is the syntactic-sugar equivalent of

        $x[0]->[1] = 12;

        (IIRC, in Perl 4, this was the required invocation.)

        And the whole sub-array as

        $x[0] = \@subarray;

        You might also think about it like this:

        @x = ( ["some", "sub", "array"], ["next", "sub", "array] );

        which is making anonymous arrays, and assigning the lot to @x. And these can be nested as deeply as you have memory for (AFAIK, but that would be a good exercise to check).

        Update:

        Yes, it seems that simply nesting empty arrays keeps going until it runs out of memory, starts swapping, and in my case, is killed by the OS.

        -QM
        --
        Quantum Mechanics: The dreams stuff is made of

Re: Question about returning array from sub
by 1nickt (Canon) on Jul 10, 2015 at 13:00 UTC

    Data::Dumper will help you immeasurably when working with complex data structures.

    use Data::Dumper; say Dumper \@rot_mat;

    When you did

    $rot_mat[0][0]=cos($phi_rad)*cos($theta_rad)*cos($psi_rad)-sin($phi_ra +d)*sin($psi_ +rad);

    Perl automagically created an anonymous array as the value of the first element in the array, and assigned your data to the first element of the anonymous array.

    You just need to dereference it.

    #!/usr/bin/env perl use strict; use warnings; use Data::Dumper; use feature qw/ say /; my @a; $a[0][0] = 'foo'; $a[0][1] = 'bar'; $a[1][0] = 'baz'; $a[1][1] = 'quux'; say Dumper @a; say $a[0]; for ( @a ) { say (join ' ', @{ $_ }); }
    [05:58][nick:~/monks]$ perl 1134141.pl $VAR1 = [ 'foo', 'bar' ]; $VAR2 = [ 'baz', 'quux' ]; ARRAY(0x7feb6b006280) foo bar baz quux
    Remember: Ne dederis in spiritu molere illegitimi!
      The Dumper output may be more intuitive if it is passed a reference to the array:
      say Dumper \@a;

      -QM
      --
      Quantum Mechanics: The dreams stuff is made of

        This is output of the Dumper -
        $VAR1 = [ [ 1, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, '1' ] ]; $VAR1 = [ [ '0.99985288395361', '-0.0139786208260096', '0.00994025198128738' ], [ '0.0138118911491265', '0.999766001562737', '0.0166485369370161' ], [ '-0.0101706495630081', '-0.0165087939917233', '0.999811991130535' ] ];
      That indeed showed me the values as expected. So, in the case of a 2D array, the return statement returns the array of array of references instead of the array of array of values. Is that right? If so, I need some advice because I then use this 2D array as input to the next sub which multiplies two matrices. I show part of the main code and sub code below -
      @rot_mat1=eul2mat($data_doc1[$i],$data_doc1[$i+1],$data_doc1[$i+2]); @rot_mat2=eul2mat($data_doc2[$i],$data_doc2[$i+1],$data_doc2[$ +i+2]); @cbn_mat=mat_multiply(\@rot_mat1,\@rot_mat2); sub eul2mat { my $phi_rad=deg2rad($_[0]); my $theta_rad=deg2rad($_[1]); my $psi_rad=deg2rad($_[2]); print "$phi_rad $theta_rad $psi_rad\n"; my @rot_mat; $rot_mat[0][0]=cos($phi_rad)*cos($theta_rad)*cos($psi_rad)-sin +($phi_rad)*sin($psi_rad); $rot_mat[0][1]=cos($phi_rad)*cos($theta_rad)*sin($psi_rad)+sin +($phi_rad)*cos($psi_rad); $rot_mat[0][2]=-cos($phi_rad)*sin($theta_rad); $rot_mat[1][0]=-sin($phi_rad)*cos($theta_rad)*cos($psi_rad)-si +n($psi_rad)*cos($phi_rad); $rot_mat[1][1]=-sin($phi_rad)*cos($theta_rad)*sin($psi_rad)+co +s($phi_rad)*cos($psi_rad); $rot_mat[1][2]=sin($phi_rad)*sin($theta_rad); $rot_mat[2][0]=sin($theta_rad)*cos($psi_rad); $rot_mat[2][1]=sin($theta_rad)*sin($psi_rad); $rot_mat[2][2]=cos($theta_rad); say Dumper @rot_mat; return \@rot_mat; } sub mat_multiply { my ($mat1,$mat2)=@_; my ($i,$j,$k); my $result_mat=[]; for $i (0..2) { for $j (0..2) { for $k (0..2) { $result_mat->[$i][$j]+=$mat1->[$i][$k] + * $mat2->[$k][$j]; } } } return $result_mat; }
      So I return the array of array of references in the first two lines. But I need to pass these two arrays to the next sub. Am I making a mistake there?

        You still don't quite have the concepts. There is only a "2-D array" in Perl. See a post from today from Monk::Thomas with some awesome ASCII art depicting Perl arrays.

        At the end you said: I return the array of array of references ...

        Should be: "the array of array references" or "the array of arrayrefs" or "the array of references to arrays" ...

        As for your latest code: (first I would recommend testing with a minimal script like the one I posted above. Then when you have it figured out, start plugging your own code/data back in)

        (for each of two ...)
        • In your first sub you create an actual array eg:
          @rot_mat
        • You populate the array with anonymous arrays containing your data eg:
          $rot_mat[0][0] = 'foo';
        • You return a reference to the main array (to save memory) eg:
          return \@rot_mat;
        • The second sub gets the arrayref by calling the first sub eg:
          my $aref = &first_sub();
        • Second sub really wants the data in the second level of the arrayref. That data is stored in the series of anonymous arrays that compose the first level of the arrayref. So, to access the data we have to access the anonymous arrays, which we do through references to them. eg:
          my $bar = $aref->[0]->[0]; # hard to read
        • Depending on what we are doing, we might copy the anonymous arrays into new arrayrefs inside the second sub (still just pointers to the original in-memory array) eg:
          my $baz = $aref->[0]; my $quux = $aref->[1]; # now $baz is a reference to the anonymous array Perl created # for the first element of @rot_mat when you assigned a value # to $rot_mat[0][0] in the first sub say 'yep' if $baz->[0] eq 'foo';
        • Or maybe just do a double loop to get at the data eg:
          foreach my $anon_array ( @{ $aref } ) { # dereference the main array foreach my $value ( @{ $anon_array } ) { # dereference the anon arra +y say 'yep' if $value eq 'foo'; } }
        • TMTOWTDI

        Hope this helps.

        Remember: Ne dederis in spiritu molere illegitimi!
        Warning: I haven't checked the matrix multiply closely, but it looks OK.

        If you are returning a reference from eul2mat, catch it in a scalar:

        $rot_mat1 = eul2mat(...); $rot_mat2 = eul2mat(...); # Notice the scalar here too, since mat_multiply returns a ref: $cbn_mat = mat_multiply($rot_mat1,$rot_mat2);

        (You can still catch it in an @array, but only the first element is defined.)

        -QM
        --
        Quantum Mechanics: The dreams stuff is made of

Re: Question about returning array from sub
by johngg (Canon) on Jul 10, 2015 at 12:47 UTC

    Each element of @rot_mat is a reference to another array, each of which seem tp contain three elements. Try

    print "@$_\n" for @rot_mat;

    Cheers,

    JohnGG

Re: Question about returning array from sub
by QM (Parson) on Jul 10, 2015 at 12:45 UTC
    Each element of the top level array is itself an arrayref, pointing to another array. You can't say print @array and get what you want in that case (print isn't that smart).

    Can you show a toy sample code of how your are calling and catching the results?

    However, if you use dd from Data::Dumper, or write your own array pretty printer, you'll get what you want.

    -QM
    --
    Quantum Mechanics: The dreams stuff is made of

      Here is what I am doing in the main code -
      @rot_mat1=eul2mat($data_doc1[$i],$data_doc1[$i+1],$data_doc1[$i+2]); @rot_mat2=eul2mat($data_doc2[$i],$data_doc2[$i+1],$data_doc2[$i+2]);
      The $data_doc variables carry the angles to the sub. And I would like to return the array which is the rotation matrix. I instead end up returning the references.

        You are returning the array. The array hold references to other arrays, that have to be dereferenced later when you want to access them.

        Actually, you should probably return a reference to the main array. If you are just returning it to one caller, which uses it and is done, there's no issue. But it's good practice to pass references around rather than making multiple copies of data.

        Your caller will do something like my @bar = function() while &function() is also building up and returning its own array my @foo. Better for the function to return, and the caller to take, a reference to @foo: my $bar = function() and in &function: my @foo = (1,2,3); return \@foo;

        Remember: Ne dederis in spiritu molere illegitimi!
Re: Question about returning array from sub
by GotToBTru (Prior) on Jul 10, 2015 at 13:05 UTC

    That's normal behavior.

    sub backatcha { @a = (1,2,3); return @a; } # @b will have same contents as @a @b = backatcha();
    Dum Spiro Spero