fix rotate 3D object Perl code - perl

I wrote this code using C++ example from net to rotate my set of points in 3D.
##matrix is points and their 3D coordinates.
#rotated_matrix = rotate_l(\#matrix, 0);
sub rotate_l {
my $ref = $_[0];
my $x = 0;
my $step = 1;
#if rotx
if ($_[1] == 0) {
while ($$ref[$x][0]) {
$$ref[$x][1] += ($$ref[$x][1]*cos($step) - $$ref[$x][2]*sin($step));
$$ref[$x][2] += ($$ref[$x][1]*sin($step) + $$ref[$x][2]*cos($step));
$x++;
}
}
#if roty
if ($_[1] == 1) {
while ($$ref[$x][0]) {
$$ref[$x][0] += ( $$ref[$x][0]*cos($step) + $$ref[$x][2]*sin($step));
$$ref[$x][2] += (-$$ref[$x][0]*sin($step) + $$ref[$x][2]*cos($step));
$x++;
}
}
#if rotz
if ($_[1] == 2) {
while ($$ref[$x][0]) {
$$ref[$x][0] += ($$ref[$x][0]*cos($step) - $$ref[$x][1]*sin($step));
$$ref[$x][1] += ($$ref[$x][0]*sin($step) + $$ref[$x][1]*cos($step));
$x++;
}
}
return #$ref;
}
But something is wrong. Object size/form fails to stay same. And my math is not that good to realize why. I even not sure I need += or =?

Thx amon. As suggested this works:
##matrix is points and their 3D coordinates.
#rotated_matrix = rotate_l(\#matrix, 0);
sub rotate_l {
my $ref = $_[0];
my $x = 0;
my $step = pi;
#if rotx
if ($_[1] == 0) {
while ($$ref[$x][0]) {
$$ref[$x][1] = ($$ref[$x][1]*cos($step) - $$ref[$x][2]*sin($step));
$$ref[$x][2] = ($$ref[$x][1]*sin($step) + $$ref[$x][2]*cos($step));
$x++;
}
}
#if roty
if ($_[1] == 1) {
while ($$ref[$x][0]) {
$$ref[$x][0] = ( $$ref[$x][0]*cos($step) + $$ref[$x][2]*sin($step));
$$ref[$x][2] = (-$$ref[$x][0]*sin($step) + $$ref[$x][2]*cos($step));
$x++;
}
}
#if rotz
if ($_[1] == 2) {
while ($$ref[$x][0]) {
$$ref[$x][0] = ($$ref[$x][0]*cos($step) - $$ref[$x][1]*sin($step));
$$ref[$x][1] = ($$ref[$x][0]*sin($step) + $$ref[$x][1]*cos($step));
$x++;
}
}
return #$ref;
}
If I need to rotate not around (0,0,0), but against other point the best way is to translate to 0 point rotate and then translate back?

