OSDN Git Service

Export and execute Psi4 is implemented
authorToshi Nagata <alchemist.2005@nifty.com>
Sat, 24 Sep 2022 10:43:08 +0000 (19:43 +0900)
committerToshi Nagata <alchemist.2005@nifty.com>
Sat, 24 Sep 2022 10:43:08 +0000 (19:43 +0900)
Scripts/gamess.rb

index 2a3f751..0883a59 100755 (executable)
@@ -1540,6 +1540,463 @@ class Molecule
        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 = {
@@ -1560,6 +2017,8 @@ $gamess_basis_keys = ["PM3", "STO3G", "321G", "631G"]
 
 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...",