#!/usr/bin/perl
# ---------------------------------------------------------------------------------
# name: sendsg.pl
# author: Marc Egger, modified by Mark Sprenger
# Date of last modification: 13.01.2011
# ---------------------------------------------------------------------------------
# DESC:
#
# TO DO:
#
# ---------------------------------------------------------------------------------

use Getopt::Std;
use Socket qw(:DEFAULT :crlf);
use IO::Handle;

$debug = 0;

# Global Definitons
our ( $opt_h, $opt_v, $opt_m, $opt_n, $opt_t, $opt_i );
our $version = '1.1';
my $host = 'spinglass.informatik.uni-koeln.de';
my $port = '11200';

our $meta_name;
our $meta_type;
our $email;

getopts('hvm:t:n:i:') || usage();
usage() unless argsok();
usage() if $opt_h;
if ($opt_i){
  print "INFO: submitting all instances with type: $opt_i\n";
}

create_connection(); $SIG{CHLD} = 'IGNORE'; my $child = fork(); die unless defined($child);

if ($child) {

    #the parent is the one who reads the answers from the socket
    read_socket();

}
else {

    #child is the one who writes to the socket.
    write_socket();

    #wait;
    exit;
}

#-----------------------------------------------------------------------------------------------------
sub submit_jobs {
    select(SERVER);
    print "beginmeta\n";
    print "beginheader\n";
    print "setparam name $meta_name\n";
    print "setparam output $meta_type\n";
    print "endheader\n";
    my $num_error_files   = 0;
    my $num_success_files = 0;
    my @files=@ARGV;
    if($#ARGV==0 and -d $ARGV[0]){
	   	opendir(my $dh, $ARGV[0]) || die "can't opendir: $!";
		@tmp_files = grep { -f "$ARGV[0]/$_" }  readdir($dh);
		undef(@files) if(scalar(@tmp_files)>0);
		foreach (@tmp_files){
			 push(@files,$ARGV[0].$_);
		}
		closedir $dh;
    }
    my $total_jobs = scalar(@files);
  NEXTJOB: foreach $filename (@files) {
        my $wrong_data = 0;
        open( FH, $filename ) or die "can't open file: $! \n";
        undef($job_type);
        undef($job_size);
        undef($job_name);
        undef(@data);
        while (<FH>) {

            #spaces
            next if $_ =~ /^\s*$/;

            #comments
            next if $_ =~ /^#/;

            #set type
            if (/^\s*type:\s+(\w+)/) {
                $job_type = $1;
                next;
            }

            #set size
            if (/^\s*size:\s+(\d+)/) {
                $job_size = $1;
                next;
            }

            #set name
            if (/^\s*name:\s*/) {
                $job_name = $';
                chomp($job_name);
                next;
            }

            if (/^\s*nrspins:\s*/) {
                $nrspins = $';
                chomp($nrspins);
                next;
            }

            if (/^\s*nrcouplings:\s*/) {
                $nrcouplings= $';
                chomp($nrcouplings);
                next;
            }

            #data
            if ( $_ =~m/\d+[ \t]+\d+[ \t]+[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/ ){
                push( @data, $_ );    #push data line
            }
            else {
                $wrong_data++;
                $num_error_files++ if ( $wrong_data == 1 );
            }
        }
        close(FH);
        if ( !$wrong_data ) {
            $job_type=$opt_i if($opt_i); #force overwriting jobtype!
            $job_name = $job_name || $filename;
            print "beginjob\n";
            print "beginheader\n";
            print "setparam type $job_type\n" if defined($job_type);
            print "setparam size $job_size\n" if defined($job_size);

            print "setparam nrcouplings $nrcouplings\n" if defined($nrcouplings);
            print "setparam nrspins $nrspins\n" if defined($nrspins);

            print "setparam name $job_name\n";
            print "endheader\n";
            print "begindata\n";
            print "@data";
            print "enddata\n";
            print "endjob\n";
            print STDOUT "OK:      job \'$job_name'\ submitted.\n" if $verbose;
            $num_success_files++;
        }
        else {
            print STDOUT "ERROR: File $filename contains invalid data\nskipping this file!\n";
        }
    }
    print "endmeta\n";
    print "exit\n\n";
    print STDOUT "WARNING: $num_error_files job(s) could not be submitted due to errors in your input data.\n"
      if $num_error_files > 0;

    if ( $num_error_files > 0 ) {
        $result = "$num_success_files of $total_jobs";
    }
    else {
        $result = "$total_jobs";
    }
    print STDOUT "$result job(s) submitted\n";
}

# function checks if the email-address was given in the configfile
#-----------------------------------------------------------------------------------------------------
sub readconfig {
    ( -f "$ENV{HOME}/.sendsgrc" ) && do "$ENV{HOME}/.sendsgrc";
}

#-----------------------------------------------------------------------------------------------------
sub argsok {

    # Optional Arguments
    $opt_v and $verbose = 1;
    readconfig();
    $meta_type = $opt_t || $meta_type || 'long';

    # Mandatory Arguments
    $meta_name = $opt_n;
    return 0 unless $meta_name;

   # the email-adress has to be defined either in the configfile or per argument
    $email = $opt_m || $email;
    return 0 unless $email;

    # we need the path
    return 0 if ( $#ARGV == -1 );    #AENDERN

    #everything is ok if we get here
    return 1;
}

#-----------------------------------------------------------------------------------------------------
sub usage {

    print << "EOF";
sendsg, send meta jobs to the spin glass server. $version

   Copyright (C) 2011 by Institut fuer Informatik, Koeln.
   written by Marc Egger and Mark Sprenger.

Usage: sendsg [OPTIONS] -m email\@adress -n your_metajobname FILE ...

   -h                   show summary of options
   -i [instance_type]   configure the computing-type (currently supported):
                        - ising
                        - pottsinf
                        The option 'ising' automatically identifies
                        your instances as one of
                           -- gauss
                           -- pm
                           -- gauss3
                           -- pm3
                           -- 2d
                           -- sk
                        Default Value: ising
                        This is a global option and overwrites
                        local computing-types of your instances!
                        e.g. -i pottsinf
   -t [long|short]      configure how much detail you want to see in the
                        server output (Default: long)
                        short reports the ground state characteristics only
   -m [email]           email-address the results will be sent to
   -v			verbose mode
   -n [meta jobname]	set the name of the meta job. e.g. foobar

   FILE ...             list of files with spin glass input data. The accepted
                        format can be found either in the man page or
			on www.informatik.uni-koeln.de/spinglass

EXAMPLE:
   sendsg -v -m foo\@bar.com -n gauss100 ~/gauss100/*

if you place a file named .sendsgrc in your home directory, whicht contains following line:
   \$email='your\@address';
you won't need the -m flag when you are sending jobs to the server

Report bugs to <spinglass-adm AT informatik.uni-koeln.de>. 
For further information, visit www.informatik.uni-koeln.de/spinglass
EOF

    exit 0;
}

#-----------------------------------------------------------------------------------------------------
sub create_connection {

    # create socket-connection
    my $protocol = getprotobyname('tcp');
    $host = inet_aton($host) or die "$host: unknown host";
    my $dest_addr = sockaddr_in( $port, $host );

    # create socket
    socket( SERVER, AF_INET, SOCK_STREAM, $protocol )
      or die "socket() failed: $!";

    # Connection to host!
    connect( SERVER, $dest_addr ) or die "connect() failed: $!";

    # Set autoflush ->1;
    select( ( select(SERVER), $| = 1 )[0] );
}

#-----------------------------------------------------------------------------------------------------
sub write_socket {

    select(SERVER);
    print "hello\n";
    print "auth $email\n";
    submit_jobs();
}

#-----------------------------------------------------------------------------------------------------
sub read_socket {

    while (<SERVER>) {
        print "$_" if $debug;
        if ( $_ =~ /^(5\d+)/ ) {
            chomp($_);
            print "ERROR $1:$'";
            $error++;
        }
    }
    $n = $#ARGV + 1;
    if ($error) {
        print "\nERROR: sending produced $error errors in $n job(s).\n";
    }
}

# Start sendsg documentation

=head1 NAME

sendsg.pl - send spin glass meta jobs to the spin glass server system.

=head1 SYNOPSIS

sendsg [OPTIONS] -m email@adress -n your_metajobname FILE ...

=head1 DESCRIPTION

With this client you can send spin glass samples to the spin glass
server for the computation of exact ground states. In contrast to the
mail-based server system that we have offered until now, you are not
forced any more to send your jobs one after the other. Instead, you
can now bundle your jobs to so called 'meta jobs'. A meta job contains
a number of single files. Within a meta job, each single
instance/sample has to be contained in a single file. Each file
contains a spin-glass sample in a particular format.

I<sendsg> awaits at least 3 parameters. First of all, you state the
email-address to which the results should be sent back. You can skip
the C<-m> option if you place a file named .sendsgrc in your home
directory containing a line with your email address, followed by an
empty line:

   $email='your@address';

Secondly, you specify the name of the meta job. Finally, you specify
which job files belong to the meta job. In the easiest case you simply
state the name of the directory in which the job files are stored -
followed by a slash and an asterisk, e.g. C</home/yourname/foo/*>

=head1 FORMAT INPUTDATA

The server input format is the same as for the old email-based
server. The first line contains the type specification. The second
determines the length scale of the lattice. The third line contains a
user-defined job name. if you want to name the jobs-files like the
filename, you can simply skip the C<name: > command in line 3.

The subsequent lines contain the couplings in
the format spin_i spin_j coupling(spin_i,spin_j). We count the spins
from 1 to N^2 or N^3, depending on the type. The spins are labeled
linearly row after row within one layer, and layer after layer, as you
can see here:



        |     |     |
      -(1)---(2)---(3)--
        |     |     |
      -(4)---(5)---(6)--
        |     |     |
      -(7)---(8)---(9)--
        |     |     |
 
Parameters have to be specified in the way <PARAMETER> : <VALUE>. 
Available are:

   1. type: You can choose between 
            * pm:    two dimensional Edwards Anderson (EA) spin glass 
                     with +/-1 distribution and periodic boundary conditions.

            * gauss: two dimensional EA spin glass with arbitrary continuous 
                     distributions and periodic boundary conditions. 
                     Please use the fixed point format (e.g., 0.9876 is right, while 9.876e-01 is wrong.)

            * pm3:   three dimensional Edwards Anderson spin glass 
                     with +/-1 distribution and periodic boundary conditions.

            * gauss3: three dimensional Edwards Anderson spin glass with 
                      arbitrary continuous distribution and periodic boundary conditions.

            * sk:     Sherrington-Kirkpatrick spin-glass model with arbitrary distribution 
                      for the couplings. 
                      This model is computed by an exact branch-and-bound code 
                      using semidefinite programming. 
                      It has been provided to us by the group of Prof. Dr. Franz Rendl in Klagenfurt. 

   2. size: determines the linear size of the grid, i.e. size = L for an LxLxL grid. 
            In case of the sk model it is the total number of spins.

   3. name of the sample. The value can be any line of characters (including blanks)

These four lines can appear in any order. Blanc lines anywhere are ignored. 
You can include comment lines into your mail. 
These comment lines have to start with the following characters: #

A legal input looks like this:

	type: gauss
	size: 3
	name: 3x3 system as a test for this nice service

	1 2 -1.089460678023
	2 3 0.526296171959
	3 1 1.492510060258
	4 5 0.542925533731
	5 6 0.440804946137
	6 4 -0.555618772831
	7 8 0.400843564493
	8 9 0.005781770273
	9 7 -0.283108779696
	1 4 -1.428048251086
	2 5 0.063139021975
	3 6 1.529559080159
	4 7 -1.669458764103
	5 8 0.193231695455
	6 9 1.291781128584
	7 1 0.604670335948
	8 2 0.890948261885
	9 3 -0.822280541834



=head1 OPTIONS

=over 4

=item -h

show summary of options

=item -t [long|short]

configure how much detail you want to see in the server output (Default: long)
short reports the ground state characteristics only

=item -m [email]

email address the results will be sent to

=item -v

verbose mode

=item -n [meta job name]

set the name of the meta job. e.g. foobar

=item FILE ...

list of files with spin glass input data

=back 4

=head1 EXAMPLE

sendsg -v -m foo@bar.com -n gauss100 ~/gauss100/*

=head1 AUTHOR

Written by Marc Egger.

=head1 BUGS

Report bugs to <spinglass-adm AT informatik.uni-koeln.de>.
For further information, visit www.informatik.uni-koeln.de/spinglass.

=head1 COPYRIGHT

Copyright (C) 2007 by Institut fuer Informatik, Koeln.

=cut

