OSDN Git Service

Start implementing 'additional exponent' for JANPA output
[molby/Molby.git] / Scripts / loadsave.rb
index 069c4f7..909d777 100755 (executable)
@@ -1,3 +1,4 @@
+# coding: utf-8
 #
 #  loadsave.rb
 #
@@ -25,20 +26,24 @@ class Molecule
     count = 0
        frame = 0
        coords = (0...natoms).collect { Vector3D[0, 0, 0] }
-       show_progress_panel("Loading a crd file...")
+    periodic = (self.box && self.box[4].any? { |n| n != 0 })
+       show_progress_panel("Loading AMBER crd file...")
 #    puts "sframe = #{sframe}, pos = #{fp.pos}"
-    while 1
-      line = fp.gets
-      if line == nil
-           if (count > 0)
-                 raise MolbyError, sprintf("crd format error - file ended in the middle of frame %d", frame)
+    while line = fp.gets
+      line.chomp!
+         values = line.scan(/......../)
+         if fp.lineno == 1
+           #  The first line should be skipped. However, if this line seems to contain
+               #  coordinates, then try reading them
+               if values.size != 10 && (values.size > 10 || values.size != natoms * 3)
+                 next  #  Wrong number of coordinates
                end
-           fp.close
-               break
-      end
-      next if line.match(/^TITLE/)
-      line.chomp
-      values = line.split(' ')
+               if values.each { |v| Float(v) rescue break } == nil
+                 #  The line contains non-number 
+                 next
+           end
+               puts "Loadcrd: The title line seems to be missing"
+         end
       if count + values.size > natoms * 3
         raise MolbyError, sprintf("crd format error - too many values at line %d in file %s; number of atoms = %d, current frame = %d", fp.lineno, fp.path, natoms, frame)
       end
@@ -54,13 +59,28 @@ class Molecule
                else
                        create_frame([coords])
                end
+               if periodic
+                 #  Should have box information
+                 line = fp.gets
+                 if line == nil || (values = line.chomp.scan(/......../)).length != 3
+                   #  Periodic but no cell information
+                       puts "Loadcrd: the molecule has a periodic cell but the crd file does not contain cell info"
+                       periodic = false
+                       redo
+             end
+                 self.cell = [Float(values[0]), Float(values[1]), Float(values[2]), 90, 90, 90]
+               end
         count = 0
         frame += 1
                if frame % 5 == 0
-                 set_progress_message("Loading a crd file...\n(#{frame} frames completed)")
+                 set_progress_message("Loading AMBER crd file...\n(#{frame} frames completed)")
                end
       end
     end
+       fp.close
+       if (count > 0)
+         raise MolbyError, sprintf("crd format error - file ended in the middle of frame %d", frame)
+       end
        hide_progress_panel
        if frame > 0
          self.frame = self.nframes - 1
@@ -75,7 +95,7 @@ class Molecule
       raise MolbyError, "cannot save crd; the molecule is empty"
     end
     fp = open(filename, "wb")
-       show_progress_panel("Saving a crd file...")
+       show_progress_panel("Saving AMBER crd file...")
        fp.printf("TITLE: %d atoms\n", natoms)
        cframe = self.frame
        nframes = self.nframes
@@ -92,7 +112,7 @@ class Molecule
                        j += 1
                  end
                  if i % 5 == 0
-                       set_progress_message("Saving a crd file...\n(#{i} frames completed)")
+                       set_progress_message("Saving AMBER crd file...\n(#{i} frames completed)")
                  end
                }
        ensure
@@ -104,39 +124,167 @@ class Molecule
        true
   end
 
