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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2671 - (download) (as text) (annotate)
Sun Aug 22 21:18:06 2004 UTC (8 years, 8 months ago) by dpvc
File size: 10559 byte(s)
Fixed some inconsistencies between handing of matrices within the
parser and Value packages.  Added a predefined Matrix context.

    1 ###########################################################################
    2 #
    3 #  Implements the Formula class.
    4 #
    5 package Value::Formula;
    6 my $pkg = 'Value::Formula';
    7 
    8 use strict;
    9 use vars qw(@ISA);
   10 @ISA = qw(Parser Value);
   11 
   12 use overload
   13        '+'    => \&add,
   14        '-'    => \&sub,
   15        '*'    => \&mult,
   16        '/'    => \&div,
   17        '**'   => \&power,
   18        '.'    => \&dot,
   19        'x'    => \&cross,
   20        '<=>'  => \&compare,
   21        'cmp'  => \&Value::cmp,
   22        '~'    => sub {Parser::Function->call('conj',$_[0])},
   23        'neg'  => sub {$_[0]->neg},
   24        'sin'  => sub {Parser::Function->call('sin',$_[0])},
   25        'cos'  => sub {Parser::Function->call('cos',$_[0])},
   26        'exp'  => sub {Parser::Function->call('exp',$_[0])},
   27        'abs'  => sub {Parser::Function->call('abs',$_[0])},
   28        'log'  => sub {Parser::Function->call('log',$_[0])},
   29        'sqrt' => sub {Parser::Function->call('sqrt',$_[0])},
   30       'atan2' => \&atan2,
   31    'nomethod' => \&Value::nomethod,
   32          '""' => \&Value::stringify;
   33 
   34 #
   35 #  Call Parser to make the new item
   36 #
   37 sub new {shift; $pkg->SUPER::new(@_)}
   38 
   39 #
   40 #  Create the new parser with no string
   41 #    (we'll fill in its tree by hand)
   42 #
   43 sub blank {$pkg->SUPER::new('')}
   44 
   45 #
   46 #  Get the type from the tree
   47 #
   48 sub typeRef {(shift)->{tree}->typeRef}
   49 
   50 ############################################
   51 #
   52 #  Create a BOP from two operands
   53 #
   54 #  Get the context and variables from the left and right operands
   55 #    if they are formulas
   56 #  Make them into Value objects if they aren't already.
   57 #  Convert '+' to union for intervals or unions.
   58 #  Make a new BOP with the two operands.
   59 #  Record the variables.
   60 #  Evaluate the formula if it is constant.
   61 #
   62 sub bop {
   63   my ($l,$r,$flag,$bop) = @_;
   64   if ($l->promotePrecedence($r)) {return $r->add($l,!$flag)}
   65   if ($flag) {my $tmp = $l; $l = $r; $r = $tmp}
   66   my $formula = $pkg->blank;
   67   my $vars = {};
   68   if (ref($r) eq $pkg) {
   69     $formula->{context} = $r->{context};
   70     $vars = {%{$vars},%{$r->{variables}}};
   71     $r = $r->{tree}->copy($formula);
   72   }
   73   if (ref($l) eq $pkg) {
   74     $formula->{context} = $l->{context};
   75     $vars = {%{$vars},%{$l->{variables}}};
   76     $l = $l->{tree}->copy($formula);
   77   }
   78   $l = $pkg->new($l) if (!ref($l) && Value::getType($formula,$l) eq "unknown");
   79   $r = $pkg->new($r) if (!ref($r) && Value::getType($formula,$r) eq "unknown");
   80   $l = Parser::Value->new($formula,$l) unless ref($l) =~ m/^Parser::/;
   81   $r = Parser::Value->new($formula,$r) unless ref($r) =~ m/^Parser::/;
   82   $bop = 'U' if $bop eq '+' &&
   83     ($l->type =~ m/Interval|Union/ || $r->type =~ m/Interval|Union/);
   84   $formula->{tree} = Parser::BOP->new($formula,$bop,$l,$r);
   85   $formula->{variables} = {%{$vars}};
   86   return $formula->eval if scalar(%{$vars}) == 0;
   87   return $formula;
   88 }
   89 
   90 sub add   {bop(@_,'+')}
   91 sub sub   {bop(@_,'-')}
   92 sub mult  {bop(@_,'*')}
   93 sub div   {bop(@_,'/')}
   94 sub power {bop(@_,'**')}
   95 sub cross {bop(@_,'><')}
   96 
   97 #
   98 #  Make dot work for vector operands
   99 #
  100 sub dot   {
  101   my ($l,$r,$flag) = @_;
  102   if ($l->promotePrecedence($r)) {return $r->compare($l,!$flag)}
  103   return bop(@_,'.') if $l->type eq 'Vector' &&
  104      Value::isValue($r) && $r->type eq 'Vector';
  105   Value::_dot(@_);
  106 }
  107 
  108 ############################################
  109 #
  110 #  Form the negation of a formula
  111 #
  112 sub neg {
  113   my $self = shift;
  114   my $formula = $self->blank;
  115   $formula->{context} = $self->{context};
  116   $formula->{variables} = $self->{variables};
  117   $formula->{tree} = Parser::UOP->new($formula,'u-',$self->{tree}->copy($formula));
  118   return $formula->eval if scalar(%{$formula->{variables}}) == 0;
  119   return $formula;
  120 }
  121 
  122 #
  123 #  Form the function atan2 function call on two operands
  124 #
  125 sub atan2 {
  126   my ($l,$r,$flag) = @_;
  127   if ($l->promotePrecedence($r)) {return $r->compare($l,!$flag)}
  128   if ($flag) {my $tmp = $l; $l = $r; $r = $tmp}
  129   Parser::Function->call('atan2',$l,$r);
  130 }
  131 
  132 ############################################
  133 #
  134 #  Compare two functions for equality
  135 #
  136 sub compare {
  137   my ($l,$r,$flag) = @_; my $self = $l;
  138   if ($l->promotePrecedence($r)) {return $r->compare($l,!$flag)}
  139   $r = Value::Formula->new($r) unless Value::isFormula($r);
  140   Value::Error("Functions from different contexts can't be compared")
  141     unless $l->{context} == $r->{context};
  142 
  143   #
  144   #  Get the test points and evaluate the functions at those points
  145   #
  146   ##  FIXME: Check given points for consistency
  147   my $points = $l->{test_points} || $r->{test_points} || $l->createRandomPoints;
  148   my $lvalues = $l->{test_values} || $l->createPointValues($points,1);
  149   my $rvalues = $r->createPointValues($points);
  150   #
  151   # Note: $l is bigger if $r can't be evaluated at one of the points
  152   return 1 unless $rvalues;
  153 
  154   #
  155   #  Look through the two lists to see if they are equal.
  156   #  If not, return the comparison of the first unequal value
  157   #    (not good for < and >, but OK for ==).
  158   #
  159   my ($i, $cmp);
  160   foreach $i (0..scalar(@{$lvalues})-1) {
  161     $cmp = $lvalues->[$i] <=> $rvalues->[$i];
  162     return $cmp if $cmp;
  163   }
  164   return 0;
  165 }
  166 
  167 #
  168 #  Create the value list from a given set of test points
  169 #
  170 sub createPointValues {
  171   my $self = shift;
  172   my $points = shift || $self->{test_points} || $self->createRandomPoints;
  173   my $showError = shift;
  174   my $f = $self->{f};
  175   $f = $self->{f} = $self->perlFunction(undef,[$self->{context}->variables->names])
  176      unless $f;
  177 
  178   my $values = []; my $v;
  179   foreach my $p (@{$points}) {
  180     $v = eval {&$f(@{$p})};
  181     if (!defined($v)) {
  182       return unless $showError;
  183       Value::Error("Can't evaluate formula on test point (".join(',',@{$p}).")");
  184     }
  185     push @{$values}, Value::makeValue($v);
  186   }
  187 
  188   $self->{test_points} = $points;
  189   $self->{test_values} = $values;
  190 }
  191 
  192 #
  193 #  Create a list of random points, making sure that the function
  194 #  is defined at the given points.  Error if we can't find enough.
  195 #
  196 sub createRandomPoints {
  197   my $self = shift;
  198   my $num_points = @_[0];
  199   $num_points = int($self->getFlag('num_points',5)) unless defined($num_points);
  200   $num_points = 1 if $num_points < 1;
  201 
  202   my @vars = $self->{context}->variables->names;
  203   my @limits = $self->getVariableLimits(@vars);
  204   my @make = $self->getVariableTypes(@vars);
  205   my $f = $self->{f}; $f = $self->{f} = $self->perlFunction(undef,[@vars]) unless $f;
  206   my $seedRandom = $self->{context}->flag('random_seed')? 'PGseedRandom' : 'seedRandom';
  207   my $getRandom  = $self->{context}->flag('random_seed')? 'PGgetRandom'  : 'getRandom';
  208 
  209   $self->$seedRandom;
  210   my $points = []; my $values = [];
  211   my (@P,@p,$v,$i); my $k = 0;
  212   while (scalar(@{$points}) < $num_points && $k < 10) {
  213     @P = (); $i = 0;
  214     foreach my $limit (@limits) {
  215       @p = (); foreach my $I (@{$limit}) {push @p, $self->$getRandom(@{$I})}
  216       push @P, $make[$i++]->make(@p);
  217     }
  218     $v = eval {&$f(@P)};
  219     if (!defined($v)) {$k++} else {
  220       push @{$points}, [@P];
  221       push @{$values}, Value::makeValue($v);
  222       $k = 0; # reset count when we find a point
  223     }
  224   }
  225 
  226   Value::Error("Can't generate enough valid points for comparison") if $k;
  227   return ($points,$values) if defined(@_[0]);
  228   $self->{test_values} = $values;
  229   $self->{test_points} = $points;
  230 }
  231 
  232 #
  233 #  Get the array of variable limits
  234 #
  235 sub getVariableLimits {
  236   my $self = shift;
  237   my $userlimits = $self->{limits};
  238   if (defined($userlimits)) {
  239     $userlimits = [[[-$userlimits,$userlimits]]] unless ref($userlimits) eq 'ARRAY';
  240     $userlimits = [$userlimits] unless ref($userlimits->[0]) eq 'ARRAY';
  241     $userlimits = [$userlimits] if scalar(@_) == 1 && ref($userlimits->[0][0]) ne 'ARRAY';
  242     foreach my $I (@{$userlimits}) {$I = [$I] unless ref($I->[0]) eq 'ARRAY'};
  243   }
  244   $userlimits = [] unless $userlimits; my @limits;
  245   my $default;  $default = $userlimits->[0][0] if defined($userlimits->[0]);
  246   my $default = $default || $self->{context}{flags}{limits} || [-2,2];
  247   my $granularity = $self->getFlag('granularity',1000);
  248   my $resolution = $self->getFlag('resolution');
  249   my $i = 0;
  250   foreach my $x (@_) {
  251     my $def = $self->{context}{variables}{$x};
  252     my $limit = $userlimits->[$i++] || $def->{limits} || [];
  253     $limit = [$limit] if defined($limit->[0]) && ref($limit->[0]) ne 'ARRAY';
  254     push(@{$limit},$limit->[0] || $default) while (scalar(@{$limit}) < $def->{type}{length});
  255     pop(@{$limit}) while (scalar(@{$limit}) > $def->{type}{length});
  256     push @limits, $self->addGranularity($limit,$def,$granularity,$resolution);
  257   }
  258   return @limits;
  259 }
  260 
  261 #
  262 #  Add the granularity to the limit intervals
  263 #
  264 sub addGranularity {
  265   my $self = shift; my $limit = shift; my $def = shift;
  266   my $granularity = shift; my $resolution = shift;
  267   $granularity = $def->{granularity} || $granularity;
  268   $resolution = $def->{resolution} || $resolution;
  269   foreach my $I (@{$limit}) {
  270     my ($a,$b,$n) = @{$I}; $b = -$a unless defined $b;
  271     $I = [$a,$b,($n || $resolution || abs($b-$a)/$granularity)];
  272   }
  273   return $limit;
  274 }
  275 
  276 #
  277 #  Get the routines to make the coordinates of the points
  278 #
  279 sub getVariableTypes {
  280   my $self = shift;
  281   my @make;
  282   foreach my $x (@_) {
  283     my $type = $self->{context}{variables}{$x}{type};
  284     if ($type->{name} eq 'Number') {
  285       push @make,($type->{length} == 1)? 'Value::Formula::number': 'Value::Complex';
  286     } else {
  287       push @make, "Value::$type->{name}";
  288     }
  289   }
  290   return @make;
  291 }
  292 
  293 #
  294 #  Fake object for making reals (rather than use overhead of Value::Real)
  295 #
  296 sub Value::Formula::number::make {shift; shift}
  297 
  298 ##
  299 ##  debugging routine
  300 ##
  301 #sub main::Format {
  302 #  my $v = scalar(@_) > 1? [@_]: shift;
  303 #  $v = [%{$v}] if ref($v) eq 'HASH';
  304 #  return $v unless ref($v) eq 'ARRAY';
  305 #  my @V; foreach my $x (@{$v}) {push @V, main::Format($x)}
  306 #  return '['.join(",",@V).']';
  307 #}
  308 
  309 #
  310 #  Random number generator  (replaced by Value::WeBWorK.pm)
  311 #
  312 sub seedRandom {srand}
  313 sub getRandom {
  314   my $self = shift;
  315   my ($m,$M,$n) = @_; $n = 1 unless $n;
  316   return $m + $n*int(rand()*(int(($M-$m)/$n)+1));
  317 }
  318 
  319 #
  320 #  Get the value of a flag from the object itself,
  321 #  or from the context, or from the default context
  322 #  or from the given default, whichever is found first.
  323 #
  324 sub getFlag {
  325   my $self = shift; my $name = shift;
  326   return $self->{$name} if defined($self->{$name});
  327   return $self->{context}{flags}{$name} if defined($self->{context}{flags}{$name});
  328   return $$Value::context->{flags}{$name} if defined($$Value::context->{flags}{$name});
  329   return shift;
  330 }
  331 
  332 ############################################
  333 #
  334 #  Check if the value of a formula is constant
  335 #    (could use shift->{tree}{isConstant}, but I don't trust it)
  336 #
  337 sub isConstant {scalar(%{shift->{variables}}) == 0}
  338 
  339 ###########################################################################
  340 
  341 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9