#!/usr/bin/perl # double-torus-relations: given a set of t/i numbers # ($t/$i, $u/$j, $v/$k), first map the corresponding # knot into a path of handle/row/column numbers # ($h, $r, $c), and then to a set of relations. # Whether or not to output verbose intermediate step # information for debugging $DEBUG = 0; use CGI; open STDERR, '>&STDOUT'; my $q=CGI->new; $t1 = $q->param('t1'); $i1 = $q->param('i1'); $t2 = $q->param('t2'); $i2 = $q->param('i2'); $t3 = $q->param('t3'); $i3 = $q->param('i3'); my @ti = ($t1,$i1,$t2,$i2,$t3,$i3); print $q->header(); print '
';

# el letters, for now.
my @ELS = ('a' .. 'd', 'f' .. 'z');

my @t = @ti[0, 2, 4];
my @i = @ti[1, 3, 5];

unless ( $i1 =~ /^[+-]?[0-9]+$/ ) {
  error( "i1 is not an integer.\n" );
}

unless ( $t1 =~ /^[+-]?[0-9]+$/ ) {
  error( "t1 is not an integer.\n" );
}

unless ( $i2 =~ /^[+-]?[0-9]+$/ ) {
  error( "i2 is not an integer.\n" );
}

unless ( $t2 =~ /^[+-]?[0-9]+$/ ) {
  error( "t2 is not an integer.\n" );
}

unless ( $i3 =~ /^[+-]?[0-9]+$/ ) {
  error( "i3 is not an integer.\n" );
}

unless ( $t3 =~ /^[+-]?[0-9]+$/ ) {
  error( "t3 is not an integer.\n" );
}

# immediately check if we have a 0 denom with a non-zero num.
if ($i[0] == 0 && $t[0] != 0 or
    $i[1] == 0 && $t[1] != 0 or
    $i[2] == 0 && $t[2] != 0) {
  error("a/0, with a != 0: must be not-knot.\n");
}

# verify that @i are non-negative and sum of @i is even and middle $i is maximum
verify_i(@i);

# total # of crossings.
my $crossings = abs($t[0]) * ($i[0] - 1)
              + abs($t[1]) * ($i[1] - 1)
              + abs($t[2]) * ($i[2] - 1);

# make the handle-map.
my %h_map = new_h_map( $t[0], $t[1], $t[2], $i[0], $i[1], $i[2] );
if ( $DEBUG ) 
{
    print( "handle map:\n" );
    print( h_map_str(%h_map), "\n" );
    print_map(%h_map);
    print( "\n" );
}

# make the path.
if( $DEBUG ) { print "path:\n" };

my($els, $path, $tops, %crossings) = path(\@t, \@i, \%h_map);

if( $DEBUG )
{
    print "els(@$els)\n";
    print "\n";
}

# error-checking.
{
  # XXX check this.
  if (@$els == 1 or $els->[-1] != 0) {
    my $message = "DID NOT CLOSE PATH: ELS(@$els)\n";
    error($message, $els, $path, %crossings);
  }
  pop @$els;
  if (@$els == 1) {
    # one element; must be unknot/link/unlink.
    my $message = "ONLY ONE ELEMENT\n";
    error($message, $els, $path, %crossings);
  }
  # this checks that we visited all top strands.
  if ($tops != $i[0] + $i[1] + $i[2]) {
    my $i_sum = $i[0] + $i[1] + $i[2];
    my $message = "DID NOT VISIT ALL TOP STRANDS ($tops < $i_sum)\n";
    error($message, $els, $path, %crossings);
  }
}

if( $DEBUG )
{
	print "crossings:\n";
	print_crossings(%crossings);
	print "\n";
}

# get the relators.
if( $DEBUG ) { print "relators:\n"; }
my @relators = relators(%crossings);
if( $DEBUG ) { print "\n"; }

if ( $DEBUG )
{
	print "relators again:\n";
    for my $ret (@relators)
    {
        print "$ret\n";
    }
print "\n";
}

# make the Jacobian matrix.
if ( $DEBUG ) { print "matrix:\n"; }
my @matrix = jacobian_matrix(@relators);
if( $DEBUG )
{
    print "\n";
    print "\n";
}

# finally, the real Jacobian.
print_matrix(@matrix);
print "determinant:\n";
print "\n";
my $det = det(@matrix);
$det = poly_prune($det);
$det = poly_center($det);
my $det_str = poly_str(%$det);
print "DET: $det_str\n";
my $rs = rolfsen_str($det);
print "RS: $rs\n";

