OSDN Git Service

Start implementing JANPA integration with Psi4
[molby/Molby.git] / Scripts / loadsave.rb
index 1f7f922..b6735b4 100755 (executable)
@@ -511,18 +511,397 @@ class Molecule
        (n > 0 ? true : false)
   end
 
+  def sub_load_psi4_log(fp)
+    if natoms == 0
+      new_unit = true
+    else
+      new_unit = false
+    end
+    n = 0
+    nf = 0
+    energy = nil
+  
+    show_progress_panel("Loading Psi4 output file...")
+
+    getline = lambda { @lineno += 1; return fp.gets }
+
+    #  Import coordinates and energies
+    vecs = []
+    ats = []
+    first_frame = nframes
+    trans = nil
+    hf_type = nil
+    nalpha = nil
+    nbeta = nil
+    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(' ')
+          if natoms > 0 && first_frame == nframes
+            if index >= natoms || tokens[0] != atoms[index].element
+              hide_progress_panel
+              raise MolbyError, "The atom list does not match the current structure at line #{@lineno}"
+            end
+          end
+          vecs.push(Vector3D[Float(tokens[1]), Float(tokens[2]), Float(tokens[3])])
+          if natoms == 0
+            ats.push(tokens[0])
+          end
+          index += 1
+        end
+        if natoms == 0
+          #  Create molecule from the initial geometry
+          ats.each_with_index { |aname, i|
+            #  Create atoms
+            ap = add_atom(aname)
+            ap.element = aname
+            ap.atom_type = ap.element
+            ap.name = sprintf("%s%d", aname, i)
+            ap.r = vecs[i]
+          }
+          guess_bonds
+        else
+          if vecs.length != natoms
+            break  #  Log file is incomplete
+          end
+          #  Does this geometry differ from the last one?
+          vecs.length.times { |i|
+            if (atoms[i].r - vecs[i]).length2 > 1.0e-14
+              #  Create a new frame and break
+              create_frame
+              vecs.length.times { |j|
+                atoms[j].r = vecs[j]
+              }
+              break
+            end
+          }
+        end
+        #  end geometry
+      elsif line =~ /Final Energy: +([-.0-9]+)/
+        #  Energy for this geometry
+        energy = Float($1)
+        set_property("energy", energy)
+        if line =~ /RHF/
+          hf_type = "RHF"
+        elsif line =~ /UHF/
+          hf_type = "UHF"
+        elsif line =~ /ROHF/
+          hf_type = "ROHF"
+        end
+      elsif line =~ /^ *Nalpha *= *(\d+)/
+        nalpha = Integer($1)
+      elsif line =~ /^ *Nbeta *= *(\d+)/
+        nbeta = Integer($1)
+      end
+    end
+    hide_progress_panel
+    clear_basis_set
+    clear_mo_coefficients
+    set_mo_info(:type => hf_type, :alpha => nalpha, :beta => nbeta)
+    return true
+  end
+
+  #  mol.set_mo_info should be set before calling this function
+  #  Optional label is for importing JANPA output: "NAO" or "CPLO"
+  #  If label is not nil, then returns a hash containing the following key/value pairs:
+  #    :atoms => an array of [element_symbol, seq_num, atomic_num, x, y, z] (angstrom)
+  #    :gto => an array of an array of [sym, [ex0, c0, ex1, c1, ...]]
+  #    :moinfo => an array of [sym, energy, spin (0 or 1), occ]
+  #    :mo => an array of [c0, c1, ...]
+  def sub_load_molden(fp, label = nil)
+    getline = lambda { @lineno += 1; return fp.gets }
+    bohr = 0.529177210903
+    errmsg = nil
+    ncomps = 0  #  Number of components (AOs)
+    occ_alpha = 0  #  Number of occupied alpha orbitals
+    occ_beta = 0   #  Number of occupied beta orbitals
+    if label
+      hash = Hash.new
+    end
+    catch :ignore do
+      while line = getline.call
+        if line =~ /^\[Atoms\]/
+          i = 0
+          while line = getline.call
+            if line =~ /^[A-Z]/
+              #  element, index, atomic_number, x, y, z (in AU)
+              a = line.split(' ')
+              if label
+                (hash[:atoms] ||= []).push([a[0], Integer(a[1]), Integer(a[2]), Float(a[3]) * bohr, Float(a[4]) * bohr, Float(a[5]) * bohr])
+              else
+                if atoms[i].atomic_number != Integer(a[2]) ||
+                  (atoms[i].x - Float(a[3]) * bohr).abs > 1e-4 ||
+                  (atoms[i].y - Float(a[4]) * bohr).abs > 1e-4 ||
+                  (atoms[i].z - Float(a[5]) * bohr).abs > 1e-4
+                  errmsg = "The atom list does not match the current molecule."
+                  throw :ignore
+                end
+              end
+              i += 1
+            else
+              break
+            end
+          end
+          redo  #  The next line will be the beginning of the next block
+        elsif line =~ /^\[GTO\]/
+          shell = 0
+          atom_index = 0
+          if label
+            gtos = []
+            hash[:gto] = []
+          end
+          while line = getline.call
+            #  index, 0?
+            a = line.split(' ')
+            break if a.length != 2
+            #  loop for shells
+            while line = getline.call
+              #  type, no_of_primitives, 1.00?
+              a = line.split(' ')
+              break if a.length != 3   #  Terminated by a blank line
+              case a[0]
+              when "s"
+                sym = 0; n = 1
+              when "p"
+                sym = 1; n = 3
+              when "d"
+                sym = 2; n = 6    #  TODO: handle both spherical and cartesian
+              when "f"
+                sym = 3; n = 10
+              when "g"
+                sym = 4; n = 15
+              else
+                raise MolbyError, "Unknown gaussian shell type '#{a[0]}' at line #{@lineno} in MOLDEN file"
+              end
+              nprimitives = Integer(a[1])
+              if label
+                gtoline = [sym, []]
+                gtos.push(gtoline)
+              end
+              nprimitives.times { |i|
+                line = getline.call   #  exponent, contraction
+                b = line.split(' ')
+                if label
+                  gtoline[1].push(Float(b[0]), Float(b[1]))
+                end
+                add_gaussian_primitive_coefficients(Float(b[0]), Float(b[1]), 0.0)
+              }
+              #  end of one shell
+              if label == nil
+                add_gaussian_orbital_shell(atom_index, sym, nprimitives)
+              end
+              shell += 1
+              ncomps += n
+            end
+            #  end of one atom
+            atom_index += 1
+            if label
+              hash[:gto].push(gtos)
+            end
+          end
+          redo  #  The next line will be the beginning of the next block
+        elsif line =~ /^\[MO\]/
+          m = []
+          idx_alpha = 1   #  set_mo_coefficients() accepts 1-based index of MO
+          idx_beta = 1
+          if label
+            hash[:mo] = []
+            hash[:moinfo] = []
+          end
+          while true
+            #  Loop for each MO
+            m.clear
+            ene = nil
+            spin = nil
+            sym = nil   #  Not used in Molby
+            occ = nil
+            i = 0
+            while line = getline.call
+              if line =~ /^ *Sym= *(\w+)/
+                sym = $1
+              elsif line =~ /^ *Ene= *([-+.0-9e]+)/
+                ene = Float($1)
+              elsif line =~ /^ *Spin= *(\w+)/
+                spin = $1
+              elsif line =~ /^ *Occup= *([-+.0-9e]+)/
+                occ = Float($1)
+                if occ > 0.0
+                  if spin == "Alpha"
+                    occ_alpha += 1
+                  else
+                    occ_beta += 1
+                  end
+                end
+                if label
+                  hash[:moinfo].push([sym, ene, (spin == "Alpha" ? 0 : 1), occ])
+                end
+              elsif line =~ /^ *([0-9]+) +([-+.0-9e]+)/
+                m[i] = Float($2)
+                i += 1
+                if i >= ncomps
+                  if spin == "Alpha"
+                    idx = idx_alpha
+                    idx_alpha += 1
+                  else
+                    idx = idx_beta
+                    idx_beta += 1
+                  end
+                  set_mo_coefficients(idx, ene, m)
+                  if label
+                    hash[:mo].push(m.dup)
+                  end
+                  break
+                end
+              else
+                break
+              end
+            end
+            break if i < ncomps  #  no MO info was found
+          end
+          next
+        end
+      end   #  end while
+    end     #  end catch
+    if errmsg
+      message_box("The MOLDEN file was found but not imported. " + errmsg, "Psi4 import info", :ok)
+      return (label ? nil : false)
+    end
+    return (label ? hash : true)
+  end
+
+  #  Import the JANPA log and related molden files
+  #  Files: inppath.{NAO.molden,CLPO.molden,janpa.log}
+  def sub_load_janpa_log(inppath)
+    begin
+      fp = File.open(inppath + ".janpa.log", "rt") rescue fp = nil
+      if fp == nil
+        message_box("Cannot open JANPA log file #{inppath + '.janpa.log'}: " + $!.to_s)
+        return false
+      end
+      print("Importing #{inppath}.janpa.log.\n")
+      lineno = 0
+      getline = lambda { lineno += 1; return fp.gets }
+      h = Hash.new
+      mfiles = Hash.new
+      while line = getline.call
+        if line =~ /^NAO \#/
+          h["NAO"] = []
+          while line = getline.call
+            break if line !~ /^\s*[1-9]/
+            num = Integer(line[0, 5])
+            name = line[5, 21]
+            occ = Float(line[26, 11])
+            #  like A1*: R1*s(0)
+            #  atom_number, occupied?, shell_number, orb_sym, angular_number
+            name =~ /\s*[A-Z]+([0-9]+)(\*?):\s* R([0-9]+)\*([a-z]+)\(([-0-9]+)\)/
+            anum = Integer($1)
+            occupied = $2
+            shell_num = Integer($3)
+            orb_sym = $4
+            ang_num = Integer($5)
+            h["NAO"].push([num, anum, occupied, shell_num, orb_sym, ang_num, occ])
+          end
+        elsif line =~ /^\s*CLPO\s+D e s c r i p t i o n\s+Occupancy\s+Composition/
+          h["CLPO"] = []
+          while line = getline.call
+            break if line =~ /^\s*$/
+            num = Integer(line[0, 5])
+            label1 = line[5, 6].strip
+            desc = line[11, 30].strip
+            desc =~ /\s*([-A-Za-z0-9]+)(,\s*(.*$))?/
+            desc1 = $1
+            desc2 = ($3 || "")
+            if desc2 =~ /^(.*)*\(NB\)\s*$/ && label1 == ""
+              label1 = "(NB)"
+              desc2 = $1.strip
+            end
+            #  like ["(BD)", "C1-H3", "Io = 0.2237"]
+            h["CLPO"][num - 1] = [label1, desc1, desc2]
+          end
+        elsif line =~ /^ -NAO_Molden_File: (\S*)/
+          mfiles["NAO"] = $1
+        elsif line =~ /^ -LHO_Molden_File: (\S*)/
+          mfiles["LHO"] = $1
+        elsif line =~ /^ -CLPO_Molden_File: (\S*)/
+          mfiles["CLPO"] = $1
+        elsif line =~ /^ -PNAO_Molden_File: (\S*)/
+          mfiles["PNAO"] = $1
+        elsif line =~ /^ -AHO_Molden_File: (\S*)/
+          mfiles["AHO"] = $1
+        elsif line =~ /^ -LPO_Molden_File: (\S*)/
+          mfiles["LPO"] = $1
+        end
+      end
+      fp.close
+      #  Read molden files
+      mfiles.each { |key, value|
+        fp = Kernel.open(value, "rt") rescue fp = nil
+        if fp
+          print("Importing #{value}.\n")
+          res = sub_load_molden(fp, key)
+          if res
+            #  Some kind of orbital based on AO
+            h["AO/#{key}"] = LAMatrix.new(res[:mo])
+          end
+          fp.close
+        end
+      }
+      @nbo = h
+      return true
+    rescue => e
+      $stderr.write(e.message + "\n")
+      $stderr.write(e.backtrace.inspect + "\n")
+    end
+  end
+
   def loadout(filename)
