end
end
+ def export_psi4(fname, hash)
+ now = Time.now.to_s
+ if fname
+ fp = File.open(fname, "wb")
+ mname = File.basename(fname, ".*")
+ else
+ fp = MemoryIO.new
+ mname = self.name
+ if mname == nil || mname == ""
+ mname = "mol"
+ end
+ end
+ if fp
+ fp.print "# Psi4 input\n"
+ fp.print "# Generated by Molby at #{now}\n"
+ fp.print "import sys\n"
+ fp.print "import re\n"
+ fp.print "molecule #{mname} {\n"
+ atoms.each { |ap|
+ fp.printf "%-8s %10.6f %10.6f %10.6f\n", ap.element, ap.x, ap.y, ap.z
+ }
+ fp.print "units angstrom\n"
+ fp.print "}\n"
+ fp.print "set basis #{hash['basis']}\n"
+ if hash['scftype'] != "RHF"
+ fp.print "set reference #{hash['scftype']}\n"
+ end
+ options = "return_wfn=True"
+ if hash['dft'] != 0
+ options += ", dft_functional='#{hash['dfttype']}'"
+ end
+ runtype = hash['runtype'].downcase
+ fp.print "energy, wfn = #{runtype}('scf', #{options})\n"
+ fp.print "wfn.write_molden(re.sub('\\.\\w*$', '.molden', sys.argv[1]))\n"
+ end
+ if fname == nil
+ s = fp.buffer
+ fp.empty
+ return s
+ else
+ fp.close
+ return fname
+ end
+ end
+
+ # Execute Psi4 (inferior copy of rungms script)
+ # inpname is the input file
+ # mol (optional) is the molecule from which the Psi4 input was built.
+ def Molecule.execute_psi4(inpname, mol = nil)
+
+ inpbase = File.basename(inpname)
+ inpbody = File.basename(inpname, ".*")
+ inpdir = File.dirname(inpname)
+
+ # Set PATH for psi4conda
+ psi4folder = get_global_settings("psi4.psi4conda_folder")
+ orgpath = ENV["PATH"]
+ orgdir = Dir.pwd
+ if $platform == "win"
+ ENV["PATH"] = "#{psi4folder};#{psi4folder}\\Library\\mingw-w64\\bin;#{psi4folder}\\Library\\usr\\bin;#{psi4folder}\\Library\\bin;#{psi4folder}\\Scripts;#{psi4folder}\\bin;#{psi4folder}\\condabin;" + ENV["PATH"]
+ else
+ ENV["PATH"] = "#{psi4folder}/bin:#{psi4folder}/condabin:" + ENV["PATH"]
+ end
+ Dir.chdir(inpdir)
+ cmdline = "psi4 #{inpbase}"
+
+ term_callback = lambda { |m, n|
+ msg = "Psi4 execution of #{inpbase}"
+ hmsg = "Psi4 "
+ if n == 0
+ msg += "succeeded."
+ hmsg += "Completed"
+ icon = :info
+ else
+ msg += "failed with status #{n}."
+ hmsg += "Failed"
+ icon = :error
+ end
+ msg += "\n(In directory #{inpdir})"
+ ENV["PATH"] = orgpath
+ Dir.chdir(orgdir)
+ if mol != nil
+ message_box(msg, hmsg, :ok, icon)
+ end
+ if n == 0
+ # Try to load molden file if available
+ molden = inpdir + "/" + inpbody +".molden"
+ if File.exists?(molden)
+ fp = Kernel.open(molden, "rt")
+ mol.instance_eval { @lineno = 0 }
+ begin
+ # mol.@hf_type should be set before calling sub_load_molden
+ mol.sub_load_molden(fp)
+ fp.close
+ rescue => e
+ print(e.message + "\n")
+ print(e.backtrace.inspect + "\n")
+ end
+ end
+ end
+ }
+
+ timer_count = 0
+ fplog = nil
+ last_size = 0
+ lines = []
+ last_line = ""
+ next_index = 0
+ timer_callback = lambda { |m, n|
+ begin
+ timer_count += 1
+ if timer_count < 10 # Only handle every 1 seconds
+ return true
+ end
+ timer_count = 0
+ if fplog == nil
+ fplog = Kernel.open(inpdir + "/" + inpbody + ".out", "rt")
+ if fplog == nil
+ return true
+ end
+ last_size = 0
+ end
+ fplog.seek(0, IO::SEEK_END)
+ current_size = fplog.tell
+ if current_size > last_size
+ # Read new lines
+ fplog.seek(last_size, IO::SEEK_SET)
+ fplog.each_line { |line|
+ if line[-1, 1] == "\n"
+ lines.push(last_line + line)
+ last_line = ""
+ else
+ last_line += line
+ break
+ end
+ }
+ last_size = fplog.tell
+ end
+ li = next_index
+ getline = lambda { if li < lines.length; li += 1; return lines[li - 1]; else return nil; end }
+ vecs = []
+ while (line = getline.call)
+ if line =~ /==> Geometry <==/
+ # Skip until line containing "------"
+ while line = getline.call
+ break if line =~ /------/
+ end
+ vecs.clear
+ index = 0
+ # Read atom positions
+ while line = getline.call
+ line.chomp!
+ break if line =~ /^\s*$/
+ tokens = line.split(' ')
+ vecs.push(Vector3D[Float(tokens[1]), Float(tokens[2]), Float(tokens[3])])
+ index += 1
+ end
+ if vecs.length < mol.natoms
+ break # Log file is incomplete
+ end
+ # Does this geometry differ from the last one?
+ vecs.length.times { |i|
+ if (mol.atoms[i].r - vecs[i]).length2 > 1.0e-14
+ # Create a new frame and break
+ mol.create_frame
+ vecs.length.times { |j|
+ mol.atoms[j].r = vecs[j]
+ }
+ break
+ end
+ }
+ next_index = li
+ # end geometry
+ elsif line =~ /Final Energy: +([-.0-9]+)/
+ # Energy for this geometry
+ energy = Float($1)
+ mol.set_property("energy", energy)
+ mol.show_text("Frame: #{mol.nframes - 1}\nEnergy: #{energy}")
+ if line =~ /RHF/
+ hf_type = "RHF"
+ elsif line =~ /UHF/
+ hf_type = "UHF"
+ elsif line =~ /ROHF/
+ hf_type = "ROHF"
+ end
+ next_index = li
+ end
+ end
+ if next_index > 0
+ lines.slice!(next_index, lines.length - next_index)
+ next_index = 0
+ end
+ return true
+ rescue => e
+ # Some error occurred during callback. Show the message in the console and abort.
+ p e.message
+ p e.backtrace
+ return false
+ end
+ }
+
+ if mol
+ pid = mol.call_subprocess_async(cmdline, term_callback, timer_callback)
+ if pid < 0
+ error_message_box("Psi4 failed to start. Please examine Psi4 installation.")
+ return -1
+ end
+ else
+ status = call_subprocess(cmdline, "Running Psi4")
+ term_callback.call(nil, status)
+ return status
+ end
+ end
+
+ def cmd_edit_psi4_input(s)
+ mol = self
+ h = Dialog.run("Edit Psi4 Input", "OK", "Cancel", :resizable=>true) {
+ layout(1,
+ item(:textview, :value=>s, :tag=>"edit", :width=>400, :height=>400, :flex=>[0,0,0,0,1,1]),
+ )
+ set_min_size(300, 300)
+ }
+ if h[:status] == 0
+ return h["edit"]
+ else
+ return nil
+ end
+ end
+
+ def cmd_create_psi4_input
+
+ mol = self
+
+ if natoms == 0
+ raise MolbyError, "cannot create Psi4 input; the molecule is empty"
+ end
+
+ # Basis sets Cf. https://psicode.org/psi4manual/master/basissets_tables.html#apdx-basistables
+ 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"]
+ dfttype_desc = ["B3LYP"]
+ runtype_desc = ["Energy", "Optimize"]
+ scftype_desc = ["RHF", "ROHF", "UHF"]
+# nbo_desc = ["nao", "nbo", "nho", "nlmo", "pnao", "pnbo", "pnho", "pnlmo"]
+ user_input = Hash.new
+ defaults = {"scftype"=>0, "runtype"=>0, "use_internal"=>1, "eliminate_freedom"=>1, "charge"=>"0", "mult"=>"1",
+ "basis"=>1, "use_secondary_basis"=>0, "secondary_elements"=>"",
+ "secondary_basis"=>8, "esp"=>0, "ncpus"=>"1"}
+ psi4_input_direct = nil
+
+ hash = Dialog.run("Psi4 Export") {
+ def load_basis_set_sub(item)
+ fname = Dialog.open_panel("Select a file containing GAMESS basis set:")
+ if fname
+ Molecule.read_gamess_basis_sets(fname)
+ bset_desc_new = $gamess_basis_keys.map { |key| $gamess_basis_desc[key] }
+ sel1 = attr("basis", :value)
+ sel2 = attr("secondary_basis", :value)
+ set_attr("basis", :subitems=>bset_desc_new)
+ set_attr("basis", :value=>sel1)
+ set_attr("secondary_basis", :subitems=>bset_desc_new)
+ set_attr("secondary_basis", :value=>sel2)
+ end
+ end
+ def select_psi4_folder(item)
+ # By default, psi4conda is installed in the home directory
+ while 1
+ fname = Dialog.open_panel("Locate 'psi4conda' Folder:", ENV["HOME"] || ENV["USERPROFILE"], nil, true)
+ return if fname == nil
+ bname = File.basename(fname)
+ if bname == "psi4conda"
+ break
+ else
+ h = Dialog.run("", "OK", "Choose Again") {
+ layout(1,
+ item(:text, :title=>"The folder does not have the name 'psi4conda'."),
+ item(:text, :title=>"Do you want to use it anyway?"))
+ }
+ if h[:status] == 0
+ break
+ end
+ end
+ end
+ set_attr("psi4conda_folder", :value=>fname)
+ end
+ layout(4,
+ # Line 1
+ item(:text, :title=>"SCF type"),
+ item(:popup, :subitems=>scftype_desc, :tag=>"scftype"),
+ item(:text, :title=>"Run type"),
+ item(:popup, :subitems=>runtype_desc, :tag=>"runtype",
+ :action=>lambda { |it| set_attr("use_internal", :enabled=>(it[:value] >= 2)) } ),
+ # Line 2
+ item(:checkbox, :title=>"Use internal coordinates for structure optimization", :tag=>"use_internal"),
+ -1, -1, -1,
+ # Line 3
+ item(:checkbox, :title=>"Eliminate translation and rotational degrees of freedom", :tag=>"eliminate_freedom"),
+ -1, -1, -1,
+ # Line 4
+ item(:text, :title=>"Charge"),
+ item(:textfield, :width=>80, :tag=>"charge"),
+ item(:text, :title=>"Multiplicity"),
+ item(:textfield, :width=>80, :tag=>"mult"),
+ # Line 5
+ item(:checkbox, :title=>"Use DFT", :tag=>"dft",
+ :action=>lambda { |it| set_attr("dfttype", :enabled=>(it[:value] != 0)) } ),
+ -1,
+ item(:text, :title=>"DFT type"),
+ item(:popup, :subitems=>dfttype_desc, :tag=>"dfttype"),
+ # Line 6
+ item(:line),
+ -1, -1, -1,
+ # Line 7
+ item(:text, :title=>"Basis set"),
+ item(:popup, :subitems=>bset_desc, :tag=>"basis"),
+ -1,
+ -1,
+ # Line 8
+ #item(:button, :title=>"Load Basis Set...", :action=>:load_basis_set_sub),
+ #-1, -1, -1,
+ # Line 9
+ item(:checkbox, :title=>"Use secondary basis set", :tag=>"use_secondary_basis",
+ :action=>lambda { |it|
+ flag = (it[:value] != 0)
+ set_attr("secondary_elements", :enabled=>flag)
+ set_attr("secondary_basis", :enabled=>flag)
+ }),
+ -1, -1, -1,
+ # Line 10
+ item(:text, :title=>" Elements"),
+ item(:textfield, :width=>80, :tag=>"secondary_elements"),
+ item(:text, :title=>"Basis set"),
+ item(:popup, :subitems=>bset_desc, :tag=>"secondary_basis"),
+ # Line 11
+ item(:line),
+ -1, -1, -1,
+ # Line 12
+ #item(:checkbox, :title=>"Calculate electrostatic potential (ESP)", :tag=>"esp"),
+ #-1, -1, -1,
+ # Line 13
+ #item(:line),
+ #-1, -1, -1,
+ # Line 14
+ #item(:checkbox, :title=>"Include NBO instructions", :tag=>"include_nbo",
+ # :action=>lambda { |it|
+ # flag = (it[:value] != 0)
+ # nbos.each { |nbo| set_attr(nbo, :enabled=>flag) }
+ # }),
+ #-1, -1, -1,
+ # Line 15
+ #item(:checkbox, :title=>"NAO", :tag=>"nao"),
+ #item(:checkbox, :title=>"NBO", :tag=>"nbo"),
+ #item(:checkbox, :title=>"NHO", :tag=>"nho"),
+ #item(:checkbox, :title=>"NLMO", :tag=>"nlmo"),
+ # Line 16
+ #item(:checkbox, :title=>"PNAO", :tag=>"pnao"),
+ #item(:checkbox, :title=>"PNBO", :tag=>"pnbo"),
+ #item(:checkbox, :title=>"PNHO", :tag=>"pnho"),
+ #item(:checkbox, :title=>"PNLMO", :tag=>"pnlmo"),
+ # Line 17
+ item(:line),
+ -1, -1, -1,
+ # Line 18
+ item(:checkbox, :title=>"Execute Psi4 on this machine", :tag=>"execute_local",
+ :action=>lambda { |it|
+ flag = (it[:value] != 0)
+ set_attr("psi4conda_folder", :enabled=>flag)
+ set_attr("select_path", :enabled=>flag)
+ set_attr("ncpus", :enabled=>flag)
+ }),
+ -1, -1, -1,
+ # Line 19
+ item(:text, :title=>" Folder"),
+ item(:textfield, :width=>300, :tag=>"psi4conda_folder"),
+ -1, -1,
+ # Line 20
+ -1,
+ item(:button, :title=>"Select Path...", :tag=>"select_path", :action=>:select_psi4_folder),
+ -1, -1,
+ # item(:button, :title=>"Optional Scripts...", :action=>:set_optional_scripts),
+ # Line 21
+ item(:text, :title=>" N of CPUs"),
+ item(:textfield, :width=>80, :tag=>"ncpus"),
+ -1, -1,
+ # Line 22
+ item(:line),
+ -1, -1, -1,
+ # Line 23
+ item(:button, :title=>"Edit Psi4 Input and Go", :action=>lambda { |it|
+ h = Hash.new
+ each_item { |it2|
+ if (tag = it2[:tag]) != nil
+ h[tag] = it2[:value]
+ end
+ }
+ h["basis"] = bset_desc[h["basis"]]
+ h["secondary_basis"] = bset_desc[h["secondary_basis"]]
+ h["dfttype"] = dfttype_desc[h["dfttype"]]
+ h["runtype"] = runtype_desc[h["runtype"]]
+ h["scftype"] = scftype_desc[h["scftype"]]
+ psi4_input_direct = mol.cmd_edit_psi4_input(mol.export_psi4(nil, h))
+ if psi4_input_direct
+ end_modal(0)
+ end
+ }),
+ -1, -1, -1
+ )
+ values = Hash.new
+ each_item { |it|
+ tag = it[:tag]
+ if tag
+ values[tag] = (user_input[tag] || get_global_settings("psi4.#{tag}") || defaults[tag])
+ if values[tag]
+ it[:value] = values[tag]
+ end
+ end
+ }
+ set_attr("secondary_elements", :enabled=>(values["use_secondary_basis"] == 1))
+ set_attr("secondary_basis", :enabled=>(values["use_secondary_basis"] == 1))
+ set_attr("dfttype", :enabled=>(values["dft"] == 1))
+ set_attr("use_internal", :enabled=>((values["runtype"] || 0) >= 2))
+ set_attr("psi4conda_folder", :enabled=>(values["execute_local"] == 1))
+ set_attr("ncpus", :enabled=>(values["execute_local"] == 1))
+ #nbos.each { |nao|
+ # set_attr(nao, :enabled=>(values["include_nbo"] == 1))
+ #}
+ } # end Dialog.run
+
+ hash.each_pair { |key, value|
+ next if key == :status
+ set_global_settings("psi4.#{key}", value)
+ }
+ if hash[:status] == 0
+ # Specify basis by internal keys
+ hash["basis"] = bset_desc[hash["basis"]]
+ hash["secondary_basis"] = bset_desc[hash["secondary_basis"]]
+ hash["dfttype"] = dfttype_desc[hash["dfttype"]]
+ hash["runtype"] = runtype_desc[hash["runtype"]]
+ hash["scftype"] = scftype_desc[hash["scftype"]]
+ basename = (self.path ? File.basename(self.path, ".*") : self.name)
+ fname = Dialog.save_panel("Export Psi4 input file:", self.dir, basename + ".in", "Psi4 input file (*.in)|*.in|All files|*.*")
+ return nil if !fname
+ if psi4_input_direct
+ File.open(fname, "w") { |fp| fp.print(psi4_input_direct) }
+ else
+ export_psi4(fname, hash)
+ end
+ if hash["execute_local"] == 1
+ @hf_type = hash["scftype"]
+ Molecule.execute_psi4(fname, self)
+ end
+ else
+ nil
+ end
+
+ end # End def create_psi4_input
+
+
end
$gamess_basis = {
register_menu("QChem\tCreate GAMESS Input...",
:cmd_create_gamess_input, :non_empty)
+register_menu("QChem\tCreate Psi4 Input...",
+ :cmd_create_psi4_input, :non_empty)
register_menu("QChem\tCreate MOPAC6 Input...",
:cmd_create_mopac_input, :non_empty) # mopac6.rb
register_menu("QChem\tCreate MO Cube...",