exit;

sub new_h_map
{
	my ( $a, $b, $c, $x, $y, $z ) = @_;

    my $l = ( ( -( $x ) + $y - $z ) / 2 );
    
    if( $l < 0 ) { $l = 0; }
			
	my $xy = ( ( $x + $y - $z ) / 2 ) - $l;
	my $xz = ( ( $x - $y + $z ) / 2 ) + $l;
	my $yz = ( ( -( $x ) + $y + $z ) / 2) - $l;
	
	my $n = 0;
		
	my %h_map;
	
	if( $l == 0 )
	{	
		do
		{
			$h_map{ cs( [ 0, 0, $n ] ) } = cs( [ 2, 0, $z - $n - 1 ] );
			$h_map{ cs( [ 2, 0, $z - $n - 1 ] ) } = cs( [ 0, 0, $n ] );
			$h_map{ cs( [ 0, abs( $a ) + 1, $n ] ) } = cs( [ 2, abs( $c ) + 1, $z - $n - 1 ] );
			$h_map{ cs( [ 2, abs( $c ) + 1, $z - $n - 1 ] ) } = cs( [ 0, abs( $a ) + 1, $n ] );
			$n++;
		} while $n < $xz;
		
		$n = 0;
		
		do
		{
			$h_map{ cs( [ 0, 0, $xz + $n ] ) } = cs( [ 1, 0, $xy - $n - 1 ] );
			$h_map{ cs( [ 1, 0, $xy - $n - 1 ] ) } = cs( [ 0, 0, $xz + $n ] );
			$h_map{ cs( [ 0, abs( $a ) + 1, $xz + $n ] ) } = cs( [ 1, abs( $b ) + 1, $xy - $n - 1 ] );
			$h_map{ cs( [ 1, abs( $b ) + 1, $xy - $n - 1 ] ) } = cs( [ 0, abs( $a ) + 1, $xz + $n ] );
			$n++;
		} while $n < $xy;
		
		$n = 0;
		
		do
		{
			$h_map{ cs( [ 1, 0, $xy + $n ] ) } = cs( [ 2, 0, $yz - $n - 1 ] );
			$h_map{ cs( [ 2, 0, $yz - $n - 1 ] ) } = cs( [ 1, 0, $xy + $n ] );
			$h_map{ cs( [ 1, abs( $b ) + 1, $xy + $n ] ) } = cs( [ 2, abs( $c ) + 1, $yz - $n - 1 ] );
			$h_map{ cs( [ 2, abs( $c ) + 1, $yz - $n - 1 ] ) } = cs( [ 1, abs( $b ) + 1, $xy + $n ] );
			$n++;
		} while $n < $yz;
	} else 
	{
		$n = 0;
		
		do
		{
			$h_map{ cs( [ 0, 0, $n ] ) } = cs( [ 1, 0, $xy - $n - 1 ] );
            $h_map{ cs( [ 1, 0, $xy - $n - 1 ] ) } = cs( [ 0, 0, $n ] );
            $h_map{ cs( [ 0, abs( $a ) + 1, $n ] ) } = cs( [ 1, abs( $b ) + 1, $x - $n - 1 ]  );
            $h_map{ cs( [ 1, abs( $b ) + 1, $x - $n - 1 ] ) } = cs( [ 0, abs( $a ) + 1, $n ]  );
            $n++;
		} while ( $n < $xy );
		
		$n = 0;
		
		do
		{
            $h_map{ cs( [ 1, 0, $x + $l + $n ] ) } = cs( [ 2, 0, $z - $n - 1 ] );
            $h_map{ cs( [ 2, 0, $z - $n - 1 ] ) } = cs( [ 1, 0, $x + $l + $n ] );
            $h_map{ cs( [ 1, abs( $b ) + 1, $x + $l + $n ] ) } = cs( [ 2, abs( $c ) + 1, $z - $n - 1 ] );
            $h_map{ cs( [ 2, abs( $c ) + 1, $z - $n - 1 ] ) } = cs( [ 1, abs( $b ) + 1, $x + $l + $n ] );
            $n++;
		} while $n < $yz;
		
		$n = 0;
		
		do
		{
            $h_map{ cs( [ 1, 0, $x + $n ] ) } = cs( [ 1, 0, $y - 1 - $n ] );
            $h_map{ cs( [ 1, 0, $y - 1 - $n ] ) } = cs( [ 1, 0, $x + $n ] );
            $h_map{ cs( [ 1, abs( $b ) + 1, $x + $n ] ) } = cs( [ 1, abs( $b ) + 1, $y - 1 - $n ] );
            $h_map{ cs( [ 1, abs( $b ) + 1, $y - 1 - $n ] ) } = cs( [ 1, abs( $b ) + 1, $x + $n ] );
            $n++;
		} while $n < $l;
		
	}
	
	return( %h_map )
}