-  def loadlog(filename)
+  alias :loadmdcrd :loadcrd
+  alias :savemdcrd :savecrd
+
+  def sub_load_gamess_log_basis_set(lines, lineno)
+    ln = 0
+       while (line = lines[ln])
+               ln += 1
+               break if line =~ /SHELL\s+TYPE\s+PRIMITIVE/
+       end
+       ln += 1
+       i = -1
+       nprims = 0
+       sym = -10  #  undefined
+       ncomps = 0
+       clear_basis_set
+       while (line = lines[ln])
+               ln += 1
+               break if line =~ /TOTAL NUMBER OF BASIS SET/
+               if line =~ /^\s*$/
+                 #  End of one shell
+                 add_gaussian_orbital_shell(i, sym, nprims)
+                 nprims = 0
+                 sym = -10
+                 next
+               end
+               a = line.split
+               if a.length == 1
+                 i += 1
+                 ln += 1  #  Skip the blank line
+                 next
+               elsif a.length == 5 || a.length == 6
+                 if sym == -10
+                       case a[1]
+                       when "S"
+                         sym = 0; n = 1
+                       when "P"
+                         sym = 1; n = 3
+                       when "L"
+                         sym = -1; n = 4
+                       when "D"
+                         sym = 2; n = 6
+                       when "F"
+                         sym = 3; n = 10
+                       when "G"
+                         sym = 4; n = 15
+                       else
+                         raise MolbyError, "Unknown gaussian shell type at line #{lineno + ln}"
+                       end
+                       ncomps += n
+                 end
+                 if (a.length == 5 && sym == -1) || (a.length == 6 && sym != -1)
+                       raise MolbyError, "Wrong format in gaussian shell information at line #{lineno + ln}"
+                 end
+                 exp = Float(a[3])
+                 c = Float(a[4])
+                 csp = Float(a[5] || 0.0)
+                 add_gaussian_primitive_coefficients(exp, c, csp)
+                 nprims += 1
+               else
+                 raise MolbyError, "Error in reading basis set information at line #{lineno + ln}"
+               end
+       end
+       return ncomps
+  end
+
+  def sub_load_gamess_log_mo_coefficients(lines, lineno, ncomps)
+    ln = 0
+       idx = 0
+       alpha = true
+       while (line = lines[ln]) != nil
+               ln += 1
+               if line =~ /BETA SET/
+                       alpha = false
+                       next
+               end
+               if line =~ /------------/ || line =~ /EIGENVECTORS/ || line =~ /\*\*\*\* (ALPHA|BETA) SET/
+                       next
+               end
+               next unless line =~ /^\s*\d/
+               mo_labels = line.split       #  MO numbers (1-based)
+               mo_energies = lines[ln].split
+               mo_symmetries = lines[ln + 1].split
+       #       puts "mo #{mo_labels.inspect}"
+               ln += 2
+               mo = mo_labels.map { [] }    #  array of *independent* empty arrays
+               while (line = lines[ln]) != nil
+                       ln += 1
+                       break unless line =~ /^\s*\d/
+                       5.times { |i|
+                         s = line[15 + 11 * i, 11].chomp
+                         break if s =~ /^\s*$/
+                         mo[i].push(Float(s)) rescue print "line = #{line}, s = #{s}"
+                       # line[15..-1].split.each_with_index { |s, i|
+                       #       mo[i].push(Float(s))
+                       }
+               end
+               mo.each_with_index { |m, i|
+                       idx = Integer(mo_labels[i])
+                       set_mo_coefficients(idx + (alpha ? 0 : ncomps), Float(mo_energies[i]), m)
+               #       if mo_labels[i] % 8 == 1
+               #               puts "set_mo_coefficients #{idx}, #{mo_energies[i]}, [#{m[0]}, ..., #{m[-1]}]"
+               #       end
+               }
+#              if line =~ /^\s*$/
+#                      next
+#              else
+#                      break
+#              end
+       end
+  end
+    
+  def sub_load_gamess_log(fp)
 
     if natoms == 0
                new_unit = true
        else
                new_unit = false
     end
-#      save_undo_enabled = self.undo_enabled?
-#      self.undo_enabled = false
        self.update_enabled = false
-       show_progress_panel("Loading GAMESS log file...")
+       mes = "Loading GAMESS log file"
+       show_progress_panel(mes)
+       energy = nil
+       ne_alpha = ne_beta = 0   #  number of electrons
+       rflag = nil  #  0, UHF; 1, RHF; 2, ROHF
+       mo_count = 0
+       search_mode = 0   #  0, no search; 1, optimize; 2, irc
+       ncomps = 0   #  Number of AO terms per one MO (sum of the number of components over all shells)
+       alpha_beta = nil   #  Flag to read alpha/beta MO appropriately
+       nsearch = 0  #  Search number for optimization
        begin
-               fp = open(filename, "rb")
-               if nframes > 0
-                       create_frame
-                       frame = nframes - 1
-               end
+       #       if nframes > 0
+       #               create_frame
+       #               frame = nframes - 1
+       #       end
                n = 0
                while 1
                        line = fp.gets
                        if line == nil
-                               fp.close
                                break
                        end
-                       line.chomp
-                       if line =~ /ATOM\s+ATOMIC\s+COORDINATES/ || line =~ /COORDINATES OF ALL ATOMS ARE/
-                               first_line = (line =~ /ATOMIC/)
-                               line = fp.gets    #  Skip one line
+                       line.chomp!
+                       if line =~ /ATOM\s+ATOMIC\s+COORDINATES/ || line =~ /COORDINATES OF ALL ATOMS ARE/ || line =~ /COORDINATES \(IN ANGSTROM\) FOR \$DATA GROUP ARE/
+                               set_progress_message(mes + "\nReading atomic coordinates...")
+                               if line =~ /ATOMIC/
+                                 first_line = true
+                               #  if !new_unit
+                               #    next   #  Skip initial atomic coordinates unless loading into an empty molecule
+                               #  end
+                                 line = fp.gets  #  Skip one line
+                               else
+                                 first_line = false
+                                 nsearch += 1
+                                 if line =~ /COORDINATES OF ALL ATOMS ARE/
+                                   line = fp.gets  #  Skip one line
+                             end
+                               end
                                n = 0
                                coords = []
                                names = []
                                while (line = fp.gets) != nil
-                                       break if line =~ /^\s*$/ || line =~ /END OF ONE/
+                                       break if line =~ /^\s*$/ || line =~ /END OF ONE/ || line =~ /\$END/
                                        next if line =~ /-----/
                                        name, charge, x, y, z = line.split
                                        v = Vector3D[x, y, z]
@@ -162,11 +310,42 @@ class Molecule
                                #               }
                                #       }
                                        new_unit = false
-                                       create_frame
+                               #       create_frame
                                else
