[system] / trunk / pg / lib / Value / Vector.pm Repository:
ViewVC logotype

View of /trunk/pg/lib/Value/Vector.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3716 - (download) (as text) (annotate)
Sun Oct 16 03:37:17 2005 UTC (14 years, 1 month ago) by dpvc
File size: 9625 byte(s)
In the past, when Value objects were inserted into strings, they would
automatically include parentheses so that if you had $f equal to 1+x
and $g equal to 1-x, then Formula("$f/$g") would mean (1+x)/(1-x)
rather than 1+(x/1)-x, which is what would happen as a straing string
substitution.

The problem is that this would also happen for real numbers, vectors,
and everything else, even when it wasn't necessary.  So if $x=Real(3),
then "Let x = $x" would be "Let x = (3)".

I have changed the behavior of the string concatenation for Value
objects so that parentheses are only added in a few cases: for
Formulas, Complex numbers, and Unions.  This makes the other Value
objects work more like regular variables in strings, but might cause
some problems with strings that are used as formulas.  For example, if
$a = Real(-3), then "x + 2 $a" will become "x + 2 -3", or "x-1" rather
than the expected "x - 6".  (The old approach would have made it "x +
2 (-3)" which would have worked properly).  For the most part, it is
easier to use something like "x + 2*$a" or even "x" + 2*$a in this
case, so the extra trouble of having to avoid parentheses when you
really meant to substitute the value into a string didn't seem worth
it.

    1 ###########################################################################
    2 #
    3 #  Implements Vector class
    4 #
    5 package Value::Vector;
    6 my $pkg = 'Value::Vector';
    7 
    8 use strict;
    9 use vars qw(@ISA);
   10 @ISA = qw(Value);
   11 
   12 use overload
   13        '+'   => sub {shift->add(@_)},
   14        '-'   => sub {shift->sub(@_)},
   15        '*'   => sub {shift->mult(@_)},
   16        '/'   => sub {shift->div(@_)},
   17        '**'  => sub {shift->power(@_)},
   18        '.'   => sub {shift->_dot(@_)},
   19        'x'   => sub {shift->cross(@_)},
   20        '<=>' => sub {shift->compare(@_)},
   21        'cmp' => sub {shift->compare_string(@_)},
   22        'neg' => sub {shift->neg},
   23        'abs' => sub {shift->abs},
   24   'nomethod' => sub {shift->nomethod(@_)},
   25         '""' => sub {shift->stringify(@_)};
   26 
   27 #
   28 #  Convert a value to a Vector.  The value can be
   29 #    a list of numbers, or an reference to an array of numbers
   30 #    a point or vector object (demote a vector)
   31 #    a matrix if it is  n x 1  or  1 x n
   32 #    a string that parses to a vector
   33 #
   34 sub new {
   35   my $self = shift; my $class = ref($self) || $self;
   36   my $p = shift; $p = [$p,@_] if (scalar(@_) > 0);
   37   $p = Value::makeValue($p) if (defined($p) && !ref($p));
   38   return $p if (Value::isFormula($p) && $p->type eq Value::class($self));
   39   my $pclass = Value::class($p); my $isFormula = 0;
   40   my @d; @d = $p->dimensions if $pclass eq 'Matrix';
   41   if ($pclass =~ m/Point|Vector/) {$p = $p->data}
   42   elsif ($pclass eq 'Matrix' && scalar(@d) == 1) {$p = [$p->value]}
   43   elsif ($pclass eq 'Matrix' && scalar(@d) == 2 && $d[0] == 1) {$p = ($p->value)[0]}
   44   elsif ($pclass eq 'Matrix' && scalar(@d) == 2 && $d[1] == 1) {$p = ($p->transpose->value)[0]}
   45   else {
   46     $p = [$p] if (defined($p) && ref($p) ne 'ARRAY');
   47     Value::Error("Vectors must have at least one coordinate") unless defined($p) && scalar(@{$p}) > 0;
   48     foreach my $x (@{$p}) {
   49       $x = Value::makeValue($x);
   50       $isFormula = 1 if Value::isFormula($x);
   51       Value::Error("Coordinate of Vector can't be %s",Value::showClass($x))
   52         unless Value::isNumber($x);
   53     }
   54   }
   55   if ($isFormula) {
   56     my $v = $self->formula($p);
   57     if (ref($self) && $self->{ColumnVector}) {
   58       $v->{tree}{ColumnVector} = 1;
   59       $v->{tree}{open} = $v->{tree}{close} = undef;
   60     }
   61     return $v;
   62   }
   63   my $v = bless {data => $p}, $class;
   64   $v->{ColumnVector} = 1 if ref($self) && $self->{ColumnVector};
   65   return $v;
   66 }
   67 
   68 #
   69 #  Try to promote arbitary data to a vector
   70 #
   71 sub promote {
   72   my $x = shift;
   73   return $pkg->new($x,@_) if scalar(@_) > 0 || ref($x) eq 'ARRAY';
   74   return $x if ref($x) eq $pkg;
   75   return $pkg->make(@{$x->data}) if Value::class($x) eq 'Point';
   76   Value::Error("Can't convert %s to a Vector",Value::showClass($x));
   77 }
   78 
   79 ############################################
   80 #
   81 #  Operations on vectors
   82 #
   83 
   84 sub add {
   85   my ($l,$r,$flag) = @_;
   86   if ($l->promotePrecedence($r)) {return $r->add($l,!$flag)}
   87   ($l,$r) = (promote($l)->data,promote($r)->data);
   88   Value::Error("Vector addition with different number of coordiantes")
   89     unless scalar(@{$l}) == scalar(@{$r});
   90   my @s = ();
   91   foreach my $i (0..scalar(@{$l})-1) {push(@s,$l->[$i] + $r->[$i])}
   92   return $pkg->make(@s);
   93 }
   94 
   95 sub sub {
   96   my ($l,$r,$flag) = @_;
   97   if ($l->promotePrecedence($r)) {return $r->sub($l,!$flag)}
   98   ($l,$r) = (promote($l)->data,promote($r)->data);
   99   Value::Error("Vector subtraction with different number of coordiantes")
  100     unless scalar(@{$l}) == scalar(@{$r});
  101   if ($flag) {my $tmp = $l; $l = $r; $r = $tmp};
  102   my @s = ();
  103   foreach my $i (0..scalar(@{$l})-1) {push(@s,$l->[$i] - $r->[$i])}
  104   return $pkg->make(@s);
  105 }
  106 
  107 sub mult {
  108   my ($l,$r,$flag) = @_;
  109   if ($l->promotePrecedence($r)) {return $r->mult($l,!$flag)}
  110   Value::Error("Vectors can only be multiplied by numbers")
  111     unless (Value::matchNumber($r) || Value::isComplex($r));
  112   my @coords = ();
  113   foreach my $x (@{$l->data}) {push(@coords,$x*$r)}
  114   return $pkg->make(@coords);
  115 }
  116 
  117 sub div {
  118   my ($l,$r,$flag) = @_;
  119   if ($l->promotePrecedence($r)) {return $r->div($l,!$flag)}
  120   Value::Error("Can't divide by a Vector") if $flag;
  121   Value::Error("Vectors can only be divided by numbers")
  122     unless (Value::matchNumber($r) || Value::isComplex($r));
  123   Value::Error("Division by zero") if $r == 0;
  124   my @coords = ();
  125   foreach my $x (@{$l->data}) {push(@coords,$x/$r)}
  126   return $pkg->make(@coords);
  127 }
  128 
  129 sub power {
  130   my ($l,$r,$flag) = @_;
  131   if ($l->promotePrecedence($r)) {return $r->power($l,!$flag)}
  132   Value::Error("Can't raise Vectors to powers") unless $flag;
  133   Value::Error("Can't use Vectors in exponents");
  134 }
  135 
  136 sub dot {
  137   my ($l,$r,$flag) = @_;
  138   ($l,$r) = (promote($l)->data,promote($r)->data);
  139   Value::Error("Vector dot product with different number of coordiantes")
  140     unless scalar(@{$l}) == scalar(@{$r});
  141   my $s = 0;
  142   foreach my $i (0..scalar(@{$l})-1) {$s += $l->[$i] * $r->[$i]}
  143   return $s;
  144 }
  145 
  146 sub cross {
  147   my ($l,$r,$flag) = @_;
  148   if ($l->promotePrecedence($r)) {return $r->cross($l,!$flag)}
  149   ($l,$r) = (promote($l)->data,promote($r)->data);
  150   Value::Error("Vector must be in 3-space for cross product")
  151     unless scalar(@{$l}) == 3 && scalar(@{$r}) == 3;
  152   $pkg->make($l->[1]*$r->[2] - $l->[2]*$r->[1],
  153            -($l->[0]*$r->[2] - $l->[2]*$r->[0]),
  154              $l->[0]*$r->[1] - $l->[1]*$r->[0]);
  155 }
  156 
  157 #
  158 #  If points are different length, shorter is smaller,
  159 #  Otherwise, do lexicographic comparison.
  160 #
  161 sub compare {
  162   my ($l,$r,$flag) = @_;
  163   if ($l->promotePrecedence($r)) {return $r->compare($l,!$flag)}
  164   ($l,$r) = (promote($l)->data,promote($r)->data);
  165   return scalar(@{$l}) <=> scalar(@{$r}) unless scalar(@{$l}) == scalar(@{$r});
  166   if ($flag) {my $tmp = $l; $l = $r; $r = $tmp};
  167   my $cmp = 0;
  168   foreach my $i (0..scalar(@{$l})-1) {
  169     $cmp = $l->[$i] <=> $r->[$i];
  170     last if $cmp;
  171   }
  172   return $cmp;
  173 }
  174 
  175 sub neg {
  176   my $p = promote(@_)->data;
  177   my @coords = ();
  178   foreach my $x (@{$p}) {push(@coords,-$x)}
  179   return $pkg->make(@coords);
  180 }
  181 
  182 sub abs {my $self = shift; $self->norm(@_)}
  183 sub norm {
  184   my $p = promote(@_)->data;
  185   my $s = 0;
  186   foreach my $x (@{$p}) {$s += $x*$x}
  187   return CORE::sqrt($s);
  188 }
  189 
  190 sub unit {
  191   my $self = shift;
  192   my $n = $self->norm; return $self if $n == 0;
  193   return $self / $n;
  194 }
  195 
  196 ############################################
  197 #
  198 #  Check for parallel vectors
  199 #
  200 
  201 sub isParallel {
  202   my $U = shift; my $V = shift; my $sameDirection = shift;
  203   my @u = (promote($U))->value;
  204   my @v = (promote($V))->value;
  205   return 0 unless  scalar(@u) == scalar(@v);
  206   my $k = ''; # will be scaling factor for u = k v
  207   foreach my $i (0..$#u) {
  208     #
  209     #  make sure we use fuzzy math
  210     #
  211     $u[$i] = Value::Real->new($u[$i]) unless Value::isReal($u[$i]);
  212     $v[$i] = Value::Real->new($v[$i]) unless Value::isReal($v[$i]);
  213     if ($k ne '') {
  214       return 0 if ($v[$i] != $k*$u[$i]);
  215     } else {
  216       #
  217       #  if one is zero and the other isn't then not parallel
  218       #  otherwise use the ratio of the two as k.
  219       #
  220       if ($u[$i] == 0) {
  221   return 0 if $v[$i] != 0;
  222       } else {
  223   return 0 if $v[$i] == 0;
  224   $k = ($v[$i]/$u[$i])->value;
  225         return 0 if $k < 0 && $sameDirection;
  226       }
  227     }
  228   }
  229   #
  230   #  Note: it will return 1 if both are zero vectors.  This is a
  231   #  feature, since one is provided by the problem writer, and he
  232   #  should only supply the zero vector if he means it.  One could
  233   #  return ($k ne '') to return 0 if both are zero.
  234   #
  235   return 1;
  236 }
  237 
  238 sub areParallel {shift->isParallel(@_)}
  239 
  240 
  241 ############################################
  242 #
  243 #  Generate the various output formats
  244 #
  245 
  246 my $ijk_string = ['i','j','k','0'];
  247 my $ijk_TeX = ['\boldsymbol{i}','\boldsymbol{j}','\boldsymbol{k}','\boldsymbol{0}'];
  248 
  249 sub stringify {
  250   my $self = shift;
  251   return $self->TeX if $$Value::context->flag('StringifyAsTeX');
  252   $self->string;
  253 }
  254 
  255 sub string {
  256   my $self = shift; my $equation = shift;
  257   return $self->ijk($ijk_string)
  258     if ($self->{ijk} || $equation->{ijk} || $$Value::context->flag("ijk")) &&
  259         !$self->{ColumnVector};
  260   return $self->SUPER::string($equation,@_);
  261 }
  262 
  263 sub TeX {
  264   my $self = shift; my $equation = shift;
  265   if ($self->{ColumnVector}) {
  266     my $def = ($equation->{context} || $$Value::context)->lists->get('Matrix');
  267     my $open = shift; my $close = shift;
  268     $open  = $self->{open}  unless defined($open);
  269     $open  = $def->{open}   unless defined($open);
  270     $close = $self->{close} unless defined($close);
  271     $close = $def->{close}  unless defined($close);
  272     $open =~ s/([{}])/\\$1/g; $close =~ s/([{}])/\\$1/g;
  273     $open = '\left'.$open if $open; $close = '\right'.$close if $close;
  274     my @coords = ();
  275     foreach my $x (@{$self->data}) {
  276       if (Value::isValue($x)) {push(@coords,$x->TeX($equation))} else {push(@coords,$x)}
  277     }
  278     return $open.'\begin{array}{c}'.join('\\\\',@coords).'\\\\\end{array}'.$close;
  279   }
  280   return $self->ijk if ($self->{ijk} || $equation->{ijk} || $$Value::context->flag("ijk"));
  281   return $self->SUPER::TeX($equation,@_) unless $self->{ColumnVector};
  282 }
  283 
  284 sub ijk {
  285   my $self = shift; my $ijk = shift || $ijk_TeX;
  286   my @coords = @{$self->data};
  287   Value::Error("Method 'ijk' can only be used on vectors in three-space")
  288     unless (scalar(@coords) <= 3);
  289   my $string = ''; my $n; my $term;
  290   foreach $n (0..scalar(@coords)-1) {
  291     $term = $coords[$n]; $term = (Value::isValue($term))? $term->string : "$term";
  292     if ($term ne 0) {
  293       $term = '' if $term eq '1'; $term = '-' if $term eq '-1';
  294       $term = '('.$term.')' if $term =~ m/e/i;
  295       $term = '+' . $term unless $string eq '' or $term =~ m/^-/;
  296       $string .= $term . $ijk->[$n];
  297     }
  298   }
  299   $string = $ijk->[3] if $string eq '';
  300   return $string;
  301 }
  302 
  303 ###########################################################################
  304 
  305 1;
  306 

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9