#! /usr/bin/perl -w
#
# Main perl script for running scans (subsequent simulations) with McStas
#
#
#   This file is part of the McStas neutron ray-trace simulation package
#   Copyright (C) 1997-2004, All rights reserved
#   Risoe National Laborartory, Roskilde, Denmark
#   Institut Laue Langevin, Grenoble, France
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

# Config module needed for various platform checks.
# PW, 20030702
use Config;
use IPC::Open3;
use Net::Ping;

# Determine the path to the McStas system directory. This must be done
# in the BEGIN block so that it can be used in a "use lib" statement
# afterwards.
BEGIN {
  # default configuration (for all high level perl scripts)
  if($ENV{"MCSTAS"}) {
    $MCSTAS::sys_dir = $ENV{"MCSTAS"};
  } else {
    if ($Config{'osname'} eq 'MSWin32') {
      $MCSTAS::sys_dir = "c:\\mcstas\\lib";
    } else {
      $MCSTAS::sys_dir = "/usr/local/lib/mcstas";
    }
  }
  $MCSTAS::perl_dir = "$MCSTAS::sys_dir/tools/perl";

  # custom configuration (this script)
  $MCSTAS::perl_modules = "$MCSTAS::perl_dir/modules";
}

use lib $MCSTAS::perl_dir;
use lib $MCSTAS::perl_modules;
require "mcstas_config.perl";

use FileHandle;
use strict;

require "mcrunlib.pl";

# Turn off buffering on stdout. This improves the use with the Unix
# "tee" program to create log files of scans.
autoflush STDOUT 1;

# Various parameters determined by the command line.
my ($sim_def, $force_compile, $data_dir, $data_file);
my $ncount = 1e6;               # Number of neutron histories in one simulation
my $numpoints = 1;              # Number of points in scan (if any)
my @params = ();                # List of input parameters
my %vals;                       # Hash of input parameter values
my @options = ();               # Additional command line options (mcstas)
my @ccopts = ();                # Additional command line options (cc)
my $format_ext;                 # sim file extension
my $format_assign;              # assignation symbol ':', '='
my $format_start_value;         # symbol before field value
my $format_end_value;           # symbol after field value
my $format_prefix;              # symbol for line prefix
my $format_limprefix;           # symbol for 'x' / 'xy' limits definition
my $exec_test=0;                # flag for McStas package test execution
my $slave = 0;                  # 'slave' hostname for running remotely
my $slavedir="";                # temporary dir on remote machine
my $start=-1;                   # first scan number to run in slave mode
my $end=-1;                     # last scan number to run in slave mode
my $multi=0;                    # multi machine mode
my @hostlist = ();              # list of remote machines to run on...
my $mpi = 0;                    # how many nodes used with MPI? 0 implies no MPI.

# Name of compiled simulation executable.
my $out_file;
# Instrument information data structure.
my $instr_info;

# Add an input parameter assignment to the current set, overriding any
# previously assigned value.
sub set_inputpar {
    my ($par, $val) = @_;
    push @params, $par unless $vals{$par};
    $vals{$par} = $val;
}

# Set input parameter from "PAR=VAL" type string specification.
sub set_inputpar_text {
    my ($text) = @_;
    if($text =~ /^([a-z0-9_]+)\=(.*)$/) {
        set_inputpar($1, $2);
    } else {
        die "mcrun: Invalid input parameter specification '$text'";
    }
}

# Read input parameters from parameter file.
sub read_inputpar_from_file {
    my ($filename) = @_;
    open(IN, "<$filename") || die "mcrun: Failed to open parameter file '$filename'";
    while(<IN>) {
        my $p;
        for $p (split) {
            set_inputpar_text($p);
        }
    }
}

