Mercurial > forge
view extra/perl/Octave.pm @ 8:27a2b469142a octave-forge
Inline::Octave module
author | aadler |
---|---|
date | Mon, 22 Oct 2001 03:04:07 +0000 |
parents | |
children | c3f05dc96085 |
line wrap: on
line source
############################################################################### # # Inline::Octave - # # $Id$ package Inline::Octave; $VERSION = '0.01'; require Inline; @ISA = qw(Inline); use Carp; use IPC::Open2; use vars qw( $inline_object ); sub register { # print "REGISTERING\n"; return { language => 'Octave', aliases => ['octave'], type => 'interpreted', suffix => 'm', }; } sub build { # print "BUILDING\n"; my $o = shift; my $code = $o->{API}{code}; { $o->start_interpreter(); print $o->interpret($code); my @def_funcs= $o->get_defined_functions(); $o->stop_interpreter(); } croak "Octave build failed:\n$@" if $@; my $path = "$o->{API}{install_lib}/auto/$o->{API}{modpname}"; my $obj = $o->{API}{location}; $o->mkpath($path) unless -d $path; open PERL_OBJ, "> $obj" or croak "Can't open $obj for output\n$!"; print PERL_OBJ $code; close \*PERL_OBJ; } sub load { # print "LOADING\n"; my $o = shift; $o->_validate(); $inline_object = $o; my $obj = $o->{API}{location}; open OCTAVE_OBJ, "< $obj" or croak "Can't open $obj for output\n$!"; my $code; my %nargouts; while (<OCTAVE_OBJ>) { if(/\bfunction\s+(.*?=\s*\w*|\w*)\b/) { my $pat =$1; my $fnam= $1 if $pat =~ /(\w*)$/; my $nargout=0; $nargout= 1 if $pat =~ /^\w+\s*=/; $nargout= 1 if $pat =~ /^\[\s*\w+\s*\]\s*=/; $nargout= 2 if $pat =~ /^\[\s*\w+\s*,\s*\w+\s*]\s*=/; $nargout= 3 if $pat =~ /^\[\s*\w+\s*,\s*\w+\s*,\s*\w+\s*]\s*=/; $nargout= 4 if $pat =~ /^\[\s*\w+\s*,\s*\w+\s*,\s*\w+\s*,\s*\w+\s*]\s*=/; $nargouts{$fnam}=$nargout; } if (/^\s*##\s*Inline::Octave::(\w+)\s*\(nargout=(\d+)\)\s*=>\s*(\w*)/) { $o->bind_octave_function( $3, $1, $2 ); } $code.=$_; } close OCTAVE_OBJ; # use Data::Dumper; print Dumper(\%nargouts); { $o->start_interpreter(); print $o->interpret($code); my @def_funcs= $o->get_defined_functions(); foreach my $funname (@def_funcs) { $o->bind_octave_function( $funname, $funname, $nargouts{$funname} ); } @EXPORT= @def_funcs; } croak "Unable to load Octave module $obj:\n$@" if $@; } sub validate { # print "VALIDATING\n"; my $o = shift; $o->_validate(); } sub _validate { my $o = shift; $o->{ILSM}->{INTERP} = "octave -qf " unless exists $o->{ILSM}->{INTERP}; $o->{ILSM}->{MARKER} = "-9Ahv87uhBa8l_8Onq,zU9-" unless exists $o->{ILSM}->{MARKER}; } sub info { print "INFO\n"; my $o = shift; # Place holder } # here we write code to bind to an octave function and eval this # into the callers namespace # # $o->bind_octave_function( octave_funcname, perl_funcname, nargout ) # # we need to specify the nargout, because we can't infer # it from perl (other than scalar or list context) # # now, when perl6 comes out ... # sub bind_octave_function { my $o= shift; my $oct_funname = shift; my $perl_funname = shift; my $nargout = shift; my $pkg= $o->{API}->{pkg}; my $code = <<CODE; package $pkg; sub $perl_funname { # we need to prevent IOM variables from going out of scope # in the loop, but rather at the end of the function #input variables my \$inargs; my \@vin; for (my \$i=0; \$i < \@_; \$i++) { \$vin[\$i]= new Inline::Octave::Matrix( \$_[\$i] ); \$inargs.= \$vin[\$i]->name.","; } chop(\$inargs); #remove last , #output variables my \$outargs; my \@vout; for (my \$i=0; \$i < \$nargout; \$i++) { \$vout[\$i]= new Inline::Octave::Matrix( -101101.101101 ); #code \$outargs.= \$vout[\$i]->name.","; } chop(\$outargs); #remove last , \$outargs= "[".\$outargs."]="; \$outargs= "" if \$nargout==0; my \$call= "\$outargs $oct_funname(\$inargs);"; # print "--\$call--\\n"; my \$retval= \$Inline::Octave::inline_object->interpret( \$call ); # print "--\$retval--\\n"; # Get the correct size for each new variable foreach (\@vout) { \$_->store_size(); } return \@vout if wantarray(); return \$vout[0]; } CODE # print "--$code--\n"; eval $code; croak "Problem binding $oct_funname to $perl_funname: $@" if $@; } sub start_interpreter { my $o = shift; my $Oout; my $Oin; eval { open2( $Oout, $Oin , $o->{ILSM}->{INTERP} ); }; croak "Can't locate octave interpreter: $@\n" if $@ =~ /Open2/i; $o->{ILSM}->{OCTIN} = $Oin; $o->{ILSM}->{OCTOUT} = $Oout; } sub stop_interpreter { my $o = shift; my $Oin= $o->{ILSM}->{OCTIN}; my $Oout= $o->{ILSM}->{OCTOUT}; return unless $Oin and $Oout; print $Oin "exit\n"; <$Oin>; #clean up input close $Oin, $Oout; $o->{ILSM}->{OCTIN} = ""; $o->{ILSM}->{OCTOUT} = ""; } # send a string to octave and get the result sub interpret { my $o = shift; my $cmd= shift; my $marker= $o->{ILSM}->{MARKER}; my $Oin= $o->{ILSM}->{OCTIN}; my $Oout= $o->{ILSM}->{OCTOUT}; croak "octave interpreter not alive" unless $Oin and $Oout; print $Oin "$cmd\ndisp('$marker');fflush(stdout);\n"; my $input; my $marker_len= length( $marker )+1; while (1) { my $line; sysread $Oout, $line, 1024; $input.= $line; last if substr( $input, -$marker_len, -1) eq $marker; } return substr($input,0,-$marker_len); } sub get_defined_functions { my $o = shift; my $data= $o->interpret("whos -functions"); my @funclist; while ( $data =~ /user-defined function +- +- +(\w+)/g ) { push @funclist, $1; } return @funclist; } END { $inline_object->stop_interpreter() if $inline_object; } package Inline::Octave::Matrix; use Carp; $varcounter= 100001; # called as # new IOM( [1,2,3] ) -> ColumnVector # new IOM( [[1,2],[2,3],[3,4]] ) -> Matrix # new IOM( [1,2,3,4], 2, 2) -> Matrix, rows, cols sub new { my $class = shift; my ($m, $rows, $cols) = @_; my $self = {}; bless ($self, $class); my $varname= "vname_".$varcounter++; $self->{varname}= $varname; my @vals; my $do_transpose= ''; my $code; if (ref $m eq "Inline::Octave::Matrix") { my $prev_varname= $m->{varname}; $code= "$varname= $prev_varname;"; } elsif (ref $m eq "ARRAY" and ref $m->[0] eq "ARRAY" ) { # 2 dimentional array - ensure all rows are equal size; @vals= map { if ($cols) { croak "specified cols is length ${@$_} not $cols" unless $cols== @$_; } else { $cols = @$_; }; @$_ } @$m; $rows= @$m unless defined $rows; $do_transpose= q('); ($rows,$cols)= ($cols,$rows); } elsif (ref $m eq "ARRAY" ) { # 1 dimentional array; $rows= @$m unless defined $rows; $cols= 1 unless defined $cols; @vals= @{$m}; } elsif (ref $m eq "" ) { $rows= 1 unless defined $rows; $cols= 1 unless defined $cols; @vals = ($m); } else { croak "Can't construct Matrix from Perl var of type:".ref($m); } croak "Matrix is not size ${cols}x${rows}" unless @vals== $rows*$cols; # pack data into doubles and use fread to grab it from octave # since octave is column major and nested lists in perl are # row major, we need to do the transpose. unless ($code) { $code= "$varname=fread(stdin,[$rows,$cols],'double')$do_transpose;\n". pack( "d".($rows*$cols) , @vals ); } $Inline::Octave::inline_object->interpret( $code ); $self->store_size(); return $self; } sub store_size { my $self = shift; my $varname= $self->name; my $code = "disp(size($varname))"; my $size= $Inline::Octave::inline_object->interpret( $code ); croak "Problem constructing Matrix" unless $size =~ /^ *(\d+) *(\d+)/; $self->{rows}= $1; $self->{cols}= $2; } sub as_list { my $self = shift; my $varname= $self->name; my $code = "fwrite(stdout, $varname,'double');"; my $retval= $Inline::Octave::inline_object->interpret( $code ); my $size= $self->{cols} * $self->{rows}; my @list= unpack "d$size", $retval; return @list; } sub as_matrix { my $self = shift; my $varname= $self->name; my $code = "fwrite(stdout, $varname','double');"; # use transpose my $retval= $Inline::Octave::inline_object->interpret( $code ); my $size= $self->{cols} * $self->{rows}; my @list= unpack "d$size", $retval; my @m; my $cols= $self->{cols}; my $rows= $self->{rows}; for (0..$rows-1) { push @m, [ (@list)[$_*$cols .. ($_+1)*$cols-1] ]; } return \@m; } sub DESTROY { # print "DESTROYing $varname\n"; my $self = shift; my $varname= $self->name; my $code = "clear $varname;"; $Inline::Octave::inline_object->interpret( $code ); } sub disp { my $self = shift; my $varname= $self->name; my $code = "disp( $varname );"; return $Inline::Octave::inline_object->interpret( $code ); } sub name { my $self = shift; return $self->{varname}; } 1; __END__ =head1 NAME Inline::Octave - Inline octave code into your perl =head1 SYNOPSIS use Inline Octave; $f = jnk1(3); print "jnk1=",$f->disp(),"\n"; $c= new Inline::Octave::Matrix([ [1.5,2,3],[4.5,1,-1] ]); ($b, $t)= jnk2( $c, [4,4],[5,6] ); print "t=",$t->as_list(),"\n"; use Data::Dumper; print Dumper( $b->as_matrix() ); print oct_sum( [1,2,3] )->disp(); oct_plot( [0..4], [3,2,1,2,3] ); sleep(2); __DATA__ __Octave__ function x=jnk1(u); x=u+1; endfunction function [b,t]=jnk2(x,a,b); b=x+1+a'*b; t=6; endfunction ## Inline::Octave::oct_sum (nargout=1) => sum ## Inline::Octave::oct_plot (nargout=0) => plot =head1 DESCRIPTION Inline::Octave gives you the power of the octave programming language from within your Perl programs. =head1 PERFORMANCE =head1 AUTHOR Andy Adler andy@analyti.ca =head1 COPYRIGHT � MMI, Andy Adler All Rights Reserved. This module is free software. It may be used, redistributed and/or modified under the same terms as Perl itself. =head1 TODO List 1. Add import for functions 2. control matrix size inputs 3. add destructor for Octave::Matrix 4. control waiting in the interpret loop 5. support for complex variables