OSDN Git Service

560082a3e4ff91f62122726f60b366b3b0655f1c
[molby/Molby.git] / Scripts / gamess.rb
1 # coding: utf-8
2 #
3 #  gamess.rb
4 #
5 #  Created by Toshi Nagata on 2009/11/22.
6 #  Copyright 2009 Toshi Nagata. All rights reserved.
7 #
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation version 2 of the License.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16
17 class MemoryIO < IO
18   def initialize
19     @buffer = ""
20   end
21   def write(s)
22         @buffer << s
23   end
24   def buffer
25     @buffer
26   end
27   def empty
28     @buffer = ""
29   end
30 end
31
32 class Molecule
33
34   #  Import nbo log.  The argument is either an array of Strings
35   #  or an IO object.
36   def import_nbo_log(lines)
37     if lines.is_a?(IO)
38           getline = lambda { lines.gets }
39         else
40           getline = lambda { lines.shift }
41     end
42     nbo = Hash.new
43         while (ln = getline.call) != nil
44           if ln =~ /done with NBO analysis/
45             break
46           end
47           if ln =~ /NATURAL POPULATIONS:  Natural atomic orbital occupancies/
48             #  List of Natural AOs
49                 getline.call
50                 getline.call
51                 getline.call
52                 nbo["nao"] = []
53                 while (ln = getline.call) != nil
54                   if ln =~ /^\s*$/
55                     #  Skip a blank line
56                     ln = getline.call
57                         if ln =~ /^\s*$/
58                           #  Double blank lines indicates the end of the table
59                           break
60                         end
61                   end
62                   ln.chomp!
63                   next unless ln =~ /^ *(\d+) *([A-Za-z]+) *(\d+) *([a-z]+) +([A-Za-z]+)\( *(\d+)([a-z]+)\)/
64                   i = $1.to_i - 1
65                   an = $3.to_i - 1
66                   atom_label = $2 + $3.to_s
67                   label = $4
68                   type = $5
69                   pn = $6
70                   vals = $~.post_match.split.map { |e| e.to_f }
71                   #  atom_index, type, pn+label, occupancy[, energy]
72                   nbo["nao"].push([atom_label, type, pn + label] + vals)
73                 end
74           elsif ln =~ / NATURAL BOND ORBITALS \(Summary\):/
75             nbo["nbo"] = []
76                 getline.call
77             while (ln = getline.call) != nil
78                   break if ln =~ /Total Lewis/
79                   if ln =~ /^\s*$/
80                         #  Skip a blank line
81                         ln = getline.call
82                         if ln =~ /^\s*$/
83                           #  Double blank lines indicates the end of the table
84                           break
85                         end
86                   end
87                   if ln =~ /^\s*(\d+)\. *([A-Za-z]+\*?) *\( *(\d+)\) *([A-Za-z]+) *(\d+)(- *([A-Za-z]+) *(\d+))? *(\d\.\d+)/
88                         idx = $1.to_i
89                         orb_kind = $2
90                         orb_idx = $3
91                         atom_label = $4 + ($5.to_i - 1).to_s
92                         if $6 != nil
93                           atom_label += "-" + $7 + ($8.to_i - 1).to_s
94                         end
95                         occ = $9.to_f
96                         nbo["nbo"].push([orb_kind + "_" + orb_idx, atom_label, occ])
97                   end
98                 end
99           elsif ln =~ /([A-Z]+)s in the ([A-Z]+) basis:/ || ln =~ /([A-Z]+) Fock matrix:/
100             #  Read matrix
101             dst = $1
102                 src = $2
103                 if src == nil
104                   src = "F"   #  Fock
105                 end
106                 key = src + "/" + dst
107                 getline.call
108                 getline.call
109                 getline.call
110                 elements = []
111                 labels = []
112                 idx = 0
113                 block = 0
114                 while (ln = getline.call) != nil
115                   if ln =~ /^\s*$/
116                     #  Blank line: end of one block
117                     ln = getline.call
118                         if ln =~ /^\s*$/
119                           #  Double blank lines indicates the end of the table
120                           break
121                         else
122                           #  Begin next section
123                           ln = getline.call
124                           idx = 0
125                           block += 1
126                           next
127                         end
128                   end
129                   ln.chomp!
130                   ln =~ /-?\d+\.\d+/
131                   lab = $~.pre_match.strip
132                   a = ([$~[0]] + $~.post_match.split).map { |e| e.to_f }
133                   if block == 0
134                     lab =~ /^[0-9]+\. *(.*)$/
135                     labels.push($1)
136                   end
137                   (elements[idx] ||= []).concat(a)
138                   idx += 1
139                 end
140                 nbo[key] = LAMatrix.new(elements).transpose!
141                 key2 = (src == "F" ? dst : src) + "_L"
142                 if nbo[key2] == nil
143                   nbo[key2] = labels
144                 end
145           end
146         end
147         @nbo = nbo
148         if @nbo["AO/NAO"]
149           #  Convert NAO based matrix into AO based matrix
150           @nbo.keys.each { |key|
151             if key[0..3] == "NAO/"
152                   key2 = "AO/" + key[4..-1]
153                   @nbo[key2] = @nbo["AO/NAO"] * @nbo[key]
154                 end
155           }
156         end
157         # puts @nbo.inspect
158   end
159   
160   def Molecule.read_gamess_basis_sets(fname)
161     # $gamess_basis = Hash.new unless $gamess_basis
162         $gamess_ecp = Hash.new unless $gamess_ecp
163         # $gamess_basis_desc = Hash.new unless $gamess_basis_desc
164         # $gamess_basis_keys = [] unless $gamess_basis_keys
165         basename = File.basename(fname, ".*")
166         keys = []
167         descname = nil
168     File.open(fname, "r") { |fp|
169           while (s = fp.gets)
170             if s =~ /^\s*!\s*/ && descname == nil
171               #  Get the descriptive name from the first comment line
172                   s = Regexp.last_match.post_match
173                   if s =~ /  EMSL/
174                     descname = Regexp.last_match.pre_match
175                   else
176                     descname = (s.split)[0]
177                   end
178                   $gamess_basis_desc[basename] = descname
179                   next
180                 end
181                 ss, bas = (s.split)[0..1]     #  Tokens delimited by whitespaces
182                 next if ss == nil || ss == ""
183                 if ss == "$ECP"
184                 #  Read the ECP section
185                   ecp = $gamess_ecp[basename]
186                   if !ecp
187                     ecp = []
188                         $gamess_ecp[basename] = ecp
189                   end
190                   keys << basename unless keys.include?(basename)
191                   while (s = fp.gets)
192                     break if s=~ /\$END/
193                         ary = s.split  #  (PNAME, PTYPE, IZCORE, LMAX+1)
194                         elem = ary[0].match(/\w{1,2}/).to_a[0]  #  ary[0] should begin with an element name
195                         ap = Parameter.builtin.elements.find { |p| p.name.casecmp(elem) == 0 }
196                         raise MolbyError, "the ECP definition does not begin with an element name: #{s}" if !ap
197                         ecpdef = s
198                         ln = 1
199                         (0..Integer(ary[3])).each {
200                           s = fp.gets
201                           raise MolbyError, "the ECP definition ends unexpectedly at line #{ln} for: #{s}" if !s
202                           ln += 1
203                           ary = s.split  #  (NGPOT, comment)
204                           ecpdef += s
205                           (1..Integer(ary[0])).each {
206                             s = fp.gets
207                                 raise MolbyError, "the ECP definition ends unexpectedly at line #{ln} for: #{s}" if !s
208                                 ln += 1
209                                 ecpdef += s
210                           }
211                         }
212                     ecp[ap.index] = ecpdef
213                   end
214                 elsif ss =~ /\W/
215                   #  Comments or other unrecognizable lines
216                   next
217                 elsif (ap = Parameter.builtin.elements.find { |p| p.name.casecmp(ss) == 0 || p.fullname.casecmp(ss) == 0 })
218                   #  Valid basis definition
219                   if bas == nil || bas =~ /\W/
220                     bas = basename
221                   end
222                   basis = $gamess_basis[bas]
223                   if !basis
224                     basis = []
225                         $gamess_basis[bas] = basis
226                   end
227                   keys << bas unless keys.include?(bas)
228                   basdef = ""
229                   while (s = fp.gets) && s =~ /\S/
230                     basdef += s
231                   end
232                   basis[ap.index] = basdef
233                 else
234                   raise MolbyError, "ss is not a valid element symbol or name: #{s}"
235                 end
236           end
237     }
238         unless $gamess_basis_keys.include?(basename)
239           $gamess_basis_keys.push(basename)
240         end
241   end
242
243   #  Execute GAMESS (inferior copy of rungms script)
244   #  inpname is the input file
245   #  mol (optional) is the molecule from which the GAMESS input was built.
246   #  If mol is specified and RUNTYP=OPTIMIZE, then the intermediate structures are
247   #  displayed real-time.
248   def Molecule.execute_gamess(inpname, mol = nil)
249     gmsname = get_global_settings("gamess.executable_path")
250     gmsdir = nil
251     gmsvers = nil
252
253         cygwin_version = false  #  For windows: old cygwin version
254         
255     while 1
256       if gmsname == nil || !File.exist?(gmsname)
257         gmsname = Dialog.open_panel("Please locate the GAMESS executable")
258         exit if gmsname == nil
259       end
260       gmsbase = File.basename(gmsname)
261       gmsdir = File.dirname(gmsname)
262       if gmsbase =~ /gamess\.(.*)\.(exe|x)$/i
263         gmsvers = $1
264         break
265       else
266         gmsname = nil
267         error_message_box(gmsbase + " does not look like a GAMESS executable!")
268       end
269     end
270
271 #       if mol == nil
272 #               mol = Molecule.open(inpname)
273 #               if mol == nil
274 #                       error_message_box("Cannot open #{inpname} as GAMESS input")
275 #                       return
276 #               end
277 #       end
278         
279     inpbase = File.basename(inpname)
280     inpdir = File.dirname(inpname)
281     inpbody = inpbase.sub(/\.inp$/, "")
282     logbase = inpbody + ".log"
283
284     set_global_settings("gamess.executable_path", gmsname)
285
286     ncpus = get_global_settings("gamess.ncpus").to_i
287         if ncpus == 0
288           ncpus = 1
289         end
290         
291     #  Prepare the scratch directory
292         begin
293           scrdir = create_temp_dir("gamess", inpbody)
294         rescue
295           error_message_box($!.to_s)
296           return
297         end
298 #    scrdir = $home_directory + "/Molby/gamess"
299 #       begin
300 #         mkdir_recursive(scrdir)
301 #       rescue
302 #         error_message_box("Cannot create directory #{scrdir}: " + $!.to_s)
303 #         return
304 #       end
305
306 #    scrdir = scrdir + "/" + inpbody + "." + $$.to_s + ".0"
307 #    n = 0
308 #    while File.exist?(scrdir)
309 #      scrdir = scrdir.sub(".#{n}", ".#{n + 1}")
310 #      n += 1
311 #    end
312 #    Dir.mkdir(scrdir)
313
314     if $platform == "win"
315       sep = "\\"
316       scrdir.gsub!("/", sep)
317       gmsdir.gsub!("/", sep)
318     else
319       sep = "/"
320     end
321
322         #  Old (obsolete) cygwin version, using ddikick
323     if $platform == "win"
324           if gmsvers == "11"
325             cygwin_version = true
326           end
327         end
328
329     #  Get the host name etc.
330     hostname = backquote("hostname").chomp
331     if $platform == "win"
332           s = backquote("cmd.exe /c dir \"#{scrdir}\"")
333       freebytes = s.split("\n").pop.match(/([0-9,]+)[^0-9]*$/).to_a[1]
334       if freebytes
335         freebytes = (freebytes.gsub(",","").to_i / 1024).to_s + " Kbytes"
336       else
337         freebytes = "(unknown)"
338       end
339       uname = backquote("cmd.exe /c ver").to_s.gsub("\n", "")
340     else
341       freebytes = `df -k "#{scrdir}"`
342       uname = `uname`.chomp
343     end
344
345     #  Redirect standard output to the log file
346     logname = scrdir + sep + logbase
347     fpout = File.open(logname, "w")
348         if cygwin_version
349           #  The cygwin version uses LF as the eol character
350           fpout.binmode
351         end
352     fpout.print "----- GAMESS execution script -----\n"
353     fpout.print "This job is running on host #{hostname}\n"
354     fpout.print "under operating system #{uname} at #{Time.now.to_s}\n"
355     fpout.print "Available scratch disk space (Kbyte units) at beginning of the job is\n"
356     fpout.print freebytes + "\n"
357
358     #  Copy the input file
359         scrprefix = scrdir + sep + inpbody
360         if $platform == "win"
361           scrbody = inpbody
362         else
363       scrbody = scrprefix
364         end
365     filecopy(inpname, scrprefix + ".F05")
366     File.open("#{scrdir}/.in_use", "w") { |fp| }
367
368     #  Prepare environmental variables
369     auxdir = "#{gmsdir}#{sep}auxdata"
370     ENV["ERICFMT"] = "#{auxdir}#{sep}ericfmt.dat"
371     ENV["MCPPATH"] = "#{auxdir}#{sep}MCP"
372     ENV["BASPATH"] = "#{auxdir}#{sep}BASES"
373     ENV["QUANPOL"] = "#{auxdir}#{sep}QUANPOL"
374     ENV["EXTBAS"] = "/dev/null"
375     ENV["IRCDATA"] = "#{scrbody}.irc"
376         ENV["RESTART"] = "#{scrbody}.rst"
377         ENV["TRAJECT"] = "#{scrbody}.trj"
378     ENV["PUNCH"] = "#{scrbody}.dat"
379     ENV["INPUT"] = "#{scrbody}.F05"
380     ENV["AOINTS"] = "#{scrbody}.F08"
381     ENV["MOINTS"] = "#{scrbody}.F09"
382     ENV["DICTNRY"] = "#{scrbody}.F10"
383     ENV["DRTFILE"] = "#{scrbody}.F11"
384     ENV["CIVECTR"] = "#{scrbody}.F12"
385     ENV["CASINTS"] = "#{scrbody}.F13"
386     ENV["CIINTS"] = "#{scrbody}.F14"
387     ENV["WORK15"] = "#{scrbody}.F15"
388     ENV["WORK16"] = "#{scrbody}.F16"
389     ENV["CSFSAVE"] = "#{scrbody}.F17"
390     ENV["FOCKDER"] = "#{scrbody}.F18"
391     ENV["WORK19"] = "#{scrbody}.F19"
392     ENV["DASORT"] = "#{scrbody}.F20"
393     ENV["DFTINTS"] = "#{scrbody}.F21"
394     ENV["DFTGRID"] = "#{scrbody}.F22"
395     ENV["JKFILE"] = "#{scrbody}.F23"
396     ENV["ORDINT"] = "#{scrbody}.F24"
397     ENV["EFPIND"] = "#{scrbody}.F25"
398     ENV["PCMDATA"] = "#{scrbody}.F26"
399     ENV["PCMINTS"] = "#{scrbody}.F27"
400     ENV["MLTPL"] = "#{scrbody}.F28"
401     ENV["MLTPLT"] = "#{scrbody}.F29"
402     ENV["DAFL30"] = "#{scrbody}.F30"
403     ENV["SOINTX"] = "#{scrbody}.F31"
404     ENV["SOINTY"] = "#{scrbody}.F32"
405     ENV["SOINTZ"] = "#{scrbody}.F33"
406     ENV["SORESC"] = "#{scrbody}.F34"
407     ENV["SIMEN"] = "#{scrbody}.simen"
408     ENV["SIMCOR"] = "#{scrbody}.simcor"
409     ENV["GCILIST"] = "#{scrbody}.F37"
410     ENV["HESSIAN"] = "#{scrbody}.F38"
411     ENV["SOCCDAT"] = "#{scrbody}.F40"
412     ENV["AABB41"] = "#{scrbody}.F41"
413     ENV["BBAA42"] = "#{scrbody}.F42"
414     ENV["BBBB43"] = "#{scrbody}.F43"
415     ENV["MCQD50"] = "#{scrbody}.F50"
416     ENV["MCQD51"] = "#{scrbody}.F51"
417     ENV["MCQD52"] = "#{scrbody}.F52"
418     ENV["MCQD53"] = "#{scrbody}.F53"
419     ENV["MCQD54"] = "#{scrbody}.F54"
420     ENV["MCQD55"] = "#{scrbody}.F55"
421     ENV["MCQD56"] = "#{scrbody}.F56"
422     ENV["MCQD57"] = "#{scrbody}.F57"
423     ENV["MCQD58"] = "#{scrbody}.F58"
424     ENV["MCQD59"] = "#{scrbody}.F59"
425     ENV["MCQD60"] = "#{scrbody}.F60"
426     ENV["MCQD61"] = "#{scrbody}.F61"
427     ENV["MCQD62"] = "#{scrbody}.F62"
428     ENV["MCQD63"] = "#{scrbody}.F63"
429     ENV["MCQD64"] = "#{scrbody}.F64"
430     ENV["NMRINT1"] = "#{scrbody}.F61"
431     ENV["NMRINT2"] = "#{scrbody}.F62"
432     ENV["NMRINT3"] = "#{scrbody}.F63"
433     ENV["NMRINT4"] = "#{scrbody}.F64"
434     ENV["NMRINT5"] = "#{scrbody}.F65"
435     ENV["NMRINT6"] = "#{scrbody}.F66"
436     ENV["DCPHFH2"] = "#{scrbody}.F67"
437     ENV["DCPHF21"] = "#{scrbody}.F68"
438     ENV["GVVPT"] = "#{scrbody}.F69"
439
440     #    next files are used only during coupled cluster runs, so let's
441     #    display the numerous definitions only if they are to be used.
442         #    Don't set these variables on windows, where the memory for environmental variables
443         #    is limited.
444         if $platform != "win"
445     ENV["CCREST"] = "#{scrbody}.F70"
446     ENV["CCDIIS"] = "#{scrbody}.F71"
447     ENV["CCINTS"] = "#{scrbody}.F72"
448     ENV["CCT1AMP"] = "#{scrbody}.F73"
449     ENV["CCT2AMP"] = "#{scrbody}.F74"
450     ENV["CCT3AMP"] = "#{scrbody}.F75"
451     ENV["CCVM"] = "#{scrbody}.F76"
452     ENV["CCVE"] = "#{scrbody}.F77"
453     ENV["EOMSTAR"] = "#{scrbody}.F80"
454     ENV["EOMVEC1"] = "#{scrbody}.F81"
455     ENV["EOMVEC2"] = "#{scrbody}.F82"
456     ENV["EOMHC1"] = "#{scrbody}.F83"
457     ENV["EOMHC2"] = "#{scrbody}.F84"
458     ENV["EOMHHHH"] = "#{scrbody}.F85"
459     ENV["EOMPPPP"] = "#{scrbody}.F86"
460     ENV["EOMRAMP"] = "#{scrbody}.F87"
461     ENV["EOMRTMP"] = "#{scrbody}.F88"
462     ENV["EOMDG12"] = "#{scrbody}.F89"
463     ENV["MMPP"] = "#{scrbody}.F90"
464     ENV["MMHPP"] = "#{scrbody}.F91"
465     ENV["MMCIVEC"] = "#{scrbody}.F92"
466     ENV["MMCIVC1"] = "#{scrbody}.F93"
467     ENV["MMCIITR"] = "#{scrbody}.F94"
468     ENV["MMNEXM"] = "#{scrbody}.F95"
469     ENV["MMNEXE"] = "#{scrbody}.F96"
470     ENV["MMNREXM"] = "#{scrbody}.F97"
471     ENV["MMNREXE"] = "#{scrbody}.F98"
472         end
473     #
474     #     next are for TDHFX code, not used by current GAMESS
475     #
476         if false
477     ENV["OLI201"] = "#{scrbody}.F201"
478     ENV["OLI202"] = "#{scrbody}.F202"
479     ENV["OLI203"] = "#{scrbody}.F203"
480     ENV["OLI204"] = "#{scrbody}.F204"
481     ENV["OLI205"] = "#{scrbody}.F205"
482     ENV["OLI206"] = "#{scrbody}.F206"
483     ENV["OLI207"] = "#{scrbody}.F207"
484     ENV["OLI208"] = "#{scrbody}.F208"
485     ENV["OLI209"] = "#{scrbody}.F209"
486     ENV["OLI210"] = "#{scrbody}.F210"
487     ENV["OLI211"] = "#{scrbody}.F211"
488     ENV["OLI212"] = "#{scrbody}.F212"
489     ENV["OLI213"] = "#{scrbody}.F213"
490     ENV["OLI214"] = "#{scrbody}.F214"
491     ENV["OLI215"] = "#{scrbody}.F215"
492     ENV["OLI216"] = "#{scrbody}.F216"
493     ENV["OLI217"] = "#{scrbody}.F217"
494     ENV["OLI218"] = "#{scrbody}.F218"
495     ENV["OLI219"] = "#{scrbody}.F219"
496     ENV["OLI220"] = "#{scrbody}.F220"
497     ENV["OLI221"] = "#{scrbody}.F221"
498     ENV["OLI222"] = "#{scrbody}.F222"
499     ENV["OLI223"] = "#{scrbody}.F223"
500     ENV["OLI224"] = "#{scrbody}.F224"
501     ENV["OLI225"] = "#{scrbody}.F225"
502     ENV["OLI226"] = "#{scrbody}.F226"
503     ENV["OLI227"] = "#{scrbody}.F227"
504     ENV["OLI228"] = "#{scrbody}.F228"
505     ENV["OLI229"] = "#{scrbody}.F229"
506     ENV["OLI230"] = "#{scrbody}.F230"
507     ENV["OLI231"] = "#{scrbody}.F231"
508     ENV["OLI232"] = "#{scrbody}.F232"
509     ENV["OLI233"] = "#{scrbody}.F233"
510     ENV["OLI234"] = "#{scrbody}.F234"
511     ENV["OLI235"] = "#{scrbody}.F235"
512     ENV["OLI236"] = "#{scrbody}.F236"
513     ENV["OLI237"] = "#{scrbody}.F237"
514     ENV["OLI238"] = "#{scrbody}.F238"
515     ENV["OLI239"] = "#{scrbody}.F239"
516     end
517         
518     if $platform == "win"
519         #  if ncpus < 2
520         #    ncpus = 2
521         #  end
522           if cygwin_version
523             #  Old (obsolete) cygwin version, using ddikick
524         fpout.print "ddikick will run #{ncpus} compute process\n"
525                 ENV["CYGWIN"] = "nodosfilewarning"
526           else
527         fpout.print "Microsoft MPI will be running GAMESS on 1 node.\n"
528         fpout.print "The binary kicked off by 'mpiexec' is gamess.#{gmsvers}.exe\n"
529         fpout.print "MS-MPI will run #{ncpus*2} compute process\n"
530         #  File containing environmental variables
531         envfil = "#{scrprefix}.GMS.ENV"
532         fp = File.open(envfil, "w")
533         ENV.each { |k, v| fp.print "#{k}=#{v}\n" }
534         fp.close
535         #  File containing arguments to mpiexec
536         procfil = "#{scrprefix}.processes.mpd"
537         fp = File.open(procfil, "w")
538         fp.print "-env ENVFIL \"#{envfil}\" -wdir \"#{scrdir}\" -n #{ncpus*2} \"#{gmsdir}#{sep}gamess.#{gmsvers}.exe\"\n"
539         fp.close
540           end
541     end
542     
543     fpout.close
544     fplog = File.open(logname, "r")
545     size = 0
546     lines = []
547         lineno = 0
548     last_line = ""
549     search_mode = 0
550         nserch = -1
551         rflag = 0
552         ne_alpha = ne_beta = 0
553         mo_count = 0
554         ncomps = 0
555         
556     #  Callback procs
557         term_callback = lambda { |m, n|
558           msg = "GAMESS execution on #{inpbase} "
559           hmsg = "GAMESS "
560           if n == 0
561             msg += "succeeded."
562                 hmsg += "Completed"
563                 icon = :info
564           else
565             msg += "failed with status #{n}."
566                 hmsg += "Failed"
567                 icon = :error
568           end
569           msg += "\n(In directory #{inpdir})"
570
571           File.delete("#{scrdir}/.in_use")
572
573           ext_to_keep = [".dat", ".rst", ".trj", ".efp", ".gamma", ".log"]
574           ext_to_keep.each { |ex|
575             if File.exists?("#{scrprefix}#{ex}")
576                   filecopy("#{scrprefix}#{ex}", "#{inpdir}#{sep}#{inpbody}#{ex}")
577             end
578           }
579           Dir.foreach(scrdir) { |file|
580             if file != "." && file != ".." && !ext_to_keep.include?(File.extname(file))
581                   File.delete("#{scrdir}#{sep}#{file}")
582             end
583           }
584
585           begin
586             erase_old_logs(scrdir, "latest", 5)
587           rescue
588             error_message_box($!.to_s)
589                 return
590           end
591           
592           if (script = get_global_settings("gamess.postfix_script")) != nil && script != ""
593             eval(script)
594           end
595
596           if mol != nil
597             message_box(msg, hmsg, :ok, icon)
598       end
599     }
600         
601     timer_callback = lambda { |m, n|
602       fplog.seek(0, IO::SEEK_END)
603       sizec = fplog.tell
604       if sizec > size
605         #  Read new lines
606         fplog.seek(size, IO::SEEK_SET)
607         fplog.each_line { |line|
608           if line[-1, 1] == "\n"
609             lines.push(last_line + line)
610             last_line = ""
611           else
612             last_line += line
613             break
614           end
615         }
616         size = fplog.tell
617         last_i = nil
618         i = 0
619         while i < lines.count
620           line = lines[i]
621           if line =~ /GEOMETRY SEARCH POINT NSERCH= *(\d+)/
622             nserch = $1.to_i
623             last_i = i
624                         search_mode = 1
625                         energy = nil
626                   elsif line =~ /START OF DRC CALCULATION/
627                     search_mode = 3
628                         nserch = 1
629                         energy = nil
630                   elsif line =~ /CONSTRAINED OPTIMIZATION POINT/
631                     search_mode = 2
632                         energy = nil
633                   elsif line =~ /POINT *([0-9]+) *ON THE REACTION PATH/
634                     nserch = $1.to_i
635                         last_i = i
636                   elsif line =~ /ATOMIC BASIS SET/
637                         j = i
638                         while j < lines.count
639                           line = lines[j]
640                           break if line =~ /TOTAL NUMBER OF BASIS SET/
641                           j += 1
642                         end
643                         if j < lines.count
644                           #  Found
645                           bs_lines = []
646                           ii = i
647                           while ii <= j
648                             bs_lines.push(lines[ii].chomp)
649                                 ii += 1
650                           end
651                           begin
652                             if mol
653                                   ncomps = mol.sub_load_gamess_log_basis_set(bs_lines, lineno + i)
654                                 end
655                           rescue
656           $stderr.write($!.to_s + "\n")
657           $stderr.write($!.backtrace.inspect + "\n")
658                           end
659                           last_i = j
660                         else
661                           break  #  Wait until all basis set lines are read
662                         end
663                   elsif line =~ /NUMBER OF OCCUPIED ORBITALS/
664                         line =~ /=\s*(\d+)/
665                         n = Integer($1)
666                         if line =~ /ALPHA/
667                           ne_alpha = n
668                         else
669                           ne_beta = n
670                         end
671                   elsif line =~ /SCFTYP=(\w+)/
672                         scftyp = $1
673                         if ne_alpha > 0 || ne_beta > 0
674                           rflag = 0
675                           case scftyp
676                           when "RHF"
677                                 rflag = 1
678                           when "ROHF"
679                                 rflag = 2
680                           end
681                         end
682                   elsif line =~ /^\s*(EIGENVECTORS|MOLECULAR ORBITALS)\s*$/
683                         if mo_count == 0 && mol
684                           mol.clear_mo_coefficients
685                           mol.set_mo_info(:type=>["UHF", "RHF", "ROHF"][rflag], :alpha=>ne_alpha, :beta=>ne_beta)
686                         end
687                         i += 2
688                         j = i
689                         mo_count += 1
690                         while j < lines.count
691                           line = lines[j]
692                           break if line =~ /\.\.\.\.\.\./ || line =~ /----------------/
693                           j += 1
694                         end
695                         if j == lines.count
696                           break  #  Wait until complete MO info are read
697                         end
698                         ii = i
699                         mo_lines = []
700                         while ii < j
701                           mo_lines.push(lines[ii].chomp)
702                           ii += 1
703                         end
704                         begin
705                           if mol
706                             mol.sub_load_gamess_log_mo_coefficients(mo_lines, lineno + i, ncomps)
707                           end
708                         rescue
709                           $stderr.write($!.to_s + "\n")
710                           $stderr.write($!.backtrace.inspect + "\n")
711                         end
712                         last_i = j
713           elsif line =~ /NSERCH: *([0-9]+)/
714           #  print line
715                         if mol
716                           n = $1
717                           line =~ /E= +([-.0-9]+)/
718                           energy = $1.to_f
719                           line =~ /GRAD\. MAX= +([-.0-9]+)/
720                           grad = $1
721                           mol.show_text("Search: #{n}\nGradient: #{grad}")
722                           # mol.set_property("energy", energy)
723                         end
724                         last_i = i
725                   elsif line =~ /TOTAL ENERGY += *([-.0-9]+)/
726                     energy = $1
727                         if mol && search_mode == 2
728                           mol.show_text("Point: #{nserch}\nEnergy: #{energy}")
729                         end
730                         energy = energy.to_f
731                   elsif line =~ /FINAL .* ENERGY IS +([-.0-9]+) *AFTER/
732                     energy = $1.to_f
733                         if mol && search_mode == 0
734                           mol.set_property("energy", energy)
735                         end
736           elsif nserch > 0 && line =~ /ddikick\.x/
737             last_i = -1
738             break
739           elsif mol && nserch > 0 && (line =~ /COORDINATES OF ALL ATOMS/ || line =~ /COORDINATES \(IN ANGSTROM\) FOR \$DATA GROUP ARE/)
740             #  There should be (natoms) lines
741                         if line =~ /COORDINATES OF ALL ATOMS/
742                           #  Skip header lines
743                           i += 2
744                     end
745             if i + mol.natoms + 1 <= lines.count
746               coords = []
747               (i + 1...i + 1 + mol.natoms).each { |j|
748                 name, charge, x, y, z = lines[j].split
749                 coords.push(Vector3D[x.to_f, y.to_f, z.to_f])
750               }
751               mol.create_frame([coords])
752                           if search_mode == 1 && energy
753                             mol.set_property("energy", energy)
754                           end
755               mol.display
756               last_i = i + mol.natoms
757               i = last_i   #  Skip the processed lines
758             end
759                   elsif line =~ /N A T U R A L   B O N D   O R B I T A L   A N A L Y S I S/
760                     #  NBO output
761                     j = i + 1
762                         while j < lines.count
763                           break if lines[j] =~ /done with NBO analysis/
764                           j += 1
765                         end
766                         if j < lines.count
767                           nbo_lines = lines[i..j]
768                           mol.import_nbo_log(nbo_lines) rescue puts "Error: #{$!}, #{$!.backtrace.inspect}"
769                           last_i = j
770                           i = last_i  #  Skip the processed lines
771                         else
772                           break  #  Wait until NBO is done
773                         end
774           end
775           i += 1
776         end
777         if last_i == -1
778           lines.clear
779           break
780         elsif last_i
781           lines[0..last_i] = nil
782                   lineno += last_i + 1
783         end
784       end
785       print("% ")
786       true
787     }
788
789     if (script = get_global_settings("gamess.prefix_script")) != nil && script != ""
790           eval(script)
791         end
792
793     if $platform == "win"
794           if gmsvers == "11"
795             hosts = "localhost " * ncpus
796             cmdline = "cmd.exe /c \"#{gmsdir}/ddikick.exe #{gmsdir}/gamess.#{gmsvers}.exe #{inpbody} -ddi #{ncpus} #{ncpus} #{hosts} -scr #{scrdir} < NUL >>#{logname}"
797           else
798             cmdline = "cmd.exe /c \"mpiexec -configfile #{procfil} >>#{logname}"
799           end
800         else
801           hosts = "localhost " * ncpus
802           cmdline = "/bin/sh -c '#{gmsdir}/ddikick.x #{gmsdir}/gamess.#{gmsvers}.x #{inpbody} -ddi #{ncpus} #{ncpus} #{hosts} -scr #{scrdir} < /dev/null >>#{logname}'"
803         end
804         
805         if mol
806           pid = mol.call_subprocess_async(cmdline, term_callback, timer_callback)
807           if pid < 0
808             error_message_box("GAMESS failed to start. Please examine GAMESS installation.")
809             return
810           end
811         else
812           pid = call_subprocess(cmdline, "Running GAMESS")
813           term_callback.call(nil, pid)
814           return pid
815         end
816
817   end
818
819   def export_gamess(fname, hash)
820
821     def reorder_array(ary, ordered_sub_ary)
822           return (ordered_sub_ary & ary) + (ary - ordered_sub_ary)
823         end
824         
825         now = Time.now.to_s
826         if fname
827           basename = File.basename(fname, ".*")
828         else
829           basename = File.basename(self.name, ".*")
830         end
831         
832         #  Various settings
833         icharg = hash["charge"]
834         mult = hash["mult"]
835         runtyp = ["ENERGY", "PROP", "OPTIMIZE", "SADPOINT", "IRC"][hash["runtype"].to_i]
836         scftyp = ["RHF", "ROHF", "UHF"][hash["scftype"].to_i]
837         bssname = hash["basis"]
838         bssname2 = hash["secondary_basis"]
839         if hash["use_secondary_basis"].to_i != 0 && bssname2 != bssname
840           use_2nd = true
841           element2 = hash["secondary_elements"].split(/[\s,]+/).map { |name| name.capitalize }
842         else
843           use_2nd = false
844           bssname2 = bssname
845         end
846         basis = $gamess_basis[bssname]
847         basis2 = $gamess_basis[bssname2]
848
849         #  Use effective core potentials?
850         ecp = $gamess_ecp[bssname]
851         ecp2 = $gamess_ecp[bssname2]
852         ecp_read = (ecp || ecp2 ? "ECP=READ " : "")
853
854         #  Use only one built-in basis set?
855         gbasis = nil
856         if !use_2nd
857           case bssname
858           when "PM3"
859                 gbasis = "PM3 NGAUSS=3"
860           when "STO3G"
861                 gbasis = "STO NGAUSS=3"
862           when "321G"
863                 gbasis = "N21 NGAUSS=3"
864           when "631G"
865                 gbasis = "N31 NGAUSS=6"
866           when "631Gd"
867                 gbasis = "N31 NGAUSS=6 NDFUNC=1"
868           when "631Gdp"
869                 gbasis = "N31 NGAUSS=6 NDFUNC=1 NPFUNC=1"
870           when "6311G"
871                 gbasis = "N311 NGAUSS=6"
872           when "6311Gdp"
873                 gbasis = "N311 NGAUSS=6 NDFUNC=1 NPFUNC=1"
874           end
875         end
876     
877         #  Count non-dummy atoms
878         natoms = 0
879         each_atom { |ap|
880           natoms += 1 if ap.atomic_number != 0
881         }
882         
883         #  Fill hash with default values
884         h = (hash["CONTRL"] ||= Hash.new)
885         h["COORD"] ||= "UNIQUE"
886         h["EXETYP"] ||= "RUN"
887         h["ICHARG"] ||= (icharg || 0).to_s
888         h["ICUT"] ||= "20"
889         h["INTTYP"] ||= "HONDO"
890         h["ITOL"] ||= "30"
891         h["MAXIT"] ||= "200"
892         h["MOLPLT"] ||= ".T."
893         h["MPLEVL"] ||= "0"
894         h["MULT"] ||= mult.to_s
895         h["QMTTOL"] ||= "1e-08"
896         h["RUNTYP"] ||= runtyp
897         if (hash["dft"] || 0) != 0 && hash["dfttype"]
898           h["DFTTYP"] ||= hash["dfttype"]
899         end
900         if (hash["use_internal"] || 0) != 0 && (hash["runtype"] >= 2 || h["RUNTYP"] == "OPTIMIZE" || h["RUNTYP"] == "SADPOINT" || h["RUNTYP"] == "IRC")
901           nzvar = natoms * 3 - 6  #  TODO: 3N-5 for linear molecules
902           h["NZVAR"] ||= nzvar.to_s
903         else
904           nzvar = 0
905         end
906         h["SCFTYP"] ||= scftyp
907         if ecp_read != ""
908           h["ECP"] ||= "READ"
909         end
910         h["UNITS"] ||= "ANGS"
911         
912         h = (hash["SCF"] ||= Hash.new)
913         h["CONV"] ||= "1.0E-06"
914         h["DIRSCF"] ||= ".T."
915         h["FDIFF"] ||= ".T."
916         h["DAMP"] ||= ".T."
917         
918         h = (hash["STATPT"] ||= Hash.new)
919         h["NSTEP"] ||= "400"
920     if runtyp == "SADPOINT"
921         h["OPTTOL"] ||= "1.0E-05"
922         h["HESS"] ||= "CALC"
923         h["HSSEND"] ||= ".T."
924     elsif runtyp == "IRC"
925         h["OPTTOL"] ||= "1.0E-05"
926         h["HESS"] ||= "READ"
927         h["HSSEND"] ||= ".T."
928     else
929         h["OPTTOL"] ||= "1.0E-06"
930     end
931     if hash["eliminate_freedom"] == 0
932       h["PROJCT"] ||= ".F."
933     end
934
935         h = (hash["SYSTEM"] ||= Hash.new)
936         h["MEMDDI"] ||= "0"
937     if runtyp == "SADPOINT" || runtyp == "IRC"
938         h["MWORDS"] ||= "32"
939     else
940         h["MWORDS"] ||= "16"
941     end
942         h["TIMLIM"] ||= "50000"
943         
944         h = (hash["GUESS"] ||= Hash.new)
945         h["GUESS"] ||= "HUCKEL"
946         
947         if gbasis
948           h = (hash["BASIS"] ||= Hash.new)
949           h["GBASIS"] ||= gbasis
950         end
951         
952         if nzvar > 0
953           h = (hash["ZMAT"] ||= Hash.new)
954           h["DLC"] ||= ".T."
955           h["AUTO"] ||= ".T."
956         end
957         
958     if runtyp == "IRC"
959         h = (hash["IRC"] ||= Hash.new)
960         h["SADDLE"] = ".T."
961         h["TSENGY"] = ".T."
962         h["FORWRD"] = ".T."
963         h["NPOINT"] = "100"
964         h["STRIDE"] = "0.2"
965         h["OPTTOL"] = "1.0E-05"
966     end
967     
968         if (hash["esp"] || 0) != 0
969           h = (hash["ELPOT"] ||= Hash.new)
970           h["IEPOT"] ||= "1"
971           h["OUTPUT"] ||= "PUNCH"
972           h["WHERE"] ||= "PDC"
973           h = (hash["PDC"] ||= Hash.new)
974           h["CONSTR"] ||= "NONE"
975           h["PTSEL"] ||= "CONNOLLY"
976         end
977         
978     if (hash["include_nbo"] || 0) != 0
979       h = (hash["NBO"] ||= Hash.new)
980       s = ""
981       ["nao", "nbo", "nho", "nlmo", "pnao", "pnbo", "pnho", "pnlmo"].each { |nao|
982         if (hash[nao] || 0) != 0
983           s += " f" + nao + " ao" + nao
984         end
985       }
986       h[s] = ""
987     end
988
989         if fname
990           fp = File.open(fname, "wb")
991         else
992           fp = MemoryIO.new
993         end
994     if fp
995           fp.print "!  GAMESS input\n"
996           fp.print "!  Generated by Molby at #{now}\n"
997           fp.print "!  Basis set: " + ($gamess_basis_desc[bssname] || "(not specified)") + "\n"
998           if use_2nd
999             fp.print "!  [" + element2.join(", ") + "]: " + ($gamess_basis_desc[bssname2] || "(not specified)") + "\n"
1000           end
1001           ordered = ["CONTRL", "SCF", "STATPT", "SYSTEM", "GUESS", "BASIS", "ZMAT", "ELPOT", "PDC"]
1002           controls = reorder_array(hash.keys.select { |k| k != "key_order" && hash[k].is_a?(Hash) },
1003                 hash["key_order"] || ordered)
1004           controls.each { |k|
1005             h = hash[k]
1006                 next if h == nil || h.size == 0 || (h["key_order"] && h.size == 1)
1007                 if k == "CONTRL"
1008                   ordered = ["COORD", "EXETYP", "ICHARG", "ICUT", "INTTYP", "ITOL", "MAXIT", "MOLPLT", "MPLEVL",
1009                              "MULT", "QMTTOL", "RUNTYP", "SCFTYP", "ECP", "UNITS", "DFTTYP", "NZVAR"]
1010                 elsif k == "SCF"
1011                   ordered = ["CONV", "DIRSCF", "FDIFF", "DAMP"]
1012                 elsif k == "STATPT"
1013                   ordered = ["NSTEP", "OPTTOL", "HESS", "HSSEND"]
1014                 elsif k == "SYSTEM"
1015                   ordered = ["MEMDDI", "MWORDS", "TIMLIM"]
1016                 elsif k == "GUESS"
1017                   ordered = ["GUESS"]
1018                 elsif k == "BASIS"
1019                   ordered = ["GBASIS"]
1020                 elsif k == "ZMAT"
1021                   ordered = ["DLC", "AUTO"]
1022                 elsif k == "ELPOT"
1023                   ordered = ["IEPOT", "OUTPUT", "WHERE"]
1024                 elsif k == "PDC"
1025                   ordered = ["CONSTR", "PTSEL"]
1026         elsif k == "IRC"
1027           ordered = ["SADDLE", "TSENGY", "FORWRD", "NPOINT", "STRIDE", "OPTTOL"]
1028                 else
1029                   ordered = []
1030                 end
1031             keys = reorder_array(h.keys, h["key_order"] || ordered)
1032                 n = 0
1033                 keys.each_with_index { |kk, i|
1034                   v = h[kk]
1035                   if n == 0
1036                     fp.printf " $%-6s", k
1037                   elsif n % 3 == 0
1038                     fp.print "\n        "
1039                   end
1040                   if v == nil || v == ""
1041                     #  No value
1042                         fp.print " " + kk
1043                   else
1044                     fp.printf " %s=%s", kk, h[kk].to_s
1045                   end
1046                   n += 1
1047                 }
1048                 if n > 0
1049                   fp.print " $END\n"
1050                 end
1051           }
1052           fp.print " $DATA\n#{basename}\nC1 0\n"
1053           secondary = []
1054           each_atom { |ap|
1055             next if ap.atomic_number == 0
1056                 fp.printf "%-6s %4d %10.6f %10.6f %10.6f\n", ap.name, ap.atomic_number, ap.r.x, ap.r.y, ap.r.z
1057                 if use_2nd && element2.include?(ap.element)
1058                   secondary[ap.index] = true
1059                 end
1060             if !gbasis
1061                   #  Basis specification followed by a blank line
1062                   bas = (secondary[ap.index] ? basis2 : basis)
1063                   if bas.is_a?(Array)
1064                     bas = bas[ap.atomic_number]
1065                   end
1066                   if bas
1067                     fp.print bas
1068                     fp.print "\n"
1069                   else
1070                     puts "Warning: basis set is not defined for atom #{ap.index}, element #{ap.element}"
1071                   end
1072                 end
1073           }
1074           fp.print " $END\n"
1075           ecp_ary = []
1076           if ecp || ecp2
1077             fp.print " $ECP\n"
1078                 each_atom { |ap|
1079                   an = ap.atomic_number
1080                   next if an == 0
1081                   ecpp = (secondary[ap.index] ? ecp2 : ecp)
1082                   e = ecp_ary[an] || (ecpp && ecpp[an])
1083                   if e
1084                     #  Cache the PNAME of the $ECP entry and re-use it
1085                     ecp_ary[an] ||= (e.split)[0] + "\n"
1086                   else
1087                     e = ap.element.upcase + "-ECP NONE\n"
1088                   end
1089                   fp.print e
1090                 }
1091             fp.print " $END\n"
1092           end
1093         end
1094         if fname == nil
1095           s = fp.buffer
1096       fp.empty
1097           return s
1098         else
1099           fp.close
1100           return fname
1101         end
1102   end
1103   
1104   def copy_section_from_gamess_output
1105       if @gamess_output_fname
1106           dir = File.dirname(@gamess_output_fname)
1107       else
1108           dir = self.dir
1109       end
1110       fname = Dialog.open_panel("Select GAMESS Output:", dir, "*.log;*.dat")
1111       if fname
1112           fp = open(fname, "rb")
1113           if fp
1114               mtime = fp.mtime
1115               if @gamess_output_fname == fname && @gamess_output_mtime == mtime
1116                   #  Use the cached value
1117                   k = @gamess_output_sections
1118               else
1119                   pos = 0
1120                   k = Hash.new
1121                   fp.each_line { |ln|
1122                       if ln.start_with?(" $")
1123                           keyword = ln.strip
1124                           if keyword !~ /\$END/
1125                               k[keyword] = pos
1126                           end
1127                       end
1128                       pos += ln.length
1129                   }
1130                   #  Cache the information
1131                   @gamess_output_fname = fname
1132                   @gamess_output_mtime = mtime
1133                   @gamess_output_sections = k
1134               end
1135               keywords = k.keys.sort { |a, b| k[a] <=> k[b] }
1136               h = Dialog.run("Select GAMESS section to copy", "Copy", "Cancel") {
1137                   layout(1,
1138                          item(:popup, :subitems=>keywords, :tag=>"section"))
1139               }
1140               if h[:status] == 0
1141                   fp.seek(k[keywords[h["section"]]])
1142                   s = ""
1143                   fp.each_line { |ln|
1144                       s += ln
1145                       break if ln =~ /\$END/
1146                   }
1147                   export_to_clipboard(s)
1148               end
1149               fp.close
1150           end
1151       end
1152   end
1153   
1154   def cmd_edit_gamess_input(s)
1155       mol = self
1156       h = Dialog.run("Edit GAMESS Input", "OK", "Cancel", :resizable=>true) {
1157           layout(1,
1158                  item(:textview, :value=>s, :tag=>"edit", :width=>400, :height=>400, :flex=>[0,0,0,0,1,1]),
1159                  item(:button, :title=>"Copy Section from GAMESS Output...", :action=>lambda { |it| mol.copy_section_from_gamess_output } ),
1160                  :flex=>[0,0,0,0,1,1]
1161                  )
1162                  set_min_size(300, 300)
1163       }
1164       if h[:status] == 0
1165           return h["edit"]
1166       else
1167           return nil
1168       end
1169   end
1170
1171   def cmd_create_gamess_input
1172
1173     mol = self
1174         
1175     if natoms == 0
1176       raise MolbyError, "cannot create GAMESS input; the molecule is empty"
1177     end
1178
1179         #  Descriptive text and internal string for popup menus
1180 #    bset_desc = ["PM3", "STO-3G", "3-21G", "6-31G", "6-31G(d)", "6-31G(d,p)", "6-311G", "6-311G(d,p)", "LanL2DZ"]
1181 #       bset_internal = ["PM3", "STO3G", "321G", "631G", "631Gd", "631Gdp", "6311G", "6311Gdp", "LanL2DZ"]
1182     bset_desc = $gamess_basis_keys.map { |key| $gamess_basis_desc[key] }
1183         dft_desc = ["B3LYP"]
1184         dft_internal = ["B3LYP"]
1185
1186         defaults = {"scftype"=>0, "runtype"=>0, "use_internal"=>1, "eliminate_freedom"=>1, "charge"=>"0", "mult"=>"1",
1187           "basis"=>4, "use_secondary_basis"=>0, "secondary_elements"=>"",
1188           "secondary_basis"=>8, "esp"=>0, "ncpus"=>"1"}
1189
1190         gamess_input_direct = nil
1191         
1192     user_input = Hash.new
1193         ["CONTRL", "SCF", "STATPT", "SYSTEM", "GUESS", "BASIS"].each { |k|
1194           user_input[k] = Hash.new
1195         }
1196     if ".inp".casecmp(self.name[-4, 4]) == 0
1197           #  Read input and analyze commands
1198           fp = open(self.path, "r")
1199           key = hh = nil
1200           if fp
1201             while ln = fp.gets
1202                   ln.strip!
1203                   break if "$DATA".casecmp(ln[0, 5]) == 0
1204                   ln.split.each { |s|
1205                     if s[0] == "$"
1206                           #  New key
1207                           key = s[1..-1].upcase
1208                           hh = user_input[key] = Hash.new
1209                           (user_input["key_order"] ||= []).push(key)
1210                         else
1211                           k, v = s.split("=")
1212                           if key && hh
1213                             k.upcase!
1214                             hh[k] = v
1215                                 (hh["key_order"] ||= []).push(k)
1216                           end
1217                         end
1218                   }
1219                 end  #  end while
1220                 fp.close
1221                 user_input["charge"] = user_input["CONTRL"]["ICHARG"]
1222                 user_input["mult"] = user_input["CONTRL"]["MULT"]
1223                 user_input["runtype"] = ["ENERGY", "PROP", "OPTIMIZE"].find_index(user_input["CONTRL"]["RUNTYP"])
1224                 user_input["scftype"] = ["RHF", "ROHF", "UHF"].find_index(user_input["CONTRL"]["SCFTYP"])
1225                 dft_type = dft_internal.find_index(user_input["CONTRL"]["DFTTYP"])
1226                 if dft_type
1227                   user_input["dfttype"] = dft_type
1228                   user_input["dft"] = 1
1229                 end
1230                 bssname = nil
1231                 user_input["basis"] = "-1"
1232                 case user_input["BASIS"]["GBASIS"]
1233                 when "PM3"
1234                   bssname = "PM3"
1235                 when "STO"
1236                   bssname = "STO3G"
1237                 when "N21"
1238                   bssname = "321G"
1239                 when "N31"
1240                   if user_input["NDFUNC"] == "1"
1241                     if user_input["NPFUNC"] == "1"
1242                           bssname = "631Gdp"
1243                         else
1244                           bssname = "631Gd"
1245                         end
1246                   else
1247                     bssname = "631G"
1248                   end
1249                 when "N311"
1250                   if user_input["NDFUNC"] == "1" && user_input["NPFUNC"] == "1"
1251                     bssname = "6311Gdp"
1252                   else
1253                     bssname = "6311G"
1254                   end
1255                 end
1256                 if bssname
1257                   user_input["basis"] = $gamess_basis_keys.find_index(bssname).to_s
1258                 end
1259           #  puts user_input.inspect
1260           end
1261         end
1262         
1263     hash = Dialog.run("GAMESS Export") {
1264       def load_basis_set_sub(item)
1265             fname = Dialog.open_panel("Select a file containing GAMESS basis set:")
1266                 if fname
1267                   Molecule.read_gamess_basis_sets(fname)
1268                   bset_desc_new = $gamess_basis_keys.map { |key| $gamess_basis_desc[key] }
1269                   sel1 = attr("basis", :value)
1270                   sel2 = attr("secondary_basis", :value)
1271                   set_attr("basis", :subitems=>bset_desc_new)
1272                   set_attr("basis", :value=>sel1)
1273                   set_attr("secondary_basis", :subitems=>bset_desc_new)
1274                   set_attr("secondary_basis", :value=>sel2)
1275                 end
1276       end
1277           def select_gamess_path(item)
1278             while 1
1279               fname = Dialog.open_panel("Locate GAMESS executable:")
1280                   return if fname == nil
1281                   bname = File.basename(fname)
1282                   if bname =~ /gamess\.(.*)\.(exe|x)$/i
1283                     set_attr("executable_path", :value=>fname)
1284                     return
1285                   else
1286                     error_message_box("\"#{bname}\" does not look like a GAMESS executable!  Please try again.")
1287                   end
1288                 end
1289           end
1290           def set_optional_scripts(item)
1291             h = Dialog.run("GAMESS Optional Scripts") {
1292                   s_pre = get_global_settings("gamess.prefix_script")
1293                   s_post = get_global_settings("gamess.postfix_script")
1294                   layout(1,
1295                     item(:text, :title=>"Script to run before GAMESS execution:"),
1296                         item(:textview, :width=>400, :height=>200, :value=>s_pre, :tag=>"prefix"),
1297                     item(:text, :title=>"Script to run after GAMESS execution:"),
1298                         item(:textview, :width=>400, :height=>200, :value=>s_post, :tag=>"postfix"))
1299                 }
1300                 if h[:status] == 0
1301                   set_global_settings("gamess.prefix_script", h["prefix"])
1302                   set_global_settings("gamess.postfix_script", h["postfix"])
1303                 end
1304           end
1305       nbos = ["nao", "nbo", "nho", "nlmo", "pnao", "pnbo", "pnho", "pnlmo"]
1306           layout(4,
1307                 item(:text, :title=>"SCF type"),
1308                 item(:popup, :subitems=>["RHF", "ROHF", "UHF"], :tag=>"scftype"),
1309             item(:text, :title=>"Run type"),
1310                 item(:popup, :subitems=>["Energy", "Property", "Optimize", "Sadpoint", "IRC"], :tag=>"runtype",
1311                   :action=>lambda { |it| set_attr("use_internal", :enabled=>(it[:value] >= 2)) } ),
1312         item(:checkbox, :title=>"Use internal coordinates for structure optimization", :tag=>"use_internal"),
1313         -1, -1, -1,
1314                 item(:checkbox, :title=>"Eliminate translation and rotational degrees of freedom", :tag=>"eliminate_freedom"),
1315                 -1, -1, -1,
1316
1317                 item(:text, :title=>"Charge"),
1318                 item(:textfield, :width=>80, :tag=>"charge"),
1319                 item(:text, :title=>"Multiplicity"),
1320                 item(:textfield, :width=>80, :tag=>"mult"),
1321
1322                 item(:checkbox, :title=>"Use DFT", :tag=>"dft",
1323                   :action=>lambda { |it| set_attr("dfttype", :enabled=>(it[:value] != 0)) } ),
1324                 -1,
1325                 item(:text, :title=>"DFT type"),
1326                 item(:popup, :subitems=>dft_desc, :tag=>"dfttype"),
1327                 
1328                 item(:line),
1329                 -1, -1, -1,
1330
1331                 item(:text, :title=>"Basis set"),
1332                 item(:popup, :subitems=>bset_desc + ["(no select)"], :tag=>"basis"),
1333                 -1,
1334                 -1,
1335
1336                 item(:button, :title=>"Load Basis Set...", :action=>:load_basis_set_sub),
1337                 -1, -1, -1,
1338                 
1339                 item(:checkbox, :title=>"Use secondary basis set", :tag=>"use_secondary_basis",
1340                   :action=>lambda { |it|
1341                     flag = (it[:value] != 0)
1342                         set_attr("secondary_elements", :enabled=>flag)
1343                         set_attr("secondary_basis", :enabled=>flag)
1344                   }),
1345                 -1, -1, -1,
1346
1347                 item(:text, :title=>"   Elements"),
1348                 item(:textfield, :width=>80, :tag=>"secondary_elements"),
1349                 item(:text, :title=>"Basis set"),
1350                 item(:popup, :subitems=>bset_desc, :tag=>"secondary_basis"),
1351                 
1352                 item(:line),
1353                 -1, -1, -1,
1354                 
1355                 item(:checkbox, :title=>"Calculate electrostatic potential (ESP)", :tag=>"esp"),
1356                 -1, -1, -1,
1357         
1358         item(:line),
1359         -1, -1, -1,
1360
1361         item(:checkbox, :title=>"Include NBO instructions", :tag=>"include_nbo",
1362              :action=>lambda { |it|
1363                flag = (it[:value] != 0)
1364                nbos.each { |nbo| set_attr(nbo, :enabled=>flag) }
1365              }),
1366         -1, -1, -1,
1367         item(:checkbox, :title=>"NAO", :tag=>"nao"),
1368         item(:checkbox, :title=>"NBO", :tag=>"nbo"),
1369         item(:checkbox, :title=>"NHO", :tag=>"nho"),
1370         item(:checkbox, :title=>"NLMO", :tag=>"nlmo"),
1371         item(:checkbox, :title=>"PNAO", :tag=>"pnao"),
1372         item(:checkbox, :title=>"PNBO", :tag=>"pnbo"),
1373         item(:checkbox, :title=>"PNHO", :tag=>"pnho"),
1374         item(:checkbox, :title=>"PNLMO", :tag=>"pnlmo"),
1375              
1376         item(:line),
1377                 -1, -1, -1,
1378                 
1379                 item(:checkbox, :title=>"Execute GAMESS on this machine", :tag=>"execute_local",
1380                   :action=>lambda { |it|
1381                     flag = (it[:value] != 0)
1382                         set_attr("executable_path", :enabled=>flag)
1383                         set_attr("select_path", :enabled=>flag)
1384                         set_attr("ncpus", :enabled=>flag)
1385                   }),
1386                 -1, -1, -1,
1387
1388                 item(:text, :title=>"   Path"),
1389                 item(:textfield, :width=>300, :tag=>"executable_path"),
1390                 -1, -1,
1391                 
1392                 -1,
1393                 item(:button, :title=>"Select Path...", :tag=>"select_path", :action=>:select_gamess_path),
1394                 -1,
1395                 item(:button, :title=>"Optional Scripts...", :action=>:set_optional_scripts),
1396                 
1397                 item(:text, :title=>"   N of CPUs"),
1398                 item(:textfield, :width=>80, :tag=>"ncpus"),
1399                 -1, -1,
1400                 
1401                 item(:line),
1402                 -1, -1, -1,
1403
1404                 item(:button, :title=>"Edit GAMESS Input and Go", :action=>lambda { |it|
1405                   h = Hash.new
1406                   each_item { |it2|
1407                     if (tag = it2[:tag]) != nil
1408                           h[tag] = it2[:value]
1409                         end
1410                   }
1411                   h["basis"] = $gamess_basis_keys[h["basis"]]
1412                   h["secondary_basis"] = $gamess_basis_keys[h["secondary_basis"]]
1413                   h["dfttype"] = dft_internal[h["dfttype"]]
1414                   gamess_input_direct = mol.cmd_edit_gamess_input(mol.export_gamess(nil, h))
1415                   if gamess_input_direct
1416                         end_modal(0)
1417                   end
1418                 }),
1419                 -1, -1, -1
1420           )
1421           values = Hash.new
1422           each_item { |it|
1423             tag = it[:tag]
1424                 if tag
1425                   values[tag] = (user_input[tag] || get_global_settings("gamess.#{tag}") || defaults[tag])
1426                   if tag == "basis" && values[tag] == "-1"
1427                     values[tag] = (bset_desc.count).to_s
1428                   end
1429                   it[:value] = values[tag]
1430                 end
1431           }
1432           set_attr("secondary_elements", :enabled=>(values["use_secondary_basis"] == 1))
1433           set_attr("secondary_basis", :enabled=>(values["use_secondary_basis"] == 1))
1434           set_attr("dfttype", :enabled=>(values["dft"] == 1))
1435           set_attr("use_internal", :enabled=>(values["runtype"] >= 2))
1436           set_attr("executable_path", :enabled=>(values["execute_local"] == 1))
1437           set_attr("select_path", :enabled=>(values["execute_local"] == 1))
1438           set_attr("ncpus", :enabled=>(values["execute_local"] == 1))
1439       nbos.each { |nao|
1440         set_attr(nao, :enabled=>(values["include_nbo"] == 1))
1441       }
1442         }
1443         hash.each_pair { |key, value|
1444           next if key == :status
1445           set_global_settings("gamess.#{key}", value)
1446         }
1447         if hash[:status] == 0
1448           #  Specify basis by internal keys
1449           hash["basis"] = $gamess_basis_keys[hash["basis"]]
1450           hash["secondary_basis"] = $gamess_basis_keys[hash["secondary_basis"]]
1451           hash["dfttype"] = dft_internal[hash["dfttype"]]
1452           basename = (self.path ? File.basename(self.path, ".*") : self.name)
1453           fname = Dialog.save_panel("Export GAMESS input file:", self.dir, basename + ".inp", "GAMESS input file (*.inp)|*.inp|All files|*.*")
1454           return nil if !fname
1455           if gamess_input_direct
1456           #  puts "gamess_input_direct = \"#{gamess_input_direct}\""
1457             File.open(fname, "w") { |fp| fp.print(gamess_input_direct) }
1458           else
1459             export_gamess(fname, hash)
1460           end
1461           if hash["execute_local"] == 1
1462             Molecule.execute_gamess(fname, self)
1463           end
1464         else
1465           nil
1466         end
1467         
1468   end
1469   
1470   def cmd_create_cube
1471     grid = default_MO_grid
1472         if grid == nil
1473           Dialog.run {
1474             layout(1,
1475                   item(:text, :title=>"This molecule does not contain MO information."))
1476           }
1477           return
1478         end
1479
1480     mos = selected_MO
1481         if mos == nil || mos.length == 0
1482       Dialog.run {
1483             layout(1,
1484                   item(:text, :title=>"Please select MO(s) in the MO Info table."))
1485       }
1486           return
1487         end
1488         hash = Dialog.run {
1489           layout(1,
1490             item(:text, :title=>"Please specify cube dimensions (in angstrom units):"),
1491             layout(4,
1492                   item(:text, :title=>"Origin"),
1493                   item(:textfield, :width=>100, :height=>20, :tag=>"originx", :value=>sprintf("%.6f", grid[0].x)),
1494                   item(:textfield, :width=>100, :height=>20, :tag=>"originy", :value=>sprintf("%.6f", grid[0].y)),
1495                   item(:textfield, :width=>100, :height=>20, :tag=>"originz", :value=>sprintf("%.6f", grid[0].z)),
1496                   item(:text, :title=>"Delta"),
1497                   item(:textfield, :width=>100, :height=>20, :tag=>"deltax", :value=>sprintf("%.6f", grid[1])),
1498                   item(:textfield, :width=>100, :height=>20, :tag=>"deltay", :value=>sprintf("%.6f", grid[2])),
1499                   item(:textfield, :width=>100, :height=>20, :tag=>"deltaz", :value=>sprintf("%.6f", grid[3])),
1500                   item(:text, :title=>"Step"),
1501                   item(:textfield, :width=>100, :height=>20, :tag=>"stepx", :value=>grid[4].to_s),
1502                   item(:textfield, :width=>100, :height=>20, :tag=>"stepy", :value=>grid[5].to_s),
1503                   item(:textfield, :width=>100, :height=>20, :tag=>"stepz", :value=>grid[6].to_s)))
1504         }
1505         if hash[:status] == 0
1506           path = self.path || self.name
1507           dir = self.dir || Dir.pwd
1508           origin = Vector3D[hash["originx"], hash["originy"], hash["originz"]]
1509           dx = hash["deltax"]
1510           dy = hash["deltay"]
1511           dz = hash["deltaz"]
1512           nx = hash["stepx"]
1513           ny = hash["stepy"]
1514           nz = hash["stepz"]
1515           basename = File.basename(path, ".*")
1516           filenames = []
1517           mo_type = self.mo_type
1518           mos.each { |n|
1519             fname1 = fname2 = nil
1520             alpha = (mo_type != "UHF" ? "" : "alpha ")
1521                 a = (mo_type != "UHF" ? "" : "a")
1522             fname1 = Dialog.save_panel("Cube file name for #{alpha}MO #{n}", dir, basename + "_#{n}#{a}.cube", "Gaussian cube file (*.cube)|*.cube")
1523                 if (mo_type == "UHF")
1524                   fname2 = Dialog.save_panel("Cube file name for beta MO #{n}", dir, basename + "_#{n}b.cube", "Gaussian cube file (*.cube)|*.cube")
1525                 end
1526                 filenames.push([n, fname1, fname2])
1527           }
1528           filenames.each { |pair|
1529             n = pair[0]
1530                 alpha = (mo_type != "UHF" ? "" : "alpha ")
1531             show_progress_panel("Creating cube file for #{alpha}MO #{n}...")
1532                 if pair[1]
1533                   cubegen(pair[1], n, origin, dx, dy, dz, nx, ny, nz, true)
1534                 end
1535                 if pair[2] && mo_type == "UHF"
1536                   set_progress_message("Creating cube file for beta MO #{n}...")
1537                   cubegen(pair[2], n, origin, dx, dy, dz, nx, ny, nz, true, true)
1538                 end
1539             hide_progress_panel
1540           }
1541         end
1542   end
1543
1544   def export_psi4(fname, hash)
1545     now = Time.now.to_s
1546     if fname
1547       fp = File.open(fname, "wb")
1548     else
1549       fp = MemoryIO.new
1550     end
1551     if fp
1552       fp.print "#  Psi4 input\n"
1553       fp.print "#  Generated by Molby at #{now}\n"
1554       fp.print "import sys\n"
1555       fp.print "import re\n"
1556       fp.print "molecule mol {\n"
1557       charge = Integer(hash["charge"])
1558       mult = Integer(hash["mult"])
1559       if charge != 0 || mult != 1
1560         fp.printf " %d %d\n", charge, mult
1561       end
1562       atoms.each { |ap|
1563         fp.printf "%-8s %16.12f %16.12f %16.12f\n", ap.element, ap.x, ap.y, ap.z
1564       }
1565       use_symmetry = Integer(hash["use_symmetry"])
1566       move_to_com = Integer(hash["move_to_com"])
1567       do_reorient = Integer(hash["do_reorient"])
1568       if move_to_com == 0
1569         fp.print "nocom\n"
1570       end
1571       if do_reorient == 0
1572         fp.print "noreorient\n"
1573       end
1574       if use_symmetry == 0
1575         fp.print "symmetry c1\n"
1576       end
1577       fp.print "units angstrom\n"
1578       fp.print "}\n"
1579       fp.print "set basis #{hash['basis']}\n"
1580       fp.print "set reference #{hash['scftype']}\n"
1581       options = "return_wfn=True"
1582       if hash['dft'] != 0
1583         options += ", dft_functional='#{hash['dfttype']}'"
1584       end
1585       runtype = hash['runtype'].downcase
1586       fp.print "energy, wfn = #{runtype}('scf', #{options})\n"
1587       fp.print "wfn.write_molden(re.sub('\\.\\w*$', '.molden', sys.argv[1]))\n"
1588     end
1589     if fname == nil
1590       s = fp.buffer
1591       fp.empty
1592       return s
1593     else
1594       fp.close
1595       return fname
1596     end
1597   end
1598
1599   #  Execute Psi4
1600   #  inpname is the input file
1601   #  mol (optional) is the molecule from which the Psi4 input was built.
1602   def Molecule.execute_psi4(inpname, mol = nil)
1603
1604     inpbase = File.basename(inpname)
1605     inpbody = File.basename(inpname, ".*")
1606     inpdir = File.dirname(inpname)
1607
1608     #  Set PATH for psi4conda
1609     psi4folder = get_global_settings("psi4.psi4conda_folder")
1610     ncpus = get_global_settings("psi4.ncpus").to_i
1611     orgpath = ENV["PATH"]
1612     orgdir = Dir.pwd
1613     if $platform == "win"
1614       ENV["PATH"] = "#{psi4folder};#{psi4folder}\\Library\\mingw-w64\\bin;#{psi4folder}\\Library\\usr\\bin;#{psi4folder}\\Library\\bin;#{psi4folder}\\Scripts;#{psi4folder}\\bin;#{psi4folder}\\condabin;" + ENV["PATH"]
1615     else
1616       ENV["PATH"] = "#{psi4folder}/bin:#{psi4folder}/condabin:" + ENV["PATH"]
1617     end
1618     Dir.chdir(inpdir)
1619     cmdline = "psi4 #{inpbase}"
1620     if ncpus > 0
1621       cmdline += " -n #{ncpus}"
1622     end
1623     hf_type = nil
1624     nalpha = nil
1625     nbeta = nil
1626
1627     outfile = inpdir + "/" + inpbody + ".out"
1628     if File.exists?(outfile)
1629       n = 1
1630       while true
1631         outbackfile = inpdir + "/" + inpbody + "~" + (n == 1 ? "" : "{#n}") + ".out"
1632         break if !File.exists?(outbackfile)
1633         n += 1
1634       end
1635       File.rename(outfile, outbackfile)
1636     else
1637       outbackfile = nil
1638     end
1639
1640     #  Timer callback
1641     timer_count = 0
1642     fplog = nil
1643     last_size = 0
1644     lines = []
1645     last_line = ""
1646     next_index = 0
1647     timer_callback = lambda { |m, n|
1648       begin
1649         timer_count += 1
1650         if timer_count < 10   #  Only handle every 1 seconds
1651           return true
1652         end
1653         timer_count = 0
1654         if fplog == nil
1655           if File.exists?(outfile)
1656             fplog = Kernel.open(outfile, "rt")
1657             if fplog == nil
1658               return true
1659             end
1660             last_size = 0
1661           else
1662             return true  #  Skip until outfile is available
1663           end
1664         end
1665         fplog.seek(0, IO::SEEK_END)
1666         current_size = fplog.tell
1667         if current_size > last_size
1668           #  Read new lines
1669           fplog.seek(last_size, IO::SEEK_SET)
1670           fplog.each_line { |line|
1671             if line[-1, 1] == "\n"
1672               lines.push(last_line + line)
1673               last_line = ""
1674             else
1675               last_line += line
1676               break
1677             end
1678           }
1679           last_size = fplog.tell
1680         end
1681         li = next_index
1682         getline = lambda { if li < lines.length; li += 1; return lines[li - 1]; else return nil; end }
1683         vecs = []
1684         while (line = getline.call)
1685           if line =~ /==> Geometry <==/
1686             #  Skip until line containing "------"
1687             while line = getline.call
1688               break if line =~ /------/
1689             end
1690             vecs.clear
1691             index = 0
1692             #  Read atom positions
1693             while line = getline.call
1694               line.chomp!
1695               break if line =~ /^\s*$/
1696               tokens = line.split(' ')
1697               vecs.push(Vector3D[Float(tokens[1]), Float(tokens[2]), Float(tokens[3])])
1698               index += 1
1699             end
1700             if vecs.length < mol.natoms
1701               break  #  Log file is incomplete
1702             end
1703             #  Does this geometry differ from the last one?
1704             vecs.length.times { |i|
1705               if (mol.atoms[i].r - vecs[i]).length2 > 1.0e-20
1706                 #  Create a new frame and break
1707                 mol.create_frame
1708                 vecs.length.times { |j|
1709                   mol.atoms[j].r = vecs[j]
1710                 }
1711                 break
1712               end
1713             }
1714             next_index = li
1715             #  end geometry
1716           elsif line =~ /Final Energy: +([-.0-9]+)/
1717             #  Energy for this geometry
1718             energy = Float($1)
1719             mol.set_property("energy", energy)
1720             mol.show_text("Frame: #{mol.nframes - 1}\nEnergy: #{energy}")
1721             print("Frame: #{mol.nframes - 1} Energy: #{energy}\n")
1722             if line =~ /RHF/
1723               hf_type = "RHF"
1724             elsif line =~ /UHF/
1725               hf_type = "UHF"
1726             elsif line =~ /ROHF/
1727               hf_type = "ROHF"
1728             end
1729             next_index = li
1730           elsif line =~ /^ *Nalpha *= *(\d+)/
1731             nalpha = Integer($1)
1732           elsif line =~ /^ *Nbeta *= *(\d+)/
1733             nbeta = Integer($1)
1734           end
1735         end
1736         if next_index > 0
1737           lines.slice!(0, next_index)
1738           next_index = 0
1739         end
1740         return true
1741       rescue => e
1742         #  Some error occurred during callback. Show the message in the console and abort.
1743         $stderr.write("#{e.message}\n")
1744         $stderr.write("#{e.backtrace.inspect}\n")
1745         return false
1746       end
1747     }
1748
1749     #  Terminate callback
1750     term_callback = lambda { |m, n|
1751       begin
1752         msg = "Psi4 execution of #{inpbase} "
1753         hmsg = "Psi4 "
1754         if n == -1
1755           msg += "cannot be started. Please examine Psi4 installation."
1756           hmsg += "Error"
1757           icon = :error
1758         else
1759           if n == 0
1760             msg += "succeeded."
1761             hmsg += "Completed"
1762             icon = :info
1763           else
1764             msg += "failed with status #{n}."
1765             hmsg += "Failed"
1766             icon = :error
1767           end
1768           msg += "\n(In directory #{inpdir})"
1769         end
1770         ENV["PATH"] = orgpath
1771         Dir.chdir(orgdir)
1772         if mol != nil
1773           message_box(msg, hmsg, :ok, icon)
1774         end
1775         if n == 0
1776           #  Try to load final lines of the logfile
1777           timer_count = 100
1778           timer_callback.call(m, n)
1779           #  Try to load molden file if available
1780           mol.clear_basis_set
1781           mol.clear_mo_coefficients
1782           mol.set_mo_info(:type => hf_type, :alpha => nalpha, :beta => nbeta)
1783           molden = inpdir + "/" + inpbody +".molden"
1784           if File.exists?(molden)
1785             fp = Kernel.open(molden, "rt")
1786             mol.instance_eval { @lineno = 0 }
1787             begin
1788               #  mol.@hf_type should be set before calling sub_load_molden
1789               mol.sub_load_molden(fp)
1790               fp.close
1791             rescue => e
1792               $stderr.write("#{e.message}\n")
1793               $stderr.write("#{e.backtrace.inspect}\n")
1794             end
1795           end
1796         elsif n == -1
1797           #  The child process actually did not start
1798           #  Restore the old file if outbackfile is not nil
1799           if outbackfile && !File.exists?(outfile)
1800             File.rename(outbackfile, outfile)
1801           end
1802         end
1803         if outbackfile && File.exists?(outbackfile)
1804           File.delete(outbackfile)
1805         end
1806       rescue => e
1807         $stderr.write("#{e.message}\n")
1808         $stderr.write("#{e.backtrace.inspect}\n")
1809       end
1810       print("% ")
1811       true
1812     }
1813     
1814     if mol
1815       pid = mol.call_subprocess_async(cmdline, term_callback, timer_callback)
1816       if pid < 0
1817         #  This may not happen on OSX or Linux (don't know for MSW)
1818         error_message_box("Psi4 failed to start. Please examine Psi4 installation.")
1819         return -1
1820       end
1821     else
1822       status = call_subprocess(cmdline, "Running Psi4")
1823       term_callback.call(nil, status)
1824       return status
1825     end
1826   end
1827   
1828   def cmd_edit_psi4_input(s)
1829     mol = self
1830     h = Dialog.run("Edit Psi4 Input", "OK", "Cancel", :resizable=>true) {
1831       layout(1,
1832         item(:textview, :value=>s, :tag=>"edit", :width=>400, :height=>400, :flex=>[0,0,0,0,1,1]),
1833       )
1834       set_min_size(300, 300)
1835     }
1836     if h[:status] == 0
1837         return h["edit"]
1838     else
1839         return nil
1840     end
1841   end
1842
1843   def cmd_create_psi4_input
1844   
1845     mol = self
1846   
1847     if natoms == 0
1848       raise MolbyError, "cannot create Psi4 input; the molecule is empty"
1849     end
1850     
1851     #  Basis sets Cf. https://psicode.org/psi4manual/master/basissets_tables.html#apdx-basistables
1852     bset_desc = ["STO-3G", "3-21G", "6-31G", "6-31G(d)", "6-31G(d,p)", "6-311G", "6-311G(d)", "6-311G(d,p)", "LanL2DZ"]
1853     dfttype_desc = ["B3LYP"]
1854     runtype_desc = ["Energy", "Optimize"]
1855     scftype_desc = ["RHF", "ROHF", "UHF"]
1856 #   nbo_desc = ["nao", "nbo", "nho", "nlmo", "pnao", "pnbo", "pnho", "pnlmo"]
1857     user_input = Hash.new
1858     defaults = {"scftype"=>0, "runtype"=>0, "move_to_com"=>0, "do_reorient"=>0,
1859       "use_symmetry"=>0,  "charge"=>"0", "mult"=>"1",
1860       "basis"=>1, "use_secondary_basis"=>0, "secondary_elements"=>"",
1861       "secondary_basis"=>8, "esp"=>0, "ncpus"=>"1"}
1862     psi4_input_direct = nil
1863     
1864     hash = Dialog.run("Psi4 Export") {
1865       def load_basis_set_sub(item)
1866         fname = Dialog.open_panel("Select a file containing GAMESS basis set:")
1867         if fname
1868           Molecule.read_gamess_basis_sets(fname)
1869           bset_desc_new = $gamess_basis_keys.map { |key| $gamess_basis_desc[key] }
1870           sel1 = attr("basis", :value)
1871           sel2 = attr("secondary_basis", :value)
1872           set_attr("basis", :subitems=>bset_desc_new)
1873           set_attr("basis", :value=>sel1)
1874           set_attr("secondary_basis", :subitems=>bset_desc_new)
1875           set_attr("secondary_basis", :value=>sel2)
1876         end
1877       end
1878       def select_psi4_folder(item)
1879         #  By default, psi4conda is installed in the home directory
1880         while 1
1881           fname = Dialog.open_panel("Locate 'psi4conda' Folder:", ENV["HOME"] || ENV["USERPROFILE"], nil, true)
1882           return if fname == nil
1883           bname = File.basename(fname)
1884           if bname == "psi4conda"
1885             break
1886           else
1887             h = Dialog.run("", "OK", "Choose Again") {
1888               layout(1,
1889                 item(:text, :title=>"The folder does not have the name 'psi4conda'."),
1890                 item(:text, :title=>"Do you want to use it anyway?"))
1891             }
1892             if h[:status] == 0
1893               break
1894             end
1895           end
1896         end
1897         set_attr("psi4conda_folder", :value=>fname)
1898       end
1899       layout(4,
1900         #  ------
1901         item(:text, :title=>"SCF type"),
1902         item(:popup, :subitems=>scftype_desc, :tag=>"scftype"),
1903         item(:text, :title=>"Run type"),
1904         item(:popup, :subitems=>runtype_desc, :tag=>"runtype"),
1905         #  ------
1906         item(:checkbox, :title=>"Detect symmetry", :tag=>"use_symmetry"),
1907         -1, -1, -1,
1908         #  ------
1909         item(:checkbox, :title=>"Move the molecule to the center of mass", :tag=>"move_to_com"),
1910         -1, -1, -1,
1911         #  ------
1912         item(:checkbox, :title=>"Rotate the molecule to the symmetry principle axes", :tag=>"do_reorient"),
1913         -1, -1, -1,
1914         #  ------
1915         item(:text, :title=>"Charge"),
1916         item(:textfield, :width=>80, :tag=>"charge"),
1917         item(:text, :title=>"Multiplicity"),
1918         item(:textfield, :width=>80, :tag=>"mult"),
1919         #  ------
1920         item(:checkbox, :title=>"Use DFT", :tag=>"dft",
1921           :action=>lambda { |it| set_attr("dfttype", :enabled=>(it[:value] != 0)) } ),
1922         -1,
1923         item(:text, :title=>"DFT type"),
1924         item(:popup, :subitems=>dfttype_desc, :tag=>"dfttype"),
1925         #  ------
1926         item(:line),
1927         -1, -1, -1,
1928         #  ------
1929         item(:text, :title=>"Basis set"),
1930         item(:popup, :subitems=>bset_desc, :tag=>"basis"),
1931         -1,
1932         -1,
1933         #  ------
1934         #item(:button, :title=>"Load Basis Set...", :action=>:load_basis_set_sub),
1935         #-1, -1, -1,
1936         #  ------
1937         #item(:checkbox, :title=>"Use secondary basis set", :tag=>"use_secondary_basis",
1938         #  :action=>lambda { |it|
1939         #    flag = (it[:value] != 0)
1940         #    set_attr("secondary_elements", :enabled=>flag)
1941         #    set_attr("secondary_basis", :enabled=>flag)
1942         #  }),
1943         #-1, -1, -1,
1944         #  ------
1945         #item(:text, :title=>"   Elements"),
1946         #item(:textfield, :width=>80, :tag=>"secondary_elements"),
1947         #item(:text, :title=>"Basis set"),
1948         #item(:popup, :subitems=>bset_desc, :tag=>"secondary_basis"),
1949         #  ------
1950         item(:line),
1951         -1, -1, -1,
1952         #  ------
1953         #item(:checkbox, :title=>"Calculate electrostatic potential (ESP)", :tag=>"esp"),
1954         #-1, -1, -1,
1955         #  ------
1956         #item(:line),
1957         #-1, -1, -1,
1958         #  ------
1959         #item(:checkbox, :title=>"Include NBO instructions", :tag=>"include_nbo",
1960         #    :action=>lambda { |it|
1961         #      flag = (it[:value] != 0)
1962         #      nbos.each { |nbo| set_attr(nbo, :enabled=>flag) }
1963         #    }),
1964         #-1, -1, -1,
1965         #  ------
1966         #item(:checkbox, :title=>"NAO", :tag=>"nao"),
1967         #item(:checkbox, :title=>"NBO", :tag=>"nbo"),
1968         #item(:checkbox, :title=>"NHO", :tag=>"nho"),
1969         #item(:checkbox, :title=>"NLMO", :tag=>"nlmo"),
1970         #  ------
1971         #item(:checkbox, :title=>"PNAO", :tag=>"pnao"),
1972         #item(:checkbox, :title=>"PNBO", :tag=>"pnbo"),
1973         #item(:checkbox, :title=>"PNHO", :tag=>"pnho"),
1974         #item(:checkbox, :title=>"PNLMO", :tag=>"pnlmo"),
1975         #  ------
1976         item(:line),
1977         -1, -1, -1,
1978         #  ------
1979         item(:checkbox, :title=>"Execute Psi4 on this machine", :tag=>"execute_local",
1980           :action=>lambda { |it|
1981             flag = (it[:value] != 0)
1982             set_attr("psi4conda_folder", :enabled=>flag)
1983             set_attr("select_folder", :enabled=>flag)
1984             set_attr("ncpus", :enabled=>flag)
1985           }),
1986         -1, -1, -1,
1987         #  ------
1988         item(:text, :title=>" Psi4 Folder"),
1989         item(:textfield, :width=>300, :tag=>"psi4conda_folder"),
1990         -1, -1,
1991         #  ------
1992         -1,
1993         item(:button, :title=>"Select Folder...", :tag=>"select_folder", :action=>:select_psi4_folder),
1994         -1, -1,
1995         # item(:button, :title=>"Optional Scripts...", :action=>:set_optional_scripts),
1996         #  ------
1997         item(:text, :title=>"   N of CPUs"),
1998         item(:textfield, :width=>80, :tag=>"ncpus"),
1999         -1, -1,
2000         #  ------
2001         item(:line),
2002         -1, -1, -1,
2003         #  ------
2004         item(:button, :title=>"Edit Psi4 Input and Go", :action=>lambda { |it|
2005           h = Hash.new
2006           each_item { |it2|
2007             if (tag = it2[:tag]) != nil
2008               h[tag] = it2[:value]
2009             end
2010           }
2011           h["basis"] = bset_desc[h["basis"] || 0]
2012           h["secondary_basis"] = bset_desc[h["secondary_basis"] || 0]
2013           h["dfttype"] = dfttype_desc[h["dfttype"] || 0]
2014           h["runtype"] = runtype_desc[h["runtype"] || 0]
2015           h["scftype"] = scftype_desc[h["scftype"] || 0]
2016           psi4_input_direct = mol.cmd_edit_psi4_input(mol.export_psi4(nil, h))
2017           if psi4_input_direct
2018             end_modal(0)
2019           end
2020         }),
2021         -1, -1, -1
2022       )
2023       values = Hash.new
2024       each_item { |it|
2025         tag = it[:tag]
2026         if tag
2027           values[tag] = (user_input[tag] || get_global_settings("psi4.#{tag}") || defaults[tag])
2028           if values[tag]
2029             it[:value] = values[tag]
2030           end
2031         end
2032       }
2033       #set_attr("secondary_elements", :enabled=>(values["use_secondary_basis"] == 1))
2034       #set_attr("secondary_basis", :enabled=>(values["use_secondary_basis"] == 1))
2035       set_attr("dfttype", :enabled=>(values["dft"] == 1))
2036       set_attr("psi4conda_folder", :enabled=>(values["execute_local"] == 1))
2037       set_attr("select_folder", :enabled=>(values["execute_local"] == 1))
2038       set_attr("ncpus", :enabled=>(values["execute_local"] == 1))
2039       #nbos.each { |nao|
2040       #  set_attr(nao, :enabled=>(values["include_nbo"] == 1))
2041       #}
2042     }  #  end Dialog.run
2043
2044     hash.each_pair { |key, value|
2045       next if key == :status
2046       set_global_settings("psi4.#{key}", value)
2047     }
2048     if hash[:status] == 0
2049       #  Specify basis by internal keys
2050       hash["basis"] = bset_desc[hash["basis"] || 0]
2051       hash["secondary_basis"] = bset_desc[hash["secondary_basis"] || 0]
2052       hash["dfttype"] = dfttype_desc[hash["dfttype"] || 0]
2053       hash["runtype"] = runtype_desc[hash["runtype"] || 0]
2054       hash["scftype"] = scftype_desc[hash["scftype"] || 0]
2055       basename = (self.path ? File.basename(self.path, ".*") : self.name)
2056       fname = Dialog.save_panel("Export Psi4 input file:", self.dir, basename + ".in", "Psi4 input file (*.in)|*.in|All files|*.*")
2057       return nil if !fname
2058       if psi4_input_direct
2059         File.open(fname, "w") { |fp| fp.print(psi4_input_direct) }
2060       else
2061         export_psi4(fname, hash)
2062       end
2063       if hash["execute_local"] == 1
2064         @hf_type = hash["scftype"]
2065         Molecule.execute_psi4(fname, self)
2066       end
2067     else
2068       nil
2069     end
2070   
2071   end  #  End def create_psi4_input
2072   
2073
2074 end
2075
2076 $gamess_basis = {
2077   "PM3"   => " PM3 0\n",
2078   "STO3G" => " STO 3\n",
2079   "321G"  => " N21 3\n",
2080   "631G"  => " N31 6\n" }
2081 $gamess_basis_desc = {
2082   "PM3"   => "PM3",
2083   "STO3G" => "STO-3G",
2084   "321G"  => "3-21G",
2085   "631G"  => "6-31G" }
2086 $gamess_basis_keys = ["PM3", "STO3G", "321G", "631G"]
2087
2088 ["631Gd", "631Gdp", "631+Gd", "631++Gdp", "6311Gdp", "6311+Gd", "6311++Gdp", "6311++G2d2p", "6311++G3df3pd", "LanL2DZ"].each { |n|
2089   Molecule.read_gamess_basis_sets("basis_sets/#{n}.txt")
2090 }
2091
2092 register_menu("QChem\tCreate GAMESS Input...",
2093   :cmd_create_gamess_input, :non_empty)
2094 register_menu("QChem\tCreate Psi4 Input...",
2095   :cmd_create_psi4_input, :non_empty)
2096 register_menu("QChem\tCreate MOPAC6 Input...",
2097   :cmd_create_mopac_input, :non_empty)    # mopac6.rb
2098 register_menu("QChem\tCreate MO Cube...",
2099   :cmd_create_cube, lambda { |m| m && m.mo_type } )