-                                       create_frame([coords])  #  Should not be (coords)
+                                   dont_create = false
+                                       if (search_mode == 1 && nsearch == 1) || first_line
+                                               #  The input coordinate and the first frame for geometry search
+                                               #  can have the same coordinate as the last frame; if this is the case, then
+                                               #  do not create the new frame
+                                               select_frame(nframes - 1)
+                                               dont_create = true
+                                               each_atom { |ap|
+                                                 if (ap.r - coords[ap.index]).length2 > 1e-8
+                                                   dont_create = false
+                                                       break
+                                                 end
+                                               }
+                                       end
+                                       if !dont_create
+                                               create_frame([coords])  #  Should not be (coords)
+                                       end
+                               end
+                               set_property("energy", energy) if energy
+                       elsif line =~ /BEGINNING GEOMETRY SEARCH POINT/
+                               energy = nil   #  New search has begun, so clear the energy
+                               search_mode = 1
+                       elsif line =~ /CONSTRAINED OPTIMIZATION POINT/
+                               energy = nil   #  New search has begun, so clear the energy
+                               search_mode = 2
+                       elsif line =~ /FINAL .* ENERGY IS *([-.0-9]+) AFTER/
+                               if search_mode != 2
+                                       energy = $1.to_f
+                                       set_property("energy", energy)
                                end
-                       elsif line =~ /EQUILIBRIUM GEOMETRY LOCATED/i
+                       elsif line =~ /TOTAL ENERGY += +([-.0-9]+)/
+                               energy = $1.to_f
+                       elsif false && line =~ /EQUILIBRIUM GEOMETRY LOCATED/i
+                               set_progress_message(mes + "\nReading optimized coordinates...")
                                fp.gets; fp.gets; fp.gets
                                n = 0
                                while (line = fp.gets) != nil
@@ -178,111 +357,100 @@ class Molecule
                                        n += 1
                                        break if n >= natoms
                                end
+                       #       if ne_alpha > 0 && ne_beta > 0
+                       #               #  Allocate basis set record again, to update the atomic coordinates
+                       #               allocate_basis_set_record(rflag, ne_alpha, ne_beta)
+                       #       end
+                       elsif line =~ /ATOMIC BASIS SET/
+                               lines = []
+                               lineno = fp.lineno
+                               while (line = fp.gets)
+                                       break if line =~ /TOTAL NUMBER OF BASIS SET/
+                                       line.chomp!
+                                       lines.push(line)
+                               end
+                               ncomps = sub_load_gamess_log_basis_set(lines, lineno)
+                       elsif line =~ /NUMBER OF OCCUPIED ORBITALS/
+                               line =~ /=\s*(\d+)/
+                               n = Integer($1)
+                               if line =~ /ALPHA/
+                                       ne_alpha = n
+                               else
+                                       ne_beta = n
+                               end
+                       elsif line =~ /SCFTYP=(\w+)/
+                               scftyp = $1
+                               if ne_alpha > 0 || ne_beta > 0
+                                       rflag = 0
+                                       case scftyp
+                                       when "RHF"
+                                               rflag = 1
+                                       when "ROHF"
+                                               rflag = 2
+                                       end
+                               end
+                       elsif line =~ /(ALPHA|BETA)\s*SET/
+                               alpha_beta = $1
+                       elsif line =~ /^\s*(EIGENVECTORS|MOLECULAR ORBITALS)\s*$/
+                               if mo_count == 0
+                                       clear_mo_coefficients
+                                       set_mo_info(:type=>["UHF", "RHF", "ROHF"][rflag], :alpha=>ne_alpha, :beta=>ne_beta)
+                               end
+                               mo_count += 1
+                               line = fp.gets; line = fp.gets
+                               lineno = fp.lineno
+                               lines = []
+                               set_progress_message(mes + "\nReading MO coefficients...")
+                               while (line = fp.gets)
+                                       break if line =~ /\.\.\.\.\.\./ || line =~ /----------------/
+                                       line.chomp!
+                                       lines.push(line)
+                               end
+                               sub_load_gamess_log_mo_coefficients(lines, lineno, ncomps)
+                               set_progress_message(mes)
+                       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/
+                               nbo_lines = []
+                               while (line = fp.gets) != nil
+                                 break if line =~ /done with NBO analysis/
+                                 nbo_lines.push(line)
+                               end
+                               import_nbo_log(nbo_lines)
                        end
                end
-       ensure
-#              self.undo_enabled = save_undo_enabled
-        hide_progress_panel
-               self.update_enabled = true
        end
        if nframes > 0
          select_frame(nframes - 1)
        end
