OSDN Git Service

Molden import: handling of spherical functions is improved
[molby/Molby.git] / Scripts / loadsave.rb
index b6735b4..bdf545c 100755 (executable)
@@ -627,6 +627,16 @@ class Molecule
     if label
       hash = Hash.new
     end
+    #  The GTOs (orbital type, contractions and exponents) are stored in gtos[]
+    #  and set just before first [MO] is processed.
+    #  This is because we do not know whether the orbital type is cartesian or spherical
+    #  until we see lines like "[5D]".
+    gtos = []
+    spherical_d = false
+    spherical_f = false
+    spherical_g = false
+    #  Number of components for each orbital type
+    ncomp_hash = { 0=>1, 1=>3, -1=>4, 2=>6, -2=>5, 3=>10, -3=>7, 4=>15, -4=>9 }
     catch :ignore do
       while line = getline.call
         if line =~ /^\[Atoms\]/
@@ -655,61 +665,83 @@ class Molecule
         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
+            atom_gtos = []  #  [[sym1, [e11, c11, e12, c12, ...], add_exp1], [sym2, [e21, c22, ...], add_exp2], ...]
             #  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]
+              a[0] =~ /^([a-z]+)([0-9]+)?$/
+              symcode = $1
+              add_exp = ($2 == nil ? 0 : $2.to_i)
+              case symcode
               when "s"
-                sym = 0; n = 1
+                sym = 0
               when "p"
-                sym = 1; n = 3
+                sym = 1
               when "d"
-                sym = 2; n = 6    #  TODO: handle both spherical and cartesian
+                sym = 2
               when "f"
-                sym = 3; n = 10
+                sym = 3
               when "g"
-                sym = 4; n = 15
+                sym = 4
               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
+              gtoline = [sym, [], add_exp]
+              atom_gtos.push(gtoline)
               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)
+                gtoline[1].push(Float(b[0]), Float(b[1]))
               }
               #  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
+            gtos.push(atom_gtos)
+          end
+          if label
+            hash[:gto] = gtos
           end
           redo  #  The next line will be the beginning of the next block
+        elsif line =~ /^\[5D\]/ || line =~ /^\[5D7F\]/
+          spherical_d = spherical_f = true
+        elsif line =~ /^\[5D10F\]/
+          spherical_d = true
+          spherical_f = false
+        elsif line =~ /^\[7F\]/
+          spherical_f = true
+        elsif line =~ /^\[9G\]/
+          spherical_g = true
         elsif line =~ /^\[MO\]/
+          #  Add shell info and primitive coefficients to molecule
+          gtos.each_with_index { | atom_gtos, atom_index|
+            atom_gtos.each { |gtoline|
+              sym = gtoline[0]
+              #  Change orbital type if we use spherical functions
+              sym = -2 if sym == 2 && spherical_d
+              sym = -3 if sym == 3 && spherical_f
+              sym = -4 if sym == 4 && spherical_g
+              gtoline[0] = sym
+              coeffs = gtoline[1]
+              nprimitives = coeffs.length / 2
+              add_exp = gtoline[2]
+              ncomps += ncomp_hash[sym]
+              if !label
+                add_gaussian_orbital_shell(atom_index, sym, nprimitives, add_exp)
+                nprimitives.times { |prim|
+                  add_gaussian_primitive_coefficients(coeffs[prim * 2], coeffs[prim * 2 + 1], 0.0)
+                }
+              end
+            }
+          }
           m = []
           idx_alpha = 1   #  set_mo_coefficients() accepts 1-based index of MO
           idx_beta = 1
@@ -767,8 +799,9 @@ class Molecule
             end
             break if i < ncomps  #  no MO info was found
           end
+          #  TODO: reorder D, F, G coefficients for Molby order
           next
-        end
+        end #  end if
       end   #  end while
     end     #  end catch
     if errmsg
@@ -780,10 +813,13 @@ class Molecule
 
   #  Import the JANPA log and related molden files
   #  Files: inppath.{NAO.molden,CLPO.molden,janpa.log}
+  #  If inppath.spherical.molden is available, then clear existing mo info
+  #  and load from it (i.e. use the basis set converted by molden2molden)
   def sub_load_janpa_log(inppath)
     begin
       fp = File.open(inppath + ".janpa.log", "rt") rescue fp = nil
       if fp == nil
+        hide_progress_panel  #  Close if it is open
         message_box("Cannot open JANPA log file #{inppath + '.janpa.log'}: " + $!.to_s)
         return false
       end