# Handle the command line arguments (options, input parameters,
# instrument definition name).
sub parse_args {
    my $i;
    for($i = 0; $i < @ARGV; $i++) {
        $_ = $ARGV[$i];
        # Options specific to mcrun.
        if(/^--force-compile$/ || /^-c$/ || /^--force$/) {
            $force_compile = 1;
        } elsif(/^--param\=(.*)$/ || /^-p(.+)$/) {
            read_inputpar_from_file($1);
        } elsif(/^--param$/ || /^-p$/) {
            read_inputpar_from_file($ARGV[++$i]);
        } elsif(/^--numpoints\=(.*)$/ || /^-N(.+)$/) {
            $numpoints = $1;
        } elsif(/^--numpoints$/ || /^-N$/) {
            $numpoints = $ARGV[++$i];
        } elsif(/^--ncount\=(.*)$/ || /^-n(.+)$/) {
            $ncount = $1;
            push @options, "--ncount=$ncount";
        } elsif(/^--ncount$/ || /^-n$/) {
            $ncount = $ARGV[++$i];
            push @options, "--ncount=$ncount";
        } elsif (/^--start\=(.*)$/) {
            $start=$1;
        } elsif (/^--end\=(.*)$/) {
            $end=$1;
        } elsif (/^--slave\=(.*)$/) {
            $slave=$1;
        } elsif (/^--slavedir\=(.*)$/) {
            $slavedir="$1/";
        } elsif (/^--multi/ || /^-M/ || /^--grid/) {
            if ($Config{'osname'} eq 'MSWin32') {
                print STDOUT "mcrun: Sorry, --grid is not supported on windows!\n";
            } else { $multi=1; }
        } elsif (/^--mpi\=(.*)$/) {
            $mpi = $1;
        } elsif (/^--machines\=(.*)$/) {
            $MCSTAS::mcstas_config{'HOSTFILE'} = $1;
        }
        # Standard McStas options needing special treatment by mcrun.
        elsif(/^--dir\=(.*)$/ || /^-d(.+)$/) {
            $data_dir = $1;
        } elsif(/^--dir$/ || /^-d$/) {
            $data_dir = $ARGV[++$i];
        }
        elsif(/^--file\=(.*)$/ || /^-f(.+)$/) {
            $data_file = $1;
        } elsif(/^--file$/ || /^-f$/) {
            $data_file = $ARGV[++$i];
        }
        # Standard McStas options passed through unchanged to simulations.
        elsif(/^--(seed)\=(.*)$/) {
            push @options, "--$1=$2";
        } elsif(/^-([s])(.+)$/) {
            push @options, "-$1$2";
        } elsif(/^--(seed)$/) {
            push @options, "--$1=$ARGV[++$i]";
        } elsif(/^-([s])$/) {
            push @options, "-$1$ARGV[++$i]";
        } elsif(/^--(format)$/ || /^--(plotter)$/) {
            $MCSTAS::mcstas_config{'PLOTTER'} = $ARGV[$i];
        } elsif(/^--(format)\=(.*)$/ || /^--(plotter)\=(.*)$/) {
            $MCSTAS::mcstas_config{'PLOTTER'} = $2;
        } elsif(/^--test$/) {
            $exec_test="compatible and graphics";
        } elsif(/^--(test)\=(.*)$/) {
            $exec_test="$2";
        } elsif(/^--(data-only|help|info|trace|no-output-files|gravitation)$/) {
            push @options, "--$1";
        } elsif(/^-([ahitg])$/) {
            push @options, "-$1";
        }
        # Non-option arguments.
        elsif(/^-/) {                # Unrecognised option
            push @ccopts, "$_";
        } elsif(/^([a-zA-Z0-9_]+)\=(.*)$/) {
            set_inputpar($1, $2);
        } else {                        # Name of simulation definition
            if($sim_def) {
                die "mcrun: Only a single instrument definition may be given ($sim_def, $_)";
            } else {
                $sim_def = $_;
            }
        }
    }

    # tests for grid/mpi support
    if ($mpi >= 1 || $multi == 1) {
      if (! -e $MCSTAS::mcstas_config{'HOSTFILE'}) {
        print STDERR "mcrun: No MPI/grid machine list. MPI/grid disabled...
     Define $ENV{'HOME'}/.mcstas-hosts
     or $MCSTAS::mcstas_config{'HOSTFILE'}
     or use option --machines=<file>!\n";
        $multi = 0;
        $mpi   = 0;
        $MCSTAS::mcstas_config{'HOSTFILE'} = "";
      }
      if ($multi == 1 && $MCSTAS::mcstas_config{'SSH'} eq "no") {
        print STDERR "mcrun: You have no ssh/scp available, --multi disabled...\n";
        $multi = 0;
      }
      if ($mpi >= 1 && ($MCSTAS::mcstas_config{'MPICC'}  eq "no"
                     || $MCSTAS::mcstas_config{'MPIRUN'} eq "no")) {
        print STDERR "mcrun: You have no mpicc/mpirun available, --mpi disabled...\n";
        $mpi   = 0;
      }
    }

    # Adapt parameters to MPI (if used) which overrides grid.
    if ($mpi >= 1 && $MCSTAS::mcstas_config{MPICC} ne "no") {
      $multi = 0;
      $MCSTAS::mcstas_config{CC}     = $MCSTAS::mcstas_config{MPICC};
      $MCSTAS::mcstas_config{CFLAGS} = $MCSTAS::mcstas_config{CFLAGS} . " -DUSE_MPI ";
    }

    if ($data_dir && $slavedir) {
        $data_dir="$slavedir$data_dir";
    }


    if ($multi == 1) {
        # Check that something is available in the .mcstas-hosts
        print STDERR "Pinging $MCSTAS::mcstas_config{'HOSTFILE'} 1 per sec. since you requested --multi...\n";
        # Read the host file...
        my $pid = open(HOSTFILE,$MCSTAS::mcstas_config{'HOSTFILE'});
        my $host;
        while ($host = <HOSTFILE>) {
            chomp $host;
            # Remove spaces if any...
            $host =~ s! !!g;
            if (! $host eq '') {
                my $p = Net::Ping->new();
                my $response = 0;
                $response= $p->ping($host, 1);
                if ($response == 1) {
                    push @hostlist, $host;
                } else {
                    print STDERR "mcrun: Not spawning to host $host: not responding\n";
                }
            }
        }
        close(HOSTFILE);
    }

    my $cc     = $MCSTAS::mcstas_config{CC};
    my $cflags = $MCSTAS::mcstas_config{CFLAGS};

    die "Usage: mcrun [-cpnN] Instr [-sndftgahi] params={val|min,max}
  mcrun options:
   -c        --force-compile  Force rebuilding of instrument.
   -p=FILE   --param=FILE     Read parameters from file FILE.
   -n COUNT  --ncount=COUNT   Set number of neutrons to simulate.
   -N NP     --numpoints=NP   Set number of scan points.
  Instr options:
   -s SEED   --seed=SEED      Set random seed (must be != 0)
   -n COUNT  --ncount=COUNT   Set number of neutrons to simulate.
   -d DIR    --dir=DIR        Put all data files in directory DIR.
   -f FILE   --file=FILE      Put all data in a single file.
   -t        --trace          Enable trace of neutron through instrument.
   -g        --gravitation    Enable gravitation for all trajectories.
   -a        --data-only      Do not put any headers in the data files.
   --no-output-files          Do not write any data files.
   -h        --help           Show help message.
   -i        --info           Detailed instrument information.
   --test                     Execute McStas selftest and generate report
   --format=FORMAT            Output data files using format FORMAT.
                              (format list obtained from <instr>.out -h)
   -M        --multi          Spawn simulations to multiple machine grid.
             --grid           See the documentation for more info.
                              --multi Not supported on Win32.
   --mpi=NB_CPU               Spread simulation over NB_CPU machines using MPI
   --machines=MACHINES        Read machine names from file MACHINES (MPI/grid)

This program both runs mcstas with Instr and the C compiler to build an
independent simulation program. The following environment variables may be
specified for building the instrument:
  MCSTAS        Location of the McStas and component library
                  ($MCSTAS::sys_dir).
  MCSTAS_CC     Name of the C compiler               ($cc)
  MCSTAS_CFLAGS Options for compilation              ($cflags)
  MCSTAS_FORMAT Default FORMAT to use for data files ($MCSTAS::mcstas_config{'PLOTTER'})
SEE ALSO: mcstas, mcdoc, mcplot, mcdisplay, mcgui, mcresplot, mcstas2vitess
DOC:      Please visit http://www.mcstas.org/
** No instrument definition name given\n" unless $sim_def || $exec_test;
die "Number of points must be at least 1" unless $numpoints >= 1;
}