sub det {
  my @m = @_;
  if (@m == 0) {
    die "should never get here";
  }
  if (@m == 1) {
    return $m[0][0];
  }
  if (@m == 2) {
    my $det = det2(@m);
    return $det;
  }
  my $det = {};
  for my $i (0 .. $#m) {
    my %entry = %{$m[0][$i]};
    next if !%entry;  # skip zero entries

    my @minor = minor($i, @m);
    my $det_minor = det(@minor);

    my $poly_str = poly_str(%$det_minor);

    next if !%$det_minor;
    $det_minor = poly_mult_scalar( (-1) ** $i, $det_minor );
    $det_add = poly_mult(\%entry, $det_minor);
    $det = poly_plus($det, $det_add);
  }
  return $det;
}

sub minor {
# the minor of a matrix.
  my($i, @m) = @_;
  my @minor;
  for my $r (1 .. $#m) {
    my @row = @{$m[$r]};
    my @rowi = grep $_ != $i, 0 .. $#row;
    my @rowm = map $m[$r][$_], @rowi;
    push @minor, [ @rowm ];
  }
  return @minor;
}

sub det2 {
# the determinant of a 2x2 matrix.
  my @m = @_;
  my $plus = poly_mult($m[0][0], $m[1][1]);
  my $minus = poly_mult($m[0][1], $m[1][0]);
  my $det = poly_minus($plus, $minus);

  return $det;
}

sub poly_mult_scalar {
# $a * %poly.
  my($a, $p) = @_;
  my %p = %$p;
  for (keys %p) {
    $p{$_} *= $a;
  }
  return { %p };
}

sub poly_minus {
# %poly1 - %poly2.
  my($p1, $p2) = @_;
  my $p2_minus = poly_mult_scalar(-1, $p2);
  my $poly_minus = poly_plus($p1, $p2_minus);
  return $poly_minus;
}

sub poly_plus {
# %poly1 + %poly2.
  my($p1, $p2) = @_;
  if (!%$p1) { return $p2 }
  if (!%$p2) { return $p1 }
    
  my($exp1_min, $exp1_max) = (sort { $a <=> $b } keys %$p1)[0, -1];
  my($exp2_min, $exp2_max) = (sort { $a <=> $b } keys %$p2)[0, -1];
  my $exp_min = $exp1_min < $exp2_min ? $exp1_min : $exp2_min;
  my $exp_max = $exp1_max > $exp2_max ? $exp1_max : $exp2_max;
  
  my %p;
  for my $exp ($exp_min .. $exp_max) {
    my $coeff = ($p1->{$exp} || 0) + ($p2->{$exp} || 0);
    $p{$exp} = $coeff;
  }

  return { %p };
} 

sub poly_mult {
# %poly1 * %poly2.
  my($p1, $p2) = @_;
  my %p;
  for my $exp1 (keys %$p1) {
    for my $exp2 (keys %$p2) {
      my $exp = $exp1 + $exp2;
      my $coeff = $p1->{$exp1} * $p2->{$exp2};
      $p{$exp} += $coeff;
    }
  }
  return { %p };
}

sub poly_prune {
# remove leading and trailing zeros.
  my $p = shift;
  my %p = %$p;
  my %pruned = %p;
  for my $exp (sort { $a <=> $b } keys %pruned) {
    last if $pruned{$exp};
    delete $p{$exp};
  }
  %pruned = %p;
  for my $exp (sort { $b <=> $a } keys %pruned) {
    last if $pruned{$exp};
    delete $p{$exp};
  }
  return { %p };
}

sub poly_center {
  my $p = shift;
  my %p = %$p;

  my $expc = -1 * (keys(%p) - 1) / 2;
  my %pc;
  for my $exp (sort { $a <=> $b } keys %p) {
    $pc{$expc} = $p{$exp};
    $expc++;
  }
  # make the center term positive, if it isn't.
  if ($pc{0} < 0) {
    for my $exp (keys %pc) { $pc{$exp} *= -1 }
  }

  return { %pc };  
}

