#!/usr/bin/perl -ws #use Data::Dump 'pp'; our ($v,$n,$f,$h,$help); # Если опция -h, печатаем справку if ($h || $help) { (my $program = $0) =~ s/^.*[\/\\]//; print " Анимация колебаний. Usage: $program [options] file.freq Зависимости: perl file.freq - файл с колебаниями в molden-формате Файлы в таком формате могут быть созданы квантовохимическим визуализатором molden (Write--Molden format), программой openbabel (babel file.ext -omolden file.freq), либо из расчета Природой скриптом pri2mol. -v=%d Номер колебания, которое нужно анимировать. Default -v=1 -n=%d Число точек (кратное 4). Default -n=20 -f=%d Фактор, определяющий размах колебания. Default -f=1 В результате генерируются конкатенированный xyz-файл (n точек) file.v.xyz, в имени которого v - номер колебания. В особых случаях, при -n=1 и -n=-1, генерируется xyz-файл, состоящий их единственной геометрии, соответствующей смещению по колебанию в прямом и в обратном направлении. "; exit; } die "Usage: $0 [options] file.freq\n$0 -h for help\n" unless @ARGV; die "Invalid option -v\n" if $v && $v !~ /^\d+$/; die "Invalid option -n\n" if $n && !($n == -1 || $n =~ /^\d+$/); die "Invalid option -f\n" if $f && $f !~ /^\d+(?:\.\d+)?$/; $v ||= 1; $n ||= 20; $f ||= 1; # Массы изотопов my %massa = qw( H 1.007947 He 4.0026022 Li 6.9412 Be 9.0121823 B 10.8117 C 12.01078 N 14.00672 O 15.99943 F 18.99840325 Ne 20.17976 Na 22.9897702 Mg 24.30506 Al 26.9815382 Si 28.08553 P 30.9737612 S 32.0655 Cl 35.4532 Ar 39.9481 K 39.09831 Ca 40.0784 Sc 44.9559108 Ti 47.8671 V 50.94151 Cr 51.99616 Mn 54.9380499 Fe 55.8452 Co 58.9332009 Ni 58.69342 Cu 63.5463 Zn 65.4094 Ga 69.7231 Ge 72.641 As 74.921602 Se 78.963 Br 79.9041 Kr 83.7982 Rb 85.46783 Sr 87.621 Y 88.905852 Zr 91.2242 Nb 92.906382 Mo 95.942 Ru 101.072 Rh 102.905502 Pd 106.421 Ag 107.86822 Cd 112.4118 In 114.8183 Sn 118.7107 Sb 121.7601 Te 127.603 I 126.904473 Xe 131.2936 Cs 132.905452 Ba 137.3277 La 138.90552 Ce 140.1161 Pr 140.907652 Nd 144.243 Sm 150.363 Eu 151.9641 Gd 157.253 Tb 158.925342 Dy 162.5001 Ho 164.930322 Er 167.2593 Tm 168.934212 Yb 173.043 Lu 174.9671 Hf 178.492 Ta 180.94791 W 183.841 Re 186.2071 Os 190.233 Ir 192.2173 Pt 195.0782 Au 196.966552 Hg 200.592 Tl 204.38332 Pb 207.21 Bi 208.980382 Th 232.03811 Pa 231.035882 U 238.028913 ); my $BA = 0.529177249; my $n4 = $n%4 ? (int(abs($n)/4)+1)*4 : $n; foreach my $file (@ARGV) { my ($mol,$freq,$intens,$vibr,$outtype) = read_IR_file($file); if ($outtype ne 'molden') { print "Not Molden format of file $file\n"; print "Try convert to Molden format via molden (Write button)\n"; next; } print "File $file of $outtype format\n"; my $nvibr = @$vibr; if ($v>$nvibr) { warn "Vibration # $v: There is only $nvibr vibrations in $file.\n"; next; } die "Freq != Vibr\n" if @$freq != @$vibr; for (@$vibr) {die "mol != vibr\n" if @$_ != @$mol}; my $V = $vibr->[$v-1]; print "Vibration # $v, freq $freq->[$v-1] cm-1\n"; to_centre_mass($mol); to_centre_mass($V); # pp $V; # exit; my @mol_vibr; my $nn = $n4/4; my $N = $#{$mol}; for (my $i=0; $i<=$nn; $i++) { my ($mol_p, $mol_m); $mol_p->[0] = $mol_m->[0] = $mol->[0]; for (my $j=1; $j<=$N; $j++) { $mol_p->[$j][0] = $mol->[$j][0]; $mol_p->[$j][1] = ($mol->[$j][1] + $V->[$j][1]*$i/$nn*$f)*$BA; $mol_p->[$j][2] = ($mol->[$j][2] + $V->[$j][2]*$i/$nn*$f)*$BA; $mol_p->[$j][3] = ($mol->[$j][3] + $V->[$j][3]*$i/$nn*$f)*$BA; $mol_m->[$j][0] = $mol->[$j][0]; $mol_m->[$j][1] = ($mol->[$j][1] - $V->[$j][1]*$i/$nn*$f)*$BA; $mol_m->[$j][2] = ($mol->[$j][2] - $V->[$j][2]*$i/$nn*$f)*$BA; $mol_m->[$j][3] = ($mol->[$j][3] - $V->[$j][3]*$i/$nn*$f)*$BA; } $mol_vibr[$i] = $mol_p; $mol_vibr[2*$nn-$i] = $mol_p if $i != $nn; $mol_vibr[2*$nn+$i] = $mol_m; $mol_vibr[4*$nn-$i] = $mol_m if $i != $nn && $i != 0; } (my $filename = $file) =~ s/\.freq$//; if ($n==1) {@mol_vibr = ($mol_vibr[1])} if ($n==-1) {@mol_vibr = ($mol_vibr[3])} write_molden(@mol_vibr, "$filename.$v.xyz"); } sub to_centre_mass { # centre_to_atom($mol) # Помещает центр координат молекулы в центр масс my $mol = shift; my $N = $#{$mol}; #return undef if $i > $N; my ($x0,$y0,$z0,$M); for (my $i=1; $i<=$N; $i++) { my $m = $massa{$mol->[$i][0]}; $x0 += $m * $mol->[$i][1]; $y0 += $m * $mol->[$i][2]; $z0 += $m * $mol->[$i][3]; $M += $m; } #pp $mol if $M == 0; $x0 /= $M; $y0 /= $M; $z0 /= $M; for (my $i=1; $i<=$N; $i++) { $mol->[$i][1] -= $x0; $mol->[$i][2] -= $y0; $mol->[$i][3] -= $z0; } # my $rms; # for (my $i=1; $i<=$N; $i++) { # $rms += $massa{$mol->[$i][0]}*($mol->[$i][1]**2+$mol->[$i][2]**2+$mol->[$i][3]**2); # } # return sqrt($rms); return 1; } sub get_rms { # Переделанная на перл coord.c Г.Сальникова # Принимает две ссылки на геометрии (см. read_molden() из conformers) # Возвращает RMSD между геометриями # Во вторую ссылку записывает новую, сглаженную геометрию # Использует глобальный хэш %massa с массами элементов (см. smooth) my ($mol0, $mol1) = @_; my ($i, $n, $rot); my (@m, @x0, @y0, @z0, @x1, @y1, @z1, $xc, $yc, $zc, $e, $e0, $mtot, $tg1, $tg2, $phi, $phix, $phiy, $phiz); my $M_PI = atan2(0,-1); my $FLT_EPSILON = 1e-9; my $DEBUG = 0; #/*** Input data preparation ***/ $n = $#{$mol0}; for ($i=0; $i<$n; $i++) { $m[$i] = $massa{$mol0->[$i+1][0]}; die "Inconsistent data\n" if $massa{$mol1->[$i+1][0]} != $m[$i]; $mtot += $m[$i]; $x0[$i] = $mol0->[$i+1][1]; $y0[$i] = $mol0->[$i+1][2]; $z0[$i] = $mol0->[$i+1][3]; $x1[$i] = $mol1->[$i+1][1]; $y1[$i] = $mol1->[$i+1][2]; $z1[$i] = $mol1->[$i+1][3]; } #/*** 1-st translation to center of mass ***/ $xc = $yc = $zc = 0; for ($i=0; $i<$n; $i++) { $xc += $m[$i]*$x0[$i]; $yc += $m[$i]*$y0[$i]; $zc += $m[$i]*$z0[$i]; } $xc /= $mtot; $yc /= $mtot; $zc /= $mtot; for ($i=0; $i<$n; $i++) { $x0[$i] -= $xc; $y0[$i] -= $yc; $z0[$i] -= $zc; } if ($DEBUG) { printf ("1-st molecule in center of mass\n"); for ($i=0; $i<$n; $i++) { printf "%2.0f %10f %10f %10f\n", $m[$i], $x0[$i], $y0[$i], $z0[$i]; } printf "1-st center of mass translation on: (%10f, %10f, %10f)\n", -$xc, -$yc, -$zc; } #/*** 2-nd translation to center of mass ***/ $xc = $yc = $zc = 0; for ($i=0; $i<$n; $i++) { $xc += $m[$i]*$x1[$i]; $yc += $m[$i]*$y1[$i]; $zc += $m[$i]*$z1[$i]; } $xc /= $mtot; $yc /= $mtot; $zc /= $mtot; for ($i=0; $i<$n; $i++) { $x1[$i] -= $xc; $y1[$i] -= $yc; $z1[$i] -= $zc; } if ($DEBUG) { printf ("2-nd molecule in center of mass\n"); for ($i=0; $i<$n; $i++) { printf "%2.0f %10f %10f %10f\n", $m[$i], $x1[$i], $y1[$i], $z1[$i]; } printf "2-nd center of mass translation on: (%10f, %10f, %10f)\n", -$xc, -$yc, -$zc; } $e = 0; for ($i=0; $i<$n; $i++) { $e += $m[$i]*(($x1[$i]-$x0[$i])*($x1[$i]-$x0[$i])+ ($y1[$i]-$y0[$i])*($y1[$i]-$y0[$i])+ ($z1[$i]-$z0[$i])*($z1[$i]-$z0[$i])); } printf ("2*energy: %g\n", $e) if $DEBUG; for ($rot=1; ; $rot++) { $e0 = $e; #/*** Rotation around X ***/ $tg1 = $tg2 = 0; for ($i=0; $i<$n; $i++) { $tg1 += $m[$i]*($y0[$i]*$z1[$i]-$z0[$i]*$y1[$i]); $tg2 += $m[$i]*($y0[$i]*$y1[$i]+$z0[$i]*$z1[$i]); } $phi = atan2 ($tg1, $tg2); for ($i=0; $i<$n; $i++) { $yc = $y1[$i]*cos($phi)+$z1[$i]*sin($phi); $zc = $z1[$i]*cos($phi)-$y1[$i]*sin($phi); $y1[$i] = $yc; $z1[$i] = $zc; } $e = 0; for ($i=0; $i<$n; $i++) { $e += $m[$i]*(($x1[$i]-$x0[$i])*($x1[$i]-$x0[$i])+ ($y1[$i]-$y0[$i])*($y1[$i]-$y0[$i])+ ($z1[$i]-$z0[$i])*($z1[$i]-$z0[$i])); } if ($DEBUG) { for ($i=0; $i<$n; $i++) { printf "%2.0f %10f %10f %10f\n", $m[$i], $x1[$i], $y1[$i], $z1[$i]; } printf "after %d-th rotation around X on: %g (%g deg)\n2*energy: %g\n", $rot, $phi, $phi*180/$M_PI, $e; } $phix = $phi; #/*** Rotation around Y ***/ $tg1 = $tg2 = 0; for ($i=0; $i<$n; $i++) { $tg1 += $m[$i]*($z0[$i]*$x1[$i]-$x0[$i]*$z1[$i]); $tg2 += $m[$i]*($z0[$i]*$z1[$i]+$x0[$i]*$x1[$i]); } $phi = atan2 ($tg1, $tg2); for ($i=0; $i<$n; $i++) { $xc = $x1[$i]*cos($phi)-$z1[$i]*sin($phi); $zc = $z1[$i]*cos($phi)+$x1[$i]*sin($phi); $x1[$i] = $xc; $z1[$i] = $zc; } $e = 0; for ($i=0; $i<$n; $i++) { $e += $m[$i]*(($x1[$i]-$x0[$i])*($x1[$i]-$x0[$i])+ ($y1[$i]-$y0[$i])*($y1[$i]-$y0[$i])+ ($z1[$i]-$z0[$i])*($z1[$i]-$z0[$i])); } if ($DEBUG) { for ($i=0; $i<$n; $i++) { printf "%2.0f %10f %10f %10f\n", $m[$i], $x1[$i], $y1[$i], $z1[$i]; } printf "after %d-th rotation around Y on: %g (%g deg)\n2*energy: %g\n", $rot, $phi, $phi*180/$M_PI, $e; } $phiy = $phi; #/*** Rotation around Z ***/ $tg1 = $tg2 = 0; for ($i=0; $i<$n; $i++) { $tg1 += $m[$i]*($x0[$i]*$y1[$i]-$y0[$i]*$x1[$i]); $tg2 += $m[$i]*($x0[$i]*$x1[$i]+$y0[$i]*$y1[$i]); } $phi = atan2 ($tg1, $tg2); for ($i=0; $i<$n; $i++) { $xc = $x1[$i]*cos($phi)+$y1[$i]*sin($phi); $yc = $y1[$i]*cos($phi)-$x1[$i]*sin($phi); $x1[$i] = $xc; $y1[$i] = $yc; } $e = 0; for ($i=0; $i<$n; $i++) { $e += $m[$i]*(($x1[$i]-$x0[$i])*($x1[$i]-$x0[$i])+ ($y1[$i]-$y0[$i])*($y1[$i]-$y0[$i])+ ($z1[$i]-$z0[$i])*($z1[$i]-$z0[$i])); } if ($DEBUG) { for ($i=0; $i<$n; $i++) { printf "%2.0f %10f %10f %10f\n", $m[$i], $x1[$i], $y1[$i], $z1[$i]; } printf "after %d-th rotation around Z on: %g (%g deg)\n2*energy: %g\n", $rot, $phi, $phi*180/$M_PI, $e; } $phiz = $phi; last if (abs($phix) < $FLT_EPSILON && abs($phiy) < $FLT_EPSILON && abs($phiz) < $FLT_EPSILON && ($e == $e0 || abs(($e-$e0)/($e+$e0)) < $FLT_EPSILON)); } #/*** Output data preparation ***/ for ($i=0; $i<$n; $i++) { $mol1->[$i+1][1] = $x1[$i]; $mol1->[$i+1][2] = $y1[$i]; $mol1->[$i+1][3] = $z1[$i]; } if ($DEBUG) { for ($i=0; $i<$n; $i++) { printf "%2.0f %10f %10f %10f\n", $m[$i], $x1[$i], $y1[$i], $z1[$i]; } printf "2*energy: %g\n", $e; } #/*** Finished ***/ # return sqrt($e); return $e; } sub sum { my $sum = 0; foreach (@_) { $sum += $_; } return $sum; } sub read_IR_file { my $file = shift; my $d = qr/\d+(?:\.\d+)?/; my $outtype = 'unknown'; my $debug = 0; open PRI, $file or die "Can't open $file: $!\n"; # determine the creator of the file while () { if (m/Priroda(?:\s+version)?\s+(\d+)/) { # for Priroda v. 2-9 my $ver = $1; last if eof(PRI); $_ = ; next unless m/^\s*copyright\s+\(c\)\s.*\sLaikov$/; # for Priroda v. 2-9 $outtype = "priroda$ver"; last; } elsif (/^\s{8,}\*{30,}$/) { last if eof(PRI); $_ = ; next unless m/^\s*\*\s+GAMESS\s+VERSION\s+=\s+.+\s+\*$/; last if eof(PRI); $_ = ; next unless m/^\s*\*\s+FROM\s+IOWA\s+STATE\s+UNIVERSITY\s+\*$/; $outtype = 'gamessUS'; last; } elsif (m/\Q[Molden Format]/) { $outtype = 'molden'; last; } elsif (m/^\s*$d\s+$d\s*$/) { $outtype = 'txt'; seek PRI, 0, 0; last; } } warn "$file: $outtype\n" if $debug; my (@mol,@freq,@intens,@vibr); ### Parse priroda 4,6 format if ($outtype eq 'priroda6') { while () { if (m/\Q | Mode | Freq. | Mass. | IR Int. /) { # Priroda 6 ; while () { last if m/\*/; s/\|//g; my ($freq,$intens) = (split)[1,3]; $freq =~ s/i//; push @freq, $freq; push @intens, $intens; } } } } ### Parse priroda2 format elsif ($outtype eq 'priroda2') { while () { push @freq, grep {!/^i/} split if s/^ freq\.//; push @intens, split if s/^ ir i\.//; } } ### Parse gamess US format elsif ($outtype eq 'gamessUS') { while () { push @freq, grep {!/^I/} split if s/^ FREQUENCY://; push @intens, split if s/^ IR INTENSITY://; } } ### Parse molden freq format elsif ($outtype eq 'molden') { my ($key,$i); while () { next if m/^\s*$/; if (m/^\[(.*?)\]/) { $key = $1; next; } next unless defined $key; chomp; if ($key eq 'FREQ') { push @freq, 0+$_; } elsif ($key eq 'INT') { push @intens, 0+$_; } elsif ($key eq 'FR-COORD') { push @mol, [split ' ']; } elsif ($key eq 'FR-NORM-COORD') { $i=$1,next if m/^vibration\s+(\d+)/; push @{$vibr[$i-1]}, [split ' ']; } } unshift @mol, {}; foreach (@vibr) { unshift @$_, {}; for (my $i=1; $i<@mol; $i++) { unshift @{$_->[$i]}, $mol[$i][0]; } } } elsif ($outtype eq 'txt') { while () { m/^\s*($d)\s+($d)\s*$/ or last; push @freq, $1; push @intens, $2; } } close PRI; # if ($#freq != $#intens) { # warn "freq != intens for $file"; # return ''; # } #pp(\@vibr); exit; if ($debug) { for (my $i=0; $i<@freq; $i++) { print "$freq[$i]\t$intens[$i]\n"; } } return \@mol,\@freq,\@intens,\@vibr,$outtype; } sub copy_mol { my $mol = shift; my $new_mol; my $N = $#{$mol}; $new_mol->[0] = $mol->[0] if $mol->[0]; for (my $i=1; $i<=$N; $i++) { $new_mol->[$i] = [@{$mol->[$i]}]; } return $new_mol; } sub write_molden { my $file = pop @_; open F, '>', $file or do {warn "Can't write to $file: $!\n"; return}; foreach my $mol (@_) {; my $N = $#{$mol}; print F " $N\n"; print F " Energy $mol->[0]{Energy}" if $mol->[0]{Energy}; print F " Point $mol->[0]{Point}" if $mol->[0]{Point}; print F " Symmetry $mol->[0]{Symmetry}" if $mol->[0]{Symmetry}; print F " Ellips $mol->[0]{Ellips}" if $mol->[0]{Ellips}; print F "\n"; for (my $i=1; $i<=$N; $i++) { printf F " %-2s %12.8f %12.8f %12.8f\n", @{$mol->[$i]}; } } close F; }