# Check the input parameter specifications for variables to scan.
sub check_input_params {
    my $i = 0;
    my $j = 0;
    my @scanned = ();
    my @minval = ();
    my @maxval = ();
    my $v;
    for $v (@params) {
        if($vals{$v} =~ /^(.+),(.+)$/) {
            # Variable to scan from min to max.
            $minval[$j] = $1;
            $maxval[$j] = $2;
            $scanned[$j] = $i;
            if($minval[$j] != $maxval[$j] && $numpoints == 1) {
                die "mcrun: Cannot scan variable $v using only one data point.
Please use -N to specify the number of points.";
            }
            $j++;
        } elsif($vals{$v} =~ /^(.+)$/) {
            # Constant variable (no action).
        } else {
            die "mcrun: Invalid parameter specification '$vals{$v}' for parameter $v";
        }
        $i++;
    }
    return { VARS => \@scanned, MIN => \@minval, MAX => \@maxval };
}

sub exec_sim {
    my (@options) = @_;
    my @cmdlist = ($out_file, @options, map("$_=$vals{$_}", @params));
    my $cmd;

    # ToDo: This is broken in that it does not show the proper quoting
    # that would be necessary if the user actually wanted to run the
    # command manually. (Note that the exec() call is correct since it
    # does not need any quoting).
    print join(" ", @cmdlist), "\n";
    if (!$slave == 0) {
        if ($slave eq "localhost") {
            print STDERR "This is a local slave run\n";
            $cmd = "echo cd $slavedir && @cmdlist";
        } else {
            print STDERR "This is a remote slave run at $slave\n";
            $cmd = "ssh $slave \"cd $slavedir && @cmdlist\"";
        }
        exec $cmd;
    } else {
        if ($mpi >= 1) {
            $cmd = "$MCSTAS::mcstas_config{'MPIRUN'} -np $mpi -machinefile $MCSTAS::mcstas_config{'HOSTFILE'} @cmdlist";
        } else {
            $cmd = "@cmdlist";
        }
        exec $cmd;
#        exec @cmdlist;
    }
}

# Handle the case of a single run of the simulation (no -N option).
# SIGNALS are forwarded to child process
sub do_single {
    push @options, "--dir=$data_dir" if $data_dir;
    push @options, "--file=$data_file" if $data_file;

    print "Running simulation '$out_file' ...\n";
    exec_sim(@options) || die "mcrun: Failed to run simulation";
}


# Handle output of header information for .dat files and .sim information files.
sub do_instr_header {
    my ($prefix, $OUT) = @_;
    print $OUT join("", map("$prefix$_", @{$instr_info->{'RAW'}}));
}

# Write Matlab/Scilab function header to datafile
sub do_instr_init {
    my ($OUT) = @_;
    if ($MCSTAS::mcstas_config{'PLOTTER'} =~ /Matlab/i) {
        print $OUT <<ENDCODE
function mcstas = get_mcstas(p)
% Embedded function for building 'mcplot' compatible structure
% PW, RISOE, 20030701
%
% import data using s=mcstas('plot');
if nargout == 0 | nargin > 0, p=1; else p=0; end
ENDCODE
    } elsif ($MCSTAS::mcstas_config{'PLOTTER'} =~ /Scilab/i) {
        print $OUT <<ENDCODE
function mcstas = get_mcstas(p)
// Embedded function for building 'mcplot' compatible structure
// PW, RISOE, 20030701
//
// import data using exec('mcstas.sci',-1); s=get_mcstas('plot');
mode(-1); //silent execution
if argn(2) > 0, p=1; else p=0; end
ENDCODE
   }


}

# Write Matlab/Scilab embedded plotting functions to datafile
sub do_sim_tail{
    my ($OUT) = @_;
    if ($MCSTAS::mcstas_config{'PLOTTER'} =~ /Matlab/i) {
        print $OUT <<ENDCODE

function d=mcplot_inline(d,p)
% local inline function to plot data
if ~p, return; end;
eval(['l=[',d.xylimits,'];']); S=size(d.data);
t1=['[',d.parent,'] ',d.filename,': ',d.title];t = strvcat(t1,['  ',d.variables,'=[',d.values,']'],['  ',d.signal],['  ',d.statistics]);
disp(t);
if ~isempty(findstr(d.type,'1d')), return; end
figure;
if ~isempty(findstr(d.type,'1d'))
    d.x=linspace(l(1),l(2),S(1));
    h=errorbar(d.x,d.data,d.errors);
end
xlabel(d.xlabel); ylabel(d.ylabel); title(t); axis tight;set(gca,'position',[.18,.18,.7,.65]); set(gcf,'name',t1);grid on;
if ~isempty(findstr(d.type,'2d')), colorbar; end

% end of datafile...
ENDCODE
    } elsif ($MCSTAS::mcstas_config{'PLOTTER'} =~ /Scilab/i) {
        print $OUT <<ENDCODE

endfunction

function d=mcplot_inline(d,p)
// local inline func to plot data
if ~p, return; end;
execstr(['l=[',d.xylimits,'];']); S=size(d.data);
t1=['['+d.parent+'] '+d.filename+': '+d.title];t = [t1;['  '+d.variables+'=['+d.values+']'];['  '+d.signal];['  '+d.statistics]];
mprintf('%s\\n',t(:));
if ~length(strindex(d.type,'1d')),return;
else
  w=winsid();if length(w),w=w(\$)+1; else w=0; end
  xbasr(w); xset('window',w);
  if length(strindex(d.type,'1d'))
    d.x=linspace(l(1),l(2),S(1));
    mcplot_errorbar(d.x,d.data,d.errors);
    if p == 2, t = t1; end
    xtitle(t,d.xlabel,d.ylabel)
  end
end
xname(t1);
endfunction // mcplot_inline

function mcplot_errorbar(x,y,e)
// function for creating simple errorbar plots...
  // first, estimate plot range
  xmin=min(x);
  xmax=max(x);
  ymin=min(y-e);
  ymax=max(y+e);
  plot2d(x,y,rect=[xmin ymin xmax ymax]);
  errbar(x,y,e,e);
endfunction // mcplot_errorbar

ENDCODE
    }
}