-       (n > 0 ? true : false)
-  end
-  
-  def loadxyz(filename)
-#      save_undo_enabled = self.undo_enabled?
-#      self.undo_enabled = false
-       fp = open(filename, "rb")
-       n = 0
-       coords = []
-       names = []
-       cell = nil
-       while 1
-         line = fp.gets
-         if line == nil
-               fp.close
-               break
-         end
-         line.chomp
-         toks = line.split
-         if coords.length == 0
-           #  Allow "number of atoms" line or "number of atoms and crystallographic cell parameter"
-               #  (Chem3D xyz format)
-               next if toks.length == 1
-               if toks.length == 7
-                 cell = toks[1..6].map { |s| Float(s.sub(/\(\d+\)/, "")) }  #  Remove (xx) and convert to float
-                 next
-               end
-         end
-         name, x, y, z = line.split
-         next if z == nil
-         x = Float(x.sub(/\(\d+\)/, ""))
-         y = Float(y.sub(/\(\d+\)/, ""))
-         z = Float(z.sub(/\(\d+\)/, ""))
-         r = Vector3D[x, y, z]
-         coords.push(r)
-         names.push(name)
-         n += 1
-       end
-       celltr = nil
-       if cell
-         self.cell = cell
-         celltr = self.cell_transform
+       if energy && energy != 0.0
+         set_property("energy", energy)
        end
-       names.each_index { |i|
-         ap = add_atom(names[i])
-         names[i] =~ /^([A-Za-z]{1,2})/
-         element = $1.capitalize
-         ap.element = element
-         ap.atom_type = element
-         if celltr
-           ap.r = celltr * coords[i]
-         else
-           ap.r = coords[i]
-         end
-       }
-       guess_bonds
-       #  Find bonds
-#      atoms.each { |ap|
-#        j = ap.index
-#        (j + 1 ... natoms).each { |k|
-#              if calc_bond(j, k) < 1.7
-#              #  create_bond(j, k)
-#              end
-#        }
-#      }
-#      self.undo_enabled = save_undo_enabled
+       hide_progress_panel
+       self.update_enabled = true
        (n > 0 ? true : false)
   end
   
-  def loadout(filename)
+  def sub_load_gaussian_log(fp)
 
     if natoms == 0
                new_unit = true
- #     raise MolbyError, "cannot load crd; the molecule is empty"
        else
                new_unit = false
     end
-#      save_undo_enabled = undo_enabled?
-#      self.undo_enabled = false
-       fp = open(filename, "rb")
        if nframes > 0
                create_frame
                frame = nframes - 1
        end
        n = 0
        nf = 0
+       energy = nil
        use_input_orientation = false
        show_progress_panel("Loading Gaussian out file...")
        while 1
                line = fp.gets
                if line == nil
-                       fp.close
                        break
                end
-               line.chomp
+               line.chomp!
                if line =~ /(Input|Standard) orientation/
                        match = $1
                        if match == "Input"
@@ -322,18 +490,708 @@ class Molecule
                        #       }
                                guess_bonds
                                new_unit = false
-                               create_frame
+                       #       create_frame
                        else
                                create_frame([coords])  #  Should not be (coords)
                        end
+                       if energy
+                               # TODO: to ensure whether the energy output line comes before
+                               # or after the atomic coordinates.
+                               set_property("energy", energy)
+                       end
                        nf += 1
+               elsif line =~ /SCF Done: *E\(\w+\) *= *([-.0-9]+)/
+                       energy = $1.to_f
                end
        end
+       if energy
+               set_property("energy", energy)
+       end
        hide_progress_panel
