5 # Created by Toshi Nagata.
6 # Copyright 2008 Toshi Nagata. All rights reserved.
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation version 2 of the License.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
23 raise MolbyError, "cannot load crd; the molecule is empty"
25 fp = open(filename, "rb")
28 coords = (0...natoms).collect { Vector3D[0, 0, 0] }
29 periodic = (self.box && self.box[4].any? { |n| n != 0 })
30 show_progress_panel("Loading AMBER crd file...")
31 # puts "sframe = #{sframe}, pos = #{fp.pos}"
34 values = line.scan(/......../)
36 # The first line should be skipped. However, if this line seems to contain
37 # coordinates, then try reading them
38 if values.size != 10 && (values.size > 10 || values.size != natoms * 3)
39 next # Wrong number of coordinates
41 if values.each { |v| Float(v) rescue break } == nil
42 # The line contains non-number
45 puts "Loadcrd: The title line seems to be missing"
47 if count + values.size > natoms * 3
48 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)
51 coords[count / 3][count % 3] = Float(v)
54 if count == natoms * 3
56 if frame == 0 && atoms.all? { |ap| ap.r.x == 0.0 && ap.r.y == 0.0 && ap.r.z == 0.0 }
57 # Do not create a new frame
58 atoms.each_with_index { |ap, i| ap.r = coords[i] }
60 create_frame([coords])
63 # Should have box information
65 if line == nil || (values = line.chomp.scan(/......../)).length != 3
66 # Periodic but no cell information
67 puts "Loadcrd: the molecule has a periodic cell but the crd file does not contain cell info"
71 self.cell = [Float(values[0]), Float(values[1]), Float(values[2]), 90, 90, 90]
76 set_progress_message("Loading AMBER crd file...\n(#{frame} frames completed)")
82 raise MolbyError, sprintf("crd format error - file ended in the middle of frame %d", frame)
86 self.frame = self.nframes - 1
95 raise MolbyError, "cannot save crd; the molecule is empty"
97 fp = open(filename, "wb")
98 show_progress_panel("Saving AMBER crd file...")
99 fp.printf("TITLE: %d atoms\n", natoms)
101 nframes = self.nframes
103 self.update_enabled = false
105 (0...nframes).each { |i|
108 while (j < natoms * 3)
109 w = atoms[j / 3].r[j % 3]
110 fp.printf(" %7.3f", w)
111 fp.print("\n") if (j % 10 == 9 || j == natoms * 3 - 1)
115 set_progress_message("Saving AMBER crd file...\n(#{i} frames completed)")
119 self.update_enabled = true
127 alias :loadmdcrd :loadcrd
128 alias :savemdcrd :savecrd
130 def sub_load_gamess_log_basis_set(lines, lineno)
132 while (line = lines[ln])
134 break if line =~ /SHELL\s+TYPE\s+PRIMITIVE/
139 sym = -10 # undefined
142 while (line = lines[ln])
144 break if line =~ /TOTAL NUMBER OF BASIS SET/
147 add_gaussian_orbital_shell(i, sym, nprims)
155 ln += 1 # Skip the blank line
157 elsif a.length == 5 || a.length == 6
173 raise MolbyError, "Unknown gaussian shell type at line #{lineno + ln}"
177 if (a.length == 5 && sym == -1) || (a.length == 6 && sym != -1)
178 raise MolbyError, "Wrong format in gaussian shell information at line #{lineno + ln}"
182 csp = Float(a[5] || 0.0)
183 add_gaussian_primitive_coefficients(exp, c, csp)
186 raise MolbyError, "Error in reading basis set information at line #{lineno + ln}"
192 def sub_load_gamess_log_mo_coefficients(lines, lineno, ncomps)
196 while (line = lines[ln]) != nil
198 if line =~ /BETA SET/
202 if line =~ /------------/ || line =~ /EIGENVECTORS/ || line =~ /\*\*\*\* (ALPHA|BETA) SET/
205 next unless line =~ /^\s*\d/
206 mo_labels = line.split # MO numbers (1-based)
207 mo_energies = lines[ln].split
208 mo_symmetries = lines[ln + 1].split
209 # puts "mo #{mo_labels.inspect}"
211 mo = mo_labels.map { [] } # array of *independent* empty arrays
212 while (line = lines[ln]) != nil
214 break unless line =~ /^\s*\d/
216 s = line[15 + 11 * i, 11].chomp
217 break if s =~ /^\s*$/
218 mo[i].push(Float(s)) rescue print "line = #{line}, s = #{s}"
219 # line[15..-1].split.each_with_index { |s, i|
220 # mo[i].push(Float(s))
223 mo.each_with_index { |m, i|
224 idx = Integer(mo_labels[i])
225 set_mo_coefficients(idx + (alpha ? 0 : ncomps), Float(mo_energies[i]), m)
226 # if mo_labels[i] % 8 == 1
227 # puts "set_mo_coefficients #{idx}, #{mo_energies[i]}, [#{m[0]}, ..., #{m[-1]}]"
238 def sub_load_gamess_log(fp)
245 self.update_enabled = false
246 mes = "Loading GAMESS log file"
247 show_progress_panel(mes)
249 ne_alpha = ne_beta = 0 # number of electrons
250 rflag = nil # 0, UHF; 1, RHF; 2, ROHF
252 search_mode = 0 # 0, no search; 1, optimize; 2, irc
253 ncomps = 0 # Number of AO terms per one MO (sum of the number of components over all shells)
254 alpha_beta = nil # Flag to read alpha/beta MO appropriately
255 nsearch = 0 # Search number for optimization
259 # frame = nframes - 1
268 if line =~ /ATOM\s+ATOMIC\s+COORDINATES/ || line =~ /COORDINATES OF ALL ATOMS ARE/ || line =~ /COORDINATES \(IN ANGSTROM\) FOR \$DATA GROUP ARE/
269 set_progress_message(mes + "\nReading atomic coordinates...")
273 # next # Skip initial atomic coordinates unless loading into an empty molecule
275 line = fp.gets # Skip one line
279 if line =~ /COORDINATES OF ALL ATOMS ARE/
280 line = fp.gets # Skip one line
286 while (line = fp.gets) != nil
287 break if line =~ /^\s*$/ || line =~ /END OF ONE/ || line =~ /\$END/
288 next if line =~ /-----/
289 name, charge, x, y, z = line.split
290 v = Vector3D[x, y, z]
291 coords.push(v * (first_line ? 0.529177 : 1.0)) # Bohr to angstrom
292 names.push([name, charge])
295 # Build a new molecule
296 names.each_index { |i|
297 ap = add_atom(names[i][0])
298 ap.atomic_number = names[i][1].to_i
299 ap.atom_type = ap.element
306 # (j + 1 ... natoms).each { |k|
307 # if calc_bond(j, k) < 1.7
316 if (search_mode == 1 && nsearch == 1) || first_line
317 # The input coordinate and the first frame for geometry search
318 # can have the same coordinate as the last frame; if this is the case, then
319 # do not create the new frame
320 select_frame(nframes - 1)
323 if (ap.r - coords[ap.index]).length2 > 1e-8
330 create_frame([coords]) # Should not be (coords)
333 set_property("energy", energy) if energy
334 elsif line =~ /BEGINNING GEOMETRY SEARCH POINT/
335 energy = nil # New search has begun, so clear the energy
337 elsif line =~ /CONSTRAINED OPTIMIZATION POINT/
338 energy = nil # New search has begun, so clear the energy
340 elsif line =~ /FINAL .* ENERGY IS *([-.0-9]+) AFTER/
343 set_property("energy", energy)
345 elsif line =~ /TOTAL ENERGY += +([-.0-9]+)/
347 elsif false && line =~ /EQUILIBRIUM GEOMETRY LOCATED/i
348 set_progress_message(mes + "\nReading optimized coordinates...")
349 fp.gets; fp.gets; fp.gets
351 while (line = fp.gets) != nil
352 break if line =~ /^\s*$/
354 atom, an, x, y, z = line.split
356 ap.r = Vector3D[x, y, z]
360 # if ne_alpha > 0 && ne_beta > 0
361 # # Allocate basis set record again, to update the atomic coordinates
362 # allocate_basis_set_record(rflag, ne_alpha, ne_beta)
364 elsif line =~ /ATOMIC BASIS SET/
367 while (line = fp.gets)
368 break if line =~ /TOTAL NUMBER OF BASIS SET/
372 ncomps = sub_load_gamess_log_basis_set(lines, lineno)
373 elsif line =~ /NUMBER OF OCCUPIED ORBITALS/
381 elsif line =~ /SCFTYP=(\w+)/
383 if ne_alpha > 0 || ne_beta > 0
392 elsif line =~ /(ALPHA|BETA)\s*SET/
394 elsif line =~ /^\s*(EIGENVECTORS|MOLECULAR ORBITALS)\s*$/
396 clear_mo_coefficients
397 set_mo_info(:type=>["UHF", "RHF", "ROHF"][rflag], :alpha=>ne_alpha, :beta=>ne_beta)
400 line = fp.gets; line = fp.gets
403 set_progress_message(mes + "\nReading MO coefficients...")
404 while (line = fp.gets)
405 break if line =~ /\.\.\.\.\.\./ || line =~ /----------------/
409 sub_load_gamess_log_mo_coefficients(lines, lineno, ncomps)
410 set_progress_message(mes)
411 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/
413 while (line = fp.gets) != nil
414 break if line =~ /done with NBO analysis/
417 import_nbo_log(nbo_lines)
422 select_frame(nframes - 1)
424 if energy && energy != 0.0
425 set_property("energy", energy)
428 self.update_enabled = true
429 (n > 0 ? true : false)
432 def sub_load_gaussian_log(fp)
446 use_input_orientation = false
447 show_progress_panel("Loading Gaussian out file...")
454 if line =~ /(Input|Standard) orientation/
457 use_input_orientation = true if nf == 0
458 next if !use_input_orientation
460 next if use_input_orientation
462 4.times { line = fp.gets } # Skip four lines
466 while (line = fp.gets) != nil
467 break if line =~ /-----/
468 num, charge, type, x, y, z = line.split
469 coords.push(Vector3D[x, y, z])
474 # Build a new molecule
475 anums.each_index { |i|
477 ap.atomic_number = anums[i]
478 ap.atom_type = ap.element
479 ap.name = sprintf("%s%d", ap.element, i)
485 # (j + 1 ... natoms).each { |k|
486 # if calc_bond(j, k) < 1.7
495 create_frame([coords]) # Should not be (coords)
498 # TODO: to ensure whether the energy output line comes before
499 # or after the atomic coordinates.
500 set_property("energy", energy)
503 elsif line =~ /SCF Done: *E\(\w+\) *= *([-.0-9]+)/
508 set_property("energy", energy)
511 (n > 0 ? true : false)
514 def sub_load_psi4_log(fp)
524 show_progress_panel("Loading Psi4 output file...")
526 getline = lambda { @lineno += 1; return fp.gets }
528 # Import coordinates and energies
531 first_frame = nframes
536 while line = getline.call
537 if line =~ /==> Geometry <==/
538 # Skip until line containing "------"
539 while line = getline.call
540 break if line =~ /------/
544 # Read atom positions
545 while line = getline.call
547 break if line =~ /^\s*$/
548 tokens = line.split(' ')
549 if natoms > 0 && first_frame == nframes
550 if index >= natoms || tokens[0] != atoms[index].element
552 raise MolbyError, "The atom list does not match the current structure at line #{@lineno}"
555 vecs.push(Vector3D[Float(tokens[1]), Float(tokens[2]), Float(tokens[3])])
562 # Create molecule from the initial geometry
563 ats.each_with_index { |aname, i|
567 ap.atom_type = ap.element
568 ap.name = sprintf("%s%d", aname, i)
573 if vecs.length != natoms
574 break # Log file is incomplete
576 # Does this geometry differ from the last one?
577 vecs.length.times { |i|
578 if (atoms[i].r - vecs[i]).length2 > 1.0e-14
579 # Create a new frame and break
581 vecs.length.times { |j|
589 elsif line =~ /Final Energy: +([-.0-9]+)/
590 # Energy for this geometry
592 set_property("energy", energy)
600 elsif line =~ /^ *Nalpha *= *(\d+)/
602 elsif line =~ /^ *Nbeta *= *(\d+)/
608 clear_mo_coefficients
609 set_mo_info(:type => hf_type, :alpha => nalpha, :beta => nbeta)
613 # mol.set_mo_info should be set before calling this function
614 # Optional label is for importing JANPA output: "NAO" or "CPLO"
615 # If label is not nil, then returns a hash containing the following key/value pairs:
616 # :atoms => an array of [element_symbol, seq_num, atomic_num, x, y, z] (angstrom)
617 # :gto => an array of an array of [sym, [ex0, c0, ex1, c1, ...]]
618 # :moinfo => an array of [sym, energy, spin (0 or 1), occ]
619 # :mo => an array of [c0, c1, ...]
620 def sub_load_molden(fp, label = nil)
621 getline = lambda { @lineno += 1; return fp.gets }
622 bohr = 0.529177210903
624 ncomps = 0 # Number of components (AOs)
625 occ_alpha = 0 # Number of occupied alpha orbitals
626 occ_beta = 0 # Number of occupied beta orbitals
630 # The GTOs (orbital type, contractions and exponents) are stored in gtos[]
631 # and set just before first [MO] is processed.
632 # This is because we do not know whether the orbital type is cartesian or spherical
633 # until we see "[5D]" or "[9G]".
636 # Number of components for each orbital type
637 ncomp_hash = { 0=>1, 1=>3, -1=>4, 2=>6, -2=>5, 3=>10, -3=>7, 4=>15, -4=>9 }
639 while line = getline.call
640 if line =~ /^\[Atoms\]/
642 while line = getline.call
644 # element, index, atomic_number, x, y, z (in AU)
647 (hash[:atoms] ||= []).push([a[0], Integer(a[1]), Integer(a[2]), Float(a[3]) * bohr, Float(a[4]) * bohr, Float(a[5]) * bohr])
649 if atoms[i].atomic_number != Integer(a[2]) ||
650 (atoms[i].x - Float(a[3]) * bohr).abs > 1e-4 ||
651 (atoms[i].y - Float(a[4]) * bohr).abs > 1e-4 ||
652 (atoms[i].z - Float(a[5]) * bohr).abs > 1e-4
653 errmsg = "The atom list does not match the current molecule."
662 redo # The next line will be the beginning of the next block
663 elsif line =~ /^\[GTO\]/
666 while line = getline.call
669 break if a.length != 2
670 atom_gtos = [] # [[sym1, [e11, c11, e12, c12, ...], add_exp1], [sym2, [e21, c22, ...], add_exp2], ...]
672 while line = getline.call
673 # type, no_of_primitives, 1.00?
675 break if a.length != 3 # Terminated by a blank line
676 a[0] =~ /^([a-z]+)([0-9]+)?$/
678 add_exp = ($2 == nil ? 0 : $2.to_i)
691 raise MolbyError, "Unknown gaussian shell type '#{a[0]}' at line #{@lineno} in MOLDEN file"
693 nprimitives = Integer(a[1])
694 gtoline = [sym, [], add_exp]
695 atom_gtos.push(gtoline)
696 nprimitives.times { |i|
697 line = getline.call # exponent, contraction
699 gtoline[1].push(Float(b[0]), Float(b[1]))
711 redo # The next line will be the beginning of the next block
712 elsif line =~ /^\[5D\]/ || line =~ /^\[9G\]/
714 # Change the orbital type if necessary
715 gtos.each_with_index { | atom_gtos, atom_index|
716 atom_gtos.each { |gtoline|
718 gtoline[0] = -gtoline[0] # D5/F7/G9
722 elsif line =~ /^\[MO\]/
723 # Add shell info and primitive coefficients to molecule
724 gtos.each_with_index { | atom_gtos, atom_index|
725 atom_gtos.each { |gtoline|
728 nprimitives = coeffs.length / 2
730 ncomps += ncomp_hash[sym]
732 add_gaussian_orbital_shell(atom_index, sym, nprimitives, add_exp)
733 nprimitives.times { |prim|
734 add_gaussian_primitive_coefficients(coeffs[prim * 2], coeffs[prim * 2 + 1], 0.0)
740 idx_alpha = 1 # set_mo_coefficients() accepts 1-based index of MO
751 sym = nil # Not used in Molby
754 while line = getline.call
755 if line =~ /^ *Sym= *(\w+)/
757 elsif line =~ /^ *Ene= *([-+.0-9e]+)/
759 elsif line =~ /^ *Spin= *(\w+)/
761 elsif line =~ /^ *Occup= *([-+.0-9e]+)/
771 hash[:moinfo].push([sym, ene, (spin == "Alpha" ? 0 : 1), occ])
773 elsif line =~ /^ *([0-9]+) +([-+.0-9e]+)/
784 set_mo_coefficients(idx, ene, m)
786 hash[:mo].push(m.dup)
794 break if i < ncomps # no MO info was found
796 # TODO: reorder D, F, G coefficients for Molby order
802 message_box("The MOLDEN file was found but not imported. " + errmsg, "Psi4 import info", :ok)
803 return (label ? nil : false)
805 return (label ? hash : true)
808 # Import the JANPA log and related molden files
809 # Files: inppath.{NAO.molden,CLPO.molden,janpa.log}
810 # If inppath.spherical.molden is available, then clear existing mo info
811 # and load from it (i.e. use the basis set converted by molden2molden)
812 def sub_load_janpa_log(inppath)
814 fp = File.open(inppath + ".janpa.log", "rt") rescue fp = nil
816 hide_progress_panel # Close if it is open
817 message_box("Cannot open JANPA log file #{inppath + '.janpa.log'}: " + $!.to_s)
820 print("Importing #{inppath}.janpa.log.\n")
822 getline = lambda { lineno += 1; return fp.gets }
825 h["software"] = "JANPA"
826 nao_num = nil # Set later
827 nao_infos = [] # index=atom_index, value=Hash with key "s", "px", "py" etc.
828 # nao_infos[index][key]: array of [nao_num, occupancy], in the reverse order of appearance
829 while line = getline.call
830 if line =~ /molden2molden: a conversion tool for MOLDEN/
831 while line = getline.call
832 break if line =~ /^All done!/
833 if line =~ /\.spherical\.molden/
834 # The MOs are converted to spherical basis set
835 # Clear the existing MO and load *.spherical.molden
836 sname = inppath + ".spherical.molden"
837 fps = File.open(sname, "rt") rescue fps = nil
839 print("Importing #{sname}.\n")
841 type = get_mo_info(:type)
842 alpha = get_mo_info(:alpha)
843 beta = get_mo_info(:beta)
845 set_mo_info(:type=>type, :alpha=>alpha, :beta=>beta)
846 # mol.@hf_type should be set before calling sub_load_molden
853 elsif line =~ /^NAO \#/
855 while line = getline.call
856 break if line !~ /^\s*[1-9]/
857 num = Integer(line[0, 5])
859 occ = Float(line[26, 11])
861 # atom_number, occupied?, group_number, orb_sym, angular_number
862 name =~ /\s*[A-Z]+([0-9]+)(\*?):\s* R([0-9]+)\*([a-z]+)\(([-0-9]+)\)/
865 group_num = Integer($3)
867 ang_num = Integer($5)
870 orb_desc += ["z", "x", "y"][ang_num + 1]
871 elsif orb_desc == "d"
872 # TODO: handle d, f, g orbitals
874 h["NAO"].push([num, anum, occupied, group_num, orb_desc, occ])
875 nao_num = h["NAO"].length
876 ((nao_infos[anum - 1] ||= Hash.new)[orb_desc] ||= []).unshift([nao_num, occ])
880 nao_infos.each_with_index { |value, atom_index|
881 aname = self.atoms[atom_index].name
882 value.each { |orb_desc, ar|
883 ar.each_with_index { |v, group_index|
891 principle = group_index + 1
892 orb_sym = orb_desc[0]
902 h["NAO_L"][v[0] - 1] = "#{aname} (#{principle}#{orb_desc}) (#{label})"
906 elsif line =~ /^\s*(C?)LPO\s+D e s c r i p t i o n\s+Occupancy\s+Composition/
913 while line = getline.call
914 break if line =~ /^\s*$/
915 num = Integer(line[0, 5])
916 label1 = line[5, 6].strip
917 desc = line[11, 30].strip
918 occ = line[41, 11].strip
919 comp = line[52, 1000].strip
920 desc =~ /\s*([-A-Za-z0-9]+)(,\s*(.*$))?/
923 if desc2 =~ /^(.*)*\(NB\)\s*$/ && label1 == ""
927 atoms = desc1.scan(/[A-Za-z]+(\d+)/) # "C1-H3" -> [["1"], ["3"]]
928 atoms = atoms.map { |a| Integer(a[0]) } # [1, 3]
929 hybrids_a = comp.scan(/h(\d+)@[A-Za-z]+(\d+)/) # "h8@C1...h13@H3" -> "[["8", "1"], ["13", "3"]]
932 i = atoms.find_index(Integer(a[1]))
934 hybrids[i] = Integer(a[0])
937 # like ["(BD)", [1, 3], "Io = 0.2237", occ, [8, 13]]
938 # 1, 3 are the atom indices (1-based)
939 # 8, 13 are the number of hybrid orbitals (1-based)
940 h[key][num - 1] = [label1, atoms, desc2, Float(occ), hybrids]
944 # Also register labels of "LHO"
945 h["LHO_L"] = [""] * nao_num
950 label = "" # The labels for Rydberg orbitals may be replaced later
952 aname1 = self.atoms[val[1][0] - 1].name rescue aname1 = ""
953 aname2 = self.atoms[val[1][1] - 1].name rescue aname2 = ""
955 label = "#{aname1} #{val[0]}"
957 label = "#{aname1}(#{aname2}) #{val[0]}"
960 h[key + "_L"][i] = label
961 if key == "CLPO" && val != nil && val[0] != "(NB)"
963 kind = (val[0] == "(BD)" ? "(val)" : "(lp)")
965 label = "#{aname1} #{kind}"
967 label = "#{aname1}(#{aname2}) #{kind}"
969 h["LHO_L"][hybrids[0] - 1] = label
971 # aname2 should be non-empty
972 label = "#{aname2}(#{aname1}) #{kind}"
973 h["LHO_L"][hybrids[1] - 1] = label
977 elsif line =~ /^ -NAO_Molden_File: (\S*)/
979 elsif line =~ /^ -LHO_Molden_File: (\S*)/
981 elsif line =~ /^ -CLPO_Molden_File: (\S*)/
983 elsif line =~ /^ -PNAO_Molden_File: (\S*)/
985 elsif line =~ /^ -AHO_Molden_File: (\S*)/
987 elsif line =~ /^ -LPO_Molden_File: (\S*)/
993 mfiles.each { |key, value|
994 fp = Kernel.open(value, "rt") rescue fp = nil
996 print("Importing #{value}.\n")
997 res = sub_load_molden(fp, key)
999 # Some kind of orbital based on AO
1000 h["AO/#{key}"] = LAMatrix.new(res[:mo])
1003 if key == "CLPO" || key == "LPO" || key == "LHO"
1004 # Set the label of Rydberg orbitals if possible
1005 if h[key + "_L"] != nil
1008 label = h[key + "_L"][i]
1012 nao_infos.each_with_index { |inf, atom_index|
1013 atomic_contrib = 0.0
1014 inf.each { |k, v| # k is "s", "px" etc, v is array of [nao_num, occupancy]
1015 # Sum for all naos belonging to this atom
1017 atomic_contrib += a[i, num_occ[0] - 1] ** 2
1020 if atomic_contrib > max_val
1021 max_val = atomic_contrib
1022 max_idx = atom_index
1025 label = self.atoms[max_idx].name + " (ry)"
1026 h[key + "_L"][i] = label
1034 if @nbo["AO/NAO"] && @nbo["AO/LHO"] && @nbo["AO/PNAO"]
1035 # Generate PLHO from PNAO, NAO, LHO
1036 # This protocol was suggested by the JANPA author in a private commnunication.
1038 nao2lho = @nbo["AO/NAO"].inverse * @nbo["AO/LHO"]
1039 nao2pnao = @nbo["AO/NAO"].inverse * @nbo["AO/PNAO"]
1040 sign = LAMatrix.diagonal((0...nao2pnao.column_size).map { |i| (nao2pnao[i, i] < 0 ? -1 : 1)})
1041 @nbo["AO/PLHO"] = @nbo["AO/PNAO"] * sign * nao2lho
1043 @nbo["AO/PLHO"] = nil
1048 $stderr.write(e.message + "\n")
1049 $stderr.write(e.backtrace.inspect + "\n")
1053 def loadout(filename)
1055 fp = open(filename, "rb")
1061 retval = sub_load_gaussian_log(fp)
1064 retval = sub_load_gamess_log(fp)
1067 retval = sub_load_psi4_log(fp)
1069 # If .molden file exists, then try to read it
1070 namepath = filename.gsub(/\.\w*$/, "")
1071 mname = "#{namepath}.molden"
1072 if File.exists?(mname)
1073 fp2 = open(mname, "rb")
1075 flag = sub_load_molden(fp2)
1077 status = (flag ? 0 : -1)
1080 if File.exists?("#{namepath}.janpa.log")
1081 flag = sub_load_janpa_log(namepath)
1082 status = (flag ? 0 : -1)
1096 alias :loadlog :loadout
1098 def loadxyz(filename)
1099 fp = open(filename, "rb")
1112 if coords.length == 0
1113 # Allow "number of atoms" line or "number of atoms and crystallographic cell parameter"
1114 # (Chem3D xyz format)
1115 next if toks.length == 1
1117 cell = toks[1..6].map { |s| Float(s.sub(/\(\d+\)/, "")) } # Remove (xx) and convert to float
1121 name, x, y, z = line.split
1123 x = Float(x.sub(/\(\d+\)/, ""))
1124 y = Float(y.sub(/\(\d+\)/, ""))
1125 z = Float(z.sub(/\(\d+\)/, ""))
1126 r = Vector3D[x, y, z]
1134 celltr = self.cell_transform
1136 names.each_index { |i|
1137 ap = add_atom(names[i])
1138 names[i] =~ /^([A-Za-z]{1,2})/
1139 element = $1.capitalize
1140 ap.element = element
1141 ap.atom_type = element
1143 ap.r = celltr * coords[i]
1152 # (j + 1 ... natoms).each { |k|
1153 # if calc_bond(j, k) < 1.7
1154 # # create_bond(j, k)
1158 # self.undo_enabled = save_undo_enabled
1159 (n > 0 ? true : false)
1162 def savexyz(filename)
1163 open(filename, "wb") { |fp|
1164 fp.printf "%d\n", self.natoms
1166 fp.printf "%s %.5f %.5f %.5f\n", ap.element, ap.x, ap.y, ap.z
1172 def loadzmat(filename)
1174 open(filename, "rb") { |fp|
1175 while (line = fp.gets)
1178 an = Molecule.guess_atomic_number_from_name(a[0])
1179 elm = Parameter.builtin.elements[an].name
1180 base1 = a[1].to_i - 1
1181 base2 = a[3].to_i - 1
1182 base3 = a[5].to_i - 1
1183 base1 = nil if base1 < 0
1184 base2 = nil if base2 < 0
1185 base3 = nil if base3 < 0
1186 add_atom(a[0], elm, elm, a[2].to_f, base1, a[4].to_f, base2, a[6].to_f, base3)
1192 def savezmat(filename)
1195 def loadcom(filename)
1196 # save_undo_enabled = self.undo_enabled?
1197 # self.undo_enabled = false
1199 fp = open(filename, "rb")
1201 while (line = fp.gets)
1204 section = 1 if line =~ /^\#/
1206 elsif section == 1 || section == 2
1207 section += 1 if line =~ /^\s*$/
1210 # The first line is skipped (charge and multiplicity)
1211 while (line = fp.gets)
1213 break if line =~ /^\s*$/
1214 a = line.split(/\s*[ \t,\/]\s*/)
1215 r = Vector3D[Float(a[1]), Float(a[2]), Float(a[3])]
1217 a[0] =~ /^([A-Za-z]{1,2})/
1218 element = $1.capitalize
1219 ap.element = element
1220 ap.atom_type = element
1228 # self.undo_enabled = save_undo_enabled
1232 def loadinp(filename)
1233 # save_undo_enabled = self.undo_enabled?
1234 # self.undo_enabled = false
1236 fp = open(filename, "rb")
1239 while (line = fp.gets)
1240 if line =~ /\A \$BASIS/
1244 next if line !~ /\A \$DATA/
1245 line = fp.gets # Title line
1246 line = fp.gets # Symmetry line
1247 while (line = fp.gets)
1250 break if line =~ /\$END/
1253 ap.atomic_number = Integer(a[1])
1254 ap.atom_type = ap.element
1255 r = Vector3D[Float(a[2]), Float(a[3]), Float(a[4])]
1258 # Skip until one blank line is detected
1259 while (line = fp.gets) && line =~ /\S/
1267 # self.undo_enabled = save_undo_enabled
1271 def saveinp(filename)
1273 raise MolbyError, "cannot save GAMESS input; the molecule is empty"
1275 fp = open(filename, "wb")
1277 fp.print <<end_of_header
1279 ! Generated by Molby at #{now}
1280 $CONTRL COORD=UNIQUE EXETYP=RUN ICHARG=0
1281 ICUT=20 INTTYP=HONDO ITOL=30
1282 MAXIT=200 MOLPLT=.T. MPLEVL=0
1283 MULT=1 QMTTOL=1e-08 RUNTYP=OPTIMIZE
1284 SCFTYP=RHF UNITS=ANGS $END
1285 $SCF CONV=1.0E-06 DIRSCF=.T. $END
1286 $STATPT NSTEP=400 OPTTOL=1.0E-06 $END
1287 $SYSTEM MEMDDI=0 MWORDS=16 TIMLIM=50000 $END
1288 $BASIS GBASIS=N31 NDFUNC=1 NGAUSS=6 $END
1289 $GUESS GUESS=HUCKEL $END
1296 next if ap.atomic_number == 0
1297 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
1304 def savecom(filename)
1306 raise MolbyError, "cannot save Gaussian input; the molecule is empty"
1308 fp = open(filename, "wb")
1309 base = File.basename(filename, ".*")
1310 fp.print <<end_of_header
1314 #{name}; created by Molby at #{Time.now.to_s}
1319 next if ap.atomic_number == 0
1320 fp.printf "%-6s %10.6f %10.6f %10.6f\n", ap.element, ap.r.x, ap.r.y, ap.r.z
1327 alias :loadgjf :loadcom
1328 alias :savegjf :savecom
1330 def loadcif(filename)
1333 while @tokens.length == 0
1338 while line = fp.gets
1339 break if line[0] == ?;
1345 line = line[1...line.length]
1348 line.scan(/'[^\']*'|"[^\"]*"|[^#\s]+|#.*/) do |s|
1350 if s =~ /^data_|loop_|global_|save_|stop_/i
1351 s = "#" + s # Label for reserved words
1356 return @tokens.shift
1358 def float_strip_rms(str)
1359 str =~ /^(-?)(\d*)(\.(\d*))?(\((\d+)\))?$/
1360 sgn, i, frac, rms = $1, $2, $4, $6
1363 base = 0.1 ** frac.length
1364 i = i + frac.to_f * base
1369 rms = rms.to_f * base
1378 def parse_symmetry_operation(str)
1381 elsif (str =~ /(\d+)_(\d)(\d)(\d)/) || (str =~ /(\d+) +(\d)(\d)(\d)/)
1382 sym = [Integer($1) - 1, Integer($2) - 5, Integer($3) - 5, Integer($4) - 5]
1383 elsif (str =~ /^(\d+)$/)
1384 sym = [Integer($1) - 1, 0, 0, 0]
1386 if sym && (sym[0] == 0 && sym[1] == 0 && sym[2] == 0 && sym[3] == 0)
1391 def find_atom_by_name(mol, name)
1392 name = name.delete(" ()")
1393 ap = mol.atoms[name] rescue ap = nil
1396 selfname = self.name
1397 fp = open(filename, "rb")
1398 data_identifier = nil
1404 bond_defined = false
1405 special_positions = []
1408 cell_trans = cell_trans_inv = Transform.identity
1409 token = getciftoken(fp)
1410 pardigits_re = /\(\d+\)/
1411 calculated_atoms = []
1413 if token =~ /^\#data_/i
1414 if data_identifier == nil || mol.natoms == 0
1415 # First block or no atoms yet
1416 # Continue processing of this molecule
1417 data_identifier = token
1418 token = getciftoken(fp)
1421 # Description of another molecule begins here
1422 data_identifier = token
1425 elsif token =~ /^_cell/
1426 val = getciftoken(fp)
1427 if token == "_cell_length_a"
1428 cell[0], cell[6] = float_strip_rms(val)
1429 elsif token == "_cell_length_b"
1430 cell[1], cell[7] = float_strip_rms(val)
1431 elsif token == "_cell_length_c"
1432 cell[2], cell[8] = float_strip_rms(val)
1433 elsif token == "_cell_angle_alpha"
1434 cell[3], cell[9] = float_strip_rms(val)
1435 elsif token == "_cell_angle_beta"
1436 cell[4], cell[10] = float_strip_rms(val)
1437 elsif token == "_cell_angle_gamma"
1438 cell[5], cell[11] = float_strip_rms(val)
1440 if cell.length == 12 && cell.all?
1442 puts "Unit cell is set to #{mol.cell.inspect}." if verbose
1444 cell_trans = mol.cell_transform
1445 cell_trans_inv = cell_trans.inverse
1447 token = getciftoken(fp)
1449 elsif token.casecmp("#loop_") == 0
1451 while (token = getciftoken(fp)) && token[0] == ?_
1454 if labels[0] =~ /symmetry_equiv_pos|space_group_symop|atom_site_label|atom_site_aniso_label|geom_bond/
1455 hlabel = Hash.new(-10000000)
1456 labels.each_with_index { |lb, i|
1463 break if token == nil || token[0] == ?_ || token[0] == ?#
1469 token = getciftoken(fp)
1471 if labels[0] =~ /^_symmetry_equiv_pos/ || labels[0] =~ /^_space_group_symop/
1473 symstr = d[hlabel["_symmetry_equiv_pos_as_xyz"]] || d[hlabel["_space_group_symop_operation_xyz"]]
1474 symstr.delete("\"\'")
1475 exps = symstr.split(/,/)
1476 sym = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
1477 exps.each_with_index { |s, i|
1478 terms = s.scan(/([-+]?)(([.0-9]+)(\/([0-9]+))?([xXyYzZ])?|([xXyYzZ]))/)
1480 # a[0]: sign, a[2]: numerator, a[4]: denometer
1482 # The number part is a[2]/a[4]
1483 num = Float(a[2])/Float(a[4])
1485 # The number part is either integer or a floating point
1490 num = -num if a[0][0] == ?-
1491 xyz = (a[5] || a[6])
1492 if xyz == "x" || xyz == "X"
1494 elsif xyz == "y" || xyz == "Y"
1496 elsif xyz == "z" || xyz == "Z"
1503 puts "symmetry operation #{sym.inspect}" if verbose
1504 mol.add_symmetry(Transform.new(sym))
1506 puts "#{mol.nsymmetries} symmetry operations are added" if verbose
1507 elsif labels[0] =~ /^_atom_site_label/
1510 name = d[hlabel["_atom_site_label"]]
1511 elem = d[hlabel["_atom_site_type_symbol"]]
1512 fx = d[hlabel["_atom_site_fract_x"]]
1513 fy = d[hlabel["_atom_site_fract_y"]]
1514 fz = d[hlabel["_atom_site_fract_z"]]
1515 uiso = d[hlabel["_atom_site_U_iso_or_equiv"]]
1516 biso = d[hlabel["_atom_site_B_iso_or_equiv"]]
1517 occ = d[hlabel["_atom_site_occupancy"]]
1518 calc = d[hlabel["_atom_site_calc_flag"]]
1519 name = name.delete(" ()")
1520 if elem == nil || elem == ""
1521 if name =~ /[A-Za-z]{1,2}/
1522 elem = $&.capitalize
1527 ap = mol.add_atom(name, elem, elem)
1528 ap.fract_x, ap.sigma_x = float_strip_rms(fx)
1529 ap.fract_y, ap.sigma_y = float_strip_rms(fy)
1530 ap.fract_z, ap.sigma_z = float_strip_rms(fz)
1532 ap.temp_factor, sig = float_strip_rms(biso)
1534 ap.temp_factor, sig = float_strip_rms(uiso)
1535 ap.temp_factor *= 78.9568352087149 # 8*pi*pi
1537 ap.occupancy, sig = float_strip_rms(occ)
1538 if calc == "c" || calc == "calc"
1539 calculated_atoms.push(ap.index)
1541 # Guess special positions
1542 (1...mol.nsymmetries).each { |isym|
1544 sr = (mol.transform_for_symop(isym) * sr) - sr;
1545 nx = (sr.x + 0.5).floor
1546 ny = (sr.y + 0.5).floor
1547 nz = (sr.z + 0.5).floor
1548 if (Vector3D[sr.x - nx, sr.y - ny, sr.z - nz].length2 < 1e-6)
1549 # [isym, -nx, -ny, -nz] transforms this atom to itself
1550 # The following line is equivalent to:
1551 # if special_positions[ap.index] == nil; special_positions[ap.index] = []; end;
1552 # special_positions[ap.index].push(...)
1553 (special_positions[ap.index] ||= []).push([isym, -nx, -ny, -nz])
1556 if verbose && special_positions[ap.index]
1557 puts "#{name} is on the special position: #{special_positions[ap.index].inspect}"
1560 puts "#{mol.natoms} atoms are created." if verbose
1561 elsif labels[0] =~ /^_atom_site_aniso_label/
1562 # Set anisotropic parameters
1565 name = d[hlabel["_atom_site_aniso_label"]]
1566 ap = find_atom_by_name(mol, name)
1568 u11 = d[hlabel["_atom_site_aniso_U_11"]]
1571 u11, usig[0] = float_strip_rms(u11)
1572 u22, usig[1] = float_strip_rms(d[hlabel["_atom_site_aniso_U_22"]])
1573 u33, usig[2] = float_strip_rms(d[hlabel["_atom_site_aniso_U_33"]])
1574 u12, usig[3] = float_strip_rms(d[hlabel["_atom_site_aniso_U_12"]])
1575 u13, usig[4] = float_strip_rms(d[hlabel["_atom_site_aniso_U_13"]])
1576 u23, usig[5] = float_strip_rms(d[hlabel["_atom_site_aniso_U_23"]])
1577 ap.aniso = [u11, u22, u33, u12, u13, u23, 8] + usig
1581 puts "#{c} anisotropic parameters are set." if verbose
1582 elsif labels[0] =~ /^_geom_bond/
1586 n1 = d[hlabel["_geom_bond_atom_site_label_1"]]
1587 n2 = d[hlabel["_geom_bond_atom_site_label_2"]]
1588 sym1 = d[hlabel["_geom_bond_site_symmetry_1"]] || "."
1589 sym2 = d[hlabel["_geom_bond_site_symmetry_2"]] || "."
1590 n1 = find_atom_by_name(mol, n1)
1591 n2 = find_atom_by_name(mol, n2)
1592 next if n1 == nil || n2 == nil
1595 sym1 = parse_symmetry_operation(sym1)
1596 sym2 = parse_symmetry_operation(sym2)
1598 exbonds.push([n1, n2, sym1, sym2])
1600 mol.create_bond(n1, n2)
1602 tr1 = (sym1 ? mol.transform_for_symop(sym1) : Transform.identity)
1603 tr2 = (sym2 ? mol.transform_for_symop(sym2) : Transform.identity)
1604 if special_positions[n1]
1605 # Add extra bonds for equivalent positions of n1
1606 special_positions[n1].each { |symop|
1607 sym2x = mol.symop_for_transform(tr1 * mol.transform_for_symop(symop) * tr1.inverse * tr2)
1608 exbonds.push([n1, n2, sym1, sym2x])
1611 if special_positions[n2]
1612 # Add extra bonds n2-n1.symop, where symop transforms n2 to self
1613 tr = (sym1 ? mol.transform_for_symop(sym1) : Transform.identity)
1614 special_positions[n2].each { |symop|
1615 sym1x = mol.symop_for_transform(tr2 * mol.transform_for_symop(symop) * tr2.inverse * tr1)
1616 exbonds.push([n2, n1, sym2, sym1x])
1623 puts "#{mol.nbonds} bonds are created." if verbose
1624 if calculated_atoms.length > 0
1625 # Guess bonds for calculated hydrogen atoms
1627 calculated_atoms.each { |ai|
1628 if mol.atoms[ai].connects.length == 0
1629 as = mol.find_close_atoms(ai)
1631 mol.create_bond(ai, aj)
1636 puts "#{n1} bonds are guessed." if verbose
1638 if exbonds.length > 0
1639 h = Dialog.run("CIF Import: Symmetry Expansion") {
1641 item(:text, :title=>"There are bonds including symmetry related atoms.\nWhat do you want to do?"),
1642 item(:radio, :title=>"Expand only atoms that are included in those extra bonds.", :tag=>"atoms_only"),
1643 item(:radio, :title=>"Expand fragments having atoms included in the extra bonds.", :tag=>"fragment", :value=>1),
1644 item(:radio, :title=>"Ignore these extra bonds.", :tag=>"ignore")
1646 radio_group("atoms_only", "fragment", "ignore")
1648 if h[:status] == 0 && h["ignore"] == 0
1649 atoms_only = (h["atoms_only"] != 0)
1652 mol.each_fragment { |f| fragments.push(f) }
1656 if debug; puts "extra bond #{ex[0]}(#{ex[2].inspect}) - #{ex[1]}(#{ex[3].inspect})"; end
1661 ex[i + 2] = ex[i - 2]
1663 if debug; puts " symop = #{symop.inspect}"; end
1664 # Expand the atom or the fragment including the atom
1666 ig = IntGroup[ex[i - 2]]
1669 ig = fragments.find { |f| f.include?(ex[i - 2]) }
1670 ig.each_with_index { |n, ii| if n == ex[i - 2]; idx = ii; break; end }
1672 symop[4] = ex[i - 2] # Base atom
1673 if debug; puts " expanding #{ig} by #{symop.inspect}"; end
1674 a = mol.expand_by_symmetry(ig, symop[0], symop[1], symop[2], symop[3])
1675 ex[i + 2] = a[idx] # Index of the expanded atom
1678 if ex[4] && ex[5] && ex[4] != ex[5]
1679 if debug; puts " creating bond #{ex[4]} - #{ex[5]}"; end
1680 mol.create_bond(ex[4], ex[5])
1685 puts "#{mol.nbonds} bonds are created." if verbose
1689 # puts "Loop beginning with #{labels[0]} is skipped"
1693 token = getciftoken(fp)
1695 # Skip tokens until next tag or reserved word is detected
1696 while token != nil && token[0] != ?_ && token[0] != ?#
1697 token = getciftoken(fp)
1704 if token != nil && token == data_identifier
1705 # Process next molecule: open a new molecule and start adding atom on that
1708 (@aux_mols ||= []).push(mol)
1714 # self.undo_enabled = save_undo_enabled
1718 def dump(group = nil)
1723 str = str.gsub(/%/, "%%")
1724 str.gsub!(/ /, "%20")
1725 str.gsub!(/\t/, "%09")
1729 group = atom_group(group ? group : 0...natoms)
1733 s += sprintf("%4d %-7s %-4s %-4s %-2s %7.3f %7.3f %7.3f %6.3f [%s]\n",
1734 ap.index, sprintf("%3s.%d", ap.res_name, ap.res_seq),
1735 quote(ap.name), quote(ap.atom_type), quote(ap.element),
1736 ap.r.x, ap.r.y, ap.r.z, ap.charge,
1737 ap.connects.join(","))
1744 raise "Molecule must be empty"
1746 format = "index residue name atom_type element rx ry rz charge connects"
1750 # arg can be either a String or an array of String.
1751 # Iterates for each line in the string or each member of the array.
1752 if arg.is_a?(String)
1753 arg = arg.split("\n")
1757 format = line[1..-1]
1761 keys = format.split(" ").collect { |key| key.to_sym }
1763 values = line.chomp.split(" ")
1764 next if values == nil || values.length == 0
1765 ap = create_atom(sprintf("X%03d", natoms))
1766 r = Vector3D[0, 0, 0]
1767 keys.each_index { |i|
1768 break if (value = values[i]) == nil
1772 value.gsub(/%09/, "\t")
1773 value.gsub(/%20/, " ")
1774 value.gsub(/%%/, "%")
1778 if resAtoms[value] == nil
1779 resAtoms[value] = []
1781 resAtoms[value].push(ap.index)
1788 elsif key == :connects
1789 value.scan(/\d+/).each { |i|
1792 newBonds.push(ap.index)
1799 ap.set_attr(key, value)
1804 resAtoms.each_key { |key|
1805 assign_residue(atom_group(resAtoms[key]), key)
1807 create_bond(*newBonds)
1811 # Plug-in for loading mbsf
1812 def loadmbsf_plugin(s, lineno)
1816 # Plug-in for saving mbsf