sub do_sim_header {
    my ($prefix, $OUT) = @_;
    my $date = localtime(time());
    my $format_member=".";
    my $param_field  ="Param";
    if ($MCSTAS::mcstas_config{'PLOTTER'} =~ /McStas|PGPLOT/i) { $format_member=": "; }
    print $OUT "${prefix}Date$format_assign $format_start_value$date$format_end_value\n";
    print $OUT "${prefix}Ncount$format_assign $format_start_value$ncount$format_end_value\n";
    print $OUT "${prefix}Numpoints$format_assign $format_start_value$numpoints$format_end_value\n";
    if ($MCSTAS::mcstas_config{'PLOTTER'} =~ /Scilab|Matlab/i) { $param_field = "parameters"; }
    # Scilab needs initalisation of Param here...
    if ($MCSTAS::mcstas_config{'PLOTTER'} =~ /Scilab/i) {
      print $OUT "${prefix}$param_field=0; ${prefix}$param_field=struct(); \n";
    }
    if ($MCSTAS::mcstas_config{'PLOTTER'} =~ /Scilab|Matlab/i) {
      print $OUT "${prefix}${param_field}${format_member}class = 'parameters';\n";
      print $OUT "${prefix}${param_field}${format_member}name  = 'parameters';\n";
      print $OUT "${prefix}${param_field}${format_member}parent= '$sim_def';\n";
    }
    my $paramtext = join("\n", map("${prefix}$param_field$format_member$_ = $format_start_value$vals{$_}$format_end_value", @params));
    print $OUT $paramtext, "\n";
}

sub do_data_header {
    my ($pr, $OUT, $info, $youts, $variables, $datfile, $datablock) = @_;
    my $scannedvars;
    my $xvars;
    my $xvar_list;
    my $yvars = join " ", @$youts;
    my $xlabel;
    my $min;
    my $max;
    if (@{$info->{VARS}} == 0) { $xlabel = "Point number"; $scannedvars="Point"; $xvars="Point"; $min=1; $max=$numpoints; }
    else {
      $xlabel = $params[$info->{VARS}[0]];
      $scannedvars = join ", ", map($params[$_], @{$info->{VARS}});
      $xvars = join " ", map($params[$_], @{$info->{VARS}});
      push @$xvar_list, map($params[$_], @{$info->{VARS}});
      $min = $info->{MIN}[0]; $max = $info->{MAX}[0];
    }
    # Here, we need special handling of the Matlab/Scilab output cases, since
    # each monitor dataset must be defined in its own class 'data' structure for mcplot
    # But - only to be done if this is not a 'slave' run
    if ($MCSTAS::mcstas_config{'PLOTTER'} =~ /McStas|PGPLOT/i) {
      if (($slave eq 0) || ($slave eq 'localhost')) {
        print $OUT <<END
${pr}type$format_assign ${format_start_value}multiarray_1d($numpoints)$format_end_value
${pr}title$format_assign ${format_start_value}Scan of $scannedvars$format_end_value
${pr}xvars$format_assign ${format_start_value}$xvars$format_end_value
${pr}yvars$format_assign ${format_start_value}$yvars$format_end_value
${pr}xlabel$format_assign '$xlabel'
${pr}ylabel$format_assign 'Intensity'
${pr}${format_limprefix}limits$format_assign ${format_start_value}$min $max$format_end_value
${pr}filename$format_assign ${format_start_value}$datfile$format_end_value
${pr}variables$format_assign ${format_start_value}$variables$format_end_value
END
      }
    } elsif ($MCSTAS::mcstas_config{'PLOTTER'} =~ /Matlab|Scilab/i) {
        if (($slave eq 0) || ($slave eq 'localhost')) {
          if (! $datablock eq "") {
              print $OUT "DataBlock=[$datablock];\n";
              if ($slave eq 'localhost') {
                print $OUT "$format_prefix\nInsert blocks here!\n";
              }
              # Now loop across all the youts...
              my $idx = scalar(@$xvar_list) + 1;
              foreach my $y_pair (@$youts) {
                  # Cut out the relevant descriptor
                  my $start_char = index($y_pair, "(") + 1;
                  my $end_char = index($y_pair, ",") - $start_char;
                  my $y_label = substr($y_pair, $start_char, $end_char);
                  if ($MCSTAS::mcstas_config{'PLOTTER'} =~ /Scilab/i) {
                      print $OUT "$pr$y_label=0; $pr$y_label=struct();\n";
                  }
                  print $OUT "$pr$y_label.class='data';\n";
                  print $OUT "$pr$y_label.data  =[DataBlock(:,$idx)];\n";
                  print $OUT "$pr$y_label.errors=[DataBlock(:,$idx+1)];\n";
                  $idx = $idx+2;
                  print $OUT "$pr$y_label.parent='$y_label';\n";
                  print $OUT "$pr$y_label.Source='$sim_def';\n";
                  print $OUT "$pr$y_label.ratio ='$ncount';\n";
                  print $OUT "$pr$y_label.values='';\n";
                  print $OUT "$pr$y_label.signal='';\n";
                  print $OUT "$pr$y_label.statistics='';\n";
                  my $pr_label = "$pr$y_label.";
                  # Fill the struct
                  print $OUT <<ENDCODE
${pr_label}type$format_assign ${format_start_value}array_1d($numpoints)$format_end_value
${pr_label}title$format_assign ${format_start_value}Scan of $scannedvars$format_end_value
${pr_label}xvars$format_assign ${format_start_value}$xvars$format_end_value
${pr_label}yvars$format_assign ${format_start_value}$y_label$format_end_value
${pr_label}xlabel$format_assign '$xlabel';
${pr_label}ylabel$format_assign 'Intensity';
${pr_label}${format_limprefix}limits$format_assign ${format_start_value}$min $max$format_end_value
${pr_label}filename$format_assign ${format_start_value}$y_label$format_end_value
${pr_label}variables$format_assign ${format_start_value}$y_pair$format_end_value
${pr}$y_label=mcplot_inline(${pr}$y_label,p);

ENDCODE

            }
        print $OUT "clear Datablock;\n";
        }
      } else { # slave run, simply dump the datablock...
          print $OUT "DataBlock=[DataBlock;$datablock];\n";
      }
    }
}

