[system] / trunk / pg / lib / Value / Vector.pm Repository: Repository Listing bbplugincoursesdistsnplrochestersystemwww

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

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