MS-fragmenter 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.
@@ -0,0 +1,80 @@
1
+ module MS
2
+ class ChargeCalculator
3
+ # This is straight from my pI calculator, and adds the fxn of calculating a maximum charge state for the total peptide, given the sequence.
4
+ #
5
+ # Usage: charge_at_pH(identify_potential_charges(peptide_sequence), pH_desired)
6
+ PepCharges = Struct.new(:seq, :n_term, :c_term, :y_num, :c_num, :k_num, :h_num, :r_num, :d_num, :e_num, :pi)
7
+ def self.identify_potential_charges(str)
8
+ string = str.upcase
9
+ first = string[0]; last = string[-1]
10
+ puts string if first.nil? or last.nil?
11
+ begin
12
+ out = PepCharges.new(string, PkTable[first.to_sym][0], PkTable[last.to_sym][1], 0, 0, 0 ,0 ,0 ,0, 0)
13
+ rescue NoMethodError
14
+ abort string
15
+ end
16
+ string.chars.each do |letter|
17
+ case letter
18
+ when "Y"
19
+ out.y_num += 1
20
+ when "C"
21
+ out.c_num += 1
22
+ when "K"
23
+ out.k_num += 1
24
+ when "H"
25
+ out.h_num += 1
26
+ when "R"
27
+ out.r_num += 1
28
+ when "D"
29
+ out.d_num += 1
30
+ when "E"
31
+ out.e_num += 1
32
+ end
33
+ end
34
+ out
35
+ end # Returns the PepCharges structure
36
+
37
+ PkTable = {
38
+ :K => [2.18,8.95,10.53],
39
+ :E => [2.19,9.67,4.25],
40
+ :D => [1.88,9.60,3.65],
41
+ :H => [1.82,9.17,6.00],
42
+ :R => [2.17,9.04,12.48],
43
+ :Q => [2.17,9.13,nil],
44
+ :N => [2.02,8.80,nil],
45
+ :C => [1.96,10.28,8.18],
46
+ :T => [2.11,9.62,nil],
47
+ :S => [2.21,9.15,nil],
48
+ :W => [2.38,9.39,nil],
49
+ :Y => [2.20,9.11,10.07],
50
+ :F => [1.83,9.13,nil],
51
+ :M => [2.28,9.21,nil],
52
+ :I => [2.36,9.68,nil],
53
+ :L => [2.36,9.60,nil],
54
+ :V => [2.32,9.62,nil],
55
+ :P => [1.99,10.96,nil],
56
+ :A => [2.34,9.69,nil],
57
+ :G => [2.34,9.60,nil],
58
+ # These are the fringe cases... B and Z... Jerks, these are harder to calculate pIs
59
+ :B => [1.95,9.20,3.65],
60
+ :Z => [2.18,9.40,4.25],
61
+ :X => [2.20,9.40,nil],
62
+ :U => [1.96,10.28,5.20] # Unfortunately, I've only found the pKr for this... so I've used Cysteine's values.
63
+ }
64
+
65
+ def self.charge_at_pH(pep_charges, pH)
66
+ charge = 0
67
+ charge += -1/(1+10**(pep_charges.c_term-pH))
68
+ charge += -pep_charges.d_num/(1+10**(PkTable[:D][2]-pH))
69
+ charge += -pep_charges.e_num/(1+10**(PkTable[:E][2]-pH))
70
+ charge += -pep_charges.c_num/(1+10**(PkTable[:C][2]-pH))
71
+ charge += -pep_charges.y_num/(1+10**(PkTable[:Y][2]-pH))
72
+ charge += 1/(1+10**(pH - pep_charges.n_term))
73
+ charge += pep_charges.h_num/(1+10**(pH-PkTable[:H][2]))
74
+ charge += pep_charges.k_num/(1+10**(pH-PkTable[:K][2]))
75
+ charge += pep_charges.r_num/(1+10**(pH-PkTable[:R][2]))
76
+ charge
77
+ end #charge_at_pH
78
+ end # class ChargeCalculator
79
+ end
80
+
data/lib/fragmenter.rb ADDED
@@ -0,0 +1,184 @@
1
+ require_relative 'fragmenter/masses'
2
+ require_relative 'charge_calculator'
3
+ module MS
4
+ class Fragmenter
5
+ include MS
6
+
7
+ attr_accessor :list
8
+ TableEntry = Struct.new(:ion, :seq, :mass, :charge, :composition_arr)
9
+ Ion_Defaults = {:b => true, :y => true}
10
+ Defaults = {:charge_states => true, :avg => false}
11
+ def initialize(opts = {}, ion_opts = {})
12
+ set_options(opts, ion_opts)
13
+ self
14
+ end
15
+ def set_options(opts, ion_opts)
16
+ #@opts = Default_fragments.merge(opts)
17
+ opts = Defaults.merge(opts)
18
+ ion_opts = Ion_Defaults.merge(ion_opts)
19
+ @n_term_search_ion_types = []
20
+ @c_term_search_ion_types = []
21
+ @max_charge = 1 unless opts[:charge_states]
22
+ #puts "options :charge_states = #{opts[:charge_states]}"
23
+ ion_opts.each do |key, v|
24
+ if v
25
+ case key
26
+ when :b
27
+ @n_term_search_ion_types.push(:b, :b_star, :b_not)
28
+ when :a
29
+ @n_term_search_ion_types.push(:a, :a_star, :a_not)
30
+ when :c
31
+ @n_term_search_ion_types << :c
32
+ when :x
33
+ @c_term_search_ion_types << :x
34
+ when :y
35
+ @c_term_search_ion_types.push(:y, :y_star, :y_not)
36
+ when :z
37
+ @c_term_search_ion_types << :z
38
+ end
39
+ end
40
+ end
41
+ @mass_list = opts[:avg] ? MS::AvgResidueMasses : MS::MonoResidueMasses
42
+ #putsv "@mass_list: #{@mass_list}"
43
+ end #set_options
44
+
45
+ def calculate_fragments(sequence)
46
+ arr = sequence.upcase.split('')
47
+ out = [[],[]]
48
+ (0..arr.size-2).each do |i|
49
+ out[0] << arr[0..i].join
50
+ out[1] << arr[(i+1)..-1].join
51
+ end
52
+ out
53
+ end
54
+ # This fxn exists to provide the API consistent with John's request for the 689R class.
55
+ # Options may include a list of fragment classes as symbols (i.e. :b, :y)
56
+ def fragment(pep_seq, options={}) # TODO handle an intensity option to handle normalization and scaling...?
57
+ set_options(options) unless options.empty?
58
+ generate_fragment_masses(pep_seq)
59
+ @list.map(&:mass)
60
+ end
61
+ def generate_fragment_masses(sequence) # Returns the TableEntry object which should be easy to use for table generation
62
+ @sequence = sequence
63
+ @max_charge ||= MS::ChargeCalculator.charge_at_pH(MS::ChargeCalculator.identify_potential_charges(sequence), 2).ceil
64
+ ### Calculate the base ion masses
65
+ n_terms, c_terms = calculate_fragments(sequence)
66
+ n_terms.map! do |seq|
67
+ mass = MS::NTerm
68
+ seq.chars.map(&:to_sym).each do |residue|
69
+ mass += @mass_list[residue]
70
+ end
71
+ [seq, mass]
72
+ end
73
+ c_terms.map! do |seq|
74
+ mass = MS::CTerm
75
+ seq.chars.map(&:to_sym).each do |residue|
76
+ mass += @mass_list[residue]
77
+ end
78
+ [seq, mass]
79
+ end
80
+ ### Tablify and generate a comprehensive list of ions
81
+ list = []
82
+ send_to_list = lambda do |fragment_arr, iontypes_arr|
83
+ fragment_arr.each do |n_terms|
84
+ seq = n_terms.first
85
+ mass = n_terms.last
86
+ iontypes_arr.each do |iontype|
87
+ (1..@max_charge).each do |charge|
88
+ charge_legend = '+'*charge
89
+ list << TableEntry.new("#{iontype}(#{seq.size})#{charge_legend}".to_sym, seq, charge_state(mass + IonTypeMassDelta[iontype], charge), charge)
90
+ end # 1..max_charge
91
+ end # iontypes_arr
92
+ end # fragment_arr
93
+ end # lambda block
94
+ send_to_list.call(n_terms, @n_term_search_ion_types)
95
+ send_to_list.call(c_terms, @c_term_search_ion_types)
96
+ @list = list
97
+ end
98
+ def to_mgf(seq = nil)
99
+ if seq.nil?
100
+ seq = @sequence
101
+ list = @list
102
+ else
103
+ list = generate_fragment_masses(seq)
104
+ end
105
+ intensity = 1000 # An arbitrary intensity value
106
+ output_arr = []
107
+ output_arr << %Q{COM=Project: In-silico Fragmenter\nBEGIN IONS\nPEPMASS=#{precursor_mass(seq, @max_charge)}\nCHARGE=#{@max_charge}+\nTITLE=Label: Sequence is #{seq}}
108
+ list.sort_by{|a| a.mass}.each do |table_entry|
109
+ # TableEntry = Struct.new(:ion, :seq, :mass, :charge)
110
+ output_arr << "#{"%.5f" % table_entry.mass }\t#{intensity}"
111
+ end
112
+ output_arr << "END IONS"
113
+ File.open("#{seq}.mgf", 'w') {|o| o.print output_arr.join("\n") }
114
+ output_arr.join("\n")
115
+ end
116
+ def graph(list = nil)
117
+ list ? list : list = @list
118
+ require 'rserve/simpler'
119
+ robj = Rserve::Simpler.new
120
+ hash = {}
121
+ hash["mass"] = list.map(&:mass)
122
+ hash["intensity"] = list.map{ 1000.0} # Hacky standard intensity value
123
+ robj.converse( masses: hash.to_dataframe) do
124
+ "attach(masses)"
125
+ end
126
+ #robj.converse( data: Rserve::DataFrame.from_structs(list))
127
+ robj.converse "setwd('#{Dir.pwd}')"
128
+ output_file_name = "#{@sequence}_spectra.png"
129
+ robj.converse do
130
+ %Q{png(file='#{output_file_name}')
131
+ plot(masses$mass, masses$intensity, type='h')
132
+ dev.off()
133
+ }
134
+ end
135
+ output_file_name
136
+ end
137
+ end
138
+ end
139
+
140
+
141
+ ######### Testing stuff
142
+ if $0 == __FILE__
143
+ require 'optparse'
144
+ options = {charge_states: true, avg: false}
145
+ ion_options = {}
146
+ parser = OptionParser.new do |opts|
147
+ opts.banner = "Usage: #{File.basename(__FILE__)} sequence [options]"
148
+ opts.separator "Output: [Array] (containing fragment ion masses)"
149
+
150
+ opts.on('--ion_type a,b,c,x,y,z', Array, "Select ion types (default is b,y)") do |t|
151
+ arr = t.map{|a| a.downcase.to_sym}
152
+ hash = {}
153
+ arr.each {|a| hash[a] = true}
154
+ ion_options[:ion_types] = hash
155
+ end
156
+ opts.on('--[no-]charge_states', "Turn on or off the charge state output") do |s|
157
+ options[:charge_states] = s
158
+ end
159
+ opts.on('-a', '--avg', "Use average masses to calculate ions instead of monoisotopic masses") do |a|
160
+ options[:avg] = a
161
+ end
162
+ if ARGV.size == 0
163
+ puts opts
164
+ exit
165
+ end
166
+ opts.on('--[no-]charge_states', "Turn off output of multiple charge states in list") do |s|
167
+ options[:charge_states] = s
168
+ end
169
+ opts.on() do
170
+
171
+ end
172
+
173
+ opts.on_tail('-h', '--help', "Show this message") do
174
+ puts opts
175
+ exit
176
+ end
177
+ end.parse! # OptionParser
178
+ if ARGV.size >= 1
179
+ f = Fragmenter.new(options, ion_options)
180
+ f.fragment(ARGV.first)
181
+ puts "I graphed these fragments and wrote them to #{f.graph} for you."
182
+ end
183
+ end # $0 == __FILE__
184
+
@@ -0,0 +1,119 @@
1
+ module MS
2
+ Proton = 1.00782503207
3
+ def precursor_mass(seq, charge)
4
+ mass = NTerm + CTerm
5
+ seq.chars.map(&:to_sym).each do |residue|
6
+ mass += MS::MonoResidueMasses[residue]
7
+ end
8
+ charge_state(mass, charge)
9
+ end
10
+
11
+ def charge_state(mass, charge)
12
+ if charge > 0
13
+ (mass + charge) / charge.to_f
14
+ else
15
+ (mass - charge) / charge.to_f
16
+ end
17
+ end
18
+
19
+ IonTypeMassDelta = {
20
+ a: (- 29.00273),
21
+ a_star: (-(29.00273+17.02654)),
22
+ a_not: (-(17.02654 + 29.00273+18.01056)),
23
+ b: (-1.00782),
24
+ b_star: ( - 1.00782 - 17.02654),
25
+ b_not: (-17.02654 - 1.00782 - 18.01056),
26
+ c: (16.01872),
27
+ x: (27.99491 - 1.00782),
28
+ y: (1.00782),
29
+ y_star: (1.00782 - 17.02654),
30
+ y_not: (1.00782 - 18.01056),
31
+ z: (- 16.01872)
32
+ }
33
+
34
+ NTerm = 1.00782
35
+
36
+ CTerm = 27.99491 - 10.9742
37
+
38
+ MonoResidueMasses = {
39
+ :A => 71.037114,
40
+ :R => 156.101111,
41
+ :N => 114.042927,
42
+ :D => 115.026943,
43
+ :C => 103.009185,
44
+ :E => 129.042593,
45
+ :Q => 128.058578,
46
+ :G => 57.021464,
47
+ :H => 137.058912,
48
+ :I => 113.084064,
49
+ :L => 113.084064,
50
+ :K => 128.094963,
51
+ :M => 131.040485,
52
+ :F => 147.068414,
53
+ :P => 97.052764,
54
+ :S => 87.032028,
55
+ :T => 101.047679,
56
+ :U => 150.95363,
57
+ :W => 186.079313,
58
+ :Y => 163.06332,
59
+ :V => 99.068414,
60
+ :* => 118.805716,
61
+ :B => 172.048405,
62
+ :X => 118.805716,
63
+ :Z => 128.550585
64
+ }
65
+ AvgResidueMasses = {
66
+ :* => 118.88603,
67
+ :A => 71.0779,
68
+ :B => 172.1405,
69
+ :C => 103.1429,
70
+ :D => 115.0874,
71
+ :E => 129.11398,
72
+ :F => 147.17386,
73
+ :G => 57.05132,
74
+ :H => 137.13928,
75
+ :I => 113.15764,
76
+ :K => 128.17228,
77
+ :L => 113.15764,
78
+ :M => 131.19606,
79
+ :N => 114.10264,
80
+ :O => 211.28076,
81
+ :P => 97.11518,
82
+ :Q => 128.12922,
83
+ :R => 156.18568,
84
+ :S => 87.0773,
85
+ :T => 101.10388,
86
+ :U => 150.0379,
87
+ :V => 99.13106,
88
+ :W => 186.2099,
89
+ :X => 118.88603,
90
+ :Y => 163.17326,
91
+ :Z => 128.6231
92
+ }
93
+ end
94
+ ################
95
+ =begin
96
+ Formula: H3N1
97
+
98
+ Monoisotopic mass : 17.02654
99
+
100
+ Formula: C1H1O1
101
+
102
+ Monoisotopic mass : 29.00273
103
+
104
+ Formula: H2O1
105
+
106
+ Monoisotopic mass : 18.01056
107
+
108
+ Formula: H1
109
+
110
+ Monoisotopic mass : 1.00782
111
+
112
+ Formula: H2N1
113
+
114
+ Monoisotopic mass : 16.01872
115
+
116
+ Formula: C1O1
117
+
118
+ Monoisotopic mass : 27.99491
119
+ =end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fragmenter do
4
+ before :each do
5
+ @f = Fragmenter.new
6
+ end
7
+ it "generates an appropriate response" do
8
+ resp = @f.fragment("REALPEPTIDE")
9
+ resp.should be_a Array
10
+ resp.size.should == 120
11
+ resp.include?(157.101111).should be_true
12
+ end
13
+ it "handles a single charge state limitation" do
14
+ f = Fragmenter.new(:charge_states => false)
15
+ resp = f.fragment("RYANASTAFK")
16
+ resp.size.should == 54
17
+ resp.include?(982.466821).should be_true
18
+ end
19
+ it "calculates more ions for the acceptable charge states" do
20
+ resp = @f.fragment("RYANASTAFK")
21
+ resp.size.should == 162
22
+ resp.include?(491.7334105).should be_true
23
+ end
24
+ end
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+
3
+ RSpec.configure do |config|
4
+ #config.mock_with :rspec
5
+ end
6
+
7
+
8
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
9
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
10
+ require 'fragmenter'
11
+ require 'data'
12
+
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: MS-fragmenter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ryan Taylor
9
+ autorequire:
10
+ bindir:
11
+ - bin
12
+ cert_chain: []
13
+ date: 2012-12-10 00:00:00.000000000 Z
14
+ dependencies: []
15
+ description: A peptide sequence fragmenter which will handle graphing and mgf output,
16
+ as well as command line fragmentation with options
17
+ email: ryanmt@byu.net
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - lib/fragmenter.rb
23
+ - lib/charge_calculator.rb
24
+ - lib/fragmenter/masses.rb
25
+ - spec/spec_helper.rb
26
+ - spec/fragmenter_spec.rb
27
+ homepage: https://github.com/ryanmt/fragmenter
28
+ licenses: []
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ required_rubygems_version: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubyforge_project:
47
+ rubygems_version: 1.8.24
48
+ signing_key:
49
+ specification_version: 3
50
+ summary: ryanmt's peptide fragmenter
51
+ test_files: []