# Write <NAME>.sim information file, to be read by mcplot.
sub output_sim_file {
    my ($filename, $info, $youts, $variables, $datfile, $datablock) = @_;
    my $SIM = new FileHandle;
    my $loc_prefix = "";
    my $instr_name = $sim_def;
    my $i = $info->{VARS}[0]; # Index of variable to be scanned
    my $minvar = $info->{MIN}[0] ? $info->{MIN}[0] : 0;
    my $maxvar = $info->{MAX}[0] ? $info->{MAX}[0] : 0;
    my $namvar = defined($i) && $params[$i] ? $params[$i] : "nothing";

    $instr_name =~ s/\.instr$//;        # Strip any trailing ".instr" extension ...
    open($SIM, ">$filename") ||
        die "mcrun: Failed to write info file '$filename'";
    autoflush $SIM 1;                # Preserves data even if interrupted.
    if (($slave eq 0) || ($slave eq 'localhost')) {
      if ($MCSTAS::mcstas_config{'PLOTTER'} =~ /McStas|PGPLOT/i) {
        do_instr_header("", $SIM);
        print $SIM "\nbegin simulation\n";
        $loc_prefix = "";
      } elsif ($MCSTAS::mcstas_config{'PLOTTER'} =~ /Matlab/i) {
        do_instr_init($SIM);
        do_instr_header("% ", $SIM);
        print $SIM "\nsim.class='simulation';\n";
        print $SIM "sim.name='$filename';\n";
        $loc_prefix = "sim.";
      } else {
        do_instr_init($SIM);
        do_instr_header("// ", $SIM);
        print $SIM "\nsim = struct(); sim.class='simulation';\n";
        print $SIM "sim.name='$filename';\n";
        $loc_prefix = "sim.";
      }
      do_sim_header($loc_prefix, $SIM);
      if ($MCSTAS::mcstas_config{'PLOTTER'} =~ /McStas|PGPLOT/i) {
        print $SIM "end simulation\n\nbegin data\n";
      } elsif ($MCSTAS::mcstas_config{'PLOTTER'} =~ /Matlab/i) {
        print $SIM "\nmcstas.sim = sim; clear sim;\n";
        print $SIM "mcstas.File= '$filename';\n";
        print $SIM "mcstas.instrument.Source= '$sim_def';\n";
        print $SIM "mcstas.instrument.parent= 'mcstas';\n";
        print $SIM "mcstas.instrument.class = 'instrument';\n";
        print $SIM "mcstas.instrument.name  = '$instr_name';\n";
        print $SIM "mcstas.instrument.Parameters  = '...';\n";
        print $SIM "\ndata.class = 'superdata';\n";
        print $SIM "data.name = 'Scan of $namvar';\n";
        print $SIM "data.scannedvar = '$namvar';\n";
        print $SIM "data.numpoints  = $numpoints;\n";
        print $SIM "data.minvar  = $minvar;\n";
        print $SIM "data.maxvar  = $maxvar;\n";
        $loc_prefix = "data.";
      } else {
        print $SIM "\nmcstas = struct(); mcstas.sim = 0; mcstas.sim = sim; clear sim;\n";
        print $SIM "mcstas.File= '$filename';\n";
        print $SIM "instrument = struct();\n";
        print $SIM "instrument.Source= '$sim_def';\n";
        print $SIM "instrument.parent= 'mcstas';\n";
        print $SIM "instrument.class = 'instrument';\n";
        print $SIM "instrument.name  = '$instr_name';\n";
        print $SIM "instrument.Parameters  = '...';\n";
        print $SIM "mcstas.instrument = 0; mcstas.instrument = instrument; clear instrument;\n";
        print $SIM "\ndata = struct(); data.class = 'superdata';\n";
        print $SIM "data.name = 'Scan of $namvar';\n";
        print $SIM "data.scannedvar = '$namvar';\n";
        print $SIM "data.numpoints  = $numpoints;\n";
        print $SIM "data.minvar  = $minvar;\n";
        print $SIM "data.maxvar  = $maxvar;\n";
        $loc_prefix = "data.";
      }
    }
    do_data_header($loc_prefix, $SIM, $info, $youts, $variables, $datfile, $datablock);
    if (($slave eq 0) || ($slave eq 'localhost')) {
      if ($MCSTAS::mcstas_config{'PLOTTER'} =~ /McStas|PGPLOT/i) {
        print $SIM "end data\n";
      } elsif ($MCSTAS::mcstas_config{'PLOTTER'} =~ /Matlab/i) {
        print $SIM "\nmcstas.sim.data = data; clear data;\n";
        do_sim_tail($SIM);
      } else {
        print $SIM "\nmcstas.sim.data = 0; mcstas.sim.data = data; clear data;\n";
        do_sim_tail($SIM);
      }
    }
    close($SIM);
}

# Output header information for mcrun .dat scan file.
sub output_dat_header {
    my ($OUT, $format_prefix, $info, $youts, $variables, $datfile) = @_;
    print $OUT "${format_prefix}Instrument-source: '$instr_info->{'Instrument-source'}'\n";
    do_sim_header($format_prefix, $OUT);
    do_data_header($format_prefix, $OUT, $info, $youts, $variables, $datfile, "");
}