-    retval = false
-    fp = open(filename, "rb")
-       while s = fp.gets
-         if s =~ /Gaussian/
-           retval = sub_load_gaussian_log(fp)
-               break
-         elsif s =~ /GAMESS/
-           retval = sub_load_gamess_log(fp)
-               break
-         end
-       end
+  retval = false
+  fp = open(filename, "rb")
+  @lineno = 0
+  begin
+    while s = fp.gets
+      @lineno += 1
+      if s =~ /Gaussian/
+        retval = sub_load_gaussian_log(fp)
+        break
+      elsif s =~ /GAMESS/
+        retval = sub_load_gamess_log(fp)
+        break
+      elsif s =~ /Psi4/
+        retval = sub_load_psi4_log(fp)
+        if retval
+          #  If .molden file exists, then try to read it
+          namepath = filename.gsub(/\.\w*$/, "")
+          mname = "#{namepath}.molden"
+          if File.exists?(mname)
+            fp2 = open(mname, "rb")
+            if fp2
+              flag = sub_load_molden(fp2)
+              fp2.close
+              status = (flag ? 0 : -1)
+            end
+          end
+          if File.exists?("#{namepath}.janpa.log")
+            flag = sub_load_janpa_log(namepath)
+            status = (flag ? 0 : -1)
+          end
+        end
+        break
+      end
+    end
+  rescue
+    hide_progress_panel
+    raise
+  end
        fp.close
        return retval
   end