sub rolfsen_str {
# "[ 1 - 2 + 1"
  my $p = shift;
  my %p = %$p;
  my $rs;
  my $exp = 0;
  {
    my $coeff = $p{$exp};
    $rs .= $coeff < 0 ? ' - ' : ' + ';
    $rs .= abs($coeff);
    $exp++;
    last if !exists $p{$exp};
    redo;
  }
  $rs =~ s/^ \+ //;
  $rs = '[ ' . $rs;
  return $rs;
}

sub print_matrix {
  my @m = @_;

  # find the longest term length.
  my $length = 0;
  for my $row (@m) {
    my @row = @$row;
    for my $c (0 .. $#row) {
      my %poly = %{$row[$c]};
      my $poly_str = poly_str(%poly);
      if (length($poly_str) > $length) { $length = length($poly_str) }
    }
  }
  $length++;  # for a space

  # print it.
  for my $row (@m) {
    my @row = @$row;
    print "[ ";
    for my $c (0 .. $#row) {
      my %poly = %{$row[$c]};
      my $poly_str = poly_str(%poly);
      printf "%-${length}s", $poly_str;
    }
    print " ]\n";
  }
  return;
}

sub jacobian_matrix {
  my @relators = @_;

  my @m;

  # don't use the last one.
  for my $el1 (0 .. $crossings - 2) {
    for my $el2 (0 .. $crossings - 2) {
      my %poly = differential($relators[$el1], $el2);
      my $poly_str = poly_str(%poly);
      $m[$el1][$el2] = { %poly };
    }
  }
  return @m;
}

