#
# This software is Copyright 2005 by Elsevier Inc.  You may use it
# under the terms of the license at http://perl.plover.com/hop/LICENSE.txt .
#



###
### Equation.pm
###

## Chapter 9 section 4.1

sub new {
  my ($base, %self) = @_;
  $class = ref($base) || $base;
  $self{""} = 0 unless exists $self{""};
  for my $k (keys %self) {
    if ($self{$k} == 0 && $k ne "") { delete $self{$k} }
  }
  bless \%self => $class;
}


## Chapter 9 section 4.1.2

BEGIN { $Zero = Equation->new() }
sub coefficient {
  my ($self, $name) = @_;
  $self->{$name} || 0;
}
# Constant part of an equation
sub constant {
  $_[0]->coefficient("");
}
sub varlist {
  my $self = shift;
  grep $_ ne "", keys %$self;
}


## Chapter 9 section 4.1.2

sub arithmetic {
  my ($a, $ac, $b, $bc) = @_;
  my %new;
  for my $k (keys(%$a), keys %$b) {
    my ($av) = $a->coefficient($k);
    my ($bv) = $b->coefficient($k);
    $new{$k} = $ac * $av + $bc * $bv;
  }
  $a->new(%new);
}
sub add_equations {
  my ($a, $b) = @_;

  arithmetic($a, 1, $b, 1);
}
sub subtract_equations {
  my ($a, $b) = @_;

  arithmetic($a, 1, $b, -1);
}
sub scale_equation {
  my ($a, $c) = @_;
  arithmetic($a, $c, $Zero, 0);
}


## Chapter 9 section 4.1.2

# Destructive
sub substitute_for {
  my ($self, $var, $value) = @_;
  my $a = $self->coefficient($var);
  return if $a == 0;
  my $b = $value->coefficient($var);
  die "Oh NO" if $b == 0;  # Should never happen

  my $result = arithmetic($self, 1, $value, -$a/$b);
  %$self = %$result;
}


## Chapter 9 section 4.1.2

sub a_var {
  my $self = shift;
  my ($var) = $self->varlist;
  $var;
}


## Chapter 9 section 4.1.2

package Equation::System;

sub new {
  my ($base, @eqns) = @_;
  my $class = ref $base || $base;
  bless \@eqns => $class;
}
package Equation;

sub is_tautology {
  my $self = shift;
  return $self->constant == 0 && $self->varlist == 0;
}
package Equation::System;

sub equations {
  my $self = shift;
  grep defined, @$self;
}
sub apply {
  my ($self, $func) = @_;
  for my $eq ($self->equations) {
    $func->($eq);
  }
}
sub solve {
  my $self = shift;
  my $N = my @E = $self->equations;
  for my $i (0 .. $N-1) {
    next unless defined $E[$i];
    my $var = $E[$i]->a_var;
    for my $j (0 .. $N-1) {
      next if $i == $j;
      next unless defined $E[$j];
      next unless $E[$j]->coefficient($var);
      $E[$j]->substitute_for($var, $E[$i]);
      if ($E[$j]->is_tautology) { 
        undef $E[$j]; 
      } elsif ($E[$j]->is_inconsistent) {
        return ;
      }
    }
  }
  $self->normalize;
  return 1;
}


## Chapter 9 section 4.1.2

package Equation;

sub is_inconsistent {
  my $self = shift;
  return $self->constant != 0 && $self->varlist == 0;
}


## Chapter 9 section 4.1.2

package Equation::System;

sub normalize {
  my $self = shift;
  $self->apply(sub { $_[0]->normalize });
}
package Equation;

sub normalize {
  my $self = shift;
  my $var = $self->a_var;
  return unless defined $var;
  %$self = %{$self->scale_equation(1/$self->coefficient($var))};
}
sub defines_var {
  my $self = shift;
  my @keys = keys %$self;
  return unless @keys == 2;
  my $var = $keys[0] || $keys[1];
  return $self->{$var} == 1 ? $var : () ;
}
package Equation::System;

sub values {
  my $self = shift;
  my %values;
  $self->solve;
  for my $eqn ($self->equations) {
    if (my $name = $eqn->defines_var) {
      $values{$name} = -$eqn->constant;
    }
  }
  %values;
}

1;
