Новосибирский институт органической химии им. Н.Н. Ворожцова СО РАН

Лаборатория изучения механизмов органических реакций

steps


#!/usr/bin/perl -ws

%CONFIG = (
  gaussian => {
    input_extension         => '.gjf',
    output_extension        => '.log',
    outputs_dir             => "./logs",
    each_output_to_named_dir => 0,
    run => 'g09 !INPUT!',
  },
  pcgamess => {
    input_extension         => '.inp',
    # Обязательный параметр. Д.б. уникальным для каждой программы
    
    run => './pcgamess -r -f -p -i !INPUT! -o !OUTPUT!',
    #run => 'mpirun -np 2 ./pcgamess -r -f -p -i !INPUT! -o !OUTPUT!',
    # Обязательный параметр. Слова !INPUT! и !OUTPUT! не нужно изменять,
    # они лишь обозначают место, где будут соответствующие имена файлов
    # Про опции см. http://classic.chem.msu.su/gran/gamess/comm_line.html
    
    output_extension        => '.out',
    # Подставится вместо input_extension. Если отсутсвует или пустая строка, 
    # то в качестве output'а будет имя input-файла без расширения.
    
    outputs_dir             => "./outs",
    # Если отсутствует, то './outs'
    
    each_output_to_named_dir => 1,
    # Нужно ли на каждый расчет свою директорию. Default 0
    
    renamed_files => {PUNCH=>'.dat', IRCDATA=>'.IRCDATA.dat'},
    # Жестко заданные имена файлов (ключи), которые следует переименовать,
    # заменив input_extension на нужные расширения (величины)
  },
  priroda  => {
    input_extension => '.in',
    run => '/usr/local/lib/priroda/p6_32 !INPUT! !OUTPUT!',
  },
  orca     => {
    input_extension => '.ino',
    run => 'orca !INPUT! > !OUTPUT!',
  },
  test => {
    input_extension         => '.ttt',
    output_extension        => '.out',
    outputs_dir             => "./outs",
    each_output_to_named_dir => 1,
    run => 'cat !INPUT! > !OUTPUT!; touch PUNCH IRCDATA; sleep 5', # *nix
    #run => 'type !INPUT! > !OUTPUT! && copy !INPUT! PUNCH && copy !INPUT! IRCDATA && perl -e "sleep 5"', # win
    renamed_files => {PUNCH=>'.dat', IRCDATA=>'.IRCDATA.dat'},
  },
);

############################################################################

use vars qw($h $debug); # Опции

(my $proga = $0) =~ s/.*[\/\\]//;
if ($h) {
  print <<HELP;
Скрипт для запуска расчетов, input-файлы которых собраны в отдельную папку.
Usage: $proga [-h|-debug] [mask]

  Идеология такова: input-файлы собираются в отдельную папку, из этой папки
запускается скрипт. Скрипт выбирает файлы с расширениями, перечисленными
в %CONFIG (в начале скрипта). Для каждой расчетной программы д.б. свое
расширение input-файла, именно по нему скрипт определяет, какой программой
запускать расчет. В %CONFIG также должно быть поле run -- это строка запуска
расчетной программы. Остальные поля не обязательны. См. комментарии в тексте 
скрипта. 
  В начале каждого расчета выбирается самый старый input-файл, переносится 
в другую папку и в ней запускается на счет. Там же будут output-файлы. 
Так как каждый раз папка с инпутами сканируется по новой, можно добавлять 
в нее новые input-файлы.
  При запуске скрипта в домашней директории создается скрытый файл 
'.$proga.run', который удаляется по окончании работы. Если этот файл
существует, повторный запуск скрипта блокируется.
  В качестве параметра скрипту может быть дана маска (маски), например,
$proga *.inp *.in   -- расчет только pcgamess и priroda
Маски (как csh) будут работать и на Windows, хотя cmd их не понимает.
Если параметры отсутствуют, то обрабатываются все файлы, имеющие расширения, 
перечисленные в %CONFIG.
Чтобы посчитать отдельный файл (файлы), нужно просто дать его имя как параметр.

  С опцией -debug скрипт будет подробно описывать свои действия.
HELP
  exit;
}

############################################################################

use Cwd 'abs_path';

my $flag = "$ENV{HOME}/.$proga.run";
if (-e $flag) {
  die "$proga  is already running!\n";
}
else {
  warn "Create flag file $flag\n" if $debug;
  open L, '>', $flag or die "Can't create $flag: $!\n";
  close L;
}

# В качестве аргументов можно задавать маски типа *.inp в стиле csh
@ARGV = ('*.*') unless @ARGV;