@@ -845,11 +1224,9 @@ end_of_header
          calculated_atoms = []
          while token != nil
            if token =~ /^\#data_/i
-                 if token.casecmp("#data_global") == 0
-                   token = getciftoken(fp)
-                       next
-                 elsif data_identifier == nil
-                   #  Continue processing of this molecule
+                 if data_identifier == nil || mol.natoms == 0
+                   #  First block or no atoms yet
+            #  Continue processing of this molecule
                    data_identifier = token
                        token = getciftoken(fp)
                        next
@@ -875,9 +1252,9 @@ end_of_header
                  end
                  if cell.length == 12 && cell.all?
                    mol.cell = cell
-                   puts "Unit cell is set to #{cell.inspect}." if verbose
+            puts "Unit cell is set to #{mol.cell.inspect}." if verbose
                    cell = []
-                   cell_trans = self.cell_transform
+                   cell_trans = mol.cell_transform
                    cell_trans_inv = cell_trans.inverse
                  end
                  token = getciftoken(fp)
@@ -887,7 +1264,7 @@ end_of_header
                  while (token = getciftoken(fp)) && token[0] == ?_
                    labels.push(token)
                  end
-                 if labels[0] =~ /symmetry_equiv_pos|atom_site_label|atom_site_aniso_label|geom_bond/
+                 if labels[0] =~ /symmetry_equiv_pos|space_group_symop|atom_site_label|atom_site_aniso_label|geom_bond/
                    hlabel = Hash.new(-10000000)
                    labels.each_with_index { |lb, i|
                          hlabel[lb] = i
@@ -904,9 +1281,9 @@ end_of_header
                          end
                          token = getciftoken(fp)
                    end
-                   if labels[0] =~ /^_symmetry_equiv_pos/
+                   if labels[0] =~ /^_symmetry_equiv_pos/ || labels[0] =~ /^_space_group_symop/
                      data.each { |d|
-                           symstr = d[hlabel["_symmetry_equiv_pos_as_xyz"]]
+                           symstr = d[hlabel["_symmetry_equiv_pos_as_xyz"]] || d[hlabel["_space_group_symop_operation_xyz"]]
                            symstr.delete("\"\'")
                            exps = symstr.split(/,/)
                            sym = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
@@ -1053,7 +1430,9 @@ end_of_header
                                  }
                            end                         
                      }
-                         bond_defined = true
+              if mol.nbonds > 0
+                bond_defined = true
+              end
                          puts "#{mol.nbonds} bonds are created." if verbose
                          if calculated_atoms.length > 0
                            #  Guess bonds for calculated hydrogen atoms