sub differential {
# d($ret)/d($el)
  my($ret, $el) = @_;
  my %poly; 
  for my $j (1 .. length($ret)) {
    my $substr = substr($ret, 0, $j);
    next unless my($last) = $substr =~ /(\($el'?\))$/;
    my $coeff;
    if ($last =~ /'/) {
      $coeff = -1;
      $substr =~ s/\Q$last\E$//;
    }
    else {
      $coeff = 1;
    }
    my $plus = () = $substr =~ /(\(\d+\))/g;
    my $minus= () = $substr =~ /(\(\d+'\))/g;
    my $exp = $plus - $minus;
    $poly{$exp} += $coeff;
  }
  return %poly;
}

sub poly_str {
  my %poly = @_;
  return '0' if !%poly;
  my @terms;
  for my $exp (sort { $a <=> $b } keys %poly) {
    my $coeff = $poly{$exp};
    my $term = term_str($coeff, $exp);
    push @terms, $term;
  }
  my $str = join ' ', @terms;
  $str =~ s/^\+ //;
  $str =~ s/^\- /-/;
  return $str;
}

sub term_str {
  my($coeff, $exp) = @_;
  # usual forms.
  my $coeff_str = $coeff >= 0 ? ('+ ' . $coeff) : ('- ' . abs($coeff));
  my $exp_str = 'x^' . $exp;
  if ($exp == 0) {
    $exp_str = '';
  }
  elsif ($exp == 1) {
    $exp_str = 'x';
  }
  else {
  }

  my $str = "$coeff_str$exp_str";
  # " 1x" -> " x"
  $str =~ s/ 1x/ x/;

  return $str;
}

sub relators {
  my %crossings = @_;
  for my $hrc (sort by_hrc keys %crossings) {
    my($el_under, $dir_under) = @{ $crossings{$hrc}{under} };
    my($el_over, $dir_over) = @{ $crossings{$hrc}{over} };

    my $el_next = ($el_under + 1) % $crossings;
    my($exp1, $exp2, $exp_str);
    if (($dir_under eq 'E' && $dir_over =~ /^S/)
     || ($dir_under eq 'W' && $dir_over =~ /^N/)) {
      $exps = $ELS[$el_over];
      $exp1 = "$el_over'";
      $exp2 = $el_over;
    }
    else {
      $exps = uc $ELS[$el_over];
      $exp1 = $el_over;
      $exp2 = "$el_over'";
    }

    # looks better inverted.
    my $relator = "($exp1)($el_next')($exp2)($el_under)";
    push @relators, $relator;

    if ( $DEBUG ) { print "($el_under) = ($el_next)^($exp2)\n"; }
  }
  return @relators;
}

sub print_crossings {
  my %crossings = @_;
  for my $hrc (sort by_hrc keys %crossings) {
    my($el_under, $dir_under) = @{ $crossings{$hrc}{under} };
    my($el_over, $dir_over) = @{ $crossings{$hrc}{over} };
    print "($hrc): under($el_under, $dir_under) over($el_over, $dir_over)\n";
  }
  return;
}


sub path {
  my($t, $i, $h_map) = @_;
  my @t = @$t;
  my @i = @$i;
  my %h_map = %$h_map;

  my @path;

  # the crossings.
  my %crossings;

  # starting stuff.
  my @els;
  my $el = 0;
  push @els, $el;
  my($h, $r, $c) = (0, 0, 0);
  push @path, [$h, $r, $c];
  my $next = $t[$h] == 0 ? 'S' : $t[$h] > 0 ? 'SW' : 'SE';
  if( $DEBUG )
  {
	  print "EL: $el\n";
	  print "PATH: ", cs(@path), " | NEXT($next)\n";
  }

  my $count = 0;
  my $tops = 0;
  PATH: {
    if ($next eq 'N') {
      ($h, $r, $c) = ($h, $r - 1, $c);
    }
    elsif ($next eq 'S') {
      ($h, $r, $c) = ($h, $r + 1, $c);
    }
    elsif ($next eq 'E') {
      ($h, $r, $c) = ($h, $r, $c + 1);
      # hit the right side: go either NW or SW based on t for this handle.
      if ($c == $i[$h]) {
        $next = $t[$h] < 0 ? 'NW' : 'SW';
      }
      # new element unless hit the right side.
      else {
	$crossings{cs($h,$r,$c)}{under} = [ $el, 'E' ];
        if( $DEBUG ) { print "EL: $el -> "; }
        $el++;
	$el = $el % $crossings;
        push @els, $el;
        if( $DEBUG ) { print "$el\n"; }
      }
    }
    elsif ($next eq 'W') {
      ($h, $r, $c) = ($h, $r, $c - 1);
      # hit the left side: go either NE or SE based on t for this handle.
      if ($c == 0) {
        $next = $t[$h] < 0 ? 'SE' : 'NE';
      }
      # new element and a crossing unless hit the left side.
      else {
	$crossings{cs($h,$r,$c)}{under} = [ $el, 'W' ];
        if ( $DEBUG ) { print "EL: $el -> "; }
        $el++;
	$el = $el % $crossings;
        push @els, $el;
        if( $DEBUG ) { print "$el\n"; }
      }
    }
    elsif ($next eq 'SE') {
      # if ending at the bottom, and t < 0 for this handle, need to alter.
      if ($r == abs($t[$h]) && $t[$h] < 0) {
        ($h, $r, $c) = ($h, $r + 1, $c);
      }
      else {
        ($h, $r, $c) = ($h, $r + 1, $c + 1);
      }
      # hit the right side: go W.
      if ($c == $i[$h]) {
        $next = 'W';
      }
      else {
        # a crossing, if not at bottom.
	if ($r <= abs($t[$h])) {
	  $crossings{cs($h,$r,$c)}{over} = [ $el, 'SE' ];
	}
      }
    }
    elsif ($next eq 'SW') {
      # if starting from the top, and t > 0 for this handle, need to alter.
      if ($r == 0 && $t[$h] > 0) {
        ($h, $r, $c) = ($h, $r + 1, $c);
      }
      else {
        ($h, $r, $c) = ($h, $r + 1, $c - 1);
      }
      # hit the left side: go E.
      if ($c == 0) {
        $next = 'E';
      }
      else {
        # a crossing, if not at bottom.
	if ($r <= abs($t[$h])) {
	  $crossings{cs($h,$r,$c)}{over} = [ $el, 'SW' ];
	}
      }
    }
    elsif ($next eq 'NE') {
      # if ending at the top, and t > 0 for this handle, need to alter.
      if ($r == 1 && $t[$h] > 0) {
        ($h, $r, $c) = ($h, $r - 1, $c);
      }
      else {
        ($h, $r, $c) = ($h, $r - 1, $c + 1);
      }
      # hit the right side: go W.
      if ($c == $i[$h]) {
        $next = 'W';
      }
      else {
        # a crossing, if not at top.
	if ($r > 0) {
	  $crossings{cs($h,$r,$c)}{over} = [ $el, 'NE' ];
	}
      }
    }
    elsif ($next eq 'NW') {
      # if starting from the bottom, and t < 0 for this handle, need to alter.
      if ($r > abs($t[$h])) {
        ($h, $r, $c) = ($h, $r - 1, $c);
      }
      else {
        ($h, $r, $c) = ($h, $r - 1, $c - 1);
      }
      # hit the left side: go E.
      if ($c == 0) {
        $next = 'E';
      }
      else {
        # a crossing, if not at top.
	if ($r > 0) {
	  $crossings{cs($h,$r,$c)}{over} = [ $el, 'NW' ];
	}
      }
    }
    else {
      die "unknown NEXT($next)";
    }

    push @path, [$h, $r, $c];
    if( $DEBUG ) { print "PATH: ", cs($h,$r,$c), " | NEXT($next)\n"; }

    # hit the top?
    if ($r == 0) {
      my $h_map_next = $h_map{cs($h, $r, $c)}
        or die "can't find h_map for ($h, $r, $c)";
      ($h, $r, $c) = sc($h_map_next);
      push @path, [$h, $r, $c];
      $next = $t[$h] == 0 ? 'S' : $t[$h] > 0 ? 'SW' : 'SE';
      if( $DEBUG ) { print "PATH: ", cs($h,$r,$c), " | NEXT($next)\n"; }
      $tops += 2;
      if( $DEBUG ) { print "TOPS($tops)\n"; }
    }

    # hit the bottom?
    if ($r > abs($t[$h])) {
      my $h_map_next = $h_map{cs($h, $r, $c)}
        or die "can't find h_map for ($h, $r, $c)";
      ($h, $r, $c) = sc($h_map_next);
      push @path, [$h, $r, $c];
      $next = $t[$h] == 0 ? 'N' : $t[$h] > 0 ? 'NE' : 'NW';
      if( $DEBUG ) { print "PATH: ", cs($h,$r,$c), " | NEXT($next)\n"; }
    }

    last PATH if $h == 0 && $r == 0 && $c == 0;
    $count++; last if $count > 1_000_000;
    redo PATH;
  }

  return(\@els, \@path, $tops, %crossings);
}

sub h_map_str {
# returns a handle map as a single string, for testing.
# "1 - 6, 2 - 3, 4 - 5";
  my %h_map = @_;
  my %c2i;
  my $i = 0;
  for my $from (sort by_hrc keys %h_map) {
    $i++;
    $c2i{$from} = $i;
  }
  my @str;
  my %seen;
  for my $from (sort by_hrc keys %h_map) {
    next if $seen{$from};
    my $to = $h_map{$from};
    $seen{$to}++;
    my $from_i = $c2i{$from};
    my $to_i = $c2i{$to};
    push @str, "$from_i - $to_i";
  }
  my $str = join ', ', @str;
  return $str;
}

sub by_hrc {
  my @hrca = sc($a);
  my @hrcb = sc($b);
  $hrca[0] <=> $hrcb[0]
    or
  $hrca[1] <=> $hrcb[1]
    or
  $hrca[2] <=> $hrcb[2]
    ;
}

sub print_map {
  my %map = @_;
  for my $from (sort by_hrc keys %map) {
    print "$from <-> $map{$from}\n";
  }
  return;
}

sub cs {
# the coordinate-string for ($h, $r, $c).
  my $hrc = shift;
  my @hrc;
  if (ref($hrc) eq 'ARRAY') {
    @hrc = @$hrc;
  }
  else {
    @hrc = ($hrc, @_);
  }
  local $" = ', ';
  my $cs = "@hrc";
  return $cs;
}

sub sc {
# coord-string -> ($h, $r, $c)
  my $hrc = shift;
  my @sc = split /,\s*/, $hrc;
  return @sc;
}

sub verify_i {
  my @i = @_;
  
  # each must be non-negative integers.
  # each must be positive integers.
  my $sum = 0;
  for my $i (@i) {
    ($i >= 0) && (int($i) == $i)
      or error( "each i (@i) must be non-negative." );
    $sum += $i;
  }
  
  # the sum must be even.
  $sum % 2 == 0 or error( "sum of i (@i) must be even." );

  # the middle must be the maximum.
  ($i[1] >= $i[0]) && ($i[1] >= $i[2])
    or error( "middle i (@i) must be the maximum." );

  if ($i[1] > ($i[0] + $i[2])) {
    print "RS: loops\n";
  }

  # all ok.
  return;
}

sub error {
# XXX check this.
# get here if $els->[-1] != 0.  (i.e. did not close the path)
  my($message, $els, $path, %crossings) = @_;
  print $message;
  #print "RS: unknot, link, or unlink\n";
  exit;
}