hydrogen_bondifier 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -0
- data/History +9 -0
- data/LICENSE +23 -0
- data/README.rdoc +67 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/bin/hydrogen_bondifier.rb +159 -0
- data/lib/hydrogen_bondifier/utils.rb +39 -0
- data/lib/pymol/connections.rb +55 -0
- data/lib/pymol/hydrogen_bonds.rb +157 -0
- data/lib/pymol/orientation.rb +23 -0
- data/lib/pymol/surface.rb +35 -0
- data/lib/pymol.rb +93 -0
- data/reference/all_connections.py +24 -0
- data/reference/campbell_find_hb.py +18 -0
- data/reference/list_hbonds.py +47 -0
- data/reference/test_pymol.rb +125 -0
- data/spec/pymol/connections_spec.rb +25 -0
- data/spec/pymol/hydrogen_bonds_spec.rb +38 -0
- data/spec/pymol/surface_spec.rb +47 -0
- data/spec/pymol_spec.rb +41 -0
- data/spec/scripts/confirm_angle_and_h_dist.rb +126 -0
- data/spec/scripts/confirm_distances.rb +83 -0
- data/spec/scripts/mins.rb +9 -0
- data/spec/scripts/obj_ranges.rb +22 -0
- data/spec/scripts/pdb_ranges.rb +30 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/testfiles/1YQS.pdb +6655 -0
- data/spec/testfiles/1YQS_h_added.pdb +7924 -0
- data/spec/testfiles/2ERK_Hbond.out +302 -0
- data/spec/testfiles/2pERK2_HBOND.out +303 -0
- data/spec/testfiles/2pERK2_Hadded.pdb +5767 -0
- data/spec/testfiles/2pERK2_dis-surf.txt +330 -0
- data/spec/testfiles/little.pdb +210 -0
- data/validation/jtp_vs_insight_angles.png +0 -0
- data/validation/jtp_vs_insight_distances.png +0 -0
- data/validation/jtp_vs_insight_surface_distances.png +0 -0
- data/validation/jtp_vs_insight_surface_distances_aa_geometric.png +0 -0
- data/validation/jtp_vs_insight_surface_distances_center_of_grav.png +0 -0
- data/validation/jtp_vs_insight_surface_distances_closest_atom.png +0 -0
- metadata +113 -0
data/.gitignore
ADDED
data/History
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
The MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2009 Howard Hughes Medical Institute
|
|
4
|
+
Authored by John T. Prince with the help of Kevin Sours and guidance of
|
|
5
|
+
Natalie G. Ahn
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in
|
|
15
|
+
all copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
23
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
= HydrogenBondifier
|
|
2
|
+
|
|
3
|
+
Provides a scriptable interface to pymol, a few basic commands for structure information, and an executable for determining hydrogen bond characteristics as would be useful to those doing hydrogen exchange experiments.
|
|
4
|
+
|
|
5
|
+
== Examples
|
|
6
|
+
|
|
7
|
+
=== Pymol Interface
|
|
8
|
+
|
|
9
|
+
outfile = "file_with_h_added.pdb"
|
|
10
|
+
Pymol.run do |pm|
|
|
11
|
+
pm.cmd "load file.pdb, mymodel"
|
|
12
|
+
pm.cmd "h_add"
|
|
13
|
+
pm.cmd "save #{outfile}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
The real power lies in scripting pymol. Here's an example of extracting out all connections in a model:
|
|
17
|
+
|
|
18
|
+
# this script causes pymol to output all atom connections
|
|
19
|
+
cnx_script = %Q{
|
|
20
|
+
|
|
21
|
+
from pymol import cmd
|
|
22
|
+
|
|
23
|
+
def all_connections(selection):
|
|
24
|
+
"""
|
|
25
|
+
USAGE
|
|
26
|
+
|
|
27
|
+
all_connections selection
|
|
28
|
+
|
|
29
|
+
returns lines: "CONNECTION: id - id"
|
|
30
|
+
"""
|
|
31
|
+
stored.xs = []
|
|
32
|
+
cmd.iterate(selection, 'stored.xs.append( index )')
|
|
33
|
+
for i in stored.xs:
|
|
34
|
+
selName = "neighbor%s" % i
|
|
35
|
+
ids = cmd.select(selName, ("%s and neighbor id %s" % (selection, i)))
|
|
36
|
+
base = "CONNECTION: %s - " % i
|
|
37
|
+
to_print = base + "%s"
|
|
38
|
+
print_string = 'print "' + to_print + '" % index'
|
|
39
|
+
cmd.iterate(selName, print_string )
|
|
40
|
+
|
|
41
|
+
cmd.extend("all_connections", all_connections)
|
|
42
|
+
}
|
|
43
|
+
output = Pymol.run(:script => cnx_script) do |pm|
|
|
44
|
+
pm.cmd "load file.pdb, mymodel"
|
|
45
|
+
pm.cmd "all_connections mymodel"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# now we just parse the output
|
|
49
|
+
values_of_output_lines = output.map {|line| line.match(/^CONNECTION: (.*)/)[1] }.compact
|
|
50
|
+
|
|
51
|
+
connection_pairs = values_of_output_lines.map do |v|
|
|
52
|
+
v.split(' - ').map {|v| v.to_i }.sort
|
|
53
|
+
end.uniq
|
|
54
|
+
|
|
55
|
+
=== Basic methods
|
|
56
|
+
|
|
57
|
+
Some methods have been completely wrapped in a script and parser to deliver desired output:
|
|
58
|
+
|
|
59
|
+
# all atomic pairs as atom ids
|
|
60
|
+
connections = Pymol::Connections.from_pdb("file.pdb")
|
|
61
|
+
|
|
62
|
+
# coordinates of the molecules surface
|
|
63
|
+
surface_coords = Pymol::Surface.from_pdb("file.pdb")
|
|
64
|
+
|
|
65
|
+
== Copyright
|
|
66
|
+
|
|
67
|
+
See LICENSE
|
data/Rakefile
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
|
|
2
|
+
require 'rubygems'
|
|
3
|
+
require 'rake'
|
|
4
|
+
require 'jeweler'
|
|
5
|
+
require 'rake/testtask'
|
|
6
|
+
# require 'rcov/rcovtask'
|
|
7
|
+
|
|
8
|
+
NAME = "hydrogen_bondifier"
|
|
9
|
+
WEBSITE_BASE = "website"
|
|
10
|
+
WEBSITE_OUTPUT = WEBSITE_BASE + "/output"
|
|
11
|
+
|
|
12
|
+
gemspec = Gem::Specification.new do |s|
|
|
13
|
+
s.name = NAME
|
|
14
|
+
s.authors = ["John T. Prince"]
|
|
15
|
+
s.email = "jtprince@gmail.com"
|
|
16
|
+
s.homepage = "http://jtprince.github.com/" + NAME
|
|
17
|
+
s.summary = "finds hydrogen bonds using pymol"
|
|
18
|
+
s.description = "uses pymol"
|
|
19
|
+
#s.rubyforge_project = 'mspire'
|
|
20
|
+
# s.add_dependency("ms-core", ">= 0.0.2")
|
|
21
|
+
# s.add_development_dependency("ms-testdata", ">= 0.18.0")
|
|
22
|
+
s.add_development_dependency("spec-more")
|
|
23
|
+
s.files << "VERSION"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
Jeweler::Tasks.new(gemspec)
|
|
27
|
+
|
|
28
|
+
Rake::TestTask.new(:spec) do |spec|
|
|
29
|
+
spec.libs << 'lib' << 'spec'
|
|
30
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
|
31
|
+
spec.verbose = true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
#Rcov::RcovTask.new do |spec|
|
|
35
|
+
# spec.libs << 'spec'
|
|
36
|
+
# spec.pattern = 'spec/**/*_spec.rb'
|
|
37
|
+
# spec.verbose = true
|
|
38
|
+
#end
|
|
39
|
+
|
|
40
|
+
require 'rake/rdoctask'
|
|
41
|
+
Rake::RDocTask.new do |rdoc|
|
|
42
|
+
base_rdoc_output_dir = WEBSITE_OUTPUT + '/rdoc'
|
|
43
|
+
version = File.read('VERSION')
|
|
44
|
+
rdoc.rdoc_dir = base_rdoc_output_dir + "/#{version}"
|
|
45
|
+
rdoc.title = NAME + ' ' + version
|
|
46
|
+
rdoc.rdoc_files.include('README*')
|
|
47
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
task :default => :spec
|
|
51
|
+
|
|
52
|
+
task :build => :gemspec
|
|
53
|
+
|
|
54
|
+
# credit: Rakefile modeled after Jeweler's
|
data/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.0.2
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# Previous users of Insight(?) used a (<60deg acute angle) cutoff (>120 obtuse)
|
|
4
|
+
# and a 2.85, 2.90, or 3.0 Angstrom cutoff for the hydrogen
|
|
5
|
+
|
|
6
|
+
# see this on hydrogen bond finding in pymol:
|
|
7
|
+
# http://www.mail-archive.com/pymol-users@lists.sourceforge.net/msg06680.html
|
|
8
|
+
|
|
9
|
+
require 'yaml'
|
|
10
|
+
require 'narray'
|
|
11
|
+
require 'optparse'
|
|
12
|
+
require 'bio/db/pdb'
|
|
13
|
+
require 'pymol/surface'
|
|
14
|
+
require 'pymol/connections'
|
|
15
|
+
require 'pymol/hydrogen_bonds'
|
|
16
|
+
require 'hydrogen_bondifier/utils'
|
|
17
|
+
|
|
18
|
+
def putsv(*args)
|
|
19
|
+
puts(*args) if $VERBOSE
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
opt = {
|
|
23
|
+
:max_dist => 3.2,
|
|
24
|
+
:max_angle => 60,
|
|
25
|
+
:exclude_water => true,
|
|
26
|
+
:add_hydrogen => true,
|
|
27
|
+
:delim => ','
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
$VERBOSE = true
|
|
31
|
+
|
|
32
|
+
output_postfix = "_hbonds.csv"
|
|
33
|
+
|
|
34
|
+
opts = OptionParser.new do |op|
|
|
35
|
+
op.banner = "usage: #{File.basename(__FILE__)} <file>.pdb ..."
|
|
36
|
+
op.separator "outputs: <file>#{output_postfix}"
|
|
37
|
+
op.separator " "
|
|
38
|
+
op.separator "cutoffs: "
|
|
39
|
+
op.on("-d", "--max-distance <#{opt[:max_dist]}>", Float, "max distance between donor and acceptor") {|v| opt[:max_dist] = v }
|
|
40
|
+
op.on("-a", "--max-angle <#{opt[:max_angle]}>", Float, "max angle in degrees") {|v| opt[:max_angle] = v }
|
|
41
|
+
|
|
42
|
+
op.separator " "
|
|
43
|
+
op.separator "options: "
|
|
44
|
+
op.on("--radians", "output angles in radians") {|v| opt[:radians] = v }
|
|
45
|
+
op.on("--no-exclude-water", "leaves water molecules in the model") {|v| opt[:exclude_water] = false }
|
|
46
|
+
op.on("--no-add-hydrogen", "can use if pdb contains all hydrogens") {|v| opt[:add_hydrogen] = false }
|
|
47
|
+
op.on("--aa-to-surf", "outputs min dist to surface for amino acids") {|v| opt[:aa_to_surf] = v }
|
|
48
|
+
op.on("-q", "--quiet", "no unnecessary output") {|v| $VERBOSE = false }
|
|
49
|
+
op.separator " "
|
|
50
|
+
op.separator " * if pymol executable cannot be found, you can specify it as the value of the"
|
|
51
|
+
op.separator " environmental variable 'PYMOL_EXE'"
|
|
52
|
+
op.separator " * your working directory should be writeable and directory with file"
|
|
53
|
+
op.separator " * in output: D=donor, H=hydrogen, A=acceptor"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
opts.parse!
|
|
57
|
+
|
|
58
|
+
if ARGV.size == 0
|
|
59
|
+
puts opts
|
|
60
|
+
exit
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
files = ARGV.map
|
|
64
|
+
ARGV.clear
|
|
65
|
+
|
|
66
|
+
categories = %w(D_id H_id A_id H_name D_res D_res_id D_name A_res A_res_id A_name angle D_A_dist H_A_dist H_dist_to_surf)
|
|
67
|
+
|
|
68
|
+
files.each do |file|
|
|
69
|
+
|
|
70
|
+
####################
|
|
71
|
+
# need to implement copying of file, etc......
|
|
72
|
+
####################
|
|
73
|
+
|
|
74
|
+
# create filenames for output files
|
|
75
|
+
base = file.chomp(File.extname(file))
|
|
76
|
+
|
|
77
|
+
pdb_with_hydrogens =
|
|
78
|
+
if opt[:add_hydrogen]
|
|
79
|
+
pdb_plus_h_added = base + '_Hadded.pdb'
|
|
80
|
+
putsv "writing to: #{pdb_plus_h_added}"
|
|
81
|
+
Pymol::HydrogenBonds.pdb_with_hydrogens(file, pdb_plus_h_added)
|
|
82
|
+
else
|
|
83
|
+
file
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
base_h_added = pdb_with_hydrogens.chomp(File.extname(pdb_with_hydrogens))
|
|
87
|
+
|
|
88
|
+
hbond_arrays = Pymol::HydrogenBonds.from_pdb(pdb_with_hydrogens, opt)
|
|
89
|
+
|
|
90
|
+
# http://pymolwiki.org/index.php/Surface#Exporting_Surface.2FMesh_Coordinates_to_File
|
|
91
|
+
surface_coords = Pymol::Surface.from_pdb(pdb_with_hydrogens)
|
|
92
|
+
|
|
93
|
+
sc_sz = surface_coords.size
|
|
94
|
+
(xs, ys, zs) = [nil,nil,nil].map { NArray.float(sc_sz) }
|
|
95
|
+
surface_coords.each_with_index do |xyz, i|
|
|
96
|
+
xs[i] = xyz[0]
|
|
97
|
+
ys[i] = xyz[1]
|
|
98
|
+
zs[i] = xyz[2]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# get the distance from hydrogen to surface
|
|
102
|
+
# 0 => donor
|
|
103
|
+
# 1 => hydrogen
|
|
104
|
+
# 2 => acceptor
|
|
105
|
+
which_atom = 1
|
|
106
|
+
|
|
107
|
+
# just output distance to the surface of the amino acid
|
|
108
|
+
if opt[:aa_to_surf]
|
|
109
|
+
amino_acids = []
|
|
110
|
+
hbond_arrays.each do |a,b,c|
|
|
111
|
+
[a,c].each {|atom| amino_acids << atom.residue }
|
|
112
|
+
end
|
|
113
|
+
amino_acids.uniq.each do |res|
|
|
114
|
+
|
|
115
|
+
#### CENTER OF GRAVITY
|
|
116
|
+
#coord = res.centreOfGravity
|
|
117
|
+
#### GEOMETRIC CENTER
|
|
118
|
+
#coord = res.geometricCentre
|
|
119
|
+
#min_dist = Bio::PDB::Utils.distance_to_many(coord, [xs, ys, zs] ).min
|
|
120
|
+
|
|
121
|
+
#### MINIMUM DISTANCE TO ANY ATOM
|
|
122
|
+
min_dist = res.atoms.map {|atom| Bio::PDB::Utils.distance_to_many(atom.xyz, [xs, ys, zs] ).min }.min
|
|
123
|
+
# output to mimic older output
|
|
124
|
+
puts "#{res.resName}#{res.id} minimum_distance_to_surface #{min_dist}"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
next
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
putsv "calculating distances to surface ..."
|
|
131
|
+
# also we are gathering all the data we need.
|
|
132
|
+
characterized = hbond_arrays.map do |data|
|
|
133
|
+
coords = Array.new(3)
|
|
134
|
+
na_coords = Array.new(3)
|
|
135
|
+
data[0,3].each_with_index do |atom,i|
|
|
136
|
+
coords[i] = atom.xyz
|
|
137
|
+
na_coords[i] = NArray.to_na(coords[i].to_a)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
dists_to_surface = Bio::PDB::Utils.distance_to_many(coords[which_atom], [xs, ys, zs] )
|
|
141
|
+
|
|
142
|
+
data[3] = Bio::PDB::Utils.rad2deg(data[3]) unless opt[:radians]
|
|
143
|
+
ids = data[0,3].map {|atom| atom.serial }
|
|
144
|
+
(don, acc) = [data[0], data[2]].map {|at| [at.resName, at.residue.id, at.name] }
|
|
145
|
+
ids.push(data[1].name)
|
|
146
|
+
id_part = ids.push(*don).push(*acc)
|
|
147
|
+
id_part.push(*(data[3,3]))
|
|
148
|
+
id_part.push(dists_to_surface.min)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
final_output = base_h_added + output_postfix
|
|
152
|
+
File.open(final_output, 'w') do |out|
|
|
153
|
+
out.puts categories.join(opt[:delim])
|
|
154
|
+
characterized.each do |array|
|
|
155
|
+
out.puts array.join(opt[:delim])
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'narray'
|
|
2
|
+
|
|
3
|
+
module Bio
|
|
4
|
+
class PDB
|
|
5
|
+
module Utils
|
|
6
|
+
|
|
7
|
+
module_function
|
|
8
|
+
# calculates the angle between 2 Narray vecs (in radians)
|
|
9
|
+
def angle_between_vectors(vec1, vec2)
|
|
10
|
+
vec1 = NArray.to_na(vec1.to_a) unless vec1.is_a?(NArray)
|
|
11
|
+
vec2 = NArray.to_na(vec2.to_a) unless vec2.is_a?(NArray)
|
|
12
|
+
nil_vec = NArray[0.0, 0.0, 0.0]
|
|
13
|
+
return nil if (vec1 == nil_vec or vec2 == nil_vec)
|
|
14
|
+
(mag_a, mag_b) = [vec1,vec2].map {|vec| Math::sqrt((vec*vec).sum) }
|
|
15
|
+
# acos(dotprod / |a||b|)
|
|
16
|
+
Bio::PDB::Utils.acos( (vec1 * vec2).sum.to_f / (mag_a * mag_b) )
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def angle_from_coords(triplet)
|
|
20
|
+
a = triplet.last - triplet[1]
|
|
21
|
+
b = triplet.first - triplet[1]
|
|
22
|
+
angle_between_vectors(a,b)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# other is 3 parallel NArray objects with the x, y and z coordinates
|
|
26
|
+
# or a triplet like coord
|
|
27
|
+
def distance_to_many(coord, other)
|
|
28
|
+
# distance may be another narray or an array of vecs
|
|
29
|
+
sq_diffs = []
|
|
30
|
+
(0...(coord.size)).each do |i|
|
|
31
|
+
pos = coord[i]
|
|
32
|
+
oth = other[i]
|
|
33
|
+
sq_diffs << (oth - pos)**2
|
|
34
|
+
end
|
|
35
|
+
NMath.sqrt(sq_diffs.inject {|sum, vec| sum + vec })
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require 'pymol'
|
|
2
|
+
|
|
3
|
+
class Pymol
|
|
4
|
+
module Connections
|
|
5
|
+
module Script
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def all_connections_script
|
|
9
|
+
%Q{
|
|
10
|
+
from pymol import cmd
|
|
11
|
+
|
|
12
|
+
def all_connections(selection):
|
|
13
|
+
"""
|
|
14
|
+
USAGE
|
|
15
|
+
|
|
16
|
+
all_connections selection
|
|
17
|
+
|
|
18
|
+
returns lines: "CONNECTION: id - id"
|
|
19
|
+
"""
|
|
20
|
+
stored.xs = []
|
|
21
|
+
cmd.iterate(selection, 'stored.xs.append( index )')
|
|
22
|
+
for i in stored.xs:
|
|
23
|
+
selName = "neighbor%s" % i
|
|
24
|
+
ids = cmd.select(selName, ("%s and neighbor id %s" % (selection, i)))
|
|
25
|
+
base = "CONNECTION: %s - " % i
|
|
26
|
+
to_print = base + "%s"
|
|
27
|
+
print_string = 'print "' + to_print + '" % index'
|
|
28
|
+
cmd.iterate(selName, print_string )
|
|
29
|
+
|
|
30
|
+
cmd.extend("all_connections", all_connections)
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# returns an array of all pairs of atom IDs with no redundancy
|
|
35
|
+
def all_connections_parser(reply_from_all_connections, flag=/^CONNECTION: /)
|
|
36
|
+
pairs = reply_from_all_connections.split("\n").select {|v| v =~ flag }.map do |line|
|
|
37
|
+
line.split(':').last.split(' - ').map {|v| v.to_i }.sort
|
|
38
|
+
end
|
|
39
|
+
pairs.uniq
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
module_function
|
|
45
|
+
|
|
46
|
+
# returns all connections as pairs of ID's (all uniq)
|
|
47
|
+
def from_pdb(pdb)
|
|
48
|
+
reply = Pymol.run(:msg => 'getting all atom connections', :script => Pymol::Connections::Script.all_connections_script) do |pm|
|
|
49
|
+
pm.cmd "load #{pdb}, mymodel"
|
|
50
|
+
pm.cmd "all_connections mymodel"
|
|
51
|
+
end
|
|
52
|
+
Pymol::Connections::Script.all_connections_parser(reply)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
require 'pymol'
|
|
2
|
+
require 'pymol/connections'
|
|
3
|
+
|
|
4
|
+
require 'bio/db/pdb'
|
|
5
|
+
require 'hydrogen_bondifier/utils'
|
|
6
|
+
|
|
7
|
+
class Pymol
|
|
8
|
+
EXCLUDE_WATER_FILTER = " &! resn hoh"
|
|
9
|
+
module HydrogenBonds
|
|
10
|
+
|
|
11
|
+
DEFAULT_FIND_PAIRS_OPTS = {:max_dist => 3.2, :max_angle => 60, :exclude_water => true }
|
|
12
|
+
DEFAULT_H_BOND_OPTS = {
|
|
13
|
+
:select_donor => "and (elem n,o and (neighbor hydro))",
|
|
14
|
+
:select_acceptor => "and (elem o or (elem n and not (neighbor hydro)))",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
def pdb_with_hydrogens(pdb_filename, newname=nil)
|
|
20
|
+
pfile = pdb_filename
|
|
21
|
+
newname = pfile.chomp(File.extname(pfile)) + "_plus_h.pdb" unless newname
|
|
22
|
+
Pymol.run(:msg => 'creating pdb with hydrogens') do |pm|
|
|
23
|
+
pm.cmd "load #{pfile}, mymodel"
|
|
24
|
+
pm.cmd "h_add"
|
|
25
|
+
pm.cmd "save #{newname}"
|
|
26
|
+
end
|
|
27
|
+
newname
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# returns [id1, id2, distance] for each atom
|
|
31
|
+
def find_pairs(file, sel1, sel2, opt={})
|
|
32
|
+
opt = DEFAULT_FIND_PAIRS_OPTS.merge( opt )
|
|
33
|
+
exclude_water_command = opt[:exclude_water] ? EXCLUDE_WATER_FILTER : ""
|
|
34
|
+
hbond_script = Pymol::HydrogenBonds.list_hb_script(sel1, sel2)
|
|
35
|
+
reply = Pymol.run(:msg => "getting hydrogen bonds", :script => hbond_script) do |pm|
|
|
36
|
+
pm.cmd "load #{file}, mymodel"
|
|
37
|
+
pm.cmd "list_hb mymodel#{exclude_water_command}, cutoff=#{opt[:max_dist]}, angle=#{opt[:max_angle]}"
|
|
38
|
+
end
|
|
39
|
+
Pymol::HydrogenBonds.list_hb_parser(reply)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# returns [donor, hydrogen, acceptor, angle, don_to_acc_dist, h_to_acc_dist]
|
|
44
|
+
# The first three are Bio::PDB::Record::ATOM structs.
|
|
45
|
+
# respects DEFAULT_FIND_PAIRS_OPTS and DEFAULT_H_BOND_OPTS
|
|
46
|
+
# expects that hydrogen bonds are already specified in the PDB file
|
|
47
|
+
# returns an array triplet atom IDs [donor, hydrogen, acceptor]
|
|
48
|
+
# :connections can be passed in (an array of arrays of all unique pairwise
|
|
49
|
+
# connections [by ID])
|
|
50
|
+
def from_pdb(file, opt={})
|
|
51
|
+
opt = DEFAULT_H_BOND_OPTS.merge(opt)
|
|
52
|
+
|
|
53
|
+
pairs = find_pairs(file, opt[:select_donor], opt[:select_acceptor], opt)
|
|
54
|
+
|
|
55
|
+
connection_pairs = Pymol::Connections.from_pdb(file)
|
|
56
|
+
connection_index = Hash.new {|h,k| h[k] = [] }
|
|
57
|
+
connection_pairs.each do |pair|
|
|
58
|
+
connection_index[pair.first] << pair.last
|
|
59
|
+
connection_index[pair.last] << pair.first
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# make an index of the atoms
|
|
63
|
+
pdb = Bio::PDB.new(IO.read(file))
|
|
64
|
+
pdb.extend(Bio::PDB::AtomFinder)
|
|
65
|
+
atom_index = []
|
|
66
|
+
pdb.each_atom do |atom|
|
|
67
|
+
atom_index[atom.serial] = atom
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
max_angle = opt[:max_angle] || DEFAULT_FIND_PAIRS_OPTS[:max_angle]
|
|
71
|
+
cutoff_in_degress = max_angle
|
|
72
|
+
|
|
73
|
+
hbonds = []
|
|
74
|
+
puts "calculating angles and distances" if $VERBOSE
|
|
75
|
+
pairs.each do |don_id, acc_id, don_to_acc_dist|
|
|
76
|
+
donor = atom_index[don_id]
|
|
77
|
+
acceptor = atom_index[acc_id]
|
|
78
|
+
next if (acceptor.element == 'H' or donor.element == 'H') # check for sloppy queries
|
|
79
|
+
acceptor_xyz = acceptor.xyz
|
|
80
|
+
donor_xyz = donor.xyz
|
|
81
|
+
connection_index[don_id].each do |id|
|
|
82
|
+
hydrogen = atom_index[id]
|
|
83
|
+
next if hydrogen.element != 'H'
|
|
84
|
+
|
|
85
|
+
#puts "ACCEPT ID: "
|
|
86
|
+
#puts acc_id
|
|
87
|
+
#p acceptor_xyz
|
|
88
|
+
#puts "HYDRO ID: "
|
|
89
|
+
#p id
|
|
90
|
+
#p hydrogen.xyz
|
|
91
|
+
|
|
92
|
+
angle = Bio::PDB::Utils.angle_from_coords([donor_xyz, hydrogen.xyz, acceptor_xyz])
|
|
93
|
+
h_to_acc_dist = Bio::PDB::Utils.distance(hydrogen.xyz, acceptor_xyz)
|
|
94
|
+
|
|
95
|
+
#puts "DISTANCES: "
|
|
96
|
+
#p don_to_acc_dist
|
|
97
|
+
#p h_to_acc_dist
|
|
98
|
+
# abort 'here'
|
|
99
|
+
|
|
100
|
+
# I'm not sure why the angle cutoff is not being respected, but we
|
|
101
|
+
# can enforce it right here since we want the angles anyway
|
|
102
|
+
if (180.0 - Bio::PDB::Utils.rad2deg(angle)) <= cutoff_in_degress
|
|
103
|
+
hbonds << [donor, hydrogen, acceptor, angle, don_to_acc_dist, h_to_acc_dist]
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
hbonds
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def list_hb_script(select1, select2)
|
|
111
|
+
%Q{
|
|
112
|
+
# modified by JTP from here:
|
|
113
|
+
# Dr. Robert L. Campbell
|
|
114
|
+
# http://pldserver1.biochem.queensu.ca/~rlc/work/pymol/
|
|
115
|
+
# find_pairs is an undocumented method but mode==1 is hydrogen bond finding
|
|
116
|
+
|
|
117
|
+
from pymol import cmd
|
|
118
|
+
|
|
119
|
+
def list_hb(selection,cutoff=3.2,angle=55,hb_list_name='hbonds'):
|
|
120
|
+
"""
|
|
121
|
+
USAGE
|
|
122
|
+
|
|
123
|
+
list_hb selection, [cutoff (default=3.2)], [angle (default=55)], [hb_list_name]
|
|
124
|
+
|
|
125
|
+
e.g.
|
|
126
|
+
list_hb 1abc & c. a &! r. hoh, cutoff=3.2, hb_list_name=abc-hbonds
|
|
127
|
+
"""
|
|
128
|
+
cutoff=float(cutoff)
|
|
129
|
+
angle=float(angle)
|
|
130
|
+
hb = cmd.find_pairs("((byres "+selection+") #{select1})","((byres "+selection+") #{select2})",mode=1,cutoff=cutoff,angle=angle)
|
|
131
|
+
# sort the list for easier reading
|
|
132
|
+
hb.sort(lambda x,y:(cmp(x[0][1],y[0][1])))
|
|
133
|
+
|
|
134
|
+
for pairs in hb:
|
|
135
|
+
print "PAIR:",
|
|
136
|
+
for ind in [0,1]:
|
|
137
|
+
cmd.iterate("%s and index %s" % (pairs[ind][0],pairs[ind][1]), 'print "%s/%3s`%s/%s/%s " % (chain,resn,resi,name,index),')
|
|
138
|
+
print "%.2f" % cmd.distance(hb_list_name,"%s and index %s" % (pairs[0][0],pairs[0][1]),"%s and index %s" % (pairs[1][0],pairs[1][1]))
|
|
139
|
+
|
|
140
|
+
cmd.extend("list_hb",list_hb)
|
|
141
|
+
}
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# takes output of hb_script and returns an array of triplets [id1, id2, distance]
|
|
145
|
+
def list_hb_parser(pymol_hb_script_reply, flag=/^PAIR: /)
|
|
146
|
+
# grab each line of output with specified header, then remove the header
|
|
147
|
+
hbond_lines = pymol_hb_script_reply.split("\n").select {|line| line =~ flag }.map {|line| line.sub(flag,'') }
|
|
148
|
+
|
|
149
|
+
ids_and_distances = hbond_lines.map do |line|
|
|
150
|
+
# A/THR`325/N/2478 A/ALA`323/O/2468 3.05
|
|
151
|
+
(first, second, dist) = line.split(/\s+/)
|
|
152
|
+
(id1, id2) = [first, second].map {|v| v.split('/').last.to_i }
|
|
153
|
+
[id1, id2, dist.to_f]
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class Pymol
|
|
2
|
+
module Orientation
|
|
3
|
+
|
|
4
|
+
module_function
|
|
5
|
+
# http://www.mail-archive.com/pymol-users@lists.sourceforge.net/msg06973.html
|
|
6
|
+
def orient_to_pdb_coords_script
|
|
7
|
+
%q{
|
|
8
|
+
def orient_to_pdb_coords():
|
|
9
|
+
"""
|
|
10
|
+
USAGE
|
|
11
|
+
|
|
12
|
+
orient_to_pdb_coords
|
|
13
|
+
"""
|
|
14
|
+
cmd.reset()
|
|
15
|
+
cmd.origin(position=[0,0,0])
|
|
16
|
+
cmd.center("origin")
|
|
17
|
+
cmd.move('z',-cmd.get_view()[11])
|
|
18
|
+
|
|
19
|
+
cmd.extend("orient_to_pdb_coords", orient_to_pdb_coords)
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'pymol'
|
|
2
|
+
require 'pymol/orientation'
|
|
3
|
+
|
|
4
|
+
class Pymol
|
|
5
|
+
module Surface
|
|
6
|
+
module_function
|
|
7
|
+
# returns three arrays for the x,y,z coords
|
|
8
|
+
def obj_file_to_coords(file)
|
|
9
|
+
coords = []
|
|
10
|
+
IO.foreach(file) do |line|
|
|
11
|
+
if line =~ /^v /
|
|
12
|
+
pieces = line.split(' ')
|
|
13
|
+
pieces.shift # remove the 'v'
|
|
14
|
+
coords << pieces.map {|v| v.to_f }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
coords
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# returns coordinates
|
|
21
|
+
# http://pymolwiki.org/index.php/Surface#Exporting_Surface.2FMesh_Coordinates_to_File
|
|
22
|
+
def from_pdb(file, postfix = "_surface.obj", delete_tmp=true)
|
|
23
|
+
outfile = file.chomp(File.extname(file)) + postfix
|
|
24
|
+
Pymol.run(:msg => 'creating surface', :script => Pymol::Orientation.orient_to_pdb_coords_script) do |pm|
|
|
25
|
+
pm.cmd "load #{file}, mymodel"
|
|
26
|
+
pm.cmd "orient_to_pdb_coords"
|
|
27
|
+
pm.cmd "show surface, mymodel"
|
|
28
|
+
pm.cmd "save #{outfile}"
|
|
29
|
+
end
|
|
30
|
+
coords = self.obj_file_to_coords(outfile)
|
|
31
|
+
File.unlink outfile if delete_tmp
|
|
32
|
+
coords
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|