# Do a scan: Run a series of simulations, varying one or more input
# parameters.
sub do_scan {
    my ($info) = @_;
    # Create the output directory if requested.
    my $prefix = "";
    if($data_dir) {
        if ($slave eq 0) {
            if(mkdir($data_dir, 0777)) {
                $prefix = "$data_dir/";
            } else {
                die "Error: unable to create directory '$data_dir'.\n(Maybe the directory already exists?)";
                print $MCSTAS::mcstas_config{'PLOTTER'}; # unreachable code, avoids warning for mcstas_config
            }
        } else {
            $prefix = "$data_dir/";
            if (!($slave eq "localhost")) {
                system("ssh $slave mkdir $prefix") || print STDOUT "dir $slave:$prefix there already...";
            }
            system("mkdir $prefix") || print STDOUT "dir $prefix there already..." ;
        }
    }
    # Use user-specified output file name, with a default of "mcstas.dat".
    my $datfile = ($data_file || "mcstas.dat");
    # Add a default '.dat' extension if no other extension given.
    $datfile .= ".dat" unless $datfile =~ m'\.[^/]*$'; # Quote hack ';
    my $simfile = $datfile;
    $simfile =~ s/\.dat$//;         # Strip any trailing ".dat" extension ...
    $simfile .= $format_ext;        # ... and add ".sim|m|sci" extension.
    my $DAT;
    # Only initialize / use $DAT datafile if format is PGPLOT
    if ($MCSTAS::mcstas_config{'PLOTTER'} =~ /PGPLOT|McStas/i) {
      $DAT = new FileHandle;
      open($DAT, ">${prefix}$datfile");
      autoflush $DAT 1;                # Preserves data even if interrupted.
    } else {
      $datfile = $simfile;             # Any reference should be to the simfile
    }
    my $firsttime = 1;
    my $variables = "";
    my @youts = ();
    our $point;
    my @lab_datablock = ();          # Storing of scan data in variable
                                     # for saving datablock in matlab/scilab
    my $datablock = "";              # 'sim' file.
    # Initialize the @lab_datablock according to 'format'
    if ($start == -1) {
        $start = 0;
    }
    if ($end == -1) {
        $end = $numpoints;
    }
    for($point = 0; $point < $numpoints; $point++) {
        if (($point >= $start) && ($point <= $end)) {
            my $out = "";
            my $j;
            for($j = 0; $j < @{$info->{VARS}}; $j++) {
                my $i = $info->{VARS}[$j]; # Index of variable to be scanned
                $vals{$params[$i]} =
                    ($info->{MAX}[$j] - $info->{MIN}[$j])/($numpoints - 1)*$point +
                    $info->{MIN}[$j];
                $out .= "$vals{$params[$i]} ";
                $variables .= "$params[$i] " if $firsttime
                }
            if (@{$info->{VARS}} == 0) { $out .= "$point "; $variables .= "Point " if $firsttime; }
            # Decide how to handle output files.
            my $output_opt =
                $data_dir ? "--dir=$data_dir/$point" : "--no-output-files";
            my $got_error = 0;
            our $pid;
            if ($Config{'osname'} eq 'MSWin32') {
                # Win32 needs all possible parameters here, since we can not open(SIM,"-|");
                my @cmdlist = ($out_file, @options, map("$_=$vals{$_}", @params), $output_opt, "--format=$MCSTAS::mcstas_config{'PLOTTER'}");
                $pid = open(SIM, "@cmdlist |");
            } else {
                $pid = open(SIM, "-|");
            }
            die "mcrun: Failed to spawn simulation command" unless defined($pid);
            if($pid) {                # Parent
                # install SIG handler
                sub sighandler {
                  my $signame = shift;
                  kill $signame, $pid;
                  if ($signame eq "INT" || $signame eq "TERM") {
                    print STDERR "mcrun: Recieved signal $signame during scan ($point of $numpoints). Finishing.\n";
                    $point = $numpoints;
                  }
                }
                $SIG{'INT'}  = \&sighandler;
                $SIG{'TERM'} = \&sighandler;
                if ($Config{'osname'} ne 'MSWin32') {
                  $SIG{'USR1'} = \&sighandler;
                  $SIG{'USR2'} = \&sighandler;
                  $SIG{'HUP'}  = \&sighandler;
                }
                while(<SIM>) {
                    chomp;
                    if(/Detector: ([^ =]+_I) *= *([^ =]+) ([^ =]+_ERR) *= *([^ =]+) ([^ =]+_N) *= *([^ =]+) *(?:"[^"]+" *)?$/) { # Quote hack -> ") {
                        my $sim_I = $2;
                        my $sim_err = $4;
                        my $sim_N = $6;
                        $out .= " $sim_I $sim_err";
                        if($firsttime) {
                            $variables .= " $1 $3";
                            push @youts, "($1,$3)";
                        }
                    } elsif(m'^Error:') { # quote hack '
                        $got_error = 1;
                    }
                    print "$_\n";
                }
                # remove SIG handler
                $SIG{'INT'}  = 'DEFAULT';
                $SIG{'TERM'} = 'DEFAULT';
                if ($Config{'osname'} ne 'MSWin32') {
                  $SIG{'USR1'} = 'DEFAULT';
                  $SIG{'USR2'} = 'DEFAULT';
                  $SIG{'HUP'}  = 'DEFAULT';
                }
            } else {                # Child
                open(STDERR, ">&STDOUT") || die "mcrun: Can't dup stdout";
                exec_sim(@options, $output_opt);
            }

        my $ret = close(SIM);
        die "mcrun: Exit due to error returned by simulation program" if $got_error || (! $ret && ($? != 0 || $!));
              if ($firsttime eq 1) {
                if ($MCSTAS::mcstas_config{'PLOTTER'} =~ /PGPLOT|McStas/i) {
                    if (($slave eq 0) || ($slave eq 'localhost')) {
                        output_dat_header($DAT, "# ", $info, \@youts, $variables, $datfile);
                    }
                }
              } else {
                push @lab_datablock, "\n";
              }
        if ($MCSTAS::mcstas_config{'PLOTTER'} =~ /PGPLOT|McStas/i) {
                print $DAT "$out\n";
              }
        push @lab_datablock, "$out";
        $firsttime = 0;
        }
    }
    if ($MCSTAS::mcstas_config{'PLOTTER'} =~ /PGPLOT|McStas/i) {
      close($DAT);
    }
    $datablock = join(" ", @lab_datablock);
    output_sim_file("${prefix}$simfile", $info, \@youts, $variables, $datfile, $datablock);

    print "Output file: '${prefix}$datfile'\nOutput parameters: $variables\n";
}

