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

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

g2pri_basis


#!/usr/bin/perl -ws

#use Data::Dump 'pp';
our ($atom,$no_del,$gen,$file,$h,$help);
$| = 1;
(my $script = $0) =~ s/.*\///;

if ($h || $help) {
  print <<HELP;

Генерирование базисов (включая auxiliary) для Природы с помощью orca или g09

Usage: $script [-atom=1,4..9] basis_name
Зависимости: perl, orca или g09

Базисы серии L достаточны для решения многих задач, но они нигде, кроме как в Природе
не используются. Между тем, изредка возникает потребность в других, более стандартных
базисах. Например, чтобы воспроизвести расчет, сделанный в другой программе.

Этот скрипт генерирует для Природы базисы, доступные как стандартные для программ 
orca или g09.  Эти программы запускается для каждого из элементов с ключевыми словами 
ORCA -     '! PBE basis_name PrintBasis NoIter'
гауссиан - '# test BLYP/basis_name DenFit GFInput Guess=Only Pop=None'

Опции:
-gen=orca|g09  Программа-генератор базисов. Default -gen=orca 
               (генерируемые ею дополнительные базисы оказались лучше для Природы).
-atom=1..104   Номера элементов, для которых нужны базисы. Default -atom='1,4..9'
-no_del        Не удалять инпуты/аутпуты orca и g09 (для тестирования)
-file=file_name  Названия базисов берутся из файла (бьется по пробелам/строкам).
HELP
  exit;
}

$atom ||= '1,4..9';
$gen ||= 'orca';
if ($gen !~ /^(g09|orca)$/) {
  die "-gen=g09|orca\n";
}

if ($file && -f $file) {
  open F, '<', $file or die "Can't open $file: $!\n";
  my @l = <F>;
  close F;
  foreach (@l) {
    push @ARGV, split;
  }
}

die "usage $script [-atom=1,4..9] basis_name\n" unless @ARGV;

my $g09 = 'rung09';
die "No $g09\n" if $gen eq 'go9' && ! -x $g09;
my $orca = '/usr/local/bin/orca';
die "No $orca\n" if $gen eq 'orca' && ! -x $orca;

# Вгоняем таблицу Менделеева
my @ATOM = qw (XX
            H                                                  He
            Li Be                               B  C  N  O  F  Ne
            Na Mg                               Al Si P  S  Cl Ar
            K  Ca Sc Ti V  Cr Mn Fe Co Ni Cu Zn Ga Ge As Se Br Kr
            Rb Sr Y  Zr Nb Mo Tc Ru Rh Pd Ag Cd In Sn Sb Te I  Xe
            Cs Ba La
                    Ce Pr Nd Pm Sm Eu Gd Tb Dy Ho Er Tm Yb
                  Lu Hf Ta W  Re Os Ir Pt Au Hg Tl Pb Bi Po At Rn
            Fr Ra Ac
                    Th Pa U  Np Pu Am Cm Bk Cf Es Fm Md No
                  Lr Rf Db Sg Bh Hs Mt Ds Rg
           );

my %SPD = ('S'=>0,'P'=>1,'D'=>2,'F'=>3,'G'=>4,'H'=>5,
           'I'=>6,'J'=>7,'K'=>8,'L'=>8,'M'=>10,'N'=>11);