@@ -792,8 +828,35 @@ class Molecule
       getline = lambda { lineno += 1; return fp.gets }
       h = Hash.new
       mfiles = Hash.new
+      h["software"] = "JANPA"
+      nao_num = nil  #  Set later
+      nao_infos = [] #  index=atom_index, value=Hash with key "s", "px", "py" etc.
+      #  nao_infos[index][key]: array of [nao_num, occupancy], in the reverse order of appearance
       while line = getline.call
-        if line =~ /^NAO \#/
+        if line =~ /molden2molden: a conversion tool for MOLDEN/
+          while line = getline.call
+            break if line =~ /^All done!/
+            if line =~ /\.spherical\.molden/
+              #  The MOs are converted to spherical basis set
+              #  Clear the existing MO and load *.spherical.molden
+              sname = inppath + ".spherical.molden"
+              fps = File.open(sname, "rt") rescue fps = nil
+              if fps != nil
+                print("Importing #{sname}.\n")
+                @lineno = 0
+                type = get_mo_info(:type)
+                alpha = get_mo_info(:alpha)
+                beta = get_mo_info(:beta)
+                clear_basis_set
+                set_mo_info(:type=>type, :alpha=>alpha, :beta=>beta)
+                #  mol.@hf_type should be set before calling sub_load_molden
+                @hf_type = type
+                sub_load_molden(fps)
+                fps.close
+              end
+            end
+          end
+        elsif line =~ /^NAO \#/
           h["NAO"] = []
           while line = getline.call
             break if line !~ /^\s*[1-9]/
@@ -801,22 +864,65 @@ class Molecule
             name = line[5, 21]
             occ = Float(line[26, 11])
             #  like A1*: R1*s(0)
-            #  atom_number, occupied?, shell_number, orb_sym, angular_number
+            #  atom_number, occupied?, group_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)
+            group_num = Integer($3)
             orb_sym = $4
             ang_num = Integer($5)
-            h["NAO"].push([num, anum, occupied, shell_num, orb_sym, ang_num, occ])
+            orb_desc = orb_sym
+            if orb_desc == "p"
+              orb_desc += ["z", "x", "y"][ang_num + 1]
+            elsif orb_desc == "d"
+            #  TODO: handle d, f, g orbitals
+            end
+            h["NAO"].push([num, anum, occupied, group_num, orb_desc, occ])
+            nao_num = h["NAO"].length
+            ((nao_infos[anum - 1] ||= Hash.new)[orb_desc] ||= []).unshift([nao_num, occ])
+          end
+          #  Create labels
+          h["NAO_L"] = []
+          nao_infos.each_with_index { |value, atom_index|
+            aname = self.atoms[atom_index].name
+            value.each { |orb_desc, ar|
+              ar.each_with_index { |v, group_index|
+                if v[1] > 1.9
+                  label = "core"
+                elsif v[1] > 0.01
+                  label = "val"
+                else
+                  label = "ryd"
+                end
+                principle = group_index + 1
+                orb_sym = orb_desc[0]
+                if orb_sym == "p"
+                  principle += 1
+                elsif orb_sym == "d"
+                  principle += 2
+                elsif orb_sym == "f"
+                  principle += 3
+                elsif orb_sym == "g"
+                  principle += 4
+                end
+                h["NAO_L"][v[0] - 1] = "#{aname} (#{principle}#{orb_desc}) (#{label})"
+              }
+            }
+          }
+        elsif line =~ /^\s*(C?)LPO\s+D e s c r i p t i o n\s+Occupancy\s+Composition/
+          if $1 == "C"
+            key = "CLPO"
+          else
+            key = "LPO"
           end
-        elsif line =~ /^\s*CLPO\s+D e s c r i p t i o n\s+Occupancy\s+Composition/
-          h["CLPO"] = []
+          h[key] = []
           while line = getline.call
             break if line =~ /^\s*$/
             num = Integer(line[0, 5])
             label1 = line[5, 6].strip
             desc = line[11, 30].strip
+            occ = line[41, 11].strip
+            comp = line[52, 1000].strip
             desc =~ /\s*([-A-Za-z0-9]+)(,\s*(.*$))?/
             desc1 = $1
             desc2 = ($3 || "")
@@ -824,9 +930,56 @@ class Molecule
               label1 = "(NB)"
               desc2 = $1.strip
             end