-#      self.undo_enabled = save_undo_enabled
        (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
+    #  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 "[5D]" or "[9G]".
+    gtos = []
+    spherical = 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\]/
+          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
+          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
+              a[0] =~ /^([a-z]+)([0-9]+)?$/
+              symcode = $1
+              add_exp = ($2 == nil ? 0 : $2.to_i)
+              case symcode
+              when "s"
+                sym = 0
+              when "p"
+                sym = 1
+              when "d"
+                sym = 2
+              when "f"
+                sym = 3
+              when "g"
+                sym = 4
+              else
+                raise MolbyError, "Unknown gaussian shell type '#{a[0]}' at line #{@lineno} in MOLDEN file"
+              end
+              nprimitives = Integer(a[1])
+              gtoline = [sym, [], add_exp]
+              atom_gtos.push(gtoline)
+              nprimitives.times { |i|
+                line = getline.call   #  exponent, contraction
+                b = line.split(' ')
+                gtoline[1].push(Float(b[0]), Float(b[1]))
+              }
+              #  end of one shell
+              shell += 1
+            end
+            #  end of one atom
+            atom_index += 1
+            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 =~ /^\[9G\]/
+          spherical = true
+          #  Change the orbital type if necessary
+          gtos.each_with_index { | atom_gtos, atom_index|
+            atom_gtos.each { |gtoline|
+              if gtoline[0] >= 2
+                gtoline[0] = -gtoline[0]  #  D5/F7/G9
+              end
+            }
+          }
+        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]
+              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
+          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
+          #  TODO: reorder D, F, G coefficients for Molby order
+          next
+        end #  end if
+      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}
+  #  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
+      print("Importing #{inppath}.janpa.log.\n")
+      lineno = 0
+      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 =~ /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]/
+            num = Integer(line[0, 5])
+            name = line[5, 21]
+            occ = Float(line[26, 11])
+            #  like A1*: R1*s(0)
+            #  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
+            group_num = Integer($3)
+            orb_sym = $4
+            ang_num = Integer($5)
+            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
+          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 || "")
+            if desc2 =~ /^(.*)*\(NB\)\s*$/ && label1 == ""
+              label1 = "(NB)"
+              desc2 = $1.strip
+            end
+            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*)/
+          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
+          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")
+      $stderr.write(e.backtrace.inspect + "\n")
+    end
+  end
+
+  def loadout(filename)
+  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
+  
+  alias :loadlog :loadout
+
+  def loadxyz(filename)
+       fp = open(filename, "rb")
+       n = 0
+       coords = []
+       names = []
+       cell = nil
+       while 1
+         line = fp.gets
+         if line == nil
+               fp.close
+               break
+         end
+         line.chomp
+         toks = line.split
+         if coords.length == 0
+           #  Allow "number of atoms" line or "number of atoms and crystallographic cell parameter"
+               #  (Chem3D xyz format)
+               next if toks.length == 1
+               if toks.length == 7
+                 cell = toks[1..6].map { |s| Float(s.sub(/\(\d+\)/, "")) }  #  Remove (xx) and convert to float
+                 next
+               end
+         end
+         name, x, y, z = line.split
+         next if z == nil
+         x = Float(x.sub(/\(\d+\)/, ""))
+         y = Float(y.sub(/\(\d+\)/, ""))
+         z = Float(z.sub(/\(\d+\)/, ""))
+         r = Vector3D[x, y, z]
+         coords.push(r)
+         names.push(name)
+         n += 1
+       end
+       celltr = nil
+       if cell
+         self.cell = cell
+         celltr = self.cell_transform
+       end
+       names.each_index { |i|
+         ap = add_atom(names[i])
+         names[i] =~ /^([A-Za-z]{1,2})/
+         element = $1.capitalize
+         ap.element = element
+         ap.atom_type = element
+         if celltr
+           ap.r = celltr * coords[i]
+         else
+           ap.r = coords[i]
+         end
+       }
+       guess_bonds
+       #  Find bonds
+#      atoms.each { |ap|
+#        j = ap.index
+#        (j + 1 ... natoms).each { |k|
+#              if calc_bond(j, k) < 1.7
+#              #  create_bond(j, k)
+#              end
+#        }
+#      }
+#      self.undo_enabled = save_undo_enabled
+       (n > 0 ? true : false)
+  end
+  
+  def savexyz(filename)
+    open(filename, "wb") { |fp|
+         fp.printf "%d\n", self.natoms
+         each_atom { |ap|
+           fp.printf "%s %.5f %.5f %.5f\n", ap.element, ap.x, ap.y, ap.z
+         }
+       }
+       return true
+  end
+  
+  def loadzmat(filename)
+    self.remove(All)
+       open(filename, "rb") { |fp|
+         while (line = fp.gets)
+           line.chomp!
+               a = line.split
+               an = Molecule.guess_atomic_number_from_name(a[0])
+               elm = Parameter.builtin.elements[an].name
+               base1 = a[1].to_i - 1
+               base2 = a[3].to_i - 1
+               base3 = a[5].to_i - 1
+               base1 = nil if base1 < 0
+               base2 = nil if base2 < 0
+               base3 = nil if base3 < 0
+               add_atom(a[0], elm, elm, a[2].to_f, base1, a[4].to_f, base2, a[6].to_f, base3)
+         end
+       }
+       return true
+  end
+  
+  def savezmat(filename)
+  end
+  
   def loadcom(filename)
 #      save_undo_enabled = self.undo_enabled?
 #      self.undo_enabled = false
@@ -366,6 +1224,7 @@ class Molecule
          end
        end
        fp.close
+       guess_bonds
 #      self.undo_enabled = save_undo_enabled
        return true
   end
@@ -434,6 +1293,7 @@ class Molecule
  C1
 end_of_header
        each_atom { |ap|
+               next if ap.atomic_number == 0
                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
        }
        fp.print " $END\n"
@@ -456,6 +1316,7 @@ end_of_header
  0 1
 end_of_header
        each_atom { |ap|
+           next if ap.atomic_number == 0
                fp.printf "%-6s %10.6f %10.6f %10.6f\n", ap.element, ap.r.x, ap.r.y, ap.r.z
        }
        fp.print "\n"
@@ -466,6 +1327,394 @@ end_of_header
   alias :loadgjf :loadcom
   alias :savegjf :savecom
   