BASIS:
foreach my $basis_name (@ARGV) {
  print "$basis_name: ";
  my $info = "  Basis and auxiliary basis sets for PRIRODA 
  generated by $script script from $gen program outputs\n";
  $info .= sprintf(" %10s%-30s%s\n",'','basis','basis2') if $gen eq 'orca';
  
  my $basis = "\$basis\ntype=general\ndefault=$basis_name\n\nset=$basis_name\n";
  my $basis2 = "\$basis2\ntype=general\ndefault=$basis_name\n\nset=$basis_name\n";
  ELEMENT:
  foreach my $element_number (eval $atom) {
    my $charge = $element_number % 2;
    $charge = 1-$charge if $gen eq 'orca';
    my $element = $ATOM[$element_number];
    print "$element ";
    if ($gen eq 'g09') {
      open L, '>', "$element.com" or die "Can't wtite $element.com: $!\n";
      print L "# test BLYP/$basis_name DenFit GFInput Guess=Only Pop=None\n\n$element\n\n$charge 1\n$element\n\n\n";
      close L;

      system($g09, "$element.com");
  #    my @args = ($g09, "$element.com");
  #    system(@args) == 0 or die "\nSystem\n@args\nfailed: $?\n";

      open L, '<', "$element.log" or die "Can't open $element.log: $!\n";
      my (@g09_basis, @g09_fit_basis);
      while (<L>) {
        if (m/ Atomic number out of range/) {
          print "\b"x(length($element)+1);
          unlink "$element.com", "$element.log" unless $no_del;
          next ELEMENT;
        }
        if (m/ No stored fitting set matched to this AO basis/) {
          print "No stored fitting set matched to this AO basis\n";
          unlink "$element.com", "$element.log" unless $no_del;
          last ELEMENT;
        }
        if (m/ AO basis set in the form of general basis input/ .. m/ \*\*\*\*/) {
          push @g09_basis, $_;
        }
        if (m/ Standard density basis:\s+(\S+)/) {
          $info .= "\t\t\t\tbasis2: $1\n";
        }
        if (m/ Density basis set in the form of general basis input/ .. m/ \*\*\*\*/) {
          push @g09_fit_basis, $_;
        }
      }
      close L;

      if (@g09_basis > 2) {
        shift @g09_basis; pop @g09_basis;
      } else {
        print STDERR "AO basis set $basis_name for $element is wrong\n";
        next;
      }
      if (@g09_fit_basis > 2) {
        shift @g09_fit_basis; pop @g09_fit_basis;
      } else {
        print STDERR "Density basis $basis_name for $element set is wrong\n";
        next;
      }

      unlink "$element.com", "$element.log" unless $no_del;

      $info .= "$element\t ";

      my @b = g2pribas(@g09_basis);
      my $sum = 0;
      foreach (@b) {
        $sum += @$_;
      }
      $basis .= sprintf "%9d%4d\n", $element_number, $sum;
      for (my $i=0; $i<@b; $i++) {
        foreach (@{$b[$i]}) {
          $basis .= sprintf "%d%3d\n", $i, 0+@$_;
          $info .= 0+@$_;
          foreach (@$_) {
            $basis .= join(' ', map {Lprintf($_)} @$_) . "\n";
          }
        }
        $info .= '/';
      }

      $info =~ s/\/$/\n/;
      #pp @b; 
      my @b2 = g2pribas(@g09_fit_basis);
      my $sum2 = 0;
      foreach (@b2) {
        $sum2 += @$_;
      }
      $basis2 .= sprintf "%9d%4d\n", $element_number, $sum2;
      for (my $i=0; $i<@b2; $i++) {
        foreach (@{$b2[$i]}) {
          $basis2 .= sprintf "%d%3d\n", $i, 0+@$_;
          foreach (@$_) {
            $basis2 .= join(' ', map {Lprintf($_)} @$_) . "\n";
          }
        }
      }
  #    print "@g09_fit_basis\n";
      #pp @b2;
    }
    elsif ($gen eq 'orca') {
      open L, '>', "$element.inp" or die "Can't wtite $element.inp: $!\n";
      print L "! PBE $basis_name PrintBasis NoIter MiniPrint\n\n* xyz $charge 1\n $element 0 0 0\n*\n";
      close L;

      open F, '-|', "$orca $element.inp 2>/dev/null" or die "Can't run $orca: $!\n";
      my (@b,@b2,$inf,$inf2);
      my $count = 0;
      my $count2 = 0;
      while (<F>) {
        last if m/One Electron integrals/;
        if (m/There is no basis function on atom/) {
          print "\b"x(length($element)+1);
          unlink glob("$element.*") unless $no_del;
          close F;
          next ELEMENT;
        }
        
        if (m/(\S+) contracted to (\S+) pattern/) {
          if (! $inf) {
            $inf = "{$1}/[$2]";
          } else {
            $inf2 = "{$1}/[$2]";
          }
        }
        
        if (m/^ # Basis set for element/..m/end;/) {
          if (m/^ ([D-S]) (\d+)/) {
            push @b, sprintf "%d%3d\n", $SPD{$1}, $2;
            $count++;
            for (my $i=1; $i<=$2; $i++) {
              my ($alpha,$d) = map {Lprintf($_)} (split ' ', <F>)[1,2];
              push @b, "$alpha $d\n";
            }
          }
        }
        
        if (m/^ # Auxiliary basis set for element/..m/end;/) {
          if (m/^ ([D-S]) (\d+)/) {
            push @b2, sprintf "%d%3d\n", $SPD{$1}, $2;
            $count2++;
            for (my $i=1; $i<=$2; $i++) {
              my ($alpha,$d) = map {Lprintf($_)} (split ' ', <F>)[1,2];
              push @b2, "$alpha $d\n";
            }
          }
        }
      }
      close F;
      unless (@b) {
        print STDERR "No basis\n";
        unlink glob("$element.*") unless $no_del;
        next BASIS if $element eq 'H';
        next ELEMENT;
      }
      unless (@b2) {
        print STDERR "No auxiliary basis\n";
        next ELEMENT;
      }
      unshift @b, sprintf "%9d%4d\n", $element_number, $count;
      unshift @b2, sprintf "%9d%4d\n", $element_number, $count2;
      $basis .= join '', @b;
      $basis2 .= join '', @b2;
      $info .= sprintf(" %-5s%-30s%s\n",$element,$inf,$inf2) if $inf;
      unlink glob("$element.*") unless $no_del;
    }
  }

  my %h;
  $info = join "\n", grep {! $h{$_}++} split "\n", $info if $gen eq 'g09';
  open L, '>', $basis_name or die "Can't open $basis_name: $!\n";
  print L "$info\n\n";
  print L "$basis\n\$end\n";
  print L "$basis2\n\$end\n";
  close L;
  print "\n";
}

sub g2pribas {
  my @pribas;
  while (@_) {
    $_ = shift @_;
    if (m/^\s*[A-Z]/) {
      my ($shells, $contractations, $scale_factor, $unknown) = split;
      if ($scale_factor != 1 or $unknown != 0) {
        print;
        return undef;
      }
      my (@alpha,@d);
      for (my $i=0; $i<$contractations; $i++) {
        ($alpha[$i],@{$d[$i]}) = split ' ', shift @_;
        #print "$alpha[$i],@{$d[$i]}\n";
        if (length($shells) != @{$d[$i]}) {
          print STDERR "$shells but @{$d[$i]}\n";
          return undef;
        }
      }
      #pp @d;
      my $count=0;
      foreach (split '', $shells) {
        my @bas;
        for (my $i=0; $i<$contractations; $i++) {
          $bas[$i] = [$alpha[$i], $d[$i][$count]];
        }
        #pp @bas;
        push @{$pribas[$SPD{$_}]}, \@bas;
        $count++;
      }
    }
  }
  return @pribas;
}

sub Lprintf {
  my $num = shift;
  $num =~ s/d/e/i;
  my ($m,$e) = split 'e', sprintf("%+.12e", $num);
  $m =~ s/\.//;
  $m =~ s/^([+-])/$1./;
  $e = sprintf "%+03d", $e+1;
  return $m.'00000e'.$e;
}