Just as an example of what I meant by what I recommend you do:
#! /usr/bin/env perl
use common::sense;
use YAML 'Dump';
sub translate {
my ($deltaX, $deltaY) = #{pop()}; # <-- don't mind this.
for (#_) { # <--- this is the important part
$_->[0] += $deltaX;
$_->[1] += $deltaY;
}
#_
}
my #points = ([0, 1], [0, -1], [-1, 0], [1, 0]);
print Dump([translate #points, [2, 2]]);
my $box = \#points;
print Dump([translate #$box, [5, 0]]);

Related

How to debug a script that will kill by system when await multi Promise?

Answer "the script used too much memory".
I start a few start to read text file line by line, and convert the text to blob, then send it to socket:
use experimental :pack;
sub heart-msg(Str $msg, Str $device-id --> Blob) {
my $heart-msg = $msg.substr(0, $msg.chars - 8);
my $header-buf = pack("H*", $heart-msg);
my $device-id-buf = pack("L*", $device-id);
$header-buf ~ $device-id-buf
}
sub deal-message(Str $msg, Str $device-id --> Blob) {
my $now = DateTime.now();
my $year = $now.year;
my $month = $now.month;
my $day = $now.day;
my $hour = $now.hour;
my $minute = $now.minute;
my $second = $now.second.Int;
my $check-number = 0;
my $head-msg = $msg.substr(0,4);
my $device-id-length = $msg.substr(4,2);
my $pf-code = $msg.substr(14, 2);
my $msg-length = $msg.substr(16, 4);
my $rel-time = $msg.substr(20, 4);
my $end-msg = $msg.substr(38, $msg.chars - 38 - 2);
my $head-msg-buf = pack("H*", $head-msg);
my $device-id-len-buf = pack("H*", $device-id-length);
my $device-id-buf = pack("L*", $device-id);
my $pf-code-buf = pack("H*", $pf-code);
my $msg-length-buf = pack("H*", $msg-length);
my $rel-time-buf = pack("H*", $rel-time);
my $year-buf = pack("S*", $year);
my $month-buf = pack("C*", $month);
my $day-buf = pack("C*", $day);
my $hour-buf = pack("C*", $hour);
my $minute-buf = pack("C*", $minute);
my $second-buf = pack("C*", $second);
my $end-msg-buf = pack("H*", $end-msg);
my #bufs = [$device-id-len-buf, $device-id-buf, $pf-code-buf, $msg-length-buf,
$rel-time-buf, $year-buf, $month-buf, $day-buf, $hour-buf, $minute-buf, $second-buf,
$end-msg-buf];
for #bufs -> $byte {
my $byte_sum = [+] $byte.contents;
$check-number += $byte_sum;
}
$check-number = $check-number % 256;
my $checked-msg-buf = pack("C*", $check-number);
[~] $head-msg-buf, |#bufs, $checked-msg-buf
}
sub deal-data(Str $msg, Str $device-id --> Blob) {
my $header = $msg.substr(0, 4);
given $header {
when '6868' {
return deal-message($msg, $device-id);
}
when '7468' {
return heart-msg($msg, $device-id);
}
default { return Buf.new }
}
}
sub MAIN(
:$host = '10.0.0.77',
Int() :$port = 4444,
Rat() :$interval = 0.001,
:$file = 'data.txt',
Int() :$device-num = 169) {
my $conn = IO::Socket::INET.new(:$host, :$port);
my #devices = "modelId.txt".IO.lines;
my #promises = gather for #devices[159..$device-num] -> $device-id {
take start {
my $f = lazy $file.IO.lines;
my $iterator = $f.iterator;
react {
whenever Supply.interval($interval) {
try {
my $line := $iterator.pull-one;
if $line =:= IterationEnd {
done;
} else {
my $blob = deal-data($line.chomp.split(/\s+/).tail, $device-id.Str);
#say $blob;
$conn.write($blob);
}
}
QUIT {
$conn.close;
say 'connecting closed';
default {
say .^name, '→ ', .Str;
say "handled in line $?LINE";
}
}
LAST {
say "Connection closed";
done;
}
}
}
}
}
await #promises;
}
When running on CentOS 7.4(12 core, 32G RAM), after a few seconds, my script was killed by system. When running on Win11(12 core, 16G RAM), it's OK.
So how to debug this script?

Check if array elements are undefined from an XSUB

I am trying to check if an array element is undef from an XSUB like this:
void
print_array(array)
AV *array
PREINIT:
int len;
SV **sv_ptr;
SV *sv;
int i;
CODE:
len = av_len(array) + 1;
printf("[");
for (i = 0; i < len; i++) {
sv_ptr = av_fetch( array, i, 0 );
if (!sv_ptr) {
printf("empty");
}
else {
sv = *sv_ptr;
if (sv == &PL_sv_undef) {
printf("undef");
}
else {
printf("*");
}
}
if (i < (len - 1)) {
printf(", ");
}
}
printf("]\n");
If I run this sub from a Perl script:
use strict;
use warnings;
use ArrayPrint;
my $array = [];
$array->[4] = undef;
ArrayPrint::print_array($array);
The output is:
[empty, empty, empty, empty, *]
Why is the last element not showing undef?
An SV can hold an undefined value but still be a different SV than PL_sv_undef. You need to replace the PL_sv_undef test with
SvGETMAGIC(sv);
if (!SvOK(sv)) { printf "undef" } else { printf "*" }

How can I speed up this nested foreach loop in Perl?

I have a Perl script that compares two sets of data loaded into two arrays, and I'm trying to make the comparison more efficient. Currently the code is as follows:
foreach(#{FILE_DATA}) {
if((++$file_current_linenum % 200) == 0) {
$progress = int($file_current_linenum / $file_total_lines * 10000) / 100;
logg("Processed $file_current_linenum file rows, $progress%, $mismatches mismatches.");
}
$file_current_line = $_;
$match_found = 0;
foreach(#{DB_DATA}) {
$db_current_line = $_;
if($file_current_line->{"channel"} == $db_current_line->{"channel"} ) {
if ($file_current_line->{"checksum"} == $db_current_line->{"checksum"} &&
$file_current_line->{"time"} > ($db_current_line->{"date_part"} - $TIME_MATCH_TOLERANCE) &&
$file_current_line->{"time"} < ($db_current_line->{"date_part"} + $TIME_MATCH_TOLERANCE) ){
$match_found = 1;
last; # break;
}
}
}
if($match_found != 1) {
push(#results, $file_current_line);
$mismatches++;
}
}
My first thought would be to remove matches from both arrays to reduce the pool size, would that affect the iterators position?
Both sets of data can have up to a couple of million elements and the comparison can take a few hours to complete.
Your solution is O(DB * FILE).
The following is O(DB + FILE) if and only if there never more than a few lines with the same channel and checksum:
my %DB_DATA;
for my $db_line (#DB_DATA) {
push #{ $DB_DATA{ $db_line->{channel} }{ $db_line->{checksum} } }, $db_line;
}
for my $file_line_idx (0..$#FILE_DATA) {
my $file_line = $FILE_DATA[$file_line_idx];
my $found = 0;
if (my $r1 = $DB_DATA{ $file_line->{channel} } ) {
if (my $r2 = $r1->{ $file_line->{checksum} } ) {
my $file_time = $file_line->{time};
for my $db_line (#$r2) {
my $db_time = $db_line->{date_part};
if (abs($file_time - $db_time) < $TIME_MATCH_TOLERANCE) {
$found = 1;
last;
}
}
}
}
push #mismatches, $file_line if !$found;
if ((($file_line_idx+1) % 200) == 0) {
logg(sprintf("Processed %d file rows, %d%, %d mismatches.",
$file_line_idx+1,
int(($file_line_idx+1)/#FILE_DATA) * 100,
0+#mismatches,
));
}
}
The following is O(DB + FILE) even if there are many lines with the same channel and checksum, but uses a lot of memory if $TIME_MATCH_TOLERANCE is big:
my %DB_DATA;
for my $db_line (#DB_DATA) {
for my $db_time (
$db_line->{date_part} - $TIME_MATCH_TOLERANCE + 1
..
$db_line->{date_part} + $TIME_MATCH_TOLERANCE - 1
) {
++$DB_DATA{ $db_line->{channel} }{ $db_line->{checksum} }{$db_time};
}
}
for my $file_line_idx (0..$#FILE_DATA) {
my $file_line = $FILE_DATA[$file_line_idx];
my $found = 0;
if (my $r1 = $DB_DATA{ $file_line->{channel} } ) {
if (my $r2 = $r1->{ $file_line->{checksum} } ) {
if ($r2->{ $file_line->{time} } {
$found = 1;
last;
}
}
}
push #mismatches, $file_line if !$found;
if ((($file_line_idx+1) % 200) == 0) {
logg(sprintf("Processed %d file rows, %d%, %d mismatches.",
$file_line_idx+1,
int(($file_line_idx+1)/#FILE_DATA) * 100,
0+#mismatches,
));
}
}
Note: Assumes the timestamps are integers. If they're not, convert them to integers before using them as keys.
The following is O((DB + FILE) log DB) [ which is very close to O(DB + FILE) ] even if there are many lines with the same channel and checksum, and uses minimal memory:
sub binsearch(&\#) {
my ($compare, $array) = #_;
my $i = 0;
my $j = $#$array;
return 0 if $j == -1;
while (1) {
my $k = int(($i+$j)/2);
for ($array->[$k]) {
my $cmp = $compare->()
or return 1;
if ($cmp < 0) {
$j = $k-1;
return 0 if $i > $j;
} else {
$i = $k+1;
return 0 if $i > $j;
}
}
}
}
my %DB_DATA;
for my $db_line (#DB_DATA) {
push #{ $DB_DATA{ $db_line->{channel} }{ $db_line->{checksum} } }, $db_line;
}
for my $r1 (values(%DB_DATA)) {
for my $r2 (values(%$r1)) {
#$r2 = sort { $a->{date_part} <=> $b->{date_part} } #$r2;
}
}
for my $file_line_idx (0..$#FILE_DATA) {
my $file_line = $FILE_DATA[$file_line_idx];
my $found = 0;
if (my $r1 = $DB_DATA{ $file_line->{channel} } ) {
if (my $r2 = $r1->{ $file_line->{checksum} } ) {
my $file_time = $file_line->{time};
my $min_db_time = $file_time - $TIME_MATCH_TOLERANCE;
my $max_db_time = $file_time + $TIME_MATCH_TOLERANCE;
if ( binsearch {
$_->{date_part} >= $max_db_time ? -1
: $_->{date_part} <= $min_db_time ? +1
: 0
} #$r2 ) {
$found = 1;
last;
}
}
}
push #mismatches, $file_line if !$found;
if ((($file_line_idx+1) % 200) == 0) {
logg(sprintf("Processed %d file rows, %d%, %d mismatches.",
$file_line_idx+1,
int(($file_line_idx+1)/#FILE_DATA) * 100,
0+#mismatches,
));
}
}
You could probably reduce the time significantly by pre-building a hash from DB_DATA, using the concatenation of the "channel" and "checksum" values as the key, and each value being a list of all of the DB_DATA entries with that channel and checksum. That way, for each FILE_DATA entry, you only need to check that list.
If there are a lot of entries with a given channel and checksum, you try to improve even more by sorting them by date_part, and then trying to binary search to find a valid entry.
If there are very few entries with a given channel and checksum, this should reduce your run time by a factor of a million or so, since it reduces the run time from O($#FILE_DATA * $#DB_DATA) to O($#FILE_DATA + $#DB_DATA).

perl script with AnyEvent is not going fast enough

Here is the little script I created to do HTTP transactions from a file with a list of URLs. The problem is that it's not able to do HTTP transactions as fast as I would like. More precisely, I set the rate to be 200/seconds, but it was able to send only at about 50/second. The server is powerful enough to handle 100/second.
This was run on a powerful PC with E5-1650 CPU and 64GB of RAM running Ubuntu 14.04 desktop. When the script runs, the CPU usage is only about 12%. The command I used was perl httpStresser.pl urlList rate 200.
Any idea why?
use AnyEvent;
use EV;
use AnyEvent::HTTP;
use AnyEvent::Handle;
use Time::HiRes qw( gettimeofday );
my $expectedRespCode = 200;
my $rate = 1;
my #urls = ();
readUrls(shift);
my $numOfUrls = $#urls;
my $start = time();
my $printed = 0; #have we printed the completion msg.
my $gId = 0;
my $spawned;
my #ctx = ();
my $i;
for ($i=0; $i<=$#ARGV; $i++) {
if ($ARGV[$i] =~ /^expect/) {
$expectedRespCode = $ARGV[$i+1];
$i++;
} elsif ($ARGV[$i] =~ /^rate/) {
$rate = $ARGV[$i+1];
print "rate is now $rate\n";
$i++;
} elsif ($ARGV[$i] =~ /^skip/) {
$gId = $ARGV[$i+1];
$i++;
} else {
die "only max, stayup are supported\n";
}
}
my $spawned = 0;
my $w = AnyEvent->condvar;
$| = 1;
my $start = getTS();
my $_timer;
$_timer = AnyEvent->timer(after => 0, interval => 0.001, cb => ::timeoutHandler);
$w->recv;
sub kickoff {
my $id = $gId ++;
if ($id > $numOfUrls) {
if ($printed == 0) {
print "done!!\n"; $printed = 1;
}
return;
}
#print "$id\n";
http_get $urls[$id], headers => { }, sub {
my $statusCode = $_[1]->{Status};
#printf "status $statusCode %d\n", time() - $start;
if (($id % 100) == 0) {
print "$id\n";
}
if ($statusCode != $expectedRespCode) {
print "unexpected resp code $id:$statusCode $urls[$id]\n";
}
};
}
sub timeoutHandler {
#print time(), "|\n";
if (! defined $start) {
$start = getTS(); kickoff(); $spawned = 1; return;
}
my $delta = getTS() - $start;
my $target = $delta * $rate;
#printf "%.4f %4d $spawned\n", $delta, $target;
for (; $spawned <= $target; $spawned++) {
kickoff();
}
if ($delta >= 1.0 ) {
$start += 1.0; $spawned = 0;
}
}
sub readUrls {
my $fname = shift;
my $line;
open FD, $fname || die "Failed to open $fname $!\n";
while (<FD>) {
chomp($line = $_);
push #urls, $line;
}
close FD;
}
sub getTS {
my ($seconds, $microseconds) = gettimeofday;
return $seconds + (0.0+ $microseconds)/1000000.0;
}

fpdf multi cell control

I am using fpdf to generate a report but one of the field I need to be multicell as the contents can be few lines long. Below is the codes what happens now is that the multicell will be few lines long but the rest of it will be only one standard size and the content after multicell will be on the next line. So how to overcome all this problem
$row_height = 5; // set the default
$column_width = 15;
$number_of_lines = ceil( $pdf->GetStringWidth($eventMessage) / ($column_width - 1) );
$cell_height = 5;
$height_of_cell = ceil( $number_of_lines * $cell_height );
if ( $cell_height > $row_height ) {
$row_height = $cell_height;
}
$pdf->Cell( 10, $row_height,$count, 1, 0, 'L', true );
$pdf->Cell( 15, $row_height,$row['latitude'], 1, 0, 'L', true );
$pdf->Cell( 16, $row_height,$row['longitude'], 1, 0, 'L', true );
$pdf->Cell( 25, $row_height,$row['dateTimer'], 1, 0, 'L', true );
$pdf->Cell( 25, $row_height,$row['insertDateTime'], 1, 0, 'L', true );
$pdf->MultiCell( 15, $row_height,$eventMessage, 1, 'J',true);
$pdf->Cell( 15, $row_height,$eventSource, 1, 0, 'L', true );
$pdf->Cell( 15, $row_height,$eventLocation, 1, 0, 'L', true );
$pdf->Cell( 18, $row_height,$row['stat'], 1, 0, 'L', true );
class pdf extends FPDF {
function GetMultiCellHeight($w, $h, $txt, $border=null, $align='J') {
// Calculate MultiCell with automatic or explicit line breaks height
// $border is un-used, but I kept it in the parameters to keep the call
// to this function consistent with MultiCell()
$cw = &$this->CurrentFont['cw'];
if($w==0)
$w = $this->w-$this->rMargin-$this->x;
$wmax = ($w-2*$this->cMargin)*1000/$this->FontSize;
$s = str_replace("\r",'',$txt);
$nb = strlen($s);
if($nb>0 && $s[$nb-1]=="\n")
$nb--;
$sep = -1;
$i = 0;
$j = 0;
$l = 0;
$ns = 0;
$height = 0;
while($i<$nb)
{
// Get next character
$c = $s[$i];
if($c=="\n")
{
// Explicit line break
if($this->ws>0)
{
$this->ws = 0;
$this->_out('0 Tw');
}
//Increase Height
$height += $h;
$i++;
$sep = -1;
$j = $i;
$l = 0;
$ns = 0;
continue;
}
if($c==' ')
{
$sep = $i;
$ls = $l;
$ns++;
}
$l += $cw[$c];
if($l>$wmax)
{
// Automatic line break
if($sep==-1)
{
if($i==$j)
$i++;
if($this->ws>0)
{
$this->ws = 0;
$this->_out('0 Tw');
}
//Increase Height
$height += $h;
}
else
{
if($align=='J')
{
$this->ws = ($ns>1) ? ($wmax-$ls)/1000*$this->FontSize/($ns-1) : 0;
$this->_out(sprintf('%.3F Tw',$this->ws*$this->k));
}
//Increase Height
$height += $h;
$i = $sep+1;
}
$sep = -1;
$j = $i;
$l = 0;
$ns = 0;
}
else
$i++;
}
// Last chunk
if($this->ws>0)
{
$this->ws = 0;
$this->_out('0 Tw');
}
//Increase Height
$height += $h;
return $height;
}
}
So
$pdf = new pdf();
$pdf->addPage();
$pdf->MultiCell(50, 4, 'Bla bla bla');
$pdf->ln(GetMultiCellHeight(50, 4, 'Bla bla bla'));
$pdf->Output();