#!/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;
}