# Do a multi scan on several machines...
sub do_scan_multi {
    my ($info) = @_;
    # Create the output directory if requested.
    my $prefix = "";
    if($data_dir) {
        if(mkdir($data_dir, 0777)) {
            $prefix = "$data_dir/";
        } else {
            if ($slave eq 0) {
                die "Error: unable to create directory '$data_dir'.\n(Maybe the directory already exists?)";
                print $MCSTAS::mcstas_config{'PLOTTER'}; # unreachable code, avoids warning for mcstas_config
            }
        }
    }
    my ($tmpdir,$j,$k,$output);
    open(READ,"mktemp |");
    while (<READ>) {
        $tmpdir = $_;
        chomp $tmpdir;
    }
    # On some systems, we will have to remove the $tmpdir, since it is a file...
    if (-e $tmpdir) {
        my_system("rm -f $tmpdir","Problem removing $tmpdir");
    }
    # Add local machine as a slave...
    @hostlist = ("localhost", @hostlist);
    print STDOUT "hosts are: @hostlist\n";
    my $hosts = @hostlist;
    # Determine how many scan points to send to each slave
    my $perslave = int($numpoints/$hosts);
    my $extra = $hosts*($numpoints/$hosts - $perslave);
    my @scanmin = ();
    my @scanmax = ();
    my @pids = ();
    my $taken = 0;
    my $Max= 0 ;
    for ($j=0; $j<@hostlist; $j++) {
        if ($j < $numpoints) {
            $scanmin[$j]=$taken;
            if ($j<$extra) {
                $scanmax[$j]=$taken+$perslave;
            } else {
                $scanmax[$j]=$taken+$perslave-1;
            }
            $taken=$scanmax[$j]+1;
            $pids[$j]=Proc::Simple->new();
        } else {
            if ($Max == 0) {
                print STDOUT "Removing host $hostlist[$j] and beyond, no more scan points\n";
                $Max = $j;
            }
        }
    }
    if (!($Max == 0)) {
        my @tmp = @hostlist;
        @hostlist = ();
        for ($j=0; $j<$Max; $j++) {
            $hostlist[$j] = $tmp[$j];
        }
    }
    $hosts = @hostlist;
    my $stop = 0;
    my $output_opt = $data_dir ? "--dir=$data_dir" : "--no-output-files";
    my @extras = (@options, map("$_=$vals{$_}", @params), $output_opt, "--format=$MCSTAS::mcstas_config{'PLOTTER'}");
    $out_file = get_out_file($sim_def, $force_compile, @ccopts);
    # Create local tmpdir...
    my_system("mkdir $tmpdir/","mcrun: dir there already?");
    print STDOUT "Spawning child mcrun's...\n";
    for ($j=0; $j<@hostlist; $j++) {
        # Create local folders too...
        my_system("mkdir $tmpdir/$hostlist[$j]","mcrun: dir there already...");
        if ($j == 0) { # localhost...
            my_system("cp $out_file $tmpdir/$hostlist[$j]","");
            my_system("cp $sim_def $tmpdir/$hostlist[$j]","");
        } else {
            my_system("ssh $hostlist[$j] mkdir $tmpdir && ssh $hostlist[$j] mkdir $tmpdir/$hostlist[$j]","");
            my_system("scp $out_file $hostlist[$j]:$tmpdir/$hostlist[$j] ","");
            my_system("scp $sim_def $hostlist[$j]:$tmpdir/$hostlist[$j] ","");
        }
        $pids[$j]->start("mcrun -f $hostlist[$j].dat -N$numpoints --slave=$hostlist[$j] --slavedir=$tmpdir/$hostlist[$j]  $out_file --start=$scanmin[$j] --end=$scanmax[$j] @extras > $tmpdir/log.$hostlist[$j]");
    }
    print STDOUT "Waiting for child processes to end...\n";
    my $running;
    while ($stop < $hosts) {
        for ($j=0; $j<$hosts; $j++) {
            if (!$pids[$j] == 0) {
                $running = $pids[$j]->poll();
                if ($running == 0) {
                    print STDERR "Process at $hostlist[$j] terminated, copying back relevant data...\n";
                    if ($hostlist[$j] eq "localhost") {
                        if ($data_dir) {
                            my_system("cp -rp $tmpdir/localhost $prefix/ ","");
                        }
                    } else {
                        if ($data_dir) {
                            my_system("scp -rp $hostlist[$j]:$tmpdir/$hostlist[$j]/ $prefix/ ","");
                        }
                    }
                    if ($data_dir) {
                        my $PW = getcwd();
                        my_system("find $PW/$prefix$hostlist[$j]/$prefix -type d -not -name $prefix -exec cp -rp \\{\\} $prefix \\;","Problem copying slave dirs..");
                        my_system("cp $tmpdir/$hostlist[$j]/$prefix/$hostlist[$j].* $prefix/","mcrun: Problem copying slave files for $hostlist[$j]");
                    }
                    $stop++;
                    $pids[$j]=0;
                }
            }
        }
    }
    open(MCSTAS,">${prefix}mcstas${format_ext}") || die ("mcrun: could not open file ${prefix}mcstas${format_ext}");
    open(LOCAL,"<${prefix}localhost${format_ext}") || die ("mcrun: could not open file ${prefix}localhost${format_ext}");

    # Now, create a mcstas.sim/sci/m to pick up the pieces...
    if ($MCSTAS::mcstas_config{'PLOTTER'} =~ /McStas|PGPLOT/i) {
        # Use the localhost.sim as sim file (has all needed info - but must have
        # filename: localhost.dat replaced by mcstas.dat
        while(<LOCAL>) {
            s/\bfilename: localhost.dat\b/filename: mcstas.dat/;
            print MCSTAS;
        }
        # Dat's are simply to be catted together.
        my_system("touch ${prefix}mcstas.dat","mcrun: Problem creating mcstas.dat");
        for ($j=0; $j<$hosts; $j++) {
            my_system("cat $prefix$hostlist[$j].dat >> ${prefix}mcstas.dat","mcrun: Problem adding host data");
            my_system("rm $prefix$hostlist[$j].dat","mcrun: Problem removing host data");
        }
    } else {
        # With Matlab / Scilab, a special "Insert blocks here!" line marks where to
        # enlarge the DataBlock section with Matlab/Scilab syntax and data from the
        # host files...
        while(<LOCAL>) { # Self closing
            if (/^\w*Insert blocks here!/) {
                print STDERR "Inserting files...\n";
                for ($j=1; $j<@hostlist; $j++) { # The 1 important here, localhost data there already from localhost$format_ext
                    print MCSTAS "\n\n$format_prefix Insertion from ${prefix}$hostlist[$j]${format_ext}\n\n";
                    open(HOST,"<${prefix}$hostlist[$j]${format_ext}") || print STDERR "mcrun: could not open file ${prefix}$hostlist[$j]${format_ext}";
                    while(<HOST>) { # Self closing
                        print MCSTAS;
                    }
                }
            } else { #everything else printed directly to mcstas.${format_ext}
                print MCSTAS;
            }
        }
    }
    close(MCSTAS);

    # Create global logfile
    my_system("touch ${prefix}mcstas_multi.log","mcrun: Error creating multi logfile\n");
    print STDOUT "Done creating the files...\n";
    # Clean up time, remve everything with relation to the hostnames...
    # Also collect the logfiles
    for ($j=0; $j<@hostlist; $j++) {
        open(READ,"rm -rf ${prefix}$hostlist[$j]* |");
        while (<READ>) {
            $output = $_;
            print STDERR "mcrun: $output\n";
        }
        close(READ);
        my_system("echo \"###########################\" >> ${prefix}mcstas_multi.log");
        my_system("echo logfile from $hostlist[$j]: >> ${prefix}mcstas_multi.log");
        my_system("cat $tmpdir/log.$hostlist[$j] >> ${prefix}mcstas_multi.log");
        my_system("rm $tmpdir/log.$hostlist[$j]");
        if ($j>0) { # Other than localhost
            open(READ,"ssh $hostlist[$j] rm -rf $tmpdir |");
            while (<READ>) {
                $output = $_;
                print STDERR "mcrun: $output\n";
            }
            close(READ);
        }
    }
    my_system("rm -rf $tmpdir","problem removing temporary dir");
    print STDOUT "Terminating --multi run, data in ${prefix}mcstas${format_ext}\n";
}