# Вспомогательная строка типа .inp|.in для регулярного выражения
$EXT = join '|', 
       map {s/(\W)/\\$1/g; $_}
       map {$CONFIG{$_}{input_extension}} 
       keys %CONFIG;
warn "Extensions for regexp: $EXT\n" if $debug;

# Запоминаем полный путь рабочей директории, чтобы потом можно было сюда вернуться
$cwd = abs_path('.');
warn "Current work dir: $cwd\n\n" if $debug;

# Пока в рабочей директории остаются подходящие input-файлы
while ( $input_name = get_input_names() ) {
  warn "Processing $input_name\n" if $debug;
  
  # Получаем имя input-файла без расширения и расширение
  ($name,$ext) = $input_name =~ /(.+)($EXT)$/;
  # Имя программы, соответствующее расширению input-файла
  ($program) = grep {$CONFIG{$_}{input_extension} eq $ext} keys %CONFIG;
  warn "Filename: $name, extension: $ext, program: $program\n" if $debug;
  
  # Директория для output'ов (если не задана, то ./outs)
  $outputs_dir = $CONFIG{$program}{outputs_dir} || './outs';
  # Создаем эту директорию, если ее нет
  if (! -d $outputs_dir) {
    warn "Create $outputs_dir\n" if $debug;
    mkdir $outputs_dir or die "Can't create $outputs_dir: $!\n";
  }

  # Если нужно на каждый расчет свою директорию
  if ( $CONFIG{$program}{each_output_to_named_dir} ) {
    $output_dir = "$outputs_dir/$name";
    # Создаем такую директорию, если ее нет
    if (! -d $output_dir) {
      mkdir($output_dir) || die("Can't create $output_dir: $!\n");
    }
  } else {
    $output_dir = $outputs_dir;
  }
  
  # Двигаем input-файл в директорию, где будет проходить расчет
  if (abs_path($output_dir) ne $cwd) {
    warn "Move $input_name to $output_dir\n" if $debug;
    rename($input_name, "$output_dir/$input_name") || 
       die("Can't move $input_name to $output_dir: $!\n");
  } 
  # а если эта директория совпадает с рабочей, то переименовываем input-файл
  else {
    warn "Rename $input_name to ${input_name}_run\n" if $debug;
    rename($input_name, $input_name.'_run') || 
       die("Can't Rename $input_name to ${input_name}_run: $!\n");
  }
  
  # Генерируем имя output-файла
  $output_name = $name;
  $output_name .= ".$CONFIG{$program}{output_extension}" 
     if $CONFIG{$program}{output_extension};
  warn "Output: $output_name\n" if $debug;
  
  # Переходим в директорию, где будет проходить расчет
  warn "Change dir to $output_dir\n" if $debug;
  chdir $output_dir or die "Can't chdir to $output_dir: $!\n";
  
  
  # Генерируем строку запуска программы
  $run = $CONFIG{$program}{run} or 
      die "No run string for $program in %CONFIG\n";
  $run =~ s/!INPUT!/"$input_name"/g; 
  $run =~ s/!OUTPUT!/"$output_name"/g; 
  warn "Run: $run\n" if $debug;
  
  # Печатаем...
  print "$program: $name ... ";
  $time0 = time;
  # Запускаем программу
  system $run;
  # Печатаем время счета
  printf "Done for %.3f h\n", (time - $time0)/3600;
  
  # Postprocessing
  if (ref $CONFIG{$program}{renamed_files} eq 'HASH') {
    while (my ($file,$ext) = each %{$CONFIG{$program}{renamed_files}}) {
      next if ! -f $file;
      warn "Rename $file to $name$ext\n" if $debug;
      rename($file, $name.$ext) || 
        warn("Can't rename $file to $name$ext: $!\n");
    }
  }
  
  # Возвращаемся в рабочую директорию, где лежат input-файлы
  warn "Change dir to work dir $cwd\n" if $debug;
  chdir $cwd or die "Can't chdir to work dir $cwd: $!\n";
  warn "\n" if $debug;
}

print "                                    Processing finished\n";

warn "Delete flag file $flag\n" if $debug;
unlink $flag;

exit(0);

# Сканирование директории с input-файлами с раскрытием маски
# Возвращается самый старый input-файл
sub get_input_names {
  my @input_names =  
  map  { $_->[0] } 
  sort { $a->[1] <=> $b->[1] }
  map  { [$_, (stat)[9]] }
  grep { /(?:$EXT)$/ } 
  grep { -f }
  map  { glob } 
  @ARGV;
  
  warn "Current sorted by date input files: @input_names\n" if $debug;
  return $input_names[0];
}