#!/usr/bin/perl # $unused_header = q` Sloandora - Beginnings of an auto-discovery system for DBIS/OEIS This work is licensed under a Creative Commons Attribution 2.5 License. Details at: http://creativecommons.org/licenses/by/2.5/ The responsiveness of the rating commands (Y, N and Unrate) is a good benchmark for the PCMS (processor - cache - memory subsystem) and filesystem. It is one of several HPC programs and benchmarks I have developed over the years; for more see mrob.com/pub/comp/high-performance.html REVISION HISTORY 20091025 First version. It converts the raw source files into the format needed by #cdiff#, divided into a number of pieces matching the number of CPUs on this system, and indexes them. Remove A-number from each line while converting eisBTfry files to cdiff input format. 20091026 get the command-loop working, with the initial set of commands #Y#, #N#, #next#, #A012345#, #best#, #show#, #save#, #help#, #quit#. 20091112 Add #web# and two #auto# commands. 20091116 Remove content-free "%I \n" from beginning of curseq text; display sequence name in color. 20091118 Add #ratings# command; add a test to &cmd_rate to disallow rating the same sequence twice. Clean up formatting a bit (e.g. only show 3 digits of ratings). #auto show# and #auto web# now also affect the #A012345# and #best# commands. 20091119 &rate_it now displays total CPU time used by child processes. 20100118 Add #unrate# command and use new "Read from stream2" values to compute total number of key comparisons statistic. 20100705 Update help to include #unrate# and #auto# commands. 20141005 Fix a few bugs; recognize "DBIS-0027.txt" filename format for the raw data files. 20141012 Sort output into topic file; handle 'e' in ratings (e.g. '9.9e-05') 20220525 Change the name of the compiled C program from "cdiff" to "tcdist", to remove name collision with the colour "diff" tool, and because concordiff really doesn't do a "diff", it is much more like a distance metric. `; $| = 1; $0 = "Sloandora"; # Formatting for ISO color attributes (these are not "VT320" sequences # but are often thought of as being such) $avc_esc = "\033"; $avc_normal = $avc_esc . "[0m"; $avc_red = $avc_esc . "[30;41m"; $avc_yello1 = $avc_esc . "[30;43m"; $avc_yello2 = $avc_esc . "[0;1;33;43m"; # Unreadable on MacOS X Terminal $avc_yello3 = $avc_esc . "[0;1;31;43m"; # Low-contrast on Konsole on KWM on RedHat 7.2 $avc_yello4 = $avc_esc . "[0;1;33;41m"; $avc_green = $avc_esc . "[30;42m"; $avc_cyan1 = $avc_esc . "[30;46m"; $avc_cyan2 = $avc_esc . "[0;1;34;46m"; $avc_blue = $avc_esc . "[30;44m"; $avc_magenta = $avc_esc . "[30;45m"; $avc_mag_bold_black = $avc_esc . "[0;1;30;45m"; sub splash { print q@ _ _ (_` | _ ._ ._ _| _ ._ ._ , ) | ( ) ,-| | | ( | ( ) | ,-| ~ ~ ~ `~` ~ ~ ~` ~ ~ `~` User-Aided Integer Sequence Auto-Discovery System by Robert Munafo, with the seqfan list members @; } sub help { print qq@ Sloandora commands: Show Display the entire record for the current sequence Web Display the current sequence in your web browser Auto show Automatically do the "Show" command for each new recommendation Auto web Automatically do the "Web" command for each new recommendation Y, yes, Relevant The currently displayed sequence matches this topic N, no, Irrelevant The currently displayed sequence does NOT match Unrate Remove any rating previously applied to the current sequence Next, Skip Show me another sequence you think is relevant A012345 Show me sequence A012345 Best Re-select the latest recommendation Ratings List all sequences that have been rated in this topic Save Save the state of the current topic. Quit, Exit, Bye Exit Sloandora (also does "save") Help, ? Displays this help @; } sub need_dir { local($d) = @_; if (!(-e $d)) { print STDERR "There should be a directory '$d'; please create one.\n"; print STDERR "(Or perhaps you are running this script from the wrong directory?)\n"; exit(1); } if (!(-d $d)) { print STDERR "There should be a directory '$d'; there is something else by\n"; print STDERR " that name.\n"; exit(1); } } # The divider "%----" was found not to exist anywhere in my set of eisBTfry # files, but if this fact changes, another divider string can be used. $g_eis_divider = "%----"; # This routine converts an 'eisBTfry00123.txt' file into a form more useful # for our concordance metric tool, by inserting a divider between each # successive sequence entry. sub convert_eis_file { my($append, $srcpath, $dstpath, $n) = @_; my($l, $Anum, $oAnum, $tag, $rest); # print "convert_eis_file $srcpath -> $dstpath\n"; $Anum = $oAnum = 0; open(CVT_IN, $srcpath); if ($append) { open(CVT_OUT, ">> $dstpath"); } else { open(CVT_OUT, "> $dstpath"); } # Put a blank marker at the beginning of this segment print CVT_OUT "\n\n$g_eis_divider \n\n"; while($l = ) { chomp $l; if ("$l " =~ m/^(%[A-Za-z]) A([0-9]+) (.*)$/) { $tag = $1; $Anum = $2; $rest = $3; $l = "$tag $rest"; if ($Anum != $oAnum) { # Beginning of a new sequence: Emit divider tag print CVT_OUT "\n\n$g_eis_divider A$Anum \n\n"; $oAnum = $Anum; $Anum_sfile{"A$Anum"} = $n; } } if ("$l " =~ m/^%[A-Za-z] /) { # Data line print CVT_OUT "$l\n"; } } # Put a blank marker at the end print CVT_OUT "\n\n$g_eis_divider \n\n"; close CVT_IN; close CVT_OUT; } # Count the number of parsed data files in the parsed-EIS directory. sub count_parsed { my ($dstdir) = @_; my ($n); # Count number of files that currently exist # print "ls $dstdir/eis*.txt | wc -l\n"; if (!(-f "$dstdir/eis000.txt")) { # First-time initialization $n = 0; } else { $n = `ls $dstdir/eis*.txt | wc -l`; $n += 0; } return($n); } # End of count_parsed # Determine if we are on Linux, Cygwin, MacOS, etc. # For #Sloandora# all we really need to know is how do we measure the # number of threads supported in hardware -- so we shouldn't worry about # different versions of Linux unless it affects that. sub system_type { my ($mid, $rv); $rv = "unknown"; # First look for /proc/cpuinfo if (-e "/proc/cpuinfo") { $rv = "Use-cpuinfo"; } else { # Try to use #uname# $mid = `uname -a`; chomp $mid; if ($mid =~ m/Darwin/) { $rv = "MacOS X"; } } return $rv; } # End of system_type # Try to determine how many threads are available on this system. sub hw_num_threads { my($hwt, $n); $n = 2; # Default value: almost everyone has at least a dual-core these days # Try to determine system type, and based on that, use some builtin command # to try to determine number of cores or threads. $hwt = &system_type(); if ($hwt =~ m/MacOS/) { $n = (`sysctl -n hw.ncpu`) + 0; } elsif ($hwt =~ m/Use-cpuinfo/) { # On systems that have /proc/cpuinfo, "cat"ing this virtual file will # give one section for each "processor". You can count the number of # processors just by counting the number of lines that start with # "processor". We count the lines with the "-c" option of grep. $n = (`cat /proc/cpuinfo | grep -c '^processor'`) + 0; } # Sanity check if ($n <= 0) { $n = 1; } return $n; } # End of hw_num_threads # Convert standard eisBTfry format files to "our" format, and also # collect them together into a number of pieces that is equal to the # number of threads that we will use to perform the concordance operation. sub parse_eis_files { my ($srcdir, $dstdir) = @_; my ($l, $n, $i, $minsize, $mini); if (-f "$srcdir/DBIS-0000.txt") { # Munafo-style names, as used by #DBIS# script and normally kept in # e.g. ~/DBIS/20130208/DBIS-0077.txt } elsif (-f "$srcdir/eisBTfry00000.txt") { # original filenames used on old AT&T server, archived in e.g. # .../u2/dbis/eisBTfry00027.html # or .../Sloane-DBIS/eisBTfry00027.txt # or .../Sloane-DBIS-20070812/eisBTfry00027.txt } else { print STDERR "No DBIS-0NNN.txt or eisBTfry00NNN.txt files found. These need to be placed in '$srcdir'\n"; exit(1); } print "Repartitioning eisBTfry files into $g_threads pieces..."; for($i=0; $i<$g_threads; $i++) { $fsize[$i] = 0; } open(PEF_IN, "ls $srcdir |"); while($l = ) { chomp $l; if ( ($l =~ m/^eisBTfry([0-9]+).txt/) || ($l =~ m/^DBIS-([0-9]+).txt/) ) { $n = $1; $n += 0; $sfile = $l; # We will append to the smallest file. $minsize = $fsize[0]; $mini = 0; for($i=i; $i<$g_threads; $i++) { if ($fsize[$i] < $minsize) { $minsize = $fsize[$i]; $mini = $i; } } $i = sprintf("%03d", $mini); $dfile = "eis$i.txt"; print " $n"; $append = ($fsize[$i] > 0); &convert_eis_file($append, "$srcdir/$sfile", "$dstdir/$dfile", $i); # Update file sizes array $fsize[$i] = (-s "$dstdir/$dfile"); } } close PEF_IN; print "\n"; } # End of parse.eis_files # Scan through all the eis000.txt files to initialize the array $Anum_sfile{} sub index_eis_files { my ($eisdir) = @_; my ($n, $sfile, $l, $Anum, $dlen); print "Loading database index...\n"; open(PEF_IN, "ls $eisdir |"); while($l = ) { chomp $l; if ($l =~ m/^eis([0-9]+).txt/) { $n = $1; $n += 0; $sfile = $l; open(IXIN, "$eisdir/$sfile"); $dlen = 0; $Anum = ""; while($l = ) { chomp $l; if ("$l " =~ m/^$g_eis_divider +(A[0-9]+) /) { if ($Anum ne "") { $Anum_dlen{$Anum} = $dlen; } $Anum = $1; $Anum_sfile{$Anum} = $n; $dlen = 0; } else { $dlen += length($l); } } if ($Anum ne "") { $Anum_dlen{$Anum} = $dlen; } close IXIN; } } close PEF_IN; } # End of index_eis_files # Convert "123", "0123", "A123" or "A00123" to "A000123" sub num_Anum { local($s) = @_; local($n, $rv); if ($s =~ m/A([0-9]+)/) { $n = $1; } else { $n = $s + 0; } $rv = sprintf("A%06d", $n); return($rv); } # Convert "A000123" or "0123" to "123" sub Anum_num { local($s) = @_; local($n); if ($s =~ m/A([0-9]+)/) { $n = $1; } else { $n = $s; } return($n + 0); } # Get a sequence and write it into the given pathname sub get_seq { local($Anum, $dst_path) = @_; local($num, $sn, $src, $l, $i, $cpy, $len); $num = &Anum_num($Anum); $sn = $Anum_sfile{$Anum}; $src = sprintf("eis%03d.txt", $sn); $cpy = 0; # print "$src -> $Anum > $dst_path\n"; open(GSIN, "$d_seqs/$src"); open(GSOUT, "> $dst_path"); while($l = ) { chomp $l; if ($l =~ m/^$g_eis_divider +A([0-9]+)/) { $i = $1; if ($i == $num) { $cpy = 1; $len = 0; } else { $cpy = 0; } } elsif ($l =~ m/^$g_eis_divider +$/) { $l = ""; } if ($cpy) { if ($l eq "%I ") { # There is always a line like this, we don't need to show or match # against it. print GSOUT "\n"; # But I like having the blank line here } elsif ($l eq '') { # Ignore blank lines } else { print GSOUT "$l\n"; $len += (length($l)+1); } if ($l =~ m/^%N (.*)$/) { $g_cur_seqname = $1; } } } close GSIN; close GSOUT; $g_curseq_len = $len; } # Wait for all threads to finish. sub wait_all_done { local($i, $gg, $done, $wtdd); $gg = 1; $wtdd = 0; while($gg) { $done = 1; for($i=0; $i<$g_threads; $i++) { if (!(-f "$d_disp/done-$i")) { $done = 0; } } if ($done) { $gg = 0; } else { select(undef, undef, undef, 0.1); # Wait 0.1 seconds $wtdd++; if ($wtdd >= 10) { print "."; $wtdd = 0; } } } } # Clear all the "done" flag files. sub clear_all_done { local($i); for($i=0; $i<$g_threads; $i++) { if (-f "$d_disp/done-$i") { unlink("$d_disp/done-$i"); } } } # Given a pathname of a temp file containing a sequence, perform the # concordance function between that sequence and the entire database, # and update the scores array. # # The parameter "scale" indicates the strength of the rating to give. # Pass a positive value (normally 1) to indicate the sequence is relevant # to the topic, and negative if irrelevant. Strong or weak ratings can be # indicated with different magnitudes, e.g. 0.5 or 2.0 for somewhat relevant # or really, really relevant respectively. sub rate_it { my ($temp_seq, $scale, $unrate) = @_; my ($i, $cmd, $srcpath, $outpath, $l); my ($Anum, $score, $bestsc, $bestAnum); my ($cputime); if ($unrate) { print " Un-rating '$g_cur_seq' back to 0; $g_threads threads: "; } else { print " Rating '$g_cur_seq' at $scale; $g_threads threads: "; } # Spawn N tasks to perform concordance matching on N processors. &clear_all_done(); for($i=0; $i<$g_threads; $i++) { $srcpath = "$d_seqs/" . sprintf("eis%03d.txt", $i); $outpath = "$d_disp/out-$i"; $cmd = "($tcdist -n5 $temp_seq $srcpath -d '$g_eis_divider' -v" . " > $outpath ; echo 1 > $d_disp/done-$i) &"; # print "$cmd\n"; # print "."; system($cmd); } &wait_all_done(); # Scan the output files to update our scores $bestsc = -9.9e10; $cputime = 0; $g_oeis_size = 0; for($i=0; $i<$g_threads; $i++) { $outpath = "$d_disp/out-$i"; # print "Scan output $outpath\n"; # print "'"; open(RATEIN, $outpath); while($l = ) { chomp $l; if ($l =~ m/^(A[0-9]+) +([0-9.]+)/) { $Anum = $1; $score = $2; $Anum_score{$Anum} += $score * $scale; if (($seen{$Anum} == 0) && ($Anum_score{$Anum} > $bestsc)) { $bestsc = $Anum_score{$Anum}; $bestAnum = $Anum; } } elsif ($l =~ m/Time: +([0-9.]+) +s/) { $cputime += $1; } elsif ($l =~ m/Read from stream2: +([0-9.]+) +octets/) { $g_oeis_size += $1; } } close RATEIN; } $cputime = sprintf("%.2f", $cputime); print (" $cputime CPU-seconds.\n"); $total_match = $g_curseq_len * $g_oeis_size; $total_match = sprintf("%5.3g", $total_match); print " Performed $total_match key comparisons in"; $est_time = sprintf("%5.3f", ($cputime / $g_threads) * 1.1); print " approx. $est_time sec. with $g_threads threads.\n"; if ($unrate == 0) { $seen{$g_cur_seq} = 1; $rating{$g_cur_seq} = $scale; $g_topic_touched = 1; } $g_bestsc = sprintf("%.3f", $bestsc); return($bestAnum); } # Scan through all the scores, and find the best match. sub find_best { my ($Anum, $bestsc, $bestAnum); $bestsc = -9.9e10; foreach $Anum (keys %Anum_score) { if (($seen{$Anum} == 0) && ($Anum_score{$Anum} > $bestsc)) { $bestsc = $Anum_score{$Anum}; $bestAnum = $Anum; } } $g_bestsc = sprintf("%.3f", $bestsc); return($bestAnum); } # End of rate_it # Create a new "topic" sub create_topic { my ($num, $Anum, $rv); print "Enter the A-number of a sequence to start this topic: "; $num = <>; chomp $num; $num = &Anum_num($num); $g_cur_seq = &num_Anum($num); # Set the rating of the seed sequence. It could be argued that since this # sequence is the "definition" of the topic, and the user picked it rather # than us picking it for them, it should count more. $seen{$g_cur_seq} = 1; $rating{$g_cur_seq} = 1.0; $g_topic_touched = 1; $Anum = &num_Anum($num); &get_seq($Anum, $temp_seq); $rv = &rate_it($temp_seq, $rating{$g_cur_seq}); return($rv); } # End of create_topic sub load_topic { my ($l, $Anum, $score, $bestsc, $bestAnum, $i); if (!(-f "$d_topics/$g_topic.txt")) { print STDERR "There appears to be no topic '$g_topic' in '$d_topics'.\n"; exit 1; } print "Loading '$g_topic'..."; undef %Anum_score; $bestsc = -9.9e10; $i = 0; open(LT_IN, "$d_topics/$g_topic.txt"); while($l = ) { chomp $l; if ($l =~ m/^(A[0-9]+) +([-e0-9.]+)/) { $Anum = $1; $score = $2; $Anum_score{$Anum} = $score; $i++; if (($seen{$Anum} == 0) && ($Anum_score{$Anum} > $bestsc)) { $bestsc = $Anum_score{$Anum}; $bestAnum = $Anum; } } elsif ($l =~ m/^rating (A[0-9]+) +([-0-9.]+)/) { $Anum = $1; $score = $2; $rating{$Anum} = $score; } elsif ($l =~ m/^seen (A[0-9]+)/) { $Anum = $1; $seen{$Anum} = 1; } } close LT_IN; print "$i records\n"; $g_topic_touched = 0; $g_bestsc = sprintf("%.3f", $bestsc); return($bestAnum); } # End of load_topic sub save_topic { my ($k); if (($g_topic ne '') && $g_topic_touched) { print "Saving topic '$g_topic'..."; # State has changed and needs to be written out. open(ST_OUT, "> $d_topics/$g_topic.txt"); foreach $k (sort (keys %rating)) { print ST_OUT "rating $k $rating{$k}\n"; } foreach $k (sort (keys %seen)) { print ST_OUT "seen $k\n"; } foreach $k (sort (keys %Anum_score)) { print ST_OUT sprintf("%s %.10f\n", $k, $Anum_score{$k}); } close ST_OUT; } print "done\n"; # Topic has been saved, so the data is no longer "touched". $g_topic_touched = 0; } # End of save_topic sub cmd_ratings { my ($k); print "Rated sequences in topic '$g_topic':\n"; print "Uprated: "; foreach $k (sort {$Anum_dlen{$b} <=> $Anum_dlen{$a}} (keys %rating)) { if ($rating{$k} > 0) { print " $k"; } } print "\n"; print "Down-rated: "; foreach $k (sort {$Anum_dlen{$b} <=> $Anum_dlen{$a}} (keys %rating)) { if ($rating{$k} < 0) { print " $k"; } } print "\n"; } # End of cmd_ratings sub cmd_show { my ($sc, $rt); $sc = sprintf("%.3f", $Anum_score{$g_cur_seq}); $rt = sprintf("%.1f", $rating{$g_cur_seq}); print "Current sequence is $g_cur_seq"; print "; its score is $avc_cyan1$sc$avc_normal"; if ($rt > 0) { print "; its rating is $avc_green$rt$avc_normal"; } elsif ($rt > 0) { print "; its rating is $avc_red$rt$avc_normal"; } print "\n"; system('cat', $temp_seq); print "Sequence data length: $g_curseq_len bytes.\n"; print "\n"; } # End of cmd_show sub cmd_web { my ($url); # $url = "http://www.research.att.com/~njas/sequences/"; $url = "http://oeis.org/"; $url .= $g_cur_seq; print " $url\n"; # %%% 'open' command only works on MacOS; we need alternatives for # other systems system('open', $url); } sub do_autos { if ($g_auto_show) { &cmd_show(); } if ($g_auto_web) { &cmd_web(); } } sub cmd_rate { my ($rat, $unrate) = @_; my ($rc); $rc = $rating{$g_cur_seq}; if ($unrate) { if ($rc == 0) { print <<"PRINT_END"; Sequence $g_cur_seq cannot be unrated because it does not currently have a rating. Use commands 'y' or 'n' to rate it, or 'best' to see the current recommendation, or anter a new sequence number, e.g. 'A012345'. PRINT_END return(0); } undef $rating{$g_cur_seq}; # We do not clear $seen{$g_cur_seq} because the assumption is the user # has already given enough thought to this sequence, in fact probably # even more than if they had used 'skip' rather than bothering to # rate and then un-rate it. $g_topic_touched = 1; # Remove the current rating from the scores, but do not set $rating # and $seen $g_cur_seq = &rate_it($temp_seq, 0 - $rc, 1); } elsif ($rc != 0) { print <<"PRINT_END"; Sequence $g_cur_seq is already rated with strength $rating{$g_cur_seq} Use command 'best' to see the current recommendation. PRINT_END return(0); } else { # Rate this sequence $rating{$g_cur_seq} = $rat; $seen{$g_cur_seq} = 1; # We're moving on # Compute the metrics, update the ratings, and get the next recommendation $g_cur_seq = &rate_it($temp_seq, $rat); } # Display the new recommendation &get_seq($g_cur_seq, $temp_seq); print "Next recommendation: $g_cur_seq (score $g_bestsc)\n"; print "$avc_green$g_cur_seqname$avc_normal\n"; &do_autos(); } # End of cmd_rate # --------------------------------------------------------------------------- &splash(); $g_threads = &hw_num_threads(); $d_dbis = "DBIS"; # The original eisBTfry...txt files. ("DBIS" is # the acronym for "DataBase of Integer Sequences", # which is an early name for OEIS.) $d_seqs = "parsed-DBIS"; # eisBTfry files converted for our use $d_disp = "dispatch"; # Used for tempfiles and child task output $d_topics = "topics"; &need_dir($d_dbis); &need_dir($d_seqs); &need_dir($d_disp); &need_dir($d_topics); $temp_seq = "$d_disp/curseq"; $tcdist = "./tcdist"; if (!(-x $tcdist)) { print STDERR "There is no executable $tcdist. This is built from concordiff.c\n"; exit(1); } $n = &count_parsed($d_seqs); print "Found $n parsed eis/DBIS files.\n"; if ($n != $g_threads) { # Re-convert the source files into the correct number of pieces for # our hardware. &parse_eis_files($d_dbis, $d_seqs); } &index_eis_files($d_seqs); # print ("dlen[A012345] = " . $Anum_dlen{"A012345"} . "\n"); exit(0); print "Select a topic: "; $g_topic = <>; chomp $g_topic; if ($g_topic =~ m/[^-a-zA-Z0-9]/) { print STDERR "Topic name should be letters, digits and '-', nothing else.\n"; exit(1); } if (-f "$d_topics/$g_topic.txt") { # Read topic data $g_cur_seq = &load_topic(); } else { print "There is no topic '$g_topic'.\nDo you want to create one? "; $ans = <>; chomp $ans; $ans =~ tr/a-z/A-Z/; if (!($ans =~ m/^Y/)) { exit(0); } $g_cur_seq = &create_topic(); } &get_seq($g_cur_seq, $temp_seq); print "Next recommendation: $g_cur_seq (score $g_bestsc)\n"; print "$avc_green$g_cur_seqname$avc_normal\n"; # Initialize the system state $g_topic_touched = 0; $g_auto_web = 0; $g_auto_show = 0; print "\n"; print "Command loop (type 'help' for help)\n"; $gg = 1; while($gg) { print "Topic '$g_topic' > "; $cmd = <>; chomp $cmd; $ca = $cmd; $ca = lc($ca); $ca =~ s/[^a-z0-9]/ /g; $ca =~ s/^ *//; # print "got '$cmd' ca '$ca'\n"; if (0) { } elsif (($cmd eq '?') || ("help" =~ m/^$ca/)) { &help(); } elsif ($ca eq '') { # Null command print "What? (Type 'help' for help)\n"; # -------- One-letter Commands ----------- # (These must come first because of the DCTS abbreviation matching) } elsif (($ca eq 'y') || ($ca eq 'yes') || ("relevant" =~ m/^$ca/)) { &cmd_rate(1.0); } elsif (($ca eq 'n') || ($ca eq 'no') || ("irrelevant" =~ m/^$ca/)) { &cmd_rate(-1.0); # -------- Normal One-Word Commands ----------- } elsif ("bye" =~ m/^$ca/) { $gg = 0; } elsif ("exit" =~ m/^$ca/) { $gg = 0; } elsif ("quit" =~ m/^$ca/) { $gg = 0; } elsif (("next" =~ m/$ca/) || ("skip" =~ m/^$ca/)) { $seen{$g_cur_seq} = 1; # We're moving on $g_topic_touched = 1; $g_cur_seq = &find_best(); # Display the new recommendation &get_seq($g_cur_seq, $temp_seq); print "Next recommendation: $g_cur_seq (score $g_bestsc)\n"; print "$g_cur_seqname\n"; &do_autos(); } elsif ($cmd =~ m/^(A[0-9]+)/) { $newseq = $1; # Go to a new sequence $g_cur_seq = &num_Anum(&Anum_num($newseq)); print "Loading $g_cur_seq"; &get_seq($g_cur_seq, $temp_seq); $sc = sprintf("%.3f", $Anum_score{$g_cur_seq}); print " (current score: $sc"; if ($rating{$g_cur_seq} != 0) { print "; rating: "; print(($rating{$g_cur_seq} > 0) ? $avc_green : $avc_red); print "$rating{$g_cur_seq}$avc_normal"; } print ")\n"; print "$g_cur_seqname\n"; &do_autos(); } elsif ("best" =~ m/$ca/) { # Load (or re-load) the best current recommendaton $g_cur_seq = &find_best(); # Display the new recommendation &get_seq($g_cur_seq, $temp_seq); print "Current recommendation: $g_cur_seq (score $g_bestsc)\n"; print "$g_cur_seqname\n"; &do_autos(); } elsif ("ratings" =~ m/^$ca/) { &cmd_ratings(); } elsif ("save" =~ m/^$ca/) { &save_topic(); } elsif ("show" =~ m/^$ca/) { &cmd_show(); } elsif ("unrate" =~ m/^$ca/) { &cmd_rate(0, 1); } elsif ("web" =~ m/^$ca/) { &cmd_web(); # -------- Two-Word Commands ----------- } elsif ($ca =~ m/^auto *show/) { $g_auto_show = (!($g_auto_show)); print("Auto #show# is now " . ($g_auto_show ? "on" : "off") . "\n"); } elsif ($ca =~ m/^auto *web/) { $g_auto_web = (!($g_auto_web)); print("Auto #web# is now " . ($g_auto_web ? "on" : "off") . "\n"); } else { # Unknown command print "Unknown command (Type 'help' for help)\n"; } } # Save topic state &save_topic();