sub my_system {
    # Very simple error checking system call.
    my ($cmd, $err) = @_;
    my $output;
    my $WRITE = new FileHandle;
    my $READ = new FileHandle;
    my $ERR = new FileHandle;
    open3($WRITE,$READ,$ERR,"$cmd ") || die "$cmd: $err\n";
    while (<$READ>) {
        $output = $_;
        print STDERR "$output\n";
    }
    if (<$ERR>) {
        die "$cmd: $err\n";
    }
}

                    ##########################
                    # Start of main program. #
                    ##########################

parse_args();                         # Parse command line arguments

# assign output format for scans. Default is PGPLOT

$format_ext        = ".sim";
$format_assign     =":";
$format_start_value="";
$format_end_value  ="";
$format_prefix     ="# ";
$format_limprefix  ="x";
if ($MCSTAS::mcstas_config{'PLOTTER'} =~ /Matlab/i) {
  $format_ext        = ".m";
  $format_assign     ="=";
  $format_start_value="'";
  $format_end_value  ="';";
  $format_prefix     ="% ";
  $format_limprefix  ="xy";
} elsif ($MCSTAS::mcstas_config{'PLOTTER'} =~ /Scilab/i) {
  $format_ext        = ".sci";
  $format_assign     ="=";
  $format_start_value="'";
  $format_end_value  ="';";
  $format_prefix     ="// ";
  $format_limprefix  ="xy";
} elsif ($numpoints > 1 && $MCSTAS::mcstas_config{'PLOTTER'} !~ /PGPLOT|McStas/i) {
  print STDERR "mcrun: Warning: format $MCSTAS::mcstas_config{'PLOTTER'} does not support parameter scans\n";
}

# add format option to cmd stack
push @options, "--format='$MCSTAS::mcstas_config{'PLOTTER'}'";

if ($exec_test) {
  # Unfortunately, Scilab direct graphics export is broken on Win32...
  if ($Config{'osname'} eq 'MSWin32') {
      $exec_test=~s!graphics!!g;
      print STDERR "Sorry, graphics is not possible with --test on Win32...\n";
  }
  my $status;
  $status = do_test(sub { print "$_[0]\n"; }, $force_compile, $MCSTAS::mcstas_config{'PLOTTER'}, $exec_test);
  if (defined $status) {
    print STDERR "mcrun: $status";
    exit(1);
  } else {
    exit(0);
  }
}

my $scan_info = check_input_params(); # Get variables to scan, if any
$out_file = get_out_file($sim_def, $force_compile, @ccopts);
exit(1) unless $out_file;
exit(1) if $ncount == 0;

$instr_info = get_sim_info("$out_file","--format='$MCSTAS::mcstas_config{'PLOTTER'}'");

if($numpoints == 1) {
    do_single();
} else {
    if ($multi == 1) {
        print STDERR "Doing multi...\n";
        do_scan_multi($scan_info);
    } else {
        do_scan($scan_info);
    }
}


syntax highlighted by Code2HTML, v. 0.9.1