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 lines like "[5D]".
638 # Number of components for each orbital type
639 ncomp_hash = { 0=>1, 1=>3, -1=>4, 2=>6, -2=>5, 3=>10, -3=>7, 4=>15, -4=>9 }
641 while line = getline.call
642 if line =~ /^\[Atoms\]/
644 while line = getline.call
646 # element, index, atomic_number, x, y, z (in AU)
649 (hash[:atoms] ||= []).push([a[0], Integer(a[1]), Integer(a[2]), Float(a[3]) * bohr, Float(a[4]) * bohr, Float(a[5]) * bohr])
651 if atoms[i].atomic_number != Integer(a[2]) ||
652 (atoms[i].x - Float(a[3]) * bohr).abs > 1e-4 ||
653 (atoms[i].y - Float(a[4]) * bohr).abs > 1e-4 ||
654 (atoms[i].z - Float(a[5]) * bohr).abs > 1e-4
655 errmsg = "The atom list does not match the current molecule."
664 redo # The next line will be the beginning of the next block
665 elsif line =~ /^\[GTO\]/
668 while line = getline.call
671 break if a.length != 2
672 atom_gtos = [] # [[sym1, [e11, c11, e12, c12, ...], add_exp1], [sym2, [e21, c22, ...], add_exp2], ...]
674 while line = getline.call
675 # type, no_of_primitives, 1.00?
677 break if a.length != 3 # Terminated by a blank line
678 a[0] =~ /^([a-z]+)([0-9]+)?$/
680 add_exp = ($2 == nil ? 0 : $2.to_i)
693 raise MolbyError, "Unknown gaussian shell type '#{a[0]}' at line #{@lineno} in MOLDEN file"
695 nprimitives = Integer(a[1])
696 gtoline = [sym, [], add_exp]
697 atom_gtos.push(gtoline)
698 nprimitives.times { |i|
699 line = getline.call # exponent, contraction
701 gtoline[1].push(Float(b[0]), Float(b[1]))
713 redo # The next line will be the beginning of the next block
714 elsif line =~ /^\[5D\]/ || line =~ /^\[5D7F\]/
715 spherical_d = spherical_f = true
716 elsif line =~ /^\[5D10F\]/
719 elsif line =~ /^\[7F\]/
721 elsif line =~ /^\[9G\]/
723 elsif line =~ /^\[MO\]/
724 # Add shell info and primitive coefficients to molecule
725 gtos.each_with_index { | atom_gtos, atom_index|
726 atom_gtos.each { |gtoline|
728 # Change orbital type if we use spherical functions
729 sym = -2 if sym == 2 && spherical_d
730 sym = -3 if sym == 3 && spherical_f
731 sym = -4 if sym == 4 && spherical_g
734 nprimitives = coeffs.length / 2
736 ncomps += ncomp_hash[sym]
738 add_gaussian_orbital_shell(atom_index, sym, nprimitives, add_exp)
739 nprimitives.times { |prim|
740 add_gaussian_primitive_coefficients(coeffs[prim * 2], coeffs[prim * 2 + 1], 0.0)
746 idx_alpha = 1 # set_mo_coefficients() accepts 1-based index of MO
757 sym = nil # Not used in Molby
760 while line = getline.call
761 if line =~ /^ *Sym= *(\w+)/
763 elsif line =~ /^ *Ene= *([-+.0-9e]+)/
765 elsif line =~ /^ *Spin= *(\w+)/
767 elsif line =~ /^ *Occup= *([-+.0-9e]+)/
777 hash[:moinfo].push([sym, ene, (spin == "Alpha" ? 0 : 1), occ])
779 elsif line =~ /^ *([0-9]+) +([-+.0-9e]+)/
790 set_mo_coefficients(idx, ene, m)
792 hash[:mo].push(m.dup)
800 break if i < ncomps # no MO info was found
802 # TODO: reorder D, F, G coefficients for Molby order
808 message_box("The MOLDEN file was found but not imported. " + errmsg, "Psi4 import info", :ok)
809 return (label ? nil : false)
811 return (label ? hash : true)
814 # Import the JANPA log and related molden files
815 # Files: inppath.{NAO.molden,CLPO.molden,janpa.log}
816 # If inppath.spherical.molden is available, then clear existing mo info
817 # and load from it (i.e. use the basis set converted by molden2molden)
818 def sub_load_janpa_log(inppath)
820 fp = File.open(inppath + ".janpa.log", "rt") rescue fp = nil
822 hide_progress_panel # Close if it is open
823 message_box("Cannot open JANPA log file #{inppath + '.janpa.log'}: " + $!.to_s)
826 print("Importing #{inppath}.janpa.log.\n")
828 getline = lambda { lineno += 1; return fp.gets }
831 h["software"] = "JANPA"
832 nao_num = nil # Set later
833 nao_infos = [] # index=atom_index, value=Hash with key "s", "px", "py" etc.
834 # nao_infos[index][key]: array of [nao_num, occupancy], in the reverse order of appearance
835 while line = getline.call
836 if line =~ /molden2molden: a conversion tool for MOLDEN/
837 while line = getline.call
838 break if line =~ /^All done!/
839 if line =~ /\.spherical\.molden/
840 # The MOs are converted to spherical basis set
841 # Clear the existing MO and load *.spherical.molden
842 sname = inppath + ".spherical.molden"
843 fps = File.open(sname, "rt") rescue fps = nil
845 print("Importing #{sname}.\n")
847 type = get_mo_info(:type)
848 alpha = get_mo_info(:alpha)
849 beta = get_mo_info(:beta)
851 set_mo_info(:type=>type, :alpha=>alpha, :beta=>beta)
852 # mol.@hf_type should be set before calling sub_load_molden
859 elsif line =~ /^NAO \#/
861 while line = getline.call
862 break if line !~ /^\s*[1-9]/
863 num = Integer(line[0, 5])
865 occ = Float(line[26, 11])
867 # atom_number, occupied?, group_number, orb_sym, angular_number
868 name =~ /\s*[A-Z]+([0-9]+)(\*?):\s* R([0-9]+)\*([a-z]+)\(([-0-9]+)\)/
871 group_num = Integer($3)
873 ang_num = Integer($5)
876 orb_desc += ["z", "x", "y"][ang_num + 1]
877 elsif orb_desc == "d"
878 # TODO: handle d, f, g orbitals
880 h["NAO"].push([num, anum, occupied, group_num, orb_desc, occ])
881 nao_num = h["NAO"].length
882 ((nao_infos[anum - 1] ||= Hash.new)[orb_desc] ||= []).unshift([nao_num, occ])
886 nao_infos.each_with_index { |value, atom_index|
887 aname = self.atoms[atom_index].name
888 value.each { |orb_desc, ar|
889 ar.each_with_index { |v, group_index|
897 principle = group_index + 1
898 orb_sym = orb_desc[0]
908 h["NAO_L"][v[0] - 1] = "#{aname} (#{principle}#{orb_desc}) (#{label})"
912 elsif line =~ /^\s*(C?)LPO\s+D e s c r i p t i o n\s+Occupancy\s+Composition/
919 while line = getline.call
920 break if line =~ /^\s*$/
921 num = Integer(line[0, 5])
922 label1 = line[5, 6].strip
923 desc = line[11, 30].strip
924 occ = line[41, 11].strip
925 comp = line[52, 1000].strip
926 desc =~ /\s*([-A-Za-z0-9]+)(,\s*(.*$))?/
929 if desc2 =~ /^(.*)*\(NB\)\s*$/ && label1 == ""
933 atoms = desc1.scan(/[A-Za-z]+(\d+)/) # "C1-H3" -> [["1"], ["3"]]
934 atoms = atoms.map { |a| Integer(a[0]) } # [1, 3]
935 hybrids_a = comp.scan(/h(\d+)@[A-Za-z]+(\d+)/) # "h8@C1...h13@H3" -> "[["8", "1"], ["13", "3"]]
938 i = atoms.find_index(Integer(a[1]))
940 hybrids[i] = Integer(a[0])
943 # like ["(BD)", [1, 3], "Io = 0.2237", occ, [8, 13]]
944 # 1, 3 are the atom indices (1-based)
945 # 8, 13 are the number of hybrid orbitals (1-based)
946 h[key][num - 1] = [label1, atoms, desc2, Float(occ), hybrids]
950 # Also register labels of "LHO"
951 h["LHO_L"] = [""] * nao_num
956 label = "" # The labels for Rydberg orbitals may be replaced later
958 aname1 = self.atoms[val[1][0] - 1].name rescue aname1 = ""
959 aname2 = self.atoms[val[1][1] - 1].name rescue aname2 = ""
961 label = "#{aname1} #{val[0]}"
963 label = "#{aname1}(#{aname2}) #{val[0]}"
966 h[key + "_L"][i] = label
967 if key == "CLPO" && val != nil && val[0] != "(NB)"
969 kind = (val[0] == "(BD)" ? "(val)" : "(lp)")
971 label = "#{aname1} #{kind}"
973 label = "#{aname1}(#{aname2}) #{kind}"
975 h["LHO_L"][hybrids[0] - 1] = label
977 # aname2 should be non-empty
978 label = "#{aname2}(#{aname1}) #{kind}"
979 h["LHO_L"][hybrids[1] - 1] = label
983 elsif line =~ /^ -NAO_Molden_File: (\S*)/
985 elsif line =~ /^ -LHO_Molden_File: (\S*)/
987 elsif line =~ /^ -CLPO_Molden_File: (\S*)/
989 elsif line =~ /^ -PNAO_Molden_File: (\S*)/
991 elsif line =~ /^ -AHO_Molden_File: (\S*)/
993 elsif line =~ /^ -LPO_Molden_File: (\S*)/
999 mfiles.each { |key, value|
1000 fp = Kernel.open(value, "rt") rescue fp = nil
1002 print("Importing #{value}.\n")
1003 res = sub_load_molden(fp, key)
1005 # Some kind of orbital based on AO
1006 h["AO/#{key}"] = LAMatrix.new(res[:mo])
1009 if key == "CLPO" || key == "LPO" || key == "LHO"
1010 # Set the label of Rydberg orbitals if possible
1011 if h[key + "_L"] != nil
1014 label = h[key + "_L"][i]
1018 nao_infos.each_with_index { |inf, atom_index|
1019 atomic_contrib = 0.0
1020 inf.each { |k, v| # k is "s", "px" etc, v is array of [nao_num, occupancy]
1021 # Sum for all naos belonging to this atom
1023 atomic_contrib += a[i, num_occ[0] - 1] ** 2
1026 if atomic_contrib > max_val
1027 max_val = atomic_contrib
1028 max_idx = atom_index
1031 label = self.atoms[max_idx].name + " (ry)"
1032 h[key + "_L"][i] = label
1040 if @nbo["AO/NAO"] && @nbo["AO/LHO"] && @nbo["AO/PNAO"]
1041 # Generate PLHO from PNAO, NAO, LHO
1042 # This protocol was suggested by the JANPA author in a private commnunication.
1044 nao2lho = @nbo["AO/NAO"].inverse * @nbo["AO/LHO"]
1045 nao2pnao = @nbo["AO/NAO"].inverse * @nbo["AO/PNAO"]
1046 sign = LAMatrix.diagonal((0...nao2pnao.column_size).map { |i| (nao2pnao[i, i] < 0 ? -1 : 1)})
1047 @nbo["AO/PLHO"] = @nbo["AO/PNAO"] * sign * nao2lho
1049 @nbo["AO/PLHO"] = nil
1054 $stderr.write(e.message + "\n")
1055 $stderr.write(e.backtrace.inspect + "\n")
1059 def loadout(filename)
1061 fp = open(filename, "rb")
1067 retval = sub_load_gaussian_log(fp)
1070 retval = sub_load_gamess_log(fp)
1073 retval = sub_load_psi4_log(fp)
1075 # If .molden file exists, then try to read it
1076 namepath = filename.gsub(/\.\w*$/, "")
1077 mname = "#{namepath}.molden"
1078 if File.exists?(mname)
1079 fp2 = open(mname, "rb")
1081 flag = sub_load_molden(fp2)
1083 status = (flag ? 0 : -1)
1086 if File.exists?("#{namepath}.janpa.log")
1087 flag = sub_load_janpa_log(namepath)
1088 status = (flag ? 0 : -1)
1102 alias :loadlog :loadout
1104 def loadxyz(filename)
1105 fp = open(filename, "rb")
1118 if coords.length == 0
1119 # Allow "number of atoms" line or "number of atoms and crystallographic cell parameter"
1120 # (Chem3D xyz format)
1121 next if toks.length == 1
1123 cell = toks[1..6].map { |s| Float(s.sub(/\(\d+\)/, "")) } # Remove (xx) and convert to float
1127 name, x, y, z = line.split
1129 x = Float(x.sub(/\(\d+\)/, ""))
1130 y = Float(y.sub(/\(\d+\)/, ""))
1131 z = Float(z.sub(/\(\d+\)/, ""))
1132 r = Vector3D[x, y, z]
1140 celltr = self.cell_transform
1142 names.each_index { |i|
1143 ap = add_atom(names[i])
1144 names[i] =~ /^([A-Za-z]{1,2})/
1145 element = $1.capitalize
1146 ap.element = element
1147 ap.atom_type = element
1149 ap.r = celltr * coords[i]
1158 # (j + 1 ... natoms).each { |k|
1159 # if calc_bond(j, k) < 1.7
1160 # # create_bond(j, k)
1164 # self.undo_enabled = save_undo_enabled
1165 (n > 0 ? true : false)
1168 def savexyz(filename)
1169 open(filename, "wb") { |fp|
1170 fp.printf "%d\n", self.natoms
1172 fp.printf "%s %.5f %.5f %.5f\n", ap.element, ap.x, ap.y, ap.z
1178 def loadzmat(filename)
1180 open(filename, "rb") { |fp|
1181 while (line = fp.gets)
1184 an = Molecule.guess_atomic_number_from_name(a[0])
1185 elm = Parameter.builtin.elements[an].name
1186 base1 = a[1].to_i - 1
1187 base2 = a[3].to_i - 1
1188 base3 = a[5].to_i - 1
1189 base1 = nil if base1 < 0
1190 base2 = nil if base2 < 0
1191 base3 = nil if base3 < 0
1192 add_atom(a[0], elm, elm, a[2].to_f, base1, a[4].to_f, base2, a[6].to_f, base3)
1198 def savezmat(filename)
1201 def loadcom(filename)
1202 # save_undo_enabled = self.undo_enabled?
1203 # self.undo_enabled = false
1205 fp = open(filename, "rb")
1207 while (line = fp.gets)
1210 section = 1 if line =~ /^\#/
1212 elsif section == 1 || section == 2
1213 section += 1 if line =~ /^\s*$/
1216 # The first line is skipped (charge and multiplicity)
1217 while (line = fp.gets)
1219 break if line =~ /^\s*$/
1220 a = line.split(/\s*[ \t,\/]\s*/)
1221 r = Vector3D[Float(a[1]), Float(a[2]), Float(a[3])]
1223 a[0] =~ /^([A-Za-z]{1,2})/
1224 element = $1.capitalize
1225 ap.element = element
1226 ap.atom_type = element
1234 # self.undo_enabled = save_undo_enabled
1238 def loadinp(filename)
1239 # save_undo_enabled = self.undo_enabled?
1240 # self.undo_enabled = false
1242 fp = open(filename, "rb")
1245 while (line = fp.gets)
1246 if line =~ /\A \$BASIS/
1250 next if line !~ /\A \$DATA/
1251 line = fp.gets # Title line
1252 line = fp.gets # Symmetry line
1253 while (line = fp.gets)
1256 break if line =~ /\$END/
1259 ap.atomic_number = Integer(a[1])
1260 ap.atom_type = ap.element
1261 r = Vector3D[Float(a[2]), Float(a[3]), Float(a[4])]
1264 # Skip until one blank line is detected
1265 while (line = fp.gets) && line =~ /\S/
1273 # self.undo_enabled = save_undo_enabled
1277 def saveinp(filename)
1279 raise MolbyError, "cannot save GAMESS input; the molecule is empty"
1281 fp = open(filename, "wb")
1283 fp.print <<end_of_header
1285 ! Generated by Molby at #{now}
1286 $CONTRL COORD=UNIQUE EXETYP=RUN ICHARG=0
1287 ICUT=20 INTTYP=HONDO ITOL=30
1288 MAXIT=200 MOLPLT=.T. MPLEVL=0
1289 MULT=1 QMTTOL=1e-08 RUNTYP=OPTIMIZE
1290 SCFTYP=RHF UNITS=ANGS $END
1291 $SCF CONV=1.0E-06 DIRSCF=.T. $END
1292 $STATPT NSTEP=400 OPTTOL=1.0E-06 $END
1293 $SYSTEM MEMDDI=0 MWORDS=16 TIMLIM=50000 $END
1294 $BASIS GBASIS=N31 NDFUNC=1 NGAUSS=6 $END
1295 $GUESS GUESS=HUCKEL $END
1302 next if ap.atomic_number == 0
1303 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
1310 def savecom(filename)
1312 raise MolbyError, "cannot save Gaussian input; the molecule is empty"
1314 fp = open(filename, "wb")
1315 base = File.basename(filename, ".*")
1316 fp.print <<end_of_header
1320 #{name}; created by Molby at #{Time.now.to_s}
1325 next if ap.atomic_number == 0
1326 fp.printf "%-6s %10.6f %10.6f %10.6f\n", ap.element, ap.r.x, ap.r.y, ap.r.z
1333 alias :loadgjf :loadcom
1334 alias :savegjf :savecom
1336 def loadcif(filename)
1339 while @tokens.length == 0
1344 while line = fp.gets
1345 break if line[0] == ?;
1351 line = line[1...line.length]
1354 line.scan(/'[^\']*'|"[^\"]*"|[^#\s]+|#.*/) do |s|
1356 if s =~ /^data_|loop_|global_|save_|stop_/i
1357 s = "#" + s # Label for reserved words
1362 return @tokens.shift
1364 def float_strip_rms(str)
1365 str =~ /^(-?)(\d*)(\.(\d*))?(\((\d+)\))?$/
1366 sgn, i, frac, rms = $1, $2, $4, $6
1369 base = 0.1 ** frac.length
1370 i = i + frac.to_f * base
1375 rms = rms.to_f * base
1384 def parse_symmetry_operation(str)
1387 elsif (str =~ /(\d+)_(\d)(\d)(\d)/) || (str =~ /(\d+) +(\d)(\d)(\d)/)
1388 sym = [Integer($1) - 1, Integer($2) - 5, Integer($3) - 5, Integer($4) - 5]
1389 elsif (str =~ /^(\d+)$/)
1390 sym = [Integer($1) - 1, 0, 0, 0]
1392 if sym && (sym[0] == 0 && sym[1] == 0 && sym[2] == 0 && sym[3] == 0)
1397 def find_atom_by_name(mol, name)
1398 name = name.delete(" ()")
1399 ap = mol.atoms[name] rescue ap = nil
1402 selfname = self.name
1403 fp = open(filename, "rb")
1404 data_identifier = nil
1410 bond_defined = false
1411 special_positions = []
1414 cell_trans = cell_trans_inv = Transform.identity
1415 token = getciftoken(fp)
1416 pardigits_re = /\(\d+\)/
1417 calculated_atoms = []
1419 if token =~ /^\#data_/i
1420 if data_identifier == nil || mol.natoms == 0
1421 # First block or no atoms yet
1422 # Continue processing of this molecule
1423 data_identifier = token
1424 token = getciftoken(fp)
1427 # Description of another molecule begins here
1428 data_identifier = token
1431 elsif token =~ /^_cell/
1432 val = getciftoken(fp)
1433 if token == "_cell_length_a"
1434 cell[0], cell[6] = float_strip_rms(val)
1435 elsif token == "_cell_length_b"
1436 cell[1], cell[7] = float_strip_rms(val)
1437 elsif token == "_cell_length_c"
1438 cell[2], cell[8] = float_strip_rms(val)
1439 elsif token == "_cell_angle_alpha"
1440 cell[3], cell[9] = float_strip_rms(val)
1441 elsif token == "_cell_angle_beta"
1442 cell[4], cell[10] = float_strip_rms(val)
1443 elsif token == "_cell_angle_gamma"
1444 cell[5], cell[11] = float_strip_rms(val)
1446 if cell.length == 12 && cell.all?
1448 puts "Unit cell is set to #{mol.cell.inspect}." if verbose
1450 cell_trans = mol.cell_transform
1451 cell_trans_inv = cell_trans.inverse
1453 token = getciftoken(fp)
1455 elsif token.casecmp("#loop_") == 0
1457 while (token = getciftoken(fp)) && token[0] == ?_
1460 if labels[0] =~ /symmetry_equiv_pos|space_group_symop|atom_site_label|atom_site_aniso_label|geom_bond/
1461 hlabel = Hash.new(-10000000)
1462 labels.each_with_index { |lb, i|
1469 break if token == nil || token[0] == ?_ || token[0] == ?#
1475 token = getciftoken(fp)
1477 if labels[0] =~ /^_symmetry_equiv_pos/ || labels[0] =~ /^_space_group_symop/
1479 symstr = d[hlabel["_symmetry_equiv_pos_as_xyz"]] || d[hlabel["_space_group_symop_operation_xyz"]]
1480 symstr.delete("\"\'")
1481 exps = symstr.split(/,/)
1482 sym = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
1483 exps.each_with_index { |s, i|
1484 terms = s.scan(/([-+]?)(([.0-9]+)(\/([0-9]+))?([xXyYzZ])?|([xXyYzZ]))/)
1486 # a[0]: sign, a[2]: numerator, a[4]: denometer
1488 # The number part is a[2]/a[4]
1489 num = Float(a[2])/Float(a[4])
1491 # The number part is either integer or a floating point
1496 num = -num if a[0][0] == ?-
1497 xyz = (a[5] || a[6])
1498 if xyz == "x" || xyz == "X"
1500 elsif xyz == "y" || xyz == "Y"
1502 elsif xyz == "z" || xyz == "Z"
1509 puts "symmetry operation #{sym.inspect}" if verbose
1510 mol.add_symmetry(Transform.new(sym))
1512 puts "#{mol.nsymmetries} symmetry operations are added" if verbose
1513 elsif labels[0] =~ /^_atom_site_label/
1516 name = d[hlabel["_atom_site_label"]]
1517 elem = d[hlabel["_atom_site_type_symbol"]]
1518 fx = d[hlabel["_atom_site_fract_x"]]
1519 fy = d[hlabel["_atom_site_fract_y"]]
1520 fz = d[hlabel["_atom_site_fract_z"]]
1521 uiso = d[hlabel["_atom_site_U_iso_or_equiv"]]
1522 biso = d[hlabel["_atom_site_B_iso_or_equiv"]]
1523 occ = d[hlabel["_atom_site_occupancy"]]
1524 calc = d[hlabel["_atom_site_calc_flag"]]
1525 name = name.delete(" ()")
1526 if elem == nil || elem == ""
1527 if name =~ /[A-Za-z]{1,2}/
1528 elem = $&.capitalize
1533 ap = mol.add_atom(name, elem, elem)
1534 ap.fract_x, ap.sigma_x = float_strip_rms(fx)
1535 ap.fract_y, ap.sigma_y = float_strip_rms(fy)
1536 ap.fract_z, ap.sigma_z = float_strip_rms(fz)
1538 ap.temp_factor, sig = float_strip_rms(biso)
1540 ap.temp_factor, sig = float_strip_rms(uiso)
1541 ap.temp_factor *= 78.9568352087149 # 8*pi*pi
1543 ap.occupancy, sig = float_strip_rms(occ)
1544 if calc == "c" || calc == "calc"
1545 calculated_atoms.push(ap.index)
1547 # Guess special positions
1548 (1...mol.nsymmetries).each { |isym|
1550 sr = (mol.transform_for_symop(isym) * sr) - sr;
1551 nx = (sr.x + 0.5).floor
1552 ny = (sr.y + 0.5).floor
1553 nz = (sr.z + 0.5).floor
1554 if (Vector3D[sr.x - nx, sr.y - ny, sr.z - nz].length2 < 1e-6)
1555 # [isym, -nx, -ny, -nz] transforms this atom to itself
1556 # The following line is equivalent to:
1557 # if special_positions[ap.index] == nil; special_positions[ap.index] = []; end;
1558 # special_positions[ap.index].push(...)
1559 (special_positions[ap.index] ||= []).push([isym, -nx, -ny, -nz])
1562 if verbose && special_positions[ap.index]
1563 puts "#{name} is on the special position: #{special_positions[ap.index].inspect}"
1566 puts "#{mol.natoms} atoms are created." if verbose
1567 elsif labels[0] =~ /^_atom_site_aniso_label/
1568 # Set anisotropic parameters
1571 name = d[hlabel["_atom_site_aniso_label"]]
1572 ap = find_atom_by_name(mol, name)
1574 u11 = d[hlabel["_atom_site_aniso_U_11"]]
1577 u11, usig[0] = float_strip_rms(u11)
1578 u22, usig[1] = float_strip_rms(d[hlabel["_atom_site_aniso_U_22"]])
1579 u33, usig[2] = float_strip_rms(d[hlabel["_atom_site_aniso_U_33"]])
1580 u12, usig[3] = float_strip_rms(d[hlabel["_atom_site_aniso_U_12"]])
1581 u13, usig[4] = float_strip_rms(d[hlabel["_atom_site_aniso_U_13"]])
1582 u23, usig[5] = float_strip_rms(d[hlabel["_atom_site_aniso_U_23"]])
1583 ap.aniso = [u11, u22, u33, u12, u13, u23, 8] + usig
1587 puts "#{c} anisotropic parameters are set." if verbose
1588 elsif labels[0] =~ /^_geom_bond/
1592 n1 = d[hlabel["_geom_bond_atom_site_label_1"]]
1593 n2 = d[hlabel["_geom_bond_atom_site_label_2"]]
1594 sym1 = d[hlabel["_geom_bond_site_symmetry_1"]] || "."
1595 sym2 = d[hlabel["_geom_bond_site_symmetry_2"]] || "."
1596 n1 = find_atom_by_name(mol, n1)
1597 n2 = find_atom_by_name(mol, n2)
1598 next if n1 == nil || n2 == nil
1601 sym1 = parse_symmetry_operation(sym1)
1602 sym2 = parse_symmetry_operation(sym2)
1604 exbonds.push([n1, n2, sym1, sym2])
1606 mol.create_bond(n1, n2)
1608 tr1 = (sym1 ? mol.transform_for_symop(sym1) : Transform.identity)
1609 tr2 = (sym2 ? mol.transform_for_symop(sym2) : Transform.identity)
1610 if special_positions[n1]
1611 # Add extra bonds for equivalent positions of n1
1612 special_positions[n1].each { |symop|
1613 sym2x = mol.symop_for_transform(tr1 * mol.transform_for_symop(symop) * tr1.inverse * tr2)
1614 exbonds.push([n1, n2, sym1, sym2x])
1617 if special_positions[n2]
1618 # Add extra bonds n2-n1.symop, where symop transforms n2 to self
1619 tr = (sym1 ? mol.transform_for_symop(sym1) : Transform.identity)
1620 special_positions[n2].each { |symop|
1621 sym1x = mol.symop_for_transform(tr2 * mol.transform_for_symop(symop) * tr2.inverse * tr1)
1622 exbonds.push([n2, n1, sym2, sym1x])
1629 puts "#{mol.nbonds} bonds are created." if verbose
1630 if calculated_atoms.length > 0
1631 # Guess bonds for calculated hydrogen atoms
1633 calculated_atoms.each { |ai|
1634 if mol.atoms[ai].connects.length == 0
1635 as = mol.find_close_atoms(ai)
1637 mol.create_bond(ai, aj)
1642 puts "#{n1} bonds are guessed." if verbose
1644 if exbonds.length > 0
1645 h = Dialog.run("CIF Import: Symmetry Expansion") {
1647 item(:text, :title=>"There are bonds including symmetry related atoms.\nWhat do you want to do?"),
1648 item(:radio, :title=>"Expand only atoms that are included in those extra bonds.", :tag=>"atoms_only"),
1649 item(:radio, :title=>"Expand fragments having atoms included in the extra bonds.", :tag=>"fragment", :value=>1),
1650 item(:radio, :title=>"Ignore these extra bonds.", :tag=>"ignore")
1652 radio_group("atoms_only", "fragment", "ignore")
1654 if h[:status] == 0 && h["ignore"] == 0
1655 atoms_only = (h["atoms_only"] != 0)
1658 mol.each_fragment { |f| fragments.push(f) }
1662 if debug; puts "extra bond #{ex[0]}(#{ex[2].inspect}) - #{ex[1]}(#{ex[3].inspect})"; end
1667 ex[i + 2] = ex[i - 2]
1669 if debug; puts " symop = #{symop.inspect}"; end
1670 # Expand the atom or the fragment including the atom
1672 ig = IntGroup[ex[i - 2]]
1675 ig = fragments.find { |f| f.include?(ex[i - 2]) }
1676 ig.each_with_index { |n, ii| if n == ex[i - 2]; idx = ii; break; end }
1678 symop[4] = ex[i - 2] # Base atom
1679 if debug; puts " expanding #{ig} by #{symop.inspect}"; end
1680 a = mol.expand_by_symmetry(ig, symop[0], symop[1], symop[2], symop[3])
1681 ex[i + 2] = a[idx] # Index of the expanded atom
1684 if ex[4] && ex[5] && ex[4] != ex[5]
1685 if debug; puts " creating bond #{ex[4]} - #{ex[5]}"; end
1686 mol.create_bond(ex[4], ex[5])
1691 puts "#{mol.nbonds} bonds are created." if verbose
1695 # puts "Loop beginning with #{labels[0]} is skipped"
1699 token = getciftoken(fp)
1701 # Skip tokens until next tag or reserved word is detected
1702 while token != nil && token[0] != ?_ && token[0] != ?#
1703 token = getciftoken(fp)
1710 if token != nil && token == data_identifier
1711 # Process next molecule: open a new molecule and start adding atom on that
1714 (@aux_mols ||= []).push(mol)
1720 # self.undo_enabled = save_undo_enabled
1724 def dump(group = nil)
1729 str = str.gsub(/%/, "%%")
1730 str.gsub!(/ /, "%20")
1731 str.gsub!(/\t/, "%09")
1735 group = atom_group(group ? group : 0...natoms)
1739 s += sprintf("%4d %-7s %-4s %-4s %-2s %7.3f %7.3f %7.3f %6.3f [%s]\n",
1740 ap.index, sprintf("%3s.%d", ap.res_name, ap.res_seq),
1741 quote(ap.name), quote(ap.atom_type), quote(ap.element),
1742 ap.r.x, ap.r.y, ap.r.z, ap.charge,
1743 ap.connects.join(","))
1750 raise "Molecule must be empty"
1752 format = "index residue name atom_type element rx ry rz charge connects"
1756 # arg can be either a String or an array of String.
1757 # Iterates for each line in the string or each member of the array.
1758 if arg.is_a?(String)
1759 arg = arg.split("\n")
1763 format = line[1..-1]
1767 keys = format.split(" ").collect { |key| key.to_sym }
1769 values = line.chomp.split(" ")
1770 next if values == nil || values.length == 0
1771 ap = create_atom(sprintf("X%03d", natoms))
1772 r = Vector3D[0, 0, 0]
1773 keys.each_index { |i|
1774 break if (value = values[i]) == nil
1778 value.gsub(/%09/, "\t")
1779 value.gsub(/%20/, " ")
1780 value.gsub(/%%/, "%")
1784 if resAtoms[value] == nil
1785 resAtoms[value] = []
1787 resAtoms[value].push(ap.index)
1794 elsif key == :connects
1795 value.scan(/\d+/).each { |i|
1798 newBonds.push(ap.index)
1805 ap.set_attr(key, value)
1810 resAtoms.each_key { |key|
1811 assign_residue(atom_group(resAtoms[key]), key)
1813 create_bond(*newBonds)
1817 # Plug-in for loading mbsf
1818 def loadmbsf_plugin(s, lineno)
1822 # Plug-in for saving mbsf