+  def loadcif(filename)
+    mol = self
+    def getciftoken(fp)
+         while @tokens.length == 0
+           line = fp.gets
+           return nil if !line
+               if line[0] == ?;
+                 s = line
+                 while line = fp.gets
+                   break if line[0] == ?;
+                   s += line
+                 end
+                 return s if !line
+                 s += ";"
+                 @tokens.push(s)
+                 line = line[1...line.length]
+               end
+           line.strip!
+           line.scan(/'[^\']*'|"[^\"]*"|[^#\s]+|#.*/) do |s|
+             next if s[0] == ?#
+                 if s =~ /^data_|loop_|global_|save_|stop_/i
+                   s = "#" + s    #  Label for reserved words
+                 end
+                 @tokens.push(s)
+           end
+         end
+         return @tokens.shift
+       end
+       def float_strip_rms(str)
+         str =~ /^(-?)(\d*)(\.(\d*))?(\((\d+)\))?$/
+         sgn, i, frac, rms = $1, $2, $4, $6
+         i = i.to_f
+         if frac
+               base = 0.1 ** frac.length
+               i = i + frac.to_f * base
+         else
+           base = 1.0
+         end
+         if rms
+           rms = rms.to_f * base
+         else
+           rms = 0.0
+         end
+         if sgn == "-"
+           i = -i
+         end
+         return i, rms
+       end
+       def parse_symmetry_operation(str)
+         if str == "."
+           sym = nil
+         elsif (str =~ /(\d+)_(\d)(\d)(\d)/) || (str =~ /(\d+) +(\d)(\d)(\d)/)
+           sym = [Integer($1) - 1, Integer($2) - 5, Integer($3) - 5, Integer($4) - 5]
+         elsif (str =~ /^(\d+)$/)
+           sym = [Integer($1) - 1, 0, 0, 0]
+         end
+         if sym && (sym[0] == 0 && sym[1] == 0 && sym[2] == 0 && sym[3] == 0)
+           sym = nil
+         end
+         sym
+       end
+       def find_atom_by_name(mol, name)
+         name = name.delete(" ()")
+         ap = mol.atoms[name] rescue ap = nil
+         return ap
+       end
+       selfname = self.name
+       fp = open(filename, "rb")
+       data_identifier = nil
+       @tokens = []
+       count_up = 1
+       while true
+         warn_message = ""
+         verbose = nil
+         bond_defined = false
+         special_positions = []
+         mol.remove(All)
+         cell = []
+         cell_trans = cell_trans_inv = Transform.identity
+         token = getciftoken(fp)
+         pardigits_re = /\(\d+\)/
+         calculated_atoms = []
+         while token != nil
+           if token =~ /^\#data_/i
+                 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
+                 else
+                   #  Description of another molecule begins here
+                       data_identifier = token
+                       break
+                 end
+           elsif token =~ /^_cell/
+                 val = getciftoken(fp)
+                 if token == "_cell_length_a"
+                   cell[0], cell[6] = float_strip_rms(val)
+                 elsif token == "_cell_length_b"
+                   cell[1], cell[7] = float_strip_rms(val)
+                 elsif token == "_cell_length_c"
+                   cell[2], cell[8] = float_strip_rms(val)
+                 elsif token == "_cell_angle_alpha"
+                   cell[3], cell[9] = float_strip_rms(val)
+                 elsif token == "_cell_angle_beta"
+                   cell[4], cell[10] = float_strip_rms(val)
+                 elsif token == "_cell_angle_gamma"
+                   cell[5], cell[11] = float_strip_rms(val)
+                 end
+                 if cell.length == 12 && cell.all?
+                   mol.cell = cell
+            puts "Unit cell is set to #{mol.cell.inspect}." if verbose
+                   cell = []
+                   cell_trans = mol.cell_transform
+                   cell_trans_inv = cell_trans.inverse
+                 end
+                 token = getciftoken(fp)
+                 next
+        elsif token.casecmp("#loop_") == 0
+             labels = []
+                 while (token = getciftoken(fp)) && token[0] == ?_
+                   labels.push(token)
+                 end
+                 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
+                   }
+                   data = []
+                   n = labels.length
+                   a = []
+                   while true
+                         break if token == nil || token[0] == ?_ || token[0] == ?#
+                         a.push(token)
+                         if a.length == n
+                           data.push(a)
+                           a = []
+                         end
+                         token = getciftoken(fp)
+                   end
+                   if labels[0] =~ /^_symmetry_equiv_pos/ || labels[0] =~ /^_space_group_symop/
+                     data.each { |d|
+                           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]
+                           exps.each_with_index { |s, i|
+                             terms = s.scan(/([-+]?)(([.0-9]+)(\/([0-9]+))?([xXyYzZ])?|([xXyYzZ]))/)
+                                 terms.each { |a|
+                                   #  a[0]: sign, a[2]: numerator, a[4]: denometer
+                                   if a[4] != nil
+                                     #  The number part is a[2]/a[4]
+                                     num = Float(a[2])/Float(a[4])
+                                   elsif a[2] != nil
+                                     #  The number part is either integer or a floating point
+                                     num = Float(a[2])
+                                   else
+                                     num = 1.0
+                                   end
+                                   num = -num if a[0][0] == ?-
+                                   xyz = (a[5] || a[6])
+                                   if xyz == "x" || xyz == "X"
+                                     sym[i] = num
+                                   elsif xyz == "y" || xyz == "Y"
+                                     sym[i + 3] = num
+                                   elsif xyz == "z" || xyz == "Z"
+                                     sym[i + 6] = num
+                                   else
+                                     sym[9 + i] = num
+                                   end
+                                 }
+                           }
+                           puts "symmetry operation #{sym.inspect}" if verbose
+                           mol.add_symmetry(Transform.new(sym))
+                         }
+                         puts "#{mol.nsymmetries} symmetry operations are added" if verbose
+                   elsif labels[0] =~ /^_atom_site_label/
+                         #  Create atoms
+                         data.each { |d|
+                           name = d[hlabel["_atom_site_label"]]
+                           elem = d[hlabel["_atom_site_type_symbol"]]
+                           fx = d[hlabel["_atom_site_fract_x"]]
+                           fy = d[hlabel["_atom_site_fract_y"]]
+                           fz = d[hlabel["_atom_site_fract_z"]]
+                           uiso = d[hlabel["_atom_site_U_iso_or_equiv"]]
+                           biso = d[hlabel["_atom_site_B_iso_or_equiv"]]
+                           occ = d[hlabel["_atom_site_occupancy"]]
+                           calc = d[hlabel["_atom_site_calc_flag"]]
+                           name = name.delete(" ()")
+                           if elem == nil || elem == ""
+                             if name =~ /[A-Za-z]{1,2}/
+                                   elem = $&.capitalize
+                                 else
+                                   elem = "Du"
+                                 end
+                           end
+                           ap = mol.add_atom(name, elem, elem)
+                           ap.fract_x, ap.sigma_x = float_strip_rms(fx)
+                           ap.fract_y, ap.sigma_y = float_strip_rms(fy)
+                           ap.fract_z, ap.sigma_z = float_strip_rms(fz)
+                           if biso
+                             ap.temp_factor, sig = float_strip_rms(biso)
+                           elsif uiso
+                             ap.temp_factor, sig = float_strip_rms(uiso)
+                                 ap.temp_factor *= 78.9568352087149          #  8*pi*pi
+                           end
+                           ap.occupancy, sig = float_strip_rms(occ)
+                           if calc == "c" || calc == "calc"
+                             calculated_atoms.push(ap.index)
+                       end
+                           #  Guess special positions
+                           (1...mol.nsymmetries).each { |isym|
+                             sr = ap.fract_r
+                             sr = (mol.transform_for_symop(isym) * sr) - sr;
+                                 nx = (sr.x + 0.5).floor
+                                 ny = (sr.y + 0.5).floor
+                                 nz = (sr.z + 0.5).floor
+                                 if (Vector3D[sr.x - nx, sr.y - ny, sr.z - nz].length2 < 1e-6)
+                                   #  [isym, -nx, -ny, -nz] transforms this atom to itself
+                                   #  The following line is equivalent to:
+                                   #    if special_positions[ap.index] == nil; special_positions[ap.index] = []; end;
+                                   #    special_positions[ap.index].push(...)
+                                   (special_positions[ap.index] ||= []).push([isym, -nx, -ny, -nz])
+                                 end
+                           }
+                           if verbose && special_positions[ap.index]
+                             puts "#{name} is on the special position: #{special_positions[ap.index].inspect}"
+                           end
+                         }
+                         puts "#{mol.natoms} atoms are created." if verbose
+                   elsif labels[0] =~ /^_atom_site_aniso_label/
+                     #  Set anisotropic parameters
+                         c = 0
+                         data.each { |d|
+                           name = d[hlabel["_atom_site_aniso_label"]]
+                           ap = find_atom_by_name(mol, name)
+                           next if !ap
+                           u11 = d[hlabel["_atom_site_aniso_U_11"]]
+                           if u11
+                             usig = []
+                             u11, usig[0] = float_strip_rms(u11)
+                             u22, usig[1] = float_strip_rms(d[hlabel["_atom_site_aniso_U_22"]])
+                             u33, usig[2] = float_strip_rms(d[hlabel["_atom_site_aniso_U_33"]])
+                             u12, usig[3] = float_strip_rms(d[hlabel["_atom_site_aniso_U_12"]])
+                             u13, usig[4] = float_strip_rms(d[hlabel["_atom_site_aniso_U_13"]])
+                             u23, usig[5] = float_strip_rms(d[hlabel["_atom_site_aniso_U_23"]])
+                             ap.aniso = [u11, u22, u33, u12, u13, u23, 8] + usig
+                                 c += 1
+                           end
+                         }
+                         puts "#{c} anisotropic parameters are set." if verbose
+                   elsif labels[0] =~ /^_geom_bond/
+                     #  Create bonds
+                         exbonds = []
+                         data.each { |d|
+                           n1 = d[hlabel["_geom_bond_atom_site_label_1"]]
+                           n2 = d[hlabel["_geom_bond_atom_site_label_2"]]
+                           sym1 = d[hlabel["_geom_bond_site_symmetry_1"]] || "."
+                           sym2 = d[hlabel["_geom_bond_site_symmetry_2"]] || "."
+                           n1 = find_atom_by_name(mol, n1)
+                           n2 = find_atom_by_name(mol, n2)
+                           next if n1 == nil || n2 == nil
+                       n1 = n1.index
+                           n2 = n2.index
+                           sym1 = parse_symmetry_operation(sym1)
+                           sym2 = parse_symmetry_operation(sym2)
+                           if sym1 || sym2
+                             exbonds.push([n1, n2, sym1, sym2])
+                           else
+                             mol.create_bond(n1, n2)
+                           end
+                           tr1 = (sym1 ? mol.transform_for_symop(sym1) : Transform.identity)
+                           tr2 = (sym2 ? mol.transform_for_symop(sym2) : Transform.identity)
+                           if special_positions[n1]
+                                 #  Add extra bonds for equivalent positions of n1
+                                 special_positions[n1].each { |symop|
+                                   sym2x = mol.symop_for_transform(tr1 * mol.transform_for_symop(symop) * tr1.inverse * tr2)
+                                   exbonds.push([n1, n2, sym1, sym2x])
+                                 }
+                           end
+                           if special_positions[n2]
+                                 #  Add extra bonds n2-n1.symop, where symop transforms n2 to self
+                                 tr = (sym1 ? mol.transform_for_symop(sym1) : Transform.identity)
+                                 special_positions[n2].each { |symop|
+                                   sym1x = mol.symop_for_transform(tr2 * mol.transform_for_symop(symop) * tr2.inverse * tr1)
+                                   exbonds.push([n2, n1, sym2, sym1x])
+                                 }
+                           end                         
+                     }
+              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
+                           n1 = 0
+                           calculated_atoms.each { |ai|
+                             if mol.atoms[ai].connects.length == 0
+                                   as = mol.find_close_atoms(ai)
+                                   as.each { |aj|
+                                     mol.create_bond(ai, aj)
+                                         n1 += 1
+                                   }
+                                 end
+                           }
+                           puts "#{n1} bonds are guessed." if verbose
+                         end
+                         if exbonds.length > 0
+                           h = Dialog.run("CIF Import: Symmetry Expansion") {
+                             layout(1,
+                                   item(:text, :title=>"There are bonds including symmetry related atoms.\nWhat do you want to do?"),
+                                   item(:radio, :title=>"Expand only atoms that are included in those extra bonds.", :tag=>"atoms_only"),
+                                   item(:radio, :title=>"Expand fragments having atoms included in the extra bonds.", :tag=>"fragment", :value=>1),
+                                   item(:radio, :title=>"Ignore these extra bonds.", :tag=>"ignore")
+                                 )
+                                 radio_group("atoms_only", "fragment", "ignore")
+                           }
+                           if h[:status] == 0 && h["ignore"] == 0
+                             atoms_only = (h["atoms_only"] != 0)
+                                 if !atoms_only
+                                   fragments = []
+                                   mol.each_fragment { |f| fragments.push(f) }
+                                 end
+                                 debug = nil
+                                 exbonds.each { |ex|
+                                   if debug; puts "extra bond #{ex[0]}(#{ex[2].inspect}) - #{ex[1]}(#{ex[3].inspect})"; end
+                                   ex0 = ex.dup
+                                   (2..3).each { |i|
+                                     symop = ex[i]
+                                         if symop == nil
+                                           ex[i + 2] = ex[i - 2]
+                                         else
+                                           if debug; puts "  symop = #{symop.inspect}"; end
+                                           #  Expand the atom or the fragment including the atom
+                                           if atoms_only
+                                                 ig = IntGroup[ex[i - 2]]
+                                                 idx = 0
+                                           else
+                                                 ig = fragments.find { |f| f.include?(ex[i - 2]) }
+                                                 ig.each_with_index { |n, ii| if n == ex[i - 2]; idx = ii; break; end }
+                                           end
+                                           symop[4] = ex[i - 2]  #  Base atom
+                                           if debug; puts "  expanding #{ig} by #{symop.inspect}"; end
+                                           a = mol.expand_by_symmetry(ig, symop[0], symop[1], symop[2], symop[3])
+                                           ex[i + 2] = a[idx]   #  Index of the expanded atom
+                                         end
+                                   }
+                                   if ex[4] && ex[5] && ex[4] != ex[5]
+                                     if debug; puts "  creating bond #{ex[4]} - #{ex[5]}"; end
+                                     mol.create_bond(ex[4], ex[5])
+                                   end
+                                 }
+                           end
+                         end
+                         puts "#{mol.nbonds} bonds are created." if verbose
+                   end
+                   next
+                 else
+                 #  puts "Loop beginning with #{labels[0]} is skipped"
+                 end
+           else
+             #  Skip this token
+                 token = getciftoken(fp)
+           end
+           #  Skip tokens until next tag or reserved word is detected
+           while token != nil && token[0] != ?_ && token[0] != ?#
+                 token = getciftoken(fp)
+           end
+           next
+         end
+      if !bond_defined
+               mol.guess_bonds
+         end
+         if token != nil && token == data_identifier
+           #  Process next molecule: open a new molecule and start adding atom on that
+               mol = Molecule.new
+               count_up += 1
+               (@aux_mols ||= []).push(mol)
+               next
+         end
+         break
+       end
+       fp.close
+#      self.undo_enabled = save_undo_enabled
+       return true
+  end
+  
   def dump(group = nil)
     def quote(str)
          if str == ""
@@ -498,10 +1747,12 @@ end_of_header
        keys = []
        resAtoms = Hash.new
        newBonds = []
+       #  arg can be either a String or an array of String.
+       #  Iterates for each line in the string or each member of the array.
+       if arg.is_a?(String)
+         arg = arg.split("\n")
+       end
     arg.each { |line|
-      #  arg can be either a String or an array of String. If it is a string,
-         #  arg.each iterates for each line in the string. If it is an array,
-         #  arg.each iterates for each member of the array.
          if line =~ /^\#/
            format = line[1..-1]
                keys = []
@@ -557,4 +1808,14 @@ end_of_header
        self
   end
 
+  #  Plug-in for loading mbsf
+  def loadmbsf_plugin(s, lineno)
+    ""
+  end
+  
+  #  Plug-in for saving mbsf
+  def savembsf_plugin
+    ""
+  end
+  
 end