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].upcase != atoms[index].element.upcase
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-9eE]+)/
765 elsif line =~ /^ *Spin= *(\w+)/
767 elsif line =~ /^ *Occup= *([-+.0-9eE]+)/
777 hash[:moinfo].push([sym, ene, (spin == "Alpha" ? 0 : 1), occ])
779 elsif line =~ /^ *([0-9]+) +([-+.0-9eE]+)/
791 hash[:mo].push(m.dup)
793 set_mo_coefficients(idx, ene, m)
801 break if i < ncomps # no MO info was found
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 # See also: MoleculeGetGaussianComponentInfo() in Molecule.c
821 m2name_p = {0=>"x", 1=>"y", -1=>"z"}
822 m2name_d = {0=>"zz-rr", 1=>"xz", -1=>"yz", 2=>"xx-yy", -2=>"xy"}
823 m2name_f = {0=>"z3-zr2", 1=>"xz2-xr2", -1=>"yz2-yr2", 2=>"x2z-y2z", -2=>"xyz", 3=>"x3-xy2", -3=>"x2y-y3"}
824 m2name_g = {0=>"z4-z2r2+r4", 1=>"xz3-xzr2", -1=>"yz3-yzr2", 2=>"x2z2-y2z2", -2=>"xyz2-xyr2", 3=>"x3z-xy2z", -3=>"x2yz-y3z", 4=>"x4-x2y2+y4", -4=>"x3y-xy3"}
825 fp = File.open(inppath + ".janpa.log", "rt") rescue fp = nil
827 hide_progress_panel # Close if it is open
828 message_box("Cannot open JANPA log file #{inppath + '.janpa.log'}: " + $!.to_s)
831 print("Importing #{inppath}.janpa.log.\n")
833 getline = lambda { lineno += 1; return fp.gets }
836 h["software"] = "JANPA"
837 nao_num = nil # Set later
838 nao_infos = [] # index=atom_index, value=Hash with key "s", "px", "py" etc.
839 # nao_infos[index][key]: array of [nao_num, occupancy], in the reverse order of appearance
840 while line = getline.call
841 if line =~ /molden2molden: a conversion tool for MOLDEN/
842 while line = getline.call
843 break if line =~ /^All done!/
844 if line =~ /\.spherical\.molden/
845 # The MOs are converted to spherical basis set
846 # Clear the existing MO and load *.spherical.molden
847 sname = inppath + ".spherical.molden"
848 fps = File.open(sname, "rt") rescue fps = nil
850 print("Importing #{sname}.\n")
852 type = get_mo_info(:type)
853 alpha = get_mo_info(:alpha)
854 beta = get_mo_info(:beta)
856 set_mo_info(:type=>type, :alpha=>alpha, :beta=>beta)
857 # mol.@hf_type should be set before calling sub_load_molden
864 elsif line =~ /^NAO \#/
865 h["NAO"] = [] # [num, anum, occupied_p, group_num, orb_desc, occ, principle*10+orb, shelltype]
866 # num: NAO index (0-based), anum: atom index (0-based),
867 # occupied_p: true if marked occupied by JANPA
868 # group_num: NAO group number (same for same atom, same principle, same orbital type)
869 # orb_desc: like "s", "px|py|pz", "dxy|dyz|dzx|dx2-y2|dz2-r2", etc.
871 # principle: principle number (calculated later from group_num)
872 # orb: 0:s, 1:p, 2:d, 3:f, 4:g
873 # shelltype: 0: core, 1: valence, 2: rydberg
874 nao_infos = [] # nao_infos[atom_index] = {orb_desc=>[[nao0, occ0], [nao1, occ1] ...]}
875 # orb_desc: same as above
876 # nao, occ: nao index (0-based) and occupancy
877 while line = getline.call
878 break if line !~ /^\s*[1-9]/
879 num = Integer(line[0, 5]) - 1
881 occ = Float(line[26, 11])
883 # atom_number, occupied?, group_number, orb_sym, angular_number
884 name =~ /\s*[A-Z]+([0-9]+)(\*?):\s* R([0-9]+)\*([a-z]+)\(([-0-9]+)\)/
885 anum = Integer($1) - 1
887 group_num = Integer($3)
889 ang_num = Integer($5)
892 orb_desc += m2name_p[ang_num]
893 elsif orb_desc == "d"
894 orb_desc += m2name_d[ang_num]
895 elsif orb_desc == "f"
896 orb_desc += m2name_f[ang_num]
897 elsif orb_desc == "g"
898 orb_desc += m2name_g[ang_num]
900 h["NAO"].push([num, anum, occupied, group_num, orb_desc, occ, nil, nil])
901 nao_num = h["NAO"].length
902 ((nao_infos[anum] ||= Hash.new)[orb_desc] ||= []).unshift([num, occ])
904 nao_num = h["NAO"].length
905 # Set principle from nao_infos
906 val_occ = [] # val_occ[atom_index][principle*10+orb] = (0: core, 1: valence, 2: ryd)
907 nao_infos.each_with_index { |value, atom_index|
908 val_occ[atom_index] ||= []
909 value.each { |orb_desc, ar|
910 ar.each_with_index { |v, idx|
919 orb_sym = orb_desc[0]
931 po = principle * 10 + orb
932 val1 = val_occ[atom_index][po]
934 val_occ[atom_index][po] = val
936 val_occ[atom_index][po] = 1 # core & val, core & ryd or val & ryd
938 # If all orbitals in the shell are "lp", then the shell should be core.
939 # If all orbitals in the shell are "ryd", then the shell should be ryd.
940 # Otherwise, the shell is valence.
942 h["NAO"][v[0]][6] = po
946 nao_num.times { |nao_index|
947 nao = h["NAO"][nao_index]
948 nao[7] = val_occ[nao[1]][nao[6]] # shell type
950 elsif line =~ /^\s*(C?)LPO\s+D e s c r i p t i o n\s+Occupancy\s+Composition/
956 h[key] = [] # [[a1, a2], LP|BD|NB, desc, occ, [h1, h2], lpo_idx, nao_idx]
957 # a1, a2: atom indices (0-based), h1, h2: hybrid indices (0-based)
958 # lpo_idx: orbital index (0-based)
959 # nao_idx: most significant nao (set later)
961 h["LHO"] = [] # [[a1, a2], lho_idx, nao_idx]
962 # a1, a2: atom indices (0-based)
963 # nao_idx: most significant nao (set later)
965 while line = getline.call
966 break if line =~ /^\s*$/
967 num = Integer(line[0, 5]) - 1
968 label1 = line[5, 6].strip
969 desc = line[11, 30].strip
970 occ = line[41, 11].strip
971 comp = line[52, 1000].strip
972 desc =~ /\s*([-A-Za-z0-9]+)(,\s*(.*$))?/
975 if desc2 =~ /^(.*)*\(NB\)\s*$/ && label1 == ""
979 label1.gsub!(/[\(\)]/, "") # Strip parens
980 atoms = desc1.scan(/[A-Za-z]+(\d+)/) # "C1-H3" -> [["1"], ["3"]]
981 atoms = atoms.map { |a| Integer(a[0]) - 1 } # [0, 2]
982 hybrids_a = comp.scan(/h(\d+)@[A-Za-z]+(\d+)/) # "h8@C1...h13@H3" -> "[["8", "1"], ["13", "3"]]
985 i = atoms.find_index(Integer(a[1]) - 1)
987 hybrids[i] = Integer(a[0]) - 1
990 # like ["BD", [0, 2], "Io = 0.2237", occ, [7, 12]]
991 # 0, 2 are the atom indices (0-based)
992 # 7, 12 are the number of hybrid orbitals (0-based)
993 h[key][num] = [atoms, label1, desc2, Float(occ), hybrids, num]
995 h["LHO"][hybrids[0]] = [atoms, hybrids[0]]
997 h["LHO"][hybrids[1]] = [atoms.reverse, hybrids[1]]
1001 nao_num.times { |idx|
1002 if h[key][idx] == nil
1003 h[key][idx] = [nil, nil, nil, nil, nil, idx]
1006 elsif line =~ /^ -NAO_Molden_File: (\S*)/
1008 elsif line =~ /^ -LHO_Molden_File: (\S*)/
1010 elsif line =~ /^ -CLPO_Molden_File: (\S*)/
1012 elsif line =~ /^ -PNAO_Molden_File: (\S*)/
1014 elsif line =~ /^ -AHO_Molden_File: (\S*)/
1016 elsif line =~ /^ -LPO_Molden_File: (\S*)/
1024 mfiles.each { |key, value|
1025 fp = Kernel.open(value, "rt") rescue fp = nil
1027 print("Importing #{value}.\n")
1028 res = sub_load_molden(fp, key)
1030 # Temporarily assign: this array will be reordered later and converted to LAMatrix
1031 h["AO/#{key}"] = res[:mo]
1032 #h["AO/#{key}"] = LAMatrix.new(res[:mo])
1040 natoms = self.natoms
1043 # [num, anum, occupied_p, group_num, orb_desc, occ, principle*10+orb, shelltype]
1044 # reorder by (1) anum and shelltype (core/val first, ryd later),
1045 # (2) principle*10+orb, (3) num
1047 h["NAO"].each { |nao|
1049 r = (nao[1] + natoms) * 3 + 2
1051 r = nao[1] * 3 + nao[7]
1053 r = r * 100 + nao[6]
1054 r = r * nao_num + nao[0]
1057 h["NAOnew2old"] = (0...nao_num).to_a.sort { |a, b| reorder[a] <=> reorder[b] }
1058 h["NAOold2new"] = []
1059 h["NAOnew2old"].each_with_index { |i, j|
1060 h["NAOold2new"][i] = j
1063 # LPO, CLPO: [[a1, a2], LP|BD|NB, desc, occ, [h1, h2], lpo_idx, nao_idx, shelltype]
1064 # LHO: [[a1, a2], lho_idx, nao_idx, shelltype]
1066 # Set the most significant nao
1067 mo = LAMatrix.new(h["AO/NAO"]).inverse * LAMatrix.new(h["AO/#{key}"])
1068 pos = (key == "LHO" ? 2 : 6) # position of nao_idx (new index)
1069 nao_num.times { |o_idx|
1070 o = (h[key][o_idx] ||= [])
1071 if o[pos - 1] == nil
1072 o[pos - 1] = o_idx # Original index
1074 # Find the NAO that has the largest contribution to this hybrid orbital
1078 a = mo[o_idx, i] ** 2
1084 o[pos] = h["NAOold2new"][i_max] # new index
1085 o[pos + 1] = h["NAO"][i_max][7] # 0:core, 1:val, 2:ryd
1087 # Set the atom which has the most significant NAO
1088 o[0] = [h["NAO"][i_max][1]]
1092 h[key].each_with_index { |o, o_idx|
1093 # Reorder by (1) atom and shelltype (core first, val second, ryd later),
1094 # if LP|BD|NB is present, then val/LP and val/BD come earlier than val/NB
1095 # (2) second atom if present, and (3) most significant NAO index
1097 r = (o[0][0] + natoms) * 4 + 3
1099 r = o[0][0] * 4 + o[pos + 1]
1100 if o[pos + 1] == 1 && (key == "LPO" || key == "CLPO") && o[1] == "NB"
1104 r = r * (natoms + 1) + (o[0][1] || -1) + 1 # Second atom index + 1 (0 if none)
1105 r = r * nao_num + o[pos]
1108 h[key + "new2old"] = (0...nao_num).to_a.sort { |a, b| reorder[a] <=> reorder[b] }
1109 h[key + "old2new"] = []
1110 h[key + "new2old"].each_with_index { |i, j|
1111 h[key + "old2new"][i] = j
1116 #print("h[\"NAO\"] = " + h["NAO"].inspect + "\n")
1117 #print("h[\"LHO\"] = " + h["LHO"].inspect + "\n")
1118 #print("h[\"LPO\"] = " + h["LPO"].inspect + "\n")
1119 #print("h[\"CLPO\"] = " + h["CLPO"].inspect + "\n")
1121 # Do reorder and make matrices
1123 if key == "NAO" || key == "PNAO"
1124 old2new = h["NAOold2new"]
1126 old2new = h["LPOold2new"]
1128 old2new = h["CLPOold2new"]
1129 elsif key == "AHO" || key == "LHO"
1130 old2new = h["LHOold2new"]
1135 mo[old2new[i]] = h["AO/" + key][i]
1139 h["AO/" + key] = LAMatrix.new(h["AO/" + key])
1144 old2new = h[key + "old2new"]
1147 nao_num.times { |nao_idx|
1148 # [num, anum, occupied_p, group_num, orb_desc, occ, principle*10+orb, shelltype]
1149 nao = h["NAO"][nao_idx]
1150 aname = self.atoms[nao[1]].name
1152 principle = nao[6] / 10
1153 shell = ["core", "val", "ryd"][nao[7]]
1154 j = (old2new ? old2new[nao_idx] : nao_idx)
1155 h["NAO_L"][j] = "#{aname} (#{principle}#{orb_desc}) (#{shell}, \##{nao_idx + 1})"
1159 nao_num.times { |lho_idx|
1160 # [[a1, a2], lho_idx, nao_idx, shelltype]
1161 lho = h["LHO"][lho_idx]
1162 aname1 = self.atoms[lho[0][0]].name
1163 aname2 = ("(" + self.atoms[lho[0][1]].name + ")") rescue ""
1164 shell = ["core", "val", "ryd"][lho[3]]
1165 j = (old2new ? old2new[lho_idx] : lho_idx)
1166 h["LHO_L"][j] = "#{aname1}#{aname2} (#{shell}, \##{lho_idx + 1})"
1168 elsif key == "LPO" || key == "CLPO"
1170 nao_num.times { |key_idx|
1171 # [[a1, a2], LP|BD|NB, desc, occ, [h1, h2], lpo_idx, nao_idx, shelltype]
1173 aname1 = self.atoms[o[0][0]].name
1174 aname2 = ("-" + self.atoms[o[0][1]].name) rescue ""
1178 type = "RY" # Rydberg
1179 elsif type == "LP" && shelltype == 0
1182 j = (old2new ? old2new[key_idx] : key_idx)
1183 h[key + "_L"][j] = "#{aname1}#{aname2} (#{type}, \##{key_idx + 1})"
1189 if @nbo["AO/NAO"] && @nbo["AO/LHO"] && @nbo["AO/PNAO"]
1190 # Generate PLHO from PNAO, NAO, LHO
1191 # This protocol was suggested by the JANPA author in a private commnunication.
1193 nao2lho = @nbo["AO/NAO"].inverse * @nbo["AO/LHO"]
1194 nao2pnao = @nbo["AO/NAO"].inverse * @nbo["AO/PNAO"]
1195 sign = LAMatrix.diagonal((0...nao2pnao.column_size).map { |i| (nao2pnao[i, i] < 0 ? -1 : 1)})
1196 @nbo["AO/PLHO"] = @nbo["AO/PNAO"] * sign * nao2lho
1198 @nbo["AO/PLHO"] = nil
1201 if @nbo["AO/AHO"] && @nbo["LHO_L"]
1202 # Copy labels from LHO
1203 @nbo["AHO_L"] = @nbo["LHO_L"].dup
1207 $stderr.write(e.message + "\n")
1208 $stderr.write(e.backtrace.inspect + "\n")
1212 # Convert @nbo info to an msbf string (multiline)
1214 # [XXX] (NAO, LHO, etc.)
1215 # nn (number of components)
1216 # (nao_labels)*nn, one per line
1218 # (%.18g)*nn, at most 6 per line
1223 keys = @nbo.keys.grep(/AO\/\w+/)
1228 labels = @nbo[key + "_L"]
1231 s += "[#{key}]\n#{nc}\n"
1233 l = (labels ? labels[i] : "")
1234 s += "#{key} #{i+1} #{l}\n"
1236 s += sprintf("%.18g%s", m[i, j], ((j == nc - 1 || j % 6 == 5) ? "\n" : " "))
1244 # Read @nbo info from a multiline string and set to @nbo
1245 def mbsfstring2nbo(s, first_lineno)
1247 lineno = first_lineno - 1
1248 errmsg = "Error reading NBO section: "
1256 next if lineno == first_lineno
1259 if ln =~ /\[(\w+)\]/
1262 return errmsg + "keyword (like NBO, NHO, ...) is expected at line #{lineno}"
1273 if ln[0, key.length] == key
1275 return errmsg + "number of components is missing"
1278 return errmsg + "too few coefficients at line #{lineno}"
1280 ln =~ /^(\w+) +(\d+) *(.*)$/
1284 labels[mo_matrix.length] = l.strip
1290 m = m.map { |c| Float(c) }
1292 return errmsg + "cannot convert to number at line #{lineno}"
1296 return errmsg + "too many coefficients at line #{lineno}"
1301 if mo_matrix.length == nc
1302 # All components are correctly read
1304 # Set the label if not specified
1306 if !labels[i] || labels[i] == ""
1307 labels[i] = "#{key}#{i+1}"
1310 h["#{key}_L"] = labels
1312 h["AO/#{key}"] = LAMatrix.new(mo_matrix)
1325 def loadout(filename)
1327 fp = open(filename, "rb")
1333 retval = sub_load_gaussian_log(fp)
1336 retval = sub_load_gamess_log(fp)
1339 retval = sub_load_psi4_log(fp)
1341 # If .molden file exists, then try to read it
1342 namepath = filename.gsub(/\.\w*$/, "")
1343 mname = "#{namepath}.molden"
1344 if File.exists?(mname)
1345 fp2 = open(mname, "rb")
1347 flag = sub_load_molden(fp2)
1349 status = (flag ? 0 : -1)
1352 if File.exists?("#{namepath}.janpa.log")
1353 flag = sub_load_janpa_log(namepath)
1354 status = (flag ? 0 : -1)
1368 alias :loadlog :loadout
1370 def loadxyz(filename)
1371 fp = open(filename, "rb")
1384 if coords.length == 0
1385 # Allow "number of atoms" line or "number of atoms and crystallographic cell parameter"
1386 # (Chem3D xyz format)
1387 next if toks.length == 1
1389 cell = toks[1..6].map { |s| Float(s.sub(/\(\d+\)/, "")) } # Remove (xx) and convert to float
1393 name, x, y, z = line.split
1395 x = Float(x.sub(/\(\d+\)/, ""))
1396 y = Float(y.sub(/\(\d+\)/, ""))
1397 z = Float(z.sub(/\(\d+\)/, ""))
1398 r = Vector3D[x, y, z]
1406 celltr = self.cell_transform
1408 names.each_index { |i|
1409 ap = add_atom(names[i])
1410 names[i] =~ /^([A-Za-z]{1,2})/
1411 element = $1.capitalize
1412 ap.element = element
1413 ap.atom_type = element
1415 ap.r = celltr * coords[i]
1424 # (j + 1 ... natoms).each { |k|
1425 # if calc_bond(j, k) < 1.7
1426 # # create_bond(j, k)
1430 # self.undo_enabled = save_undo_enabled
1431 (n > 0 ? true : false)
1434 def savexyz(filename)
1435 open(filename, "wb") { |fp|
1436 fp.printf "%d\n", self.natoms
1438 fp.printf "%s %.5f %.5f %.5f\n", ap.element, ap.x, ap.y, ap.z
1444 def loadzmat(filename)
1446 open(filename, "rb") { |fp|
1447 while (line = fp.gets)
1450 an = Molecule.guess_atomic_number_from_name(a[0])
1451 elm = Parameter.builtin.elements[an].name
1452 base1 = a[1].to_i - 1
1453 base2 = a[3].to_i - 1
1454 base3 = a[5].to_i - 1
1455 base1 = nil if base1 < 0
1456 base2 = nil if base2 < 0
1457 base3 = nil if base3 < 0
1458 add_atom(a[0], elm, elm, a[2].to_f, base1, a[4].to_f, base2, a[6].to_f, base3)
1464 def savezmat(filename)
1467 def loadcom(filename)
1468 # save_undo_enabled = self.undo_enabled?
1469 # self.undo_enabled = false
1471 fp = open(filename, "rb")
1473 while (line = fp.gets)
1476 section = 1 if line =~ /^\#/
1478 elsif section == 1 || section == 2
1479 section += 1 if line =~ /^\s*$/
1482 # The first line is skipped (charge and multiplicity)
1483 while (line = fp.gets)
1485 break if line =~ /^\s*$/
1486 a = line.split(/\s*[ \t,\/]\s*/)
1487 r = Vector3D[Float(a[1]), Float(a[2]), Float(a[3])]
1489 a[0] =~ /^([A-Za-z]{1,2})/
1490 element = $1.capitalize
1491 ap.element = element
1492 ap.atom_type = element
1500 # self.undo_enabled = save_undo_enabled
1504 def loadinp(filename)
1505 # save_undo_enabled = self.undo_enabled?
1506 # self.undo_enabled = false
1508 fp = open(filename, "rb")
1511 while (line = fp.gets)
1512 if line =~ /\A \$BASIS/
1516 next if line !~ /\A \$DATA/
1517 line = fp.gets # Title line
1518 line = fp.gets # Symmetry line
1519 while (line = fp.gets)
1522 break if line =~ /\$END/
1525 ap.atomic_number = Integer(a[1])
1526 ap.atom_type = ap.element
1527 r = Vector3D[Float(a[2]), Float(a[3]), Float(a[4])]
1530 # Skip until one blank line is detected
1531 while (line = fp.gets) && line =~ /\S/
1539 # self.undo_enabled = save_undo_enabled
1543 def saveinp(filename)
1545 raise MolbyError, "cannot save GAMESS input; the molecule is empty"
1547 fp = open(filename, "wb")
1549 fp.print <<end_of_header
1551 ! Generated by Molby at #{now}
1552 $CONTRL COORD=UNIQUE EXETYP=RUN ICHARG=0
1553 ICUT=20 INTTYP=HONDO ITOL=30
1554 MAXIT=200 MOLPLT=.T. MPLEVL=0
1555 MULT=1 QMTTOL=1e-08 RUNTYP=OPTIMIZE
1556 SCFTYP=RHF UNITS=ANGS $END
1557 $SCF CONV=1.0E-06 DIRSCF=.T. $END
1558 $STATPT NSTEP=400 OPTTOL=1.0E-06 $END
1559 $SYSTEM MEMDDI=0 MWORDS=16 TIMLIM=50000 $END
1560 $BASIS GBASIS=N31 NDFUNC=1 NGAUSS=6 $END
1561 $GUESS GUESS=HUCKEL $END
1568 next if ap.atomic_number == 0
1569 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
1576 def savecom(filename)
1578 raise MolbyError, "cannot save Gaussian input; the molecule is empty"
1580 fp = open(filename, "wb")
1581 base = File.basename(filename, ".*")
1582 fp.print <<end_of_header
1586 #{name}; created by Molby at #{Time.now.to_s}
1591 next if ap.atomic_number == 0
1592 fp.printf "%-6s %10.6f %10.6f %10.6f\n", ap.element, ap.r.x, ap.r.y, ap.r.z
1599 alias :loadgjf :loadcom
1600 alias :savegjf :savecom
1602 def loadcif(filename)
1605 while @tokens.length == 0
1610 while line = fp.gets
1611 break if line[0] == ?;
1617 line = line[1...line.length]
1620 line.scan(/'[^\']*'|"[^\"]*"|[^#\s]+|#.*/) do |s|
1622 if s =~ /^data_|loop_|global_|save_|stop_/i
1623 s = "#" + s # Label for reserved words
1628 return @tokens.shift
1630 def float_strip_rms(str)
1631 str =~ /^(-?)(\d*)(\.(\d*))?(\((\d+)\))?$/
1632 sgn, i, frac, rms = $1, $2, $4, $6
1635 base = 0.1 ** frac.length
1636 i = i + frac.to_f * base
1641 rms = rms.to_f * base
1650 def parse_symmetry_operation(str)
1653 elsif (str =~ /(\d+)_(\d)(\d)(\d)/) || (str =~ /(\d+) +(\d)(\d)(\d)/)
1654 sym = [Integer($1) - 1, Integer($2) - 5, Integer($3) - 5, Integer($4) - 5]
1655 elsif (str =~ /^(\d+)$/)
1656 sym = [Integer($1) - 1, 0, 0, 0]
1658 if sym && (sym[0] == 0 && sym[1] == 0 && sym[2] == 0 && sym[3] == 0)
1663 def find_atom_by_name(mol, name)
1664 name = name.delete(" ()")
1665 ap = mol.atoms[name] rescue ap = nil
1668 selfname = self.name
1669 fp = open(filename, "rb")
1670 data_identifier = nil
1676 bond_defined = false
1677 special_positions = []
1680 cell_trans = cell_trans_inv = Transform.identity
1681 token = getciftoken(fp)
1682 pardigits_re = /\(\d+\)/
1683 calculated_atoms = []
1685 if token =~ /^\#data_/i
1686 if data_identifier == nil || mol.natoms == 0
1687 # First block or no atoms yet
1688 # Continue processing of this molecule
1689 data_identifier = token
1690 token = getciftoken(fp)
1693 # Description of another molecule begins here
1694 data_identifier = token
1697 elsif token =~ /^_cell/
1698 val = getciftoken(fp)
1699 if token == "_cell_length_a"
1700 cell[0], cell[6] = float_strip_rms(val)
1701 elsif token == "_cell_length_b"
1702 cell[1], cell[7] = float_strip_rms(val)
1703 elsif token == "_cell_length_c"
1704 cell[2], cell[8] = float_strip_rms(val)
1705 elsif token == "_cell_angle_alpha"
1706 cell[3], cell[9] = float_strip_rms(val)
1707 elsif token == "_cell_angle_beta"
1708 cell[4], cell[10] = float_strip_rms(val)
1709 elsif token == "_cell_angle_gamma"
1710 cell[5], cell[11] = float_strip_rms(val)
1712 if cell.length == 12 && cell.all?
1714 puts "Unit cell is set to #{mol.cell.inspect}." if verbose
1716 cell_trans = mol.cell_transform
1717 cell_trans_inv = cell_trans.inverse
1719 token = getciftoken(fp)
1721 elsif token.casecmp("#loop_") == 0
1723 while (token = getciftoken(fp)) && token[0] == ?_
1726 if labels[0] =~ /symmetry_equiv_pos|space_group_symop|atom_site_label|atom_site_aniso_label|geom_bond/
1727 hlabel = Hash.new(-10000000)
1728 labels.each_with_index { |lb, i|
1735 break if token == nil || token[0] == ?_ || token[0] == ?#
1741 token = getciftoken(fp)
1743 if labels[0] =~ /^_symmetry_equiv_pos/ || labels[0] =~ /^_space_group_symop/
1745 symstr = d[hlabel["_symmetry_equiv_pos_as_xyz"]] || d[hlabel["_space_group_symop_operation_xyz"]]
1746 symstr.delete("\"\'")
1747 exps = symstr.split(/,/)
1748 sym = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
1749 exps.each_with_index { |s, i|
1750 terms = s.scan(/([-+]?)(([.0-9]+)(\/([0-9]+))?([xXyYzZ])?|([xXyYzZ]))/)
1752 # a[0]: sign, a[2]: numerator, a[4]: denometer
1754 # The number part is a[2]/a[4]
1755 num = Float(a[2])/Float(a[4])
1757 # The number part is either integer or a floating point
1762 num = -num if a[0][0] == ?-
1763 xyz = (a[5] || a[6])
1764 if xyz == "x" || xyz == "X"
1766 elsif xyz == "y" || xyz == "Y"
1768 elsif xyz == "z" || xyz == "Z"
1775 puts "symmetry operation #{sym.inspect}" if verbose
1776 mol.add_symmetry(Transform.new(sym))
1778 puts "#{mol.nsymmetries} symmetry operations are added" if verbose
1779 elsif labels[0] =~ /^_atom_site_label/
1782 name = d[hlabel["_atom_site_label"]]
1783 elem = d[hlabel["_atom_site_type_symbol"]]
1784 fx = d[hlabel["_atom_site_fract_x"]]
1785 fy = d[hlabel["_atom_site_fract_y"]]
1786 fz = d[hlabel["_atom_site_fract_z"]]
1787 uiso = d[hlabel["_atom_site_U_iso_or_equiv"]]
1788 biso = d[hlabel["_atom_site_B_iso_or_equiv"]]
1789 occ = d[hlabel["_atom_site_occupancy"]]
1790 calc = d[hlabel["_atom_site_calc_flag"]]
1791 name = name.delete(" ()")
1792 if elem == nil || elem == ""
1793 if name =~ /[A-Za-z]{1,2}/
1794 elem = $&.capitalize
1799 ap = mol.add_atom(name, elem, elem)
1800 ap.fract_x, ap.sigma_x = float_strip_rms(fx)
1801 ap.fract_y, ap.sigma_y = float_strip_rms(fy)
1802 ap.fract_z, ap.sigma_z = float_strip_rms(fz)
1804 ap.temp_factor, sig = float_strip_rms(biso)
1806 ap.temp_factor, sig = float_strip_rms(uiso)
1807 ap.temp_factor *= 78.9568352087149 # 8*pi*pi
1809 ap.occupancy, sig = float_strip_rms(occ)
1810 if calc == "c" || calc == "calc"
1811 calculated_atoms.push(ap.index)
1813 # Guess special positions
1814 (1...mol.nsymmetries).each { |isym|
1816 sr = (mol.transform_for_symop(isym) * sr) - sr;
1817 nx = (sr.x + 0.5).floor
1818 ny = (sr.y + 0.5).floor
1819 nz = (sr.z + 0.5).floor
1820 if (Vector3D[sr.x - nx, sr.y - ny, sr.z - nz].length2 < 1e-6)
1821 # [isym, -nx, -ny, -nz] transforms this atom to itself
1822 # The following line is equivalent to:
1823 # if special_positions[ap.index] == nil; special_positions[ap.index] = []; end;
1824 # special_positions[ap.index].push(...)
1825 (special_positions[ap.index] ||= []).push([isym, -nx, -ny, -nz])
1828 if verbose && special_positions[ap.index]
1829 puts "#{name} is on the special position: #{special_positions[ap.index].inspect}"
1832 puts "#{mol.natoms} atoms are created." if verbose
1833 elsif labels[0] =~ /^_atom_site_aniso_label/
1834 # Set anisotropic parameters
1837 name = d[hlabel["_atom_site_aniso_label"]]
1838 ap = find_atom_by_name(mol, name)
1840 u11 = d[hlabel["_atom_site_aniso_U_11"]]
1843 u11, usig[0] = float_strip_rms(u11)
1844 u22, usig[1] = float_strip_rms(d[hlabel["_atom_site_aniso_U_22"]])
1845 u33, usig[2] = float_strip_rms(d[hlabel["_atom_site_aniso_U_33"]])
1846 u12, usig[3] = float_strip_rms(d[hlabel["_atom_site_aniso_U_12"]])
1847 u13, usig[4] = float_strip_rms(d[hlabel["_atom_site_aniso_U_13"]])
1848 u23, usig[5] = float_strip_rms(d[hlabel["_atom_site_aniso_U_23"]])
1849 ap.aniso = [u11, u22, u33, u12, u13, u23, 8] + usig
1853 puts "#{c} anisotropic parameters are set." if verbose
1854 elsif labels[0] =~ /^_geom_bond/
1858 n1 = d[hlabel["_geom_bond_atom_site_label_1"]]
1859 n2 = d[hlabel["_geom_bond_atom_site_label_2"]]
1860 sym1 = d[hlabel["_geom_bond_site_symmetry_1"]] || "."
1861 sym2 = d[hlabel["_geom_bond_site_symmetry_2"]] || "."
1862 n1 = find_atom_by_name(mol, n1)
1863 n2 = find_atom_by_name(mol, n2)
1864 next if n1 == nil || n2 == nil
1867 sym1 = parse_symmetry_operation(sym1)
1868 sym2 = parse_symmetry_operation(sym2)
1870 exbonds.push([n1, n2, sym1, sym2])
1872 mol.create_bond(n1, n2)
1874 tr1 = (sym1 ? mol.transform_for_symop(sym1) : Transform.identity)
1875 tr2 = (sym2 ? mol.transform_for_symop(sym2) : Transform.identity)
1876 if special_positions[n1]
1877 # Add extra bonds for equivalent positions of n1
1878 special_positions[n1].each { |symop|
1879 sym2x = mol.symop_for_transform(tr1 * mol.transform_for_symop(symop) * tr1.inverse * tr2)
1880 exbonds.push([n1, n2, sym1, sym2x])
1883 if special_positions[n2]
1884 # Add extra bonds n2-n1.symop, where symop transforms n2 to self
1885 tr = (sym1 ? mol.transform_for_symop(sym1) : Transform.identity)
1886 special_positions[n2].each { |symop|
1887 sym1x = mol.symop_for_transform(tr2 * mol.transform_for_symop(symop) * tr2.inverse * tr1)
1888 exbonds.push([n2, n1, sym2, sym1x])
1895 puts "#{mol.nbonds} bonds are created." if verbose
1896 if calculated_atoms.length > 0
1897 # Guess bonds for calculated hydrogen atoms
1899 calculated_atoms.each { |ai|
1900 if mol.atoms[ai].connects.length == 0
1901 as = mol.find_close_atoms(ai)
1903 mol.create_bond(ai, aj)
1908 puts "#{n1} bonds are guessed." if verbose
1910 if exbonds.length > 0
1911 h = Dialog.run("CIF Import: Symmetry Expansion") {
1913 item(:text, :title=>"There are bonds including symmetry related atoms.\nWhat do you want to do?"),
1914 item(:radio, :title=>"Expand only atoms that are included in those extra bonds.", :tag=>"atoms_only"),
1915 item(:radio, :title=>"Expand fragments having atoms included in the extra bonds.", :tag=>"fragment", :value=>1),
1916 item(:radio, :title=>"Ignore these extra bonds.", :tag=>"ignore")
1918 radio_group("atoms_only", "fragment", "ignore")
1920 if h[:status] == 0 && h["ignore"] == 0
1921 atoms_only = (h["atoms_only"] != 0)
1924 mol.each_fragment { |f| fragments.push(f) }
1928 if debug; puts "extra bond #{ex[0]}(#{ex[2].inspect}) - #{ex[1]}(#{ex[3].inspect})"; end
1933 ex[i + 2] = ex[i - 2]
1935 if debug; puts " symop = #{symop.inspect}"; end
1936 # Expand the atom or the fragment including the atom
1938 ig = IntGroup[ex[i - 2]]
1941 ig = fragments.find { |f| f.include?(ex[i - 2]) }
1942 ig.each_with_index { |n, ii| if n == ex[i - 2]; idx = ii; break; end }
1944 symop[4] = ex[i - 2] # Base atom
1945 if debug; puts " expanding #{ig} by #{symop.inspect}"; end
1946 a = mol.expand_by_symmetry(ig, symop[0], symop[1], symop[2], symop[3])
1947 ex[i + 2] = a[idx] # Index of the expanded atom
1950 if ex[4] && ex[5] && ex[4] != ex[5]
1951 if debug; puts " creating bond #{ex[4]} - #{ex[5]}"; end
1952 mol.create_bond(ex[4], ex[5])
1957 puts "#{mol.nbonds} bonds are created." if verbose
1961 # puts "Loop beginning with #{labels[0]} is skipped"
1965 token = getciftoken(fp)
1967 # Skip tokens until next tag or reserved word is detected
1968 while token != nil && token[0] != ?_ && token[0] != ?#
1969 token = getciftoken(fp)
1976 if token != nil && token == data_identifier
1977 # Process next molecule: open a new molecule and start adding atom on that
1980 (@aux_mols ||= []).push(mol)
1986 # self.undo_enabled = save_undo_enabled
1990 def dump(group = nil)
1995 str = str.gsub(/%/, "%%")
1996 str.gsub!(/ /, "%20")
1997 str.gsub!(/\t/, "%09")
2001 group = atom_group(group ? group : 0...natoms)
2005 s += sprintf("%4d %-7s %-4s %-4s %-2s %7.3f %7.3f %7.3f %6.3f [%s]\n",
2006 ap.index, sprintf("%3s.%d", ap.res_name, ap.res_seq),
2007 quote(ap.name), quote(ap.atom_type), quote(ap.element),
2008 ap.r.x, ap.r.y, ap.r.z, ap.charge,
2009 ap.connects.join(","))
2016 raise "Molecule must be empty"
2018 format = "index residue name atom_type element rx ry rz charge connects"
2022 # arg can be either a String or an array of String.
2023 # Iterates for each line in the string or each member of the array.
2024 if arg.is_a?(String)
2025 arg = arg.split("\n")
2029 format = line[1..-1]
2033 keys = format.split(" ").collect { |key| key.to_sym }
2035 values = line.chomp.split(" ")
2036 next if values == nil || values.length == 0
2037 ap = create_atom(sprintf("X%03d", natoms))
2038 r = Vector3D[0, 0, 0]
2039 keys.each_index { |i|
2040 break if (value = values[i]) == nil
2044 value.gsub(/%09/, "\t")
2045 value.gsub(/%20/, " ")
2046 value.gsub(/%%/, "%")
2050 if resAtoms[value] == nil
2051 resAtoms[value] = []
2053 resAtoms[value].push(ap.index)
2060 elsif key == :connects
2061 value.scan(/\d+/).each { |i|
2064 newBonds.push(ap.index)
2071 ap.set_attr(key, value)
2076 resAtoms.each_key { |key|
2077 assign_residue(atom_group(resAtoms[key]), key)
2079 create_bond(*newBonds)
2083 # Plug-in for loading mbsf
2084 def loadmbsf_plugin(s, lineno)
2088 # Plug-in for saving mbsf