-            #  like ["(BD)", "C1-H3", "Io = 0.2237"]
-            h["CLPO"][num - 1] = [label1, desc1, desc2]
+            atoms = desc1.scan(/[A-Za-z]+(\d+)/)   # "C1-H3" -> [["1"], ["3"]]
+            atoms = atoms.map { |a| Integer(a[0]) }  # [1, 3]
+            hybrids_a = comp.scan(/h(\d+)@[A-Za-z]+(\d+)/)  #  "h8@C1...h13@H3" -> "[["8", "1"], ["13", "3"]]
+            hybrids = []
+            hybrids_a.each { |a|
+              i = atoms.find_index(Integer(a[1]))
+              if i != nil
+                hybrids[i] = Integer(a[0])
+              end
+            } # [8, 13]
+            #  like ["(BD)", [1, 3], "Io = 0.2237", occ, [8, 13]]
+            #  1, 3 are the atom indices (1-based)
+            #  8, 13 are the number of hybrid orbitals (1-based)
+            h[key][num - 1] = [label1, atoms, desc2, Float(occ), hybrids]
+          end
+          h[key + "_L"] = []
+          if key == "CLPO"
+            #  Also register labels of "LHO"
+            h["LHO_L"] = [""] * nao_num
           end
+          nao_num.times { |i|
+            val = h[key][i]
+            if val == nil
+              label = ""  #  The labels for Rydberg orbitals may be replaced later
+            else
+              aname1 = self.atoms[val[1][0] - 1].name rescue aname1 = ""
+              aname2 = self.atoms[val[1][1] - 1].name rescue aname2 = ""
+              if aname2 == ""
+                label = "#{aname1} #{val[0]}"
+              else
+                label = "#{aname1}(#{aname2}) #{val[0]}"
+              end
+            end
+            h[key + "_L"][i] = label
+            if key == "CLPO" && val != nil && val[0] != "(NB)"
+              hybrids = val[4]
+              kind = (val[0] == "(BD)" ? "(val)" : "(lp)")
+              if aname2 == ""
+                label = "#{aname1} #{kind}"
+              else
+                label = "#{aname1}(#{aname2}) #{kind}"
+              end
+              h["LHO_L"][hybrids[0] - 1] = label
+              if hybrids[1] != nil
+                #  aname2 should be non-empty
+                label = "#{aname2}(#{aname1}) #{kind}"
+                h["LHO_L"][hybrids[1] - 1] = label
+              end
+            end
+          }
         elsif line =~ /^ -NAO_Molden_File: (\S*)/
           mfiles["NAO"] = $1
         elsif line =~ /^ -LHO_Molden_File: (\S*)/
@@ -853,9 +1006,49 @@ class Molecule
             h["AO/#{key}"] = LAMatrix.new(res[:mo])
           end
           fp.close
+          if key == "CLPO" || key == "LPO" || key == "LHO"
+            #  Set the label of Rydberg orbitals if possible
+            if h[key + "_L"] != nil
+              a = h["AO/#{key}"]
+              nao_num.times { |i|
+                label = h[key + "_L"][i]
+                if label == ""
+                  max_idx = nil
+                  max_val = -1.0
+                  nao_infos.each_with_index { |inf, atom_index|
+                    atomic_contrib = 0.0
+                    inf.each { |k, v| # k is "s", "px" etc, v is array of [nao_num, occupancy]
+                      #  Sum for all naos belonging to this atom
+                      v.each { |num_occ|
+                        atomic_contrib += a[i, num_occ[0] - 1] ** 2
+                      }
+                    }
+                    if atomic_contrib > max_val
+                      max_val = atomic_contrib
+                      max_idx = atom_index
+                    end
+                  }
+                  label = self.atoms[max_idx].name + " (ry)"
+                  h[key + "_L"][i] = label
+                end
+              }
+            end
+          end
         end
       }
       @nbo = h
+      if @nbo["AO/NAO"] && @nbo["AO/LHO"] && @nbo["AO/PNAO"]
+        #  Generate PLHO from PNAO, NAO, LHO
+        #  This protocol was suggested by the JANPA author in a private commnunication.
+        begin
+          nao2lho = @nbo["AO/NAO"].inverse * @nbo["AO/LHO"]
+          nao2pnao = @nbo["AO/NAO"].inverse * @nbo["AO/PNAO"]
+          sign = LAMatrix.diagonal((0...nao2pnao.column_size).map { |i| (nao2pnao[i, i] < 0 ? -1 : 1)})
+          @nbo["AO/PLHO"] = @nbo["AO/PNAO"] * sign * nao2lho
+        rescue
+          @nbo["AO/PLHO"] = nil
+        end
+      end
       return true
     rescue => e
       $stderr.write(e.message + "\n")