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].all? { |n| n != 0 })
29 show_progress_panel("Loading AMBER crd file...")
30 # puts "sframe = #{sframe}, pos = #{fp.pos}"
31 line = fp.gets # Skip first line
36 raise MolbyError, sprintf("crd format error - file ended in the middle of frame %d", frame)
42 # values = line.split(' ')
43 values = line.scan(/......../)
44 if count + values.size > natoms * 3
45 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)
48 coords[count / 3][count % 3] = Float(v)
51 if count == natoms * 3
53 if frame == 0 && atoms.all? { |ap| ap.r.x == 0.0 && ap.r.y == 0.0 && ap.r.z == 0.0 }
54 # Do not create a new frame
55 atoms.each_with_index { |ap, i| ap.r = coords[i] }
57 create_frame([coords])
60 # Should have box information
62 if line == nil || (values = line.chomp.split(' ')).length != 3
63 raise "The molecule has a periodic cell but the crd file does not contain cell information"
65 self.cell = [Float(values[0]), Float(values[1]), Float(values[2]), 90, 90, 90]
70 set_progress_message("Loading AMBER crd file...\n(#{frame} frames completed)")
76 self.frame = self.nframes - 1
85 raise MolbyError, "cannot save crd; the molecule is empty"
87 fp = open(filename, "wb")
88 show_progress_panel("Saving AMBER crd file...")
89 fp.printf("TITLE: %d atoms\n", natoms)
91 nframes = self.nframes
93 self.update_enabled = false
95 (0...nframes).each { |i|
98 while (j < natoms * 3)
99 w = atoms[j / 3].r[j % 3]
100 fp.printf(" %7.3f", w)
101 fp.print("\n") if (j % 10 == 9 || j == natoms * 3 - 1)
105 set_progress_message("Saving AMBER crd file...\n(#{i} frames completed)")
109 self.update_enabled = true
117 alias :loadmdcrd :loadcrd
118 alias :savemdcrd :savecrd
120 def loadlog(filename)
127 # save_undo_enabled = self.undo_enabled?
128 # self.undo_enabled = false
129 self.update_enabled = false
130 mes = "Loading GAMESS log file"
131 show_progress_panel(mes)
132 ne_alpha = ne_beta = 0 # number of electrons
133 rflag = nil # 0, UHF; 1, RHF; 2, ROHF
135 ncomps = 0 # Number of AO terms per one MO (sum of the number of components over all shells)
136 alpha_beta = nil # Flag to read alpha/beta MO appropriately
138 fp = open(filename, "rb")
151 if line =~ /ATOM\s+ATOMIC\s+COORDINATES/ || line =~ /COORDINATES OF ALL ATOMS ARE/
152 set_progress_message(mes + "\nReading atomic coordinates...")
153 first_line = (line =~ /ATOMIC/)
154 line = fp.gets # Skip one line
158 while (line = fp.gets) != nil
159 break if line =~ /^\s*$/ || line =~ /END OF ONE/
160 next if line =~ /-----/
161 name, charge, x, y, z = line.split
162 v = Vector3D[x, y, z]
163 coords.push(v * (first_line ? 0.529177 : 1.0)) # Bohr to angstrom
164 names.push([name, charge])
167 # Build a new molecule
168 names.each_index { |i|
169 ap = add_atom(names[i][0])
170 ap.atomic_number = names[i][1].to_i
171 ap.atom_type = ap.element
178 # (j + 1 ... natoms).each { |k|
179 # if calc_bond(j, k) < 1.7
187 create_frame([coords]) # Should not be (coords)
189 elsif line =~ /EQUILIBRIUM GEOMETRY LOCATED/i
190 set_progress_message(mes + "\nReading optimized coordinates...")
191 fp.gets; fp.gets; fp.gets
193 while (line = fp.gets) != nil
194 break if line =~ /^\s*$/
196 atom, an, x, y, z = line.split
198 ap.r = Vector3D[x, y, z]
202 if ne_alpha > 0 && ne_beta > 0
203 # Allocate basis set record again, to update the atomic coordinates
204 allocate_basis_set_record(rflag, ne_alpha, ne_beta)
206 elsif line =~ /ATOMIC BASIS SET/
207 while (line = fp.gets)
208 break if line =~ /SHELL\s+TYPE\s+PRIMITIVE/
213 sym = -10 # undefined
215 while (line = fp.gets)
216 break if line =~ /TOTAL NUMBER OF BASIS SET/
220 add_gaussian_orbital_shell(sym, nprims, i)
221 # puts "add_gaussian_orbital_shell #{sym}, #{nprims}, #{i}"
229 line = fp.gets # Skip the blank line
231 elsif a.length == 5 || a.length == 6
243 raise MolbyError, "Unknown gaussian shell type at line #{fp.lineno}"
247 if (a.length == 5 && sym == -1) || (a.length == 6 && sym != -1)
248 raise MolbyError, "Wrong format in gaussian shell information at line #{fp.lineno}"
252 csp = Float(a[5] || 0.0)
253 add_gaussian_primitive_coefficients(exp, c, csp)
255 # puts "add_gaussian_primitive_coefficients #{exp}, #{c}, #{csp}"
257 raise MolbyError, "Error in reading basis set information at line #{fp.lineno}"
260 elsif line =~ /NUMBER OF OCCUPIED ORBITALS/
268 elsif line =~ /SCFTYP=(\w+)/
270 if ne_alpha > 0 && ne_beta > 0
279 elsif line =~ /(ALPHA|BETA)\s*SET/
281 elsif line =~ /^\s*(EIGENVECTORS|MOLECULAR ORBITALS)\s*$/
283 allocate_basis_set_record(rflag, ne_alpha, ne_beta)
284 # puts "allocate_basis_set_record #{rflag}, #{ne_alpha}, #{ne_beta}"
288 line = fp.gets; line = fp.gets;
289 set_progress_message(mes + "\nReading MO coefficients...")
290 while (line = fp.gets) != nil
291 break unless line =~ /^\s*\d/
292 mo_labels = line.split # MO numbers (1-based)
293 mo_energies = fp.gets.split
294 mo_symmetries = fp.gets.split
295 mo = mo_labels.map { [] } # array of *independent* empty arrays
296 while (line = fp.gets) != nil
297 break unless line =~ /^\s*\d/
298 line[14..-1].split.each_with_index { |s, i|
302 mo.each_with_index { |m, i|
303 idx = Integer(mo_labels[i]) - 1
304 set_mo_coefficients(idx + (alpha_beta == "BETA" ? ncomps : 0), Float(mo_energies[i]), m)
305 # if mo_labels[i] % 8 == 1
306 # puts "set_mo_coefficients #{idx}, #{mo_energies[i]}, [#{m[0]}, ..., #{m[-1]}]"
309 if line =~ /^\s*$/ && idx < ncomps - 1
315 set_progress_message(mes)
319 # self.undo_enabled = save_undo_enabled
320 # hide_progress_panel
321 # self.update_enabled = true
324 select_frame(nframes - 1)
327 self.update_enabled = true
328 (n > 0 ? true : false)
331 def loadxyz(filename)
332 # save_undo_enabled = self.undo_enabled?
333 # self.undo_enabled = false
334 fp = open(filename, "rb")
347 if coords.length == 0
348 # Allow "number of atoms" line or "number of atoms and crystallographic cell parameter"
349 # (Chem3D xyz format)
350 next if toks.length == 1
352 cell = toks[1..6].map { |s| Float(s.sub(/\(\d+\)/, "")) } # Remove (xx) and convert to float
356 name, x, y, z = line.split
358 x = Float(x.sub(/\(\d+\)/, ""))
359 y = Float(y.sub(/\(\d+\)/, ""))
360 z = Float(z.sub(/\(\d+\)/, ""))
361 r = Vector3D[x, y, z]
369 celltr = self.cell_transform
371 names.each_index { |i|
372 ap = add_atom(names[i])
373 names[i] =~ /^([A-Za-z]{1,2})/
374 element = $1.capitalize
376 ap.atom_type = element
378 ap.r = celltr * coords[i]
387 # (j + 1 ... natoms).each { |k|
388 # if calc_bond(j, k) < 1.7
389 # # create_bond(j, k)
393 # self.undo_enabled = save_undo_enabled
394 (n > 0 ? true : false)
397 def loadout(filename)
401 # raise MolbyError, "cannot load crd; the molecule is empty"
405 # save_undo_enabled = undo_enabled?
406 # self.undo_enabled = false
407 fp = open(filename, "rb")
414 use_input_orientation = false
415 show_progress_panel("Loading Gaussian out file...")
423 if line =~ /(Input|Standard) orientation/
426 use_input_orientation = true if nf == 0
427 next if !use_input_orientation
429 next if use_input_orientation
431 4.times { line = fp.gets } # Skip four lines
435 while (line = fp.gets) != nil
436 break if line =~ /-----/
437 num, charge, type, x, y, z = line.split
438 coords.push(Vector3D[x, y, z])
443 # Build a new molecule
444 anums.each_index { |i|
446 ap.atomic_number = anums[i]
447 ap.atom_type = ap.element
448 ap.name = sprintf("%s%d", ap.element, i)
454 # (j + 1 ... natoms).each { |k|
455 # if calc_bond(j, k) < 1.7
464 create_frame([coords]) # Should not be (coords)
470 # self.undo_enabled = save_undo_enabled
471 (n > 0 ? true : false)
474 def loadcom(filename)
475 # save_undo_enabled = self.undo_enabled?
476 # self.undo_enabled = false
478 fp = open(filename, "rb")
480 while (line = fp.gets)
483 section = 1 if line =~ /^\#/
485 elsif section == 1 || section == 2
486 section += 1 if line =~ /^\s*$/
489 # The first line is skipped (charge and multiplicity)
490 while (line = fp.gets)
492 break if line =~ /^\s*$/
493 a = line.split(/\s*[ \t,\/]\s*/)
494 r = Vector3D[Float(a[1]), Float(a[2]), Float(a[3])]
496 a[0] =~ /^([A-Za-z]{1,2})/
497 element = $1.capitalize
499 ap.atom_type = element
506 # self.undo_enabled = save_undo_enabled
510 def loadinp(filename)
511 # save_undo_enabled = self.undo_enabled?
512 # self.undo_enabled = false
514 fp = open(filename, "rb")
517 while (line = fp.gets)
518 if line =~ /\A \$BASIS/
522 next if line !~ /\A \$DATA/
523 line = fp.gets # Title line
524 line = fp.gets # Symmetry line
525 while (line = fp.gets)
528 break if line =~ /\$END/
531 ap.atomic_number = Integer(a[1])
532 ap.atom_type = ap.element
533 r = Vector3D[Float(a[2]), Float(a[3]), Float(a[4])]
536 # Skip until one blank line is detected
537 while (line = fp.gets) && line =~ /\S/
545 # self.undo_enabled = save_undo_enabled
549 def saveinp(filename)
551 raise MolbyError, "cannot save GAMESS input; the molecule is empty"
553 fp = open(filename, "wb")
555 fp.print <<end_of_header
557 ! Generated by Molby at #{now}
558 $CONTRL COORD=UNIQUE EXETYP=RUN ICHARG=0
559 ICUT=20 INTTYP=HONDO ITOL=30
560 MAXIT=200 MOLPLT=.T. MPLEVL=0
561 MULT=1 QMTTOL=1e-08 RUNTYP=OPTIMIZE
562 SCFTYP=RHF UNITS=ANGS $END
563 $SCF CONV=1.0E-06 DIRSCF=.T. $END
564 $STATPT NSTEP=400 OPTTOL=1.0E-06 $END
565 $SYSTEM MEMDDI=0 MWORDS=16 TIMLIM=50000 $END
566 $BASIS GBASIS=N31 NDFUNC=1 NGAUSS=6 $END
567 $GUESS GUESS=HUCKEL $END
574 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
581 def savecom(filename)
583 raise MolbyError, "cannot save Gaussian input; the molecule is empty"
585 fp = open(filename, "wb")
586 base = File.basename(filename, ".*")
587 fp.print <<end_of_header
591 #{name}; created by Molby at #{Time.now.to_s}
596 fp.printf "%-6s %10.6f %10.6f %10.6f\n", ap.element, ap.r.x, ap.r.y, ap.r.z
603 alias :loadgjf :loadcom
604 alias :savegjf :savecom
606 def loadcif(filename)
608 while @tokens.length == 0
614 break if line[0] == ?;
620 line = line[1...line.length]
623 line.scan(/'[^\']*'|"[^\"]*"|[^#\s]+|#.*/) do |s|
625 if s =~ /^data_|loop_|global_|save_|stop_/i
626 s = "#" + s # Label for reserved words
633 def float_strip_rms(str)
634 return Float(str.sub(/\(\d+\)/, ""))
638 fp = open(filename, "rb")
640 token = getciftoken(fp)
641 pardigits_re = /\(\d+\)/
644 val = getciftoken(fp)
645 if token == "_cell_length_a"
646 cell[0] = float_strip_rms(val)
647 elsif token == "_cell_length_b"
648 cell[1] = float_strip_rms(val)
649 elsif token == "_cell_length_c"
650 cell[2] = float_strip_rms(val)
651 elsif token == "_cell_angle_alpha"
652 cell[3] = float_strip_rms(val)
653 elsif token == "_cell_angle_beta"
654 cell[4] = float_strip_rms(val)
655 elsif token == "_cell_angle_gamma"
656 cell[5] = float_strip_rms(val)
658 if cell.length == 6 && cell.all?
660 puts "Unit cell is set to #{cell.inspect}."
663 token = getciftoken(fp)
665 elsif token.casecmp("#loop_") == 0
667 while (token = getciftoken(fp)) && token[0] == ?_
670 if labels[0] =~ /symmetry_equiv_pos|atom_site_label|atom_site_aniso_label|geom_bond/
671 hlabel = Hash.new(-10000000)
672 labels.each_with_index { |lb, i|
679 break if token == nil || token[0] == ?_ || token[0] == ?#
685 token = getciftoken(fp)
687 if labels[0] =~ /^_symmetry_equiv_pos/
689 symstr = d[hlabel["_symmetry_equiv_pos_as_xyz"]]
690 symstr.delete("\"\'")
691 exps = symstr.split(/,/)
692 sym = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
693 exps.each_with_index { |s, i|
694 terms = s.scan(/([-+]?)(([.0-9]+)(\/([0-9]+))?([xXyYzZ])?|([xXyYzZ]))/)
696 # a[0]: sign, a[2]: numerator, a[4]: denometer
698 # The number part is a[2]/a[4]
699 num = Float(a[2])/Float(a[4])
701 # The number part is either integer or a floating point
706 num = -num if a[0][0] == ?-
708 if xyz == "x" || xyz == "X"
710 elsif xyz == "y" || xyz == "Y"
712 elsif xyz == "z" || xyz == "Z"
719 puts "symmetry operation #{sym.inspect}"
720 add_symmetry(Transform.new(sym))
722 puts "#{self.nsymmetries} symmetry operations are added"
723 elsif labels[0] =~ /^_atom_site_label/
726 name = d[hlabel["_atom_site_label"]]
727 elem = d[hlabel["_atom_site_type_symbol"]]
728 fx = d[hlabel["_atom_site_fract_x"]]
729 fy = d[hlabel["_atom_site_fract_y"]]
730 fz = d[hlabel["_atom_site_fract_z"]]
731 uiso = d[hlabel["_atom_site_U_iso_or_equiv"]]
732 biso = d[hlabel["_atom_site_B_iso_or_equiv"]]
733 occ = d[hlabel["_atom_site_occupancy"]]
734 ap = self.add_atom(name, elem, elem)
735 ap.fract_x = float_strip_rms(fx)
736 ap.fract_y = float_strip_rms(fy)
737 ap.fract_z = float_strip_rms(fz)
739 ap.temp_factor = float_strip_rms(biso)
741 ap.temp_factor = float_strip_rms(uiso) * 78.9568352087149 # 8*pi*pi
743 ap.occupancy = float_strip_rms(occ)
745 puts "#{self.natoms} atoms are created."
746 elsif labels[0] =~ /^_atom_site_aniso_label/
747 # Set anisotropic parameters
750 name = d[hlabel["_atom_site_aniso_label"]]
751 ap = self.atoms[name]
753 u11 = d[hlabel["_atom_site_aniso_U_11"]]
755 u11 = float_strip_rms(u11)
756 u22 = float_strip_rms(d[hlabel["_atom_site_aniso_U_22"]])
757 u33 = float_strip_rms(d[hlabel["_atom_site_aniso_U_33"]])
758 u12 = float_strip_rms(d[hlabel["_atom_site_aniso_U_12"]])
759 u13 = float_strip_rms(d[hlabel["_atom_site_aniso_U_13"]])
760 u23 = float_strip_rms(d[hlabel["_atom_site_aniso_U_23"]])
761 ap.aniso = [u11, u22, u33, u12, u13, u23, 8]
765 puts "#{c} anisotropic parameters are set."
766 elsif labels[0] =~ /^_geom_bond/
770 n1 = d[hlabel["_geom_bond_atom_site_label_1"]]
771 n2 = d[hlabel["_geom_bond_atom_site_label_2"]]
772 sym1 = d[hlabel["_geom_bond_site_symmetry_1"]]
773 sym2 = d[hlabel["_geom_bond_site_symmetry_2"]]
774 if sym1 != "." || sym2 != "."
775 exbonds.push([n1, n2, sym1, sym2])
777 self.create_bond(n1, n2)
780 if exbonds.length > 0
783 item(:text, :title=>"There are bonds including symmetry related atoms.\nWhat do you want to do?"),
784 item(:radio, :title=>"Expand only atoms that are included in those extra bonds.", :tag=>"atoms_only"),
785 item(:radio, :title=>"Expand fragments having atoms included in the extra bonds.", :tag=>"fragment", :value=>1),
786 item(:radio, :title=>"Ignore these extra bonds.", :tag=>"ignore")
788 radio_group("atoms_only", "fragment", "ignore")
790 if h[:status] == 0 && h["ignore"] == 0
791 atoms_only = (h["atoms_only"] != 0)
794 self.each_fragment { |f| fragments.push(f) }
796 sym_decoder = /(\d+)_(\d)(\d)(\d)/
799 # Convert name to index before expansion
804 ex[0] = self.atoms[ex[0]].index
805 ex[1] = self.atoms[ex[1]].index
808 if debug; puts "extra bond #{ex[4]}(#{ex[2]}) - #{ex[5]}(#{ex[3]})"; end
811 ex[i] = ex[i - 2] # No expansion
812 elsif ex[i] =~ /(\d+)_(\d)(\d)(\d)/
813 symop = [Integer($1) - 1, Integer($2) - 5, Integer($3) - 5, Integer($4) - 5, ex[i - 2]]
814 if debug; puts " symop = #{symop.inspect}"; end
815 ap = self.atoms.find { |ap| (s = ap.symop) != nil && s === symop }
817 if debug; puts " already expanded (atom #{ap.index}, #{ap.name}, #{ap.symop.inspect})"; end
818 ex[i] = ap.index # Already expanded
820 # Expand the atom or the fragment including the atom
822 ig = IntGroup[ex[i - 2]]
824 ig = fragments.find { |f| f.include?(ex[i - 2]) }
826 if debug; puts " expanding #{ig} by #{symop.inspect}"; end
827 self.expand_by_symmetry(ig, symop[0], symop[1], symop[2], symop[3])
828 # Find again the expanded atom
829 ap = self.atoms.find { |ap| (s = ap.symop) != nil && s === symop }
833 raise "unrecognizable symmetry operation: #{ex[i]}"
836 if debug; puts " creating bond #{ex[2]} - #{ex[3]}"; end
837 self.create_bond(ex[2], ex[3])
841 puts "#{self.nbonds} bonds are created."
845 # puts "Loop beginning with #{labels[0]} is skipped"
849 token = getciftoken(fp)
851 # Skip tokens until next tag or reserved word is detected
852 while token != nil && token[0] != ?_ && token[0] != ?#
853 token = getciftoken(fp)
858 # self.undo_enabled = save_undo_enabled
862 def dump(group = nil)
867 str = str.gsub(/%/, "%%")
868 str.gsub!(/ /, "%20")
869 str.gsub!(/\t/, "%09")
873 group = atom_group(group ? group : 0...natoms)
877 s += sprintf("%4d %-7s %-4s %-4s %-2s %7.3f %7.3f %7.3f %6.3f [%s]\n",
878 ap.index, sprintf("%3s.%d", ap.res_name, ap.res_seq),
879 quote(ap.name), quote(ap.atom_type), quote(ap.element),
880 ap.r.x, ap.r.y, ap.r.z, ap.charge,
881 ap.connects.join(","))
888 raise "Molecule must be empty"
890 format = "index residue name atom_type element rx ry rz charge connects"
895 # arg can be either a String or an array of String. If it is a string,
896 # arg.each iterates for each line in the string. If it is an array,
897 # arg.each iterates for each member of the array.
903 keys = format.split(" ").collect { |key| key.to_sym }
905 values = line.chomp.split(" ")
906 next if values == nil || values.length == 0
907 ap = create_atom(sprintf("X%03d", natoms))
908 r = Vector3D[0, 0, 0]
909 keys.each_index { |i|
910 break if (value = values[i]) == nil
914 value.gsub(/%09/, "\t")
915 value.gsub(/%20/, " ")
916 value.gsub(/%%/, "%")
920 if resAtoms[value] == nil
923 resAtoms[value].push(ap.index)
930 elsif key == :connects
931 value.scan(/\d+/).each { |i|
934 newBonds.push(ap.index)
941 ap.set_attr(key, value)
946 resAtoms.each_key { |key|
947 assign_residue(atom_group(resAtoms[key]), key)
949 create_bond(*newBonds)