4 # Created by Toshi Nagata.
5 # Copyright 2008 Toshi Nagata. All rights reserved.
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation version 2 of the License.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
22 raise MolbyError, "cannot load crd; the molecule is empty"
24 fp = open(filename, "rb")
27 coords = (0...natoms).collect { Vector3D[0, 0, 0] }
28 periodic = (self.box && self.box[4].any? { |n| n != 0 })
29 show_progress_panel("Loading AMBER crd file...")
30 # puts "sframe = #{sframe}, pos = #{fp.pos}"
33 values = line.scan(/......../)
35 # The first line should be skipped. However, if this line seems to contain
36 # coordinates, then try reading them
37 if values.size != 10 && (values.size > 10 || values.size != natoms * 3)
38 next # Wrong number of coordinates
40 if values.each { |v| Float(v) rescue break } == nil
41 # The line contains non-number
44 puts "Loadcrd: The title line seems to be missing"
46 if count + values.size > natoms * 3
47 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)
50 coords[count / 3][count % 3] = Float(v)
53 if count == natoms * 3
55 if frame == 0 && atoms.all? { |ap| ap.r.x == 0.0 && ap.r.y == 0.0 && ap.r.z == 0.0 }
56 # Do not create a new frame
57 atoms.each_with_index { |ap, i| ap.r = coords[i] }
59 create_frame([coords])
62 # Should have box information
64 if line == nil || (values = line.chomp.scan(/......../)).length != 3
65 # Periodic but no cell information
66 puts "Loadcrd: the molecule has a periodic cell but the crd file does not contain cell info"
70 self.cell = [Float(values[0]), Float(values[1]), Float(values[2]), 90, 90, 90]
75 set_progress_message("Loading AMBER crd file...\n(#{frame} frames completed)")
81 raise MolbyError, sprintf("crd format error - file ended in the middle of frame %d", frame)
85 self.frame = self.nframes - 1
94 raise MolbyError, "cannot save crd; the molecule is empty"
96 fp = open(filename, "wb")
97 show_progress_panel("Saving AMBER crd file...")
98 fp.printf("TITLE: %d atoms\n", natoms)
100 nframes = self.nframes
102 self.update_enabled = false
104 (0...nframes).each { |i|
107 while (j < natoms * 3)
108 w = atoms[j / 3].r[j % 3]
109 fp.printf(" %7.3f", w)
110 fp.print("\n") if (j % 10 == 9 || j == natoms * 3 - 1)
114 set_progress_message("Saving AMBER crd file...\n(#{i} frames completed)")
118 self.update_enabled = true
126 alias :loadmdcrd :loadcrd
127 alias :savemdcrd :savecrd
129 def sub_load_gamess_log(fp)
136 self.update_enabled = false
137 mes = "Loading GAMESS log file"
138 show_progress_panel(mes)
139 ne_alpha = ne_beta = 0 # number of electrons
140 rflag = nil # 0, UHF; 1, RHF; 2, ROHF
142 ncomps = 0 # Number of AO terms per one MO (sum of the number of components over all shells)
143 alpha_beta = nil # Flag to read alpha/beta MO appropriately
156 if line =~ /ATOM\s+ATOMIC\s+COORDINATES/ || line =~ /COORDINATES OF ALL ATOMS ARE/
157 set_progress_message(mes + "\nReading atomic coordinates...")
158 first_line = (line =~ /ATOMIC/)
159 line = fp.gets # Skip one line
163 while (line = fp.gets) != nil
164 break if line =~ /^\s*$/ || line =~ /END OF ONE/
165 next if line =~ /-----/
166 name, charge, x, y, z = line.split
167 v = Vector3D[x, y, z]
168 coords.push(v * (first_line ? 0.529177 : 1.0)) # Bohr to angstrom
169 names.push([name, charge])
172 # Build a new molecule
173 names.each_index { |i|
174 ap = add_atom(names[i][0])
175 ap.atomic_number = names[i][1].to_i
176 ap.atom_type = ap.element
183 # (j + 1 ... natoms).each { |k|
184 # if calc_bond(j, k) < 1.7
192 create_frame([coords]) # Should not be (coords)
194 elsif line =~ /EQUILIBRIUM GEOMETRY LOCATED/i
195 set_progress_message(mes + "\nReading optimized coordinates...")
196 fp.gets; fp.gets; fp.gets
198 while (line = fp.gets) != nil
199 break if line =~ /^\s*$/
201 atom, an, x, y, z = line.split
203 ap.r = Vector3D[x, y, z]
207 if ne_alpha > 0 && ne_beta > 0
208 # Allocate basis set record again, to update the atomic coordinates
209 allocate_basis_set_record(rflag, ne_alpha, ne_beta)
211 elsif line =~ /ATOMIC BASIS SET/
212 while (line = fp.gets)
213 break if line =~ /SHELL\s+TYPE\s+PRIMITIVE/
218 sym = -10 # undefined
220 while (line = fp.gets)
221 break if line =~ /TOTAL NUMBER OF BASIS SET/
225 add_gaussian_orbital_shell(sym, nprims, i)
226 # puts "add_gaussian_orbital_shell #{sym}, #{nprims}, #{i}"
234 line = fp.gets # Skip the blank line
236 elsif a.length == 5 || a.length == 6
252 raise MolbyError, "Unknown gaussian shell type at line #{fp.lineno}"
256 if (a.length == 5 && sym == -1) || (a.length == 6 && sym != -1)
257 raise MolbyError, "Wrong format in gaussian shell information at line #{fp.lineno}"
261 csp = Float(a[5] || 0.0)
262 add_gaussian_primitive_coefficients(exp, c, csp)
264 # puts "add_gaussian_primitive_coefficients #{exp}, #{c}, #{csp}"
266 raise MolbyError, "Error in reading basis set information at line #{fp.lineno}"
269 elsif line =~ /NUMBER OF OCCUPIED ORBITALS/
277 elsif line =~ /SCFTYP=(\w+)/
279 if ne_alpha > 0 && ne_beta > 0
288 elsif line =~ /(ALPHA|BETA)\s*SET/
290 elsif line =~ /^\s*(EIGENVECTORS|MOLECULAR ORBITALS)\s*$/
292 allocate_basis_set_record(rflag, ne_alpha, ne_beta)
293 # puts "allocate_basis_set_record #{rflag}, #{ne_alpha}, #{ne_beta}"
297 line = fp.gets; line = fp.gets;
298 set_progress_message(mes + "\nReading MO coefficients...")
299 while (line = fp.gets) != nil
300 break unless line =~ /^\s*\d/
301 mo_labels = line.split # MO numbers (1-based)
302 mo_energies = fp.gets.split
303 mo_symmetries = fp.gets.split
304 mo = mo_labels.map { [] } # array of *independent* empty arrays
305 while (line = fp.gets) != nil
306 break unless line =~ /^\s*\d/
308 s = line[15 + 11 * i, 11].chomp
309 break if s =~ /^\s*$/
310 mo[i].push(Float(s)) rescue print "line = #{line}, s = #{s}"
311 # line[15..-1].split.each_with_index { |s, i|
312 # mo[i].push(Float(s))
315 mo.each_with_index { |m, i|
316 idx = Integer(mo_labels[i]) - 1
317 set_mo_coefficients(idx + (alpha_beta == "BETA" ? ncomps : 0), Float(mo_energies[i]), m)
318 # if mo_labels[i] % 8 == 1
319 # puts "set_mo_coefficients #{idx}, #{mo_energies[i]}, [#{m[0]}, ..., #{m[-1]}]"
322 if line =~ /^\s*$/ && idx < ncomps - 1
328 set_progress_message(mes)
333 select_frame(nframes - 1)
336 self.update_enabled = true
337 (n > 0 ? true : false)
340 def sub_load_gaussian_log(fp)
353 use_input_orientation = false
354 show_progress_panel("Loading Gaussian out file...")
361 if line =~ /(Input|Standard) orientation/
364 use_input_orientation = true if nf == 0
365 next if !use_input_orientation
367 next if use_input_orientation
369 4.times { line = fp.gets } # Skip four lines
373 while (line = fp.gets) != nil
374 break if line =~ /-----/
375 num, charge, type, x, y, z = line.split
376 coords.push(Vector3D[x, y, z])
381 # Build a new molecule
382 anums.each_index { |i|
384 ap.atomic_number = anums[i]
385 ap.atom_type = ap.element
386 ap.name = sprintf("%s%d", ap.element, i)
392 # (j + 1 ... natoms).each { |k|
393 # if calc_bond(j, k) < 1.7
402 create_frame([coords]) # Should not be (coords)
408 (n > 0 ? true : false)
411 def loadout(filename)
413 fp = open(filename, "rb")
416 retval = sub_load_gaussian_log(fp)
419 retval = sub_load_gamess_log(fp)
427 alias :loadlog :loadout
429 def loadxyz(filename)
430 fp = open(filename, "rb")
443 if coords.length == 0
444 # Allow "number of atoms" line or "number of atoms and crystallographic cell parameter"
445 # (Chem3D xyz format)
446 next if toks.length == 1
448 cell = toks[1..6].map { |s| Float(s.sub(/\(\d+\)/, "")) } # Remove (xx) and convert to float
452 name, x, y, z = line.split
454 x = Float(x.sub(/\(\d+\)/, ""))
455 y = Float(y.sub(/\(\d+\)/, ""))
456 z = Float(z.sub(/\(\d+\)/, ""))
457 r = Vector3D[x, y, z]
465 celltr = self.cell_transform
467 names.each_index { |i|
468 ap = add_atom(names[i])
469 names[i] =~ /^([A-Za-z]{1,2})/
470 element = $1.capitalize
472 ap.atom_type = element
474 ap.r = celltr * coords[i]
483 # (j + 1 ... natoms).each { |k|
484 # if calc_bond(j, k) < 1.7
485 # # create_bond(j, k)
489 # self.undo_enabled = save_undo_enabled
490 (n > 0 ? true : false)
493 def loadcom(filename)
494 # save_undo_enabled = self.undo_enabled?
495 # self.undo_enabled = false
497 fp = open(filename, "rb")
499 while (line = fp.gets)
502 section = 1 if line =~ /^\#/
504 elsif section == 1 || section == 2
505 section += 1 if line =~ /^\s*$/
508 # The first line is skipped (charge and multiplicity)
509 while (line = fp.gets)
511 break if line =~ /^\s*$/
512 a = line.split(/\s*[ \t,\/]\s*/)
513 r = Vector3D[Float(a[1]), Float(a[2]), Float(a[3])]
515 a[0] =~ /^([A-Za-z]{1,2})/
516 element = $1.capitalize
518 ap.atom_type = element
526 # self.undo_enabled = save_undo_enabled
530 def loadinp(filename)
531 # save_undo_enabled = self.undo_enabled?
532 # self.undo_enabled = false
534 fp = open(filename, "rb")
537 while (line = fp.gets)
538 if line =~ /\A \$BASIS/
542 next if line !~ /\A \$DATA/
543 line = fp.gets # Title line
544 line = fp.gets # Symmetry line
545 while (line = fp.gets)
548 break if line =~ /\$END/
551 ap.atomic_number = Integer(a[1])
552 ap.atom_type = ap.element
553 r = Vector3D[Float(a[2]), Float(a[3]), Float(a[4])]
556 # Skip until one blank line is detected
557 while (line = fp.gets) && line =~ /\S/
565 # self.undo_enabled = save_undo_enabled
569 def saveinp(filename)
571 raise MolbyError, "cannot save GAMESS input; the molecule is empty"
573 fp = open(filename, "wb")
575 fp.print <<end_of_header
577 ! Generated by Molby at #{now}
578 $CONTRL COORD=UNIQUE EXETYP=RUN ICHARG=0
579 ICUT=20 INTTYP=HONDO ITOL=30
580 MAXIT=200 MOLPLT=.T. MPLEVL=0
581 MULT=1 QMTTOL=1e-08 RUNTYP=OPTIMIZE
582 SCFTYP=RHF UNITS=ANGS $END
583 $SCF CONV=1.0E-06 DIRSCF=.T. $END
584 $STATPT NSTEP=400 OPTTOL=1.0E-06 $END
585 $SYSTEM MEMDDI=0 MWORDS=16 TIMLIM=50000 $END
586 $BASIS GBASIS=N31 NDFUNC=1 NGAUSS=6 $END
587 $GUESS GUESS=HUCKEL $END
594 next if ap.atomic_number == 0
595 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
602 def savecom(filename)
604 raise MolbyError, "cannot save Gaussian input; the molecule is empty"
606 fp = open(filename, "wb")
607 base = File.basename(filename, ".*")
608 fp.print <<end_of_header
612 #{name}; created by Molby at #{Time.now.to_s}
617 next if ap.atomic_number == 0
618 fp.printf "%-6s %10.6f %10.6f %10.6f\n", ap.element, ap.r.x, ap.r.y, ap.r.z
625 alias :loadgjf :loadcom
626 alias :savegjf :savecom
628 def loadcif(filename)
630 while @tokens.length == 0
636 break if line[0] == ?;
642 line = line[1...line.length]
645 line.scan(/'[^\']*'|"[^\"]*"|[^#\s]+|#.*/) do |s|
647 if s =~ /^data_|loop_|global_|save_|stop_/i
648 s = "#" + s # Label for reserved words
655 def float_strip_rms(str)
656 str =~ /^(-?)(\d*)(\.(\d*))?(\((\d+)\))?$/
657 sgn, i, frac, rms = $1, $2, $4, $6
660 base = 0.1 ** frac.length
661 i = i + frac.to_f * base
666 rms = rms.to_f * base
675 def parse_symmetry_operation(str)
678 elsif (str =~ /(\d+)_(\d)(\d)(\d)/) || (str =~ /(\d+) +(\d)(\d)(\d)/)
679 return [Integer($1) - 1, Integer($2) - 5, Integer($3) - 5, Integer($4) - 5]
680 elsif (str =~ /^(\d+)$/)
681 return [Integer($1) - 1, 0, 0, 0]
687 special_positions = []
689 fp = open(filename, "rb")
691 cell_trans = cell_trans_inv = Transform.identity
692 token = getciftoken(fp)
693 pardigits_re = /\(\d+\)/
694 calculated_atoms = []
697 val = getciftoken(fp)
698 if token == "_cell_length_a"
699 cell[0], cell[6] = float_strip_rms(val)
700 elsif token == "_cell_length_b"
701 cell[1], cell[7] = float_strip_rms(val)
702 elsif token == "_cell_length_c"
703 cell[2], cell[8] = float_strip_rms(val)
704 elsif token == "_cell_angle_alpha"
705 cell[3], cell[9] = float_strip_rms(val)
706 elsif token == "_cell_angle_beta"
707 cell[4], cell[10] = float_strip_rms(val)
708 elsif token == "_cell_angle_gamma"
709 cell[5], cell[11] = float_strip_rms(val)
711 if cell.length == 12 && cell.all?
713 puts "Unit cell is set to #{cell.inspect}." if verbose
715 cell_trans = self.cell_transform
716 cell_trans_inv = cell_trans.inverse
718 token = getciftoken(fp)
720 elsif token.casecmp("#loop_") == 0
722 while (token = getciftoken(fp)) && token[0] == ?_
725 if labels[0] =~ /symmetry_equiv_pos|atom_site_label|atom_site_aniso_label|geom_bond/
726 hlabel = Hash.new(-10000000)
727 labels.each_with_index { |lb, i|
734 break if token == nil || token[0] == ?_ || token[0] == ?#
740 token = getciftoken(fp)
742 if labels[0] =~ /^_symmetry_equiv_pos/
744 symstr = d[hlabel["_symmetry_equiv_pos_as_xyz"]]
745 symstr.delete("\"\'")
746 exps = symstr.split(/,/)
747 sym = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
748 exps.each_with_index { |s, i|
749 terms = s.scan(/([-+]?)(([.0-9]+)(\/([0-9]+))?([xXyYzZ])?|([xXyYzZ]))/)
751 # a[0]: sign, a[2]: numerator, a[4]: denometer
753 # The number part is a[2]/a[4]
754 num = Float(a[2])/Float(a[4])
756 # The number part is either integer or a floating point
761 num = -num if a[0][0] == ?-
763 if xyz == "x" || xyz == "X"
765 elsif xyz == "y" || xyz == "Y"
767 elsif xyz == "z" || xyz == "Z"
774 puts "symmetry operation #{sym.inspect}" if verbose
775 add_symmetry(Transform.new(sym))
777 puts "#{self.nsymmetries} symmetry operations are added" if verbose
778 elsif labels[0] =~ /^_atom_site_label/
781 name = d[hlabel["_atom_site_label"]]
782 elem = d[hlabel["_atom_site_type_symbol"]]
783 fx = d[hlabel["_atom_site_fract_x"]]
784 fy = d[hlabel["_atom_site_fract_y"]]
785 fz = d[hlabel["_atom_site_fract_z"]]
786 uiso = d[hlabel["_atom_site_U_iso_or_equiv"]]
787 biso = d[hlabel["_atom_site_B_iso_or_equiv"]]
788 occ = d[hlabel["_atom_site_occupancy"]]
789 calc = d[hlabel["_atom_site_calc_flag"]]
790 ap = self.add_atom(name, elem, elem)
791 ap.fract_x, ap.sigma_x = float_strip_rms(fx)
792 ap.fract_y, ap.sigma_y = float_strip_rms(fy)
793 ap.fract_z, ap.sigma_z = float_strip_rms(fz)
795 ap.temp_factor, sig = float_strip_rms(biso)
797 ap.temp_factor, sig = float_strip_rms(uiso)
798 ap.temp_factor *= 78.9568352087149 # 8*pi*pi
800 ap.occupancy, sig = float_strip_rms(occ)
801 if calc == "c" || calc == "calc"
802 calculated_atoms.push(ap.index)
804 # Guess special positions
805 (1...nsymmetries).each { |isym|
807 sr = (transform_for_symop(isym) * sr) - sr;
808 nx = (sr.x + 0.5).floor
809 ny = (sr.y + 0.5).floor
810 nz = (sr.z + 0.5).floor
811 if (Vector3D[sr.x - nx, sr.y - ny, sr.z - nz].length2 < 1e-6)
812 # [isym, -nx, -ny, -nz] transforms this atom to itself
813 # The following line is equivalent to:
814 # if special_positions[ap.index] == nil; special_positions[ap.index] = []; end;
815 # special_positions[ap.index].push(...)
816 (special_positions[ap.index] ||= []).push([isym, -nx, -ny, -nz])
819 if verbose && special_positions[ap.index]
820 puts "#{name} is on the special position: #{special_positions[ap.index].inspect}"
823 puts "#{self.natoms} atoms are created." if verbose
824 elsif labels[0] =~ /^_atom_site_aniso_label/
825 # Set anisotropic parameters
828 name = d[hlabel["_atom_site_aniso_label"]]
829 ap = self.atoms[name]
831 u11 = d[hlabel["_atom_site_aniso_U_11"]]
834 u11, usig[0] = float_strip_rms(u11)
835 u22, usig[1] = float_strip_rms(d[hlabel["_atom_site_aniso_U_22"]])
836 u33, usig[2] = float_strip_rms(d[hlabel["_atom_site_aniso_U_33"]])
837 u12, usig[3] = float_strip_rms(d[hlabel["_atom_site_aniso_U_12"]])
838 u13, usig[4] = float_strip_rms(d[hlabel["_atom_site_aniso_U_13"]])
839 u23, usig[5] = float_strip_rms(d[hlabel["_atom_site_aniso_U_23"]])
840 ap.aniso = [u11, u22, u33, u12, u13, u23, 8] + usig
844 puts "#{c} anisotropic parameters are set." if verbose
845 elsif labels[0] =~ /^_geom_bond/
849 n1 = d[hlabel["_geom_bond_atom_site_label_1"]]
850 n2 = d[hlabel["_geom_bond_atom_site_label_2"]]
851 sym1 = d[hlabel["_geom_bond_site_symmetry_1"]] || "."
852 sym2 = d[hlabel["_geom_bond_site_symmetry_2"]] || "."
853 n1 = self.atoms[n1].index
854 n2 = self.atoms[n2].index
855 sym1 = parse_symmetry_operation(sym1)
856 sym2 = parse_symmetry_operation(sym2)
858 exbonds.push([n1, n2, sym1, sym2])
860 self.create_bond(n1, n2)
862 tr1 = (sym1 ? transform_for_symop(sym1) : Transform.identity)
863 tr2 = (sym2 ? transform_for_symop(sym2) : Transform.identity)
864 if special_positions[n1]
865 # Add extra bonds for equivalent positions of n1
866 special_positions[n1].each { |symop|
867 sym2x = symop_for_transform(tr1 * transform_for_symop(symop) * tr1.inverse * tr2)
868 exbonds.push([n1, n2, sym1, sym2x])
871 if special_positions[n2]
872 # Add extra bonds n2-n1.symop, where symop transforms n2 to self
873 tr = (sym1 ? transform_for_symop(sym1) : Transform.identity)
874 special_positions[n2].each { |symop|
875 sym1x = symop_for_transform(tr2 * transform_for_symop(symop) * tr2.inverse * tr1)
876 exbonds.push([n2, n1, sym2, sym1x])
880 puts "#{self.nbonds} bonds are created." if verbose
881 if calculated_atoms.length > 0
882 # Guess bonds for calculated hydrogen atoms
884 calculated_atoms.each { |ai|
885 if atoms[ai].connects.length == 0
886 as = find_close_atoms(ai)
888 self.create_bond(ai, aj)
893 puts "#{n1} bonds are guessed." if verbose
895 if exbonds.length > 0
898 item(:text, :title=>"There are bonds including symmetry related atoms.\nWhat do you want to do?"),
899 item(:radio, :title=>"Expand only atoms that are included in those extra bonds.", :tag=>"atoms_only"),
900 item(:radio, :title=>"Expand fragments having atoms included in the extra bonds.", :tag=>"fragment", :value=>1),
901 item(:radio, :title=>"Ignore these extra bonds.", :tag=>"ignore")
903 radio_group("atoms_only", "fragment", "ignore")
905 if h[:status] == 0 && h["ignore"] == 0
906 atoms_only = (h["atoms_only"] != 0)
909 self.each_fragment { |f| fragments.push(f) }
913 if debug; puts "extra bond #{ex[0]}(#{ex[2].inspect}) - #{ex[1]}(#{ex[3].inspect})"; end
918 ex[i + 2] = ex[i - 2]
920 if debug; puts " symop = #{symop.inspect}"; end
921 # Expand the atom or the fragment including the atom
923 ig = IntGroup[ex[i - 2]]
926 ig = fragments.find { |f| f.include?(ex[i - 2]) }
927 ig.each_with_index { |n, ii| if n == ex[i - 2]; idx = ii; break; end }
929 symop[4] = ex[i - 2] # Base atom
930 if debug; puts " expanding #{ig} by #{symop.inspect}"; end
931 a = self.expand_by_symmetry(ig, symop[0], symop[1], symop[2], symop[3])
932 ex[i + 2] = a[idx] # Index of the expanded atom
935 if ex[4] && ex[5] && ex[4] != ex[5]
936 if debug; puts " creating bond #{ex[4]} - #{ex[5]}"; end
937 self.create_bond(ex[4], ex[5])
942 puts "#{self.nbonds} bonds are created." if verbose
946 # puts "Loop beginning with #{labels[0]} is skipped"
950 token = getciftoken(fp)
952 # Skip tokens until next tag or reserved word is detected
953 while token != nil && token[0] != ?_ && token[0] != ?#
954 token = getciftoken(fp)
959 # self.undo_enabled = save_undo_enabled
963 def dump(group = nil)
968 str = str.gsub(/%/, "%%")
969 str.gsub!(/ /, "%20")
970 str.gsub!(/\t/, "%09")
974 group = atom_group(group ? group : 0...natoms)
978 s += sprintf("%4d %-7s %-4s %-4s %-2s %7.3f %7.3f %7.3f %6.3f [%s]\n",
979 ap.index, sprintf("%3s.%d", ap.res_name, ap.res_seq),
980 quote(ap.name), quote(ap.atom_type), quote(ap.element),
981 ap.r.x, ap.r.y, ap.r.z, ap.charge,
982 ap.connects.join(","))
989 raise "Molecule must be empty"
991 format = "index residue name atom_type element rx ry rz charge connects"
996 # arg can be either a String or an array of String. If it is a string,
997 # arg.each iterates for each line in the string. If it is an array,
998 # arg.each iterates for each member of the array.
1000 format = line[1..-1]
1004 keys = format.split(" ").collect { |key| key.to_sym }
1006 values = line.chomp.split(" ")
1007 next if values == nil || values.length == 0
1008 ap = create_atom(sprintf("X%03d", natoms))
1009 r = Vector3D[0, 0, 0]
1010 keys.each_index { |i|
1011 break if (value = values[i]) == nil
1015 value.gsub(/%09/, "\t")
1016 value.gsub(/%20/, " ")
1017 value.gsub(/%%/, "%")
1021 if resAtoms[value] == nil
1022 resAtoms[value] = []
1024 resAtoms[value].push(ap.index)
1031 elsif key == :connects
1032 value.scan(/\d+/).each { |i|
1035 newBonds.push(ap.index)
1042 ap.set_attr(key, value)
1047 resAtoms.each_key { |key|
1048 assign_residue(atom_group(resAtoms[key]), key)
1050 create_bond(*newBonds)