mspire-simulator 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,7 +1,7 @@
1
- = ms-simulate
1
+ = mspire-simulator
2
2
 
3
3
  Description:
4
- Simulates MS runs given amino acid .fasta files. Outputs a .mzML file.
4
+ Simulates MS runs given amino acid FASTA files. Outputs a .mzML file.
5
5
 
6
6
  == Install
7
7
  gem install mspire-simulator
@@ -10,8 +10,51 @@ Dependencies:
10
10
  weka 3.6.0 - May need to add to CLASSPATH see: http://weka.wikispaces.com/CLASSPATH+problems
11
11
  fftw 3.2.2 - Tested in Linux Mint 12 and Ubuntu Oneiric Ocelot
12
12
  == Examples
13
-
13
+ The simplest way to run mspire-simulator is to give it a MZML file
14
+ with a single centroided elution profile from which, the simulator
15
+ can extract needed parameters including:
16
+ - Elution parameters: front, tail, and mu
17
+ - Overlap range (for merging signals)
18
+ - Sampling rate
19
+ - m/z wobble parameters: wobA, wobB
20
+ - Intensity variance parameters: jagA, jagB, jagC
21
+
22
+ $ mspire-simulator --mzml input.mzml [options] <.fasta file>
23
+
24
+ Alternatively all parameters can be specified on the command line:
25
+
26
+ $ mspire-simulator -r 3000 -s 1.0 -n false ...
27
+
28
+ To see all the available options:
29
+
30
+ $ mspire-simulator --help
31
+
32
+
33
+ === Charge State Calculator
34
+ $ ruby lib/ms/isoelectric_calc.rb --ph 2 --distribution DRVYIHPFHL DRVYIHPF RVYIHPF VYIHPF
35
+
36
+ will return:
37
+
38
+ DRVYIHPFHL @ pH 2.0: +3, 29.040854; +4, 70.959146
39
+
40
+ DRVYIHPF @ pH 2.0: +2, 29.045885; +3, 70.954115
41
+
42
+ RVYIHPF @ pH 2.0: +2, 37.364123; +3, 62.635877
43
+
44
+ VYIHPF @ pH 2.0: +1, 40.341305; +2, 59.658695
45
+
46
+ To see all the available options:
47
+ $ ruby lib/ms/isoelectric_calc.rb --help
48
+
49
+
50
+
51
+ == TODO
52
+ Because of the many options and parameters to specify we will be moving
53
+ to a .init file format with a .init file editor.
54
+
55
+ Other improvments to mspire simulator are also pending.
14
56
  == Copyright
15
57
 
16
58
  See LICENSE.txt for further details.
17
59
 
60
+
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ require 'jeweler'
5
5
  Jeweler::Tasks.new do |gem|
6
6
  # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
7
7
  gem.name = "mspire-simulator"
8
- gem.homepage = "http://dl.dropbox.com/u/42836826/Ms_Sim_Homepage.html"
8
+ gem.homepage = "https://github.com/princelab/mspire-simulator"
9
9
  gem.license = "MIT"
10
10
  gem.summary = %Q{Simulates MS1 runs given amino acid FASTA files. Outputs an MZML file.}
11
11
  gem.description = %Q{Simulates MS1 runs given amino acid FASTA files. Outputs an MZML file.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
1
+ 0.2.0
data/bin/mspire-simulator CHANGED
@@ -19,6 +19,8 @@ require 'ms/sim_digester'
19
19
  require 'ms/sim_trollop'
20
20
  require 'ms/merger'
21
21
 
22
+
23
+
22
24
  module MspireSimulator
23
25
  @opts = MS::Troll.new.get
24
26
  begin
@@ -40,6 +42,10 @@ module MspireSimulator
40
42
 
41
43
  module_function
42
44
  def opts; @opts end
45
+
46
+
47
+ SampleLoad = 1.0 # Instrument dependent scaling, for an Orbitrap, assumed to be 1 ug
48
+ # TODO define an option for sample loading, and a scaling function to define the peak intensities
43
49
 
44
50
  #------------------------Digest-----------------------------------------------
45
51
  peptides = []
@@ -80,10 +86,12 @@ module MspireSimulator
80
86
  #-----------------------------------------------------------------------------
81
87
 
82
88
 
89
+
83
90
  #-----------------------Merge Finish------------------------------------------
84
91
  spectra.spectra = Merger.compact(spectra.spectra)
85
92
  #-----------------------------------------------------------------------------
86
93
 
94
+
87
95
 
88
96
  #-----------------------Clean UP----------------------------------------------
89
97
  spectra.features.each{|fe| fe.delete}
data/bin/sim_mail CHANGED
@@ -18,9 +18,9 @@ begin
18
18
  :password => 'chromatography',
19
19
  :authentication => :plain,
20
20
  :domain => "localhost.localdomain"
21
- },
21
+ },
22
22
  :subject => 'Mspire-Simulator', :body => msgbody
23
- )
23
+ )
24
24
  rescue
25
25
  puts "Email function failed. Check email address and internet connection."
26
26
  end
data/lib/cv_parser.rb ADDED
@@ -0,0 +1,7 @@
1
+ #require 'ms/modifications/cv'
2
+
3
+ string = DATA.read
4
+ puts string
5
+
6
+ __END__
7
+ Hello Ryan
@@ -35,10 +35,10 @@ class GenCurvefit
35
35
  init_population
36
36
  end
37
37
  end
38
-
38
+
39
39
  attr_reader :function, :paramsize, :mutation_limits, :population, :generations, :popsize
40
40
  attr_writer :paramsize, :mutation_limits, :population, :generations, :popsize
41
-
41
+
42
42
  def init_population
43
43
  @popsize.times do
44
44
  set = []
@@ -50,24 +50,24 @@ class GenCurvefit
50
50
  @population<<set
51
51
  end
52
52
  end
53
-
53
+
54
54
  def set_fit_function(func)
55
55
  @function = func
56
56
  end
57
-
57
+
58
58
  def mutate(set)
59
59
  index = rand(set.size-1)
60
60
  limits = @mutation_limits[index]
61
61
  set[index] += random_float(limits[0],limits[1])
62
62
  end
63
-
63
+
64
64
  def self.smoothave(arr)
65
65
  smooth_ave = [nil,nil,nil]
66
66
  queue = []
67
67
  arr.each do |i|
68
68
  queue.push(i)
69
69
  if queue.size > 7
70
- queue.shift
70
+ queue.shift
71
71
  end
72
72
  smooth_ave<<queue.inject(:+)/queue.size if queue.size == 7
73
73
  end
@@ -76,16 +76,16 @@ class GenCurvefit
76
76
  end
77
77
  return smooth_ave
78
78
  end
79
-
79
+
80
80
  def self.normalize(arr)
81
81
  max = arr.max
82
82
  arr.map!{|i| (i.to_f/max) * 100}
83
83
  end
84
-
84
+
85
85
  def sort_by_fitness
86
86
  @population.sort_by!{|set| set.last}
87
87
  end
88
-
88
+
89
89
  def random_float(a,b)
90
90
  a = a.to_f
91
91
  b = b.to_f
@@ -94,15 +94,15 @@ class GenCurvefit
94
94
  r = random * diff
95
95
  return a + r
96
96
  end
97
-
97
+
98
98
  def rmsd(v,w)
99
99
  n = v.size
100
100
  sum = 0.0
101
101
  n.times{|i| sum += ((v[i][0]-w[i][0])**2.0 + (v[i][1]-w[i][1])**2.0) }
102
102
  return Math.sqrt( (1/n.to_f) * sum )
103
103
  end
104
-
105
-
104
+
105
+
106
106
  def fitness(set,pts_in,plot = false)
107
107
  pts = []
108
108
  xs = pts_in.transpose[0]
@@ -110,18 +110,24 @@ class GenCurvefit
110
110
  fit_pt = function.call(set,x)
111
111
  pts<<[x,fit_pt]
112
112
  end
113
-
113
+
114
114
  if plot
115
115
  return pts
116
116
  end
117
-
117
+
118
118
  return rmsd(pts_in,pts)
119
119
  end
120
-
120
+
121
121
  def fit
122
- @start = Time.now
122
+ prog = Progress.new("Generation")
123
+ num = 0
124
+ total = @generations
125
+ step = total/100
123
126
  @generations.times do |i|
124
- Progress.progress("Generation #{i+1}:",((i/@generations.to_f)*100).to_i)
127
+ if i > step * (num + 1)
128
+ num = ((i/total.to_f)*100).to_i
129
+ prog.update(num," #{i+1}:")
130
+ end
125
131
  #Generate mutations
126
132
  index = rand(@popsize)
127
133
  clone = @population[index].clone
@@ -139,14 +145,14 @@ class GenCurvefit
139
145
  @best = @population.first
140
146
  end
141
147
  end
142
- Progress.progress("Generations Done, printing graph:",100,Time.now-@start)
148
+ prog.finish!
143
149
  return @best
144
150
  end
145
-
151
+
146
152
  def plot(file,labels = nil)
147
153
  pts = fitness(@best,@pts_in,true)
148
154
  Fit_plot.plot(@pts_in,pts,file,labels)
149
155
  puts " Output File: #{file}"
150
156
  end
151
-
157
+
152
158
  end
@@ -14,7 +14,7 @@ class Mzml_reader
14
14
  ints = spec.intensities
15
15
  mzs = spec.mzs
16
16
  rt = spec.retention_time
17
-
17
+
18
18
  if ints.empty?;else
19
19
  ints.each_with_index do |i,j|
20
20
  mzs_out<<mzs[j]
data/lib/ms/curvefit.rb CHANGED
@@ -1,12 +1,15 @@
1
-
1
+ require 'progress'
2
2
  require 'ms/curvefit/mzml_reader'
3
3
  require 'ms/curvefit/curve_fit_helper'
4
4
 
5
+ @@avg_mz = 0
6
+ @@avg_rt = 0
7
+
5
8
  class CurveFit
6
9
  def self.get_parameters(opts)
7
10
  data = Mzml_reader.get_data(opts[:mzml])
8
11
  generations = opts[:generations]
9
-
12
+
10
13
  @pts_int_var = []
11
14
  @pts_mz_var = []
12
15
  @pts_elut = []
@@ -17,13 +20,16 @@ class CurveFit
17
20
  rts_in = data[1]
18
21
  ints_in = data[2]
19
22
 
23
+ @@avg_mz = mzs_in.inject(:+)/mzs_in.size.to_f
24
+ @@avg_rt = rts_in.inject(:+)/rts_in.size.to_f
25
+
20
26
  ints_in = GenCurvefit.normalize(ints_in)
21
27
  #-----------------------overlapRange--------------------------------------------
22
28
  mean = mzs_in.inject(:+)/mzs_in.size
23
29
  opts[:overlapRange] = (mzs_in.sample_variance(mean)*10**6)/4
24
30
  #-------------------------------------------------------------------------------
25
-
26
-
31
+
32
+
27
33
  #----------------------create points/curve to fit elution-----------------------
28
34
  ints_in.each_with_index do |s,i|
29
35
  @pts_elut<<[rts_in[i],s]
@@ -46,8 +52,8 @@ class CurveFit
46
52
  labels = ["retention time","normalized intensity"]
47
53
  a_fit.plot("elution_curvefit.svg",labels)
48
54
  #-------------------------------------------------------------------------------
49
-
50
-
55
+
56
+
51
57
  #-----------------create points/curve to fit m/z variance-----------------------
52
58
  wobs = []
53
59
  mean = mzs_in.inject(:+)/mzs_in.size
@@ -77,7 +83,7 @@ class CurveFit
77
83
  labels = ["normalized intensity","m/z variance"]
78
84
  b_fit.plot("mz_var_curvefit.svg",labels)
79
85
  #-------------------------------------------------------------------------------
80
-
86
+
81
87
  #--------------------create points/curve to fit intensity variance--------------
82
88
  smooth_ave = GenCurvefit.smoothave(ints_in)
83
89
 
@@ -114,7 +120,18 @@ class CurveFit
114
120
  labels = ["normalized intensity","intensity variance"]
115
121
  c_fit.plot("intensity_var_curvefit.svg",labels)
116
122
  #-------------------------------------------------------------------------------
117
-
123
+
118
124
  return opts
119
125
  end
120
126
  end
127
+ =begin
128
+ out_file = File.open("mzvar_params.txt","w")
129
+ out_file.puts "wobA\twobB\tavg_mz\tavg_rt"
130
+ ARGV.each do |file|
131
+ p file
132
+ opts = {:mzml => file, :generations => 30000}
133
+ opts = CurveFit.get_parameters(opts)
134
+ out_file.puts "#{opts[:wobA]}\t#{opts[:wobB]}\t#{@@avg_mz}\t#{@@avg_rt}"
135
+ end
136
+ out_file.close
137
+ =end
@@ -4,119 +4,178 @@
4
4
 
5
5
  Precision = 0.001
6
6
  ResidueTable = {
7
- :K => [2.18,8.95,10.53],
8
- :E => [2.19,9.67,4.25],
9
- :D => [1.88,9.60,3.65],
10
- :H => [1.82,9.17,6.00],
11
- :R => [2.17,9.04,12.48],
12
- :Q => [2.17,9.13,nil],
13
- :N => [2.02,8.80,nil],
14
- :C => [1.96,10.28,8.18],
15
- :T => [2.11,9.62,nil],
16
- :S => [2.21,9.15,nil],
17
- :W => [2.38,9.39,nil],
18
- :Y => [2.20,9.11,10.07],
19
- :F => [1.83,9.13,nil],
20
- :M => [2.28,9.21,nil],
21
- :I => [2.36,9.68,nil],
22
- :L => [2.36,9.60,nil],
23
- :V => [2.32,9.62,nil],
24
- :P => [1.99,10.96,nil],
25
- :A => [2.34,9.69,nil],
26
- :G => [2.34,9.60,nil],
27
- # These are the fringe cases... B and Z... Jerks, these are harder to calculate pIs
28
- :B => [1.95,9.20,3.65],
29
- :Z => [2.18,9.40,4.25],
30
- :X => [2.20,9.40,nil],
31
- :U => [1.96,10.28,5.20] # Unfortunately, I've only found the pKr for this... so I've used Cysteine's values.
7
+ :K => [2.18,8.95,10.53],
8
+ :E => [2.19,9.67,4.25],
9
+ :D => [1.88,9.60,3.65],
10
+ :H => [1.82,9.17,6.00],
11
+ :R => [2.17,9.04,12.48],
12
+ :Q => [2.17,9.13,nil],
13
+ :N => [2.02,8.80,nil],
14
+ :C => [1.96,10.28,8.18],
15
+ :T => [2.11,9.62,nil],
16
+ :S => [2.21,9.15,nil],
17
+ :W => [2.38,9.39,nil],
18
+ :Y => [2.20,9.11,10.07],
19
+ :F => [1.83,9.13,nil],
20
+ :M => [2.28,9.21,nil],
21
+ :I => [2.36,9.68,nil],
22
+ :L => [2.36,9.60,nil],
23
+ :V => [2.32,9.62,nil],
24
+ :P => [1.99,10.96,nil],
25
+ :A => [2.34,9.69,nil],
26
+ :G => [2.34,9.60,nil],
27
+ # These are the fringe cases... B and Z... Jerks, these are harder to calculate pIs
28
+ :B => [1.95,9.20,3.65],
29
+ :Z => [2.18,9.40,4.25],
30
+ :X => [2.20,9.40,nil],
31
+ :U => [1.96,10.28,5.20] # Unfortunately, I've only found the pKr for this... so I've used Cysteine's values.
32
32
  }
33
33
  PepCharges = Struct.new(:seq, :n_term, :c_term, :y_num, :c_num, :k_num, :h_num, :r_num, :d_num, :e_num, :u_num, :polar_num, :hydrophobic_num, :pi)
34
34
  def identify_potential_charges(str)
35
- string = str.upcase
36
- first = string[0]; last = string[-1]
37
- puts string if first.nil? or last.nil?
38
- begin
39
- out = PepCharges.new(string, ResidueTable[first.to_sym][0], ResidueTable[last.to_sym][1], 0, 0, 0 ,0 ,0 ,0, 0, 0, 0, 0, 0)
40
- rescue NoMethodError
41
- abort string
42
- end
43
- string.chars.each do |letter|
44
- case letter
45
- when "Y"
46
- out.y_num += 1
47
- when "C"
48
- out.c_num += 1
49
- when "K"
50
- out.k_num += 1
51
- when "H"
52
- out.h_num += 1
53
- when "R"
54
- out.r_num += 1
55
- when "D"
56
- out.d_num += 1
57
- when "E"
58
- out.e_num += 1
59
- when "U"
60
- out.u_num += 1
61
- when "S", "T", "N", "Q"
62
- out.polar_num += 1
63
- when "A", "V", "I", "L", "M", "F", "W", "G", "P"
64
- out.hydrophobic_num += 1
65
- end
66
- end
67
- out
35
+ string = str.upcase
36
+ first = string[0]; last = string[-1]
37
+ puts string if first.nil? or last.nil?
38
+ begin
39
+ out = PepCharges.new(string, ResidueTable[first.to_sym][0], ResidueTable[last.to_sym][1], 0, 0, 0 ,0 ,0 ,0, 0, 0, 0, 0, 0)
40
+ rescue NoMethodError
41
+ abort string
42
+ end
43
+ string.chars.each do |letter|
44
+ case letter
45
+ when "Y"
46
+ out.y_num += 1
47
+ when "C"
48
+ out.c_num += 1
49
+ when "K"
50
+ out.k_num += 1
51
+ when "H"
52
+ out.h_num += 1
53
+ when "R"
54
+ out.r_num += 1
55
+ when "D"
56
+ out.d_num += 1
57
+ when "E"
58
+ out.e_num += 1
59
+ when "U"
60
+ out.u_num += 1
61
+ when "S", "T", "N", "Q"
62
+ out.polar_num += 1
63
+ when "A", "V", "I", "L", "M", "F", "W", "G", "P"
64
+ out.hydrophobic_num += 1
65
+ end
66
+ end
67
+ out
68
68
  end # Returns the PepCharges structure
69
69
 
70
70
  def charge_at_pH(pep_charges, pH)
71
- charge = 0
72
- charge += -1/(1+10**(pep_charges.c_term-pH))
73
- charge += -pep_charges.d_num/(1+10**(ResidueTable[:D][2]-pH))
74
- charge += -pep_charges.e_num/(1+10**(ResidueTable[:E][2]-pH))
75
- charge += -pep_charges.c_num/(1+10**(ResidueTable[:C][2]-pH))
76
- charge += -pep_charges.y_num/(1+10**(ResidueTable[:Y][2]-pH))
77
- charge += 1/(1+10**(pH - pep_charges.n_term))
78
- charge += pep_charges.h_num/(1+10**(pH-ResidueTable[:H][2]))
79
- charge += pep_charges.k_num/(1+10**(pH-ResidueTable[:K][2]))
80
- charge += pep_charges.r_num/(1+10**(pH-ResidueTable[:R][2]))
81
- charge
71
+ charge = 0
72
+ charge += -1/(1+10**(pep_charges.c_term-pH))
73
+ charge += -pep_charges.d_num/(1+10**(ResidueTable[:D][2]-pH))
74
+ charge += -pep_charges.e_num/(1+10**(ResidueTable[:E][2]-pH))
75
+ charge += -pep_charges.c_num/(1+10**(ResidueTable[:C][2]-pH))
76
+ charge += -pep_charges.y_num/(1+10**(ResidueTable[:Y][2]-pH))
77
+ charge += 1/(1+10**(pH - pep_charges.n_term))
78
+ charge += pep_charges.h_num/(1+10**(pH-ResidueTable[:H][2]))
79
+ charge += pep_charges.k_num/(1+10**(pH-ResidueTable[:K][2]))
80
+ charge += pep_charges.r_num/(1+10**(pH-ResidueTable[:R][2]))
81
+ charge
82
82
  end
83
83
 
84
84
 
85
85
  def calc_PI(pep_charges)
86
- pH = 8; pH_prev = 0.0; pH_next = 14.0
87
- charge = charge_at_pH(pep_charges, pH)
88
- while pH-pH_prev > Precision and pH_next-pH > Precision
89
- if charge < 0.0
90
- tmp = pH
91
- pH = pH - ((pH-pH_prev)/2)
92
- charge = charge_at_pH(pep_charges, pH)
93
- pH_next = tmp
94
- else
95
- tmp = pH
96
- pH = pH + ((pH_next - pH)/2)
97
- charge = charge_at_pH(pep_charges, pH)
98
- pH_prev = tmp
99
- end
100
- # puts "charge: #{charge.round(2)}\tpH: #{pH.round(2)}\tpH_next: #{pH_next.round(2)}\tpH_prev: #{pH_prev.round(2)}"
101
- end
102
- pH
86
+ pH = 8; pH_prev = 0.0; pH_next = 14.0
87
+ charge = charge_at_pH(pep_charges, pH)
88
+ while pH-pH_prev > Precision and pH_next-pH > Precision
89
+ if charge < 0.0
90
+ tmp = pH
91
+ pH = pH - ((pH-pH_prev)/2)
92
+ charge = charge_at_pH(pep_charges, pH)
93
+ pH_next = tmp
94
+ else
95
+ tmp = pH
96
+ pH = pH + ((pH_next - pH)/2)
97
+ charge = charge_at_pH(pep_charges, pH)
98
+ pH_prev = tmp
99
+ end
100
+ # puts "charge: #{charge.round(2)}\tpH: #{pH.round(2)}\tpH_next: #{pH_next.round(2)}\tpH_prev: #{pH_prev.round(2)}"
101
+ end
102
+ pH
103
103
  end
104
- #pepcharges =[]
105
- =begin
106
- # RUN the ENTRY FILE HERE
107
- pi = []
108
- io = File.open(ARGV.shift, 'r')
109
- io.each_line do |line|
110
- pi << calc_PI(identify_potential_charges(line[/^([A-Z]+):.*/]))
111
- end
112
- =end
113
- =begin
114
- pIes = []
115
- pepcharges.each do |a|
116
- pIes << [a, calc_PI(a)]
104
+ def distribution_from_charge(charge, normalization=100)
105
+ threshold = normalization.to_f
106
+ f = charge.floor
107
+ c = charge.ceil
108
+ charge_ratio = charge - f
109
+ num = charge_ratio*normalization
110
+ denom = normalization
111
+ while num + denom > threshold
112
+ factor = threshold/(num+denom)
113
+ num = num * factor
114
+ denom = denom * factor
115
+ end
116
+ [["+#{f}" + ", " + "%5f" % num],["+#{c}" + ", " + "%5f" % denom]]
117
117
  end
118
- =end
119
- #out_pi = pepcharges.map {|a| calc_PI(a)}
120
118
 
121
- #require 'yaml'
122
- #File.open('pi_list.yml', 'w') {|f| YAML.dump( pi, f) }
119
+
120
+ #pepcharges =[]
121
+ if $0 == __FILE__
122
+ VERBOSE = false
123
+ def putsv(object)
124
+ puts object if VERBOSE
125
+ end
126
+ def out(line, object)
127
+ line + ":\t" + object.to_s
128
+ end
129
+ require 'optparse'
130
+
131
+ options = {pi: true, distribution: false, ph: 7.0}
132
+ parser = OptionParser.new do |opts|
133
+ opts.banner = "Takes strings and outputs the PI, or charge distribution"
134
+
135
+ opts.on('-h','--help', "Displays this help message") do |h|
136
+ puts opts
137
+ exit
138
+ end
139
+ opts.on('-v','--verbose') {|v| VERBOSE = v}
140
+ opts.on("--[no]-pi", "Turns on (default) or off the pI output") do |p|
141
+ options[:pi] = p
142
+ end
143
+ opts.on("-d", "--distribution", "Output a string representation of the charge state distribution array") do |d|
144
+ options[:distribution] = true
145
+ options[:pi] = false
146
+ end
147
+ opts.on('--pH N', Float, "Takes a float value representing a pH at which to make the distribution. DEFAULT: 7.0") do |ph|
148
+ options[:ph] = ph
149
+ end
150
+ opts.on('-f', "--file FILENAME", String, "Takes an input file for parsing") do |f|
151
+ options[:in_file] = f
152
+ end
153
+ end
154
+ parser.parse!
155
+
156
+ # RUN
157
+ pi = []
158
+ lines = []
159
+ if options[:in_file]
160
+ file_lines = File.readlines(options[:in_file]).map(&:chomp)
161
+ lines = file_lines.map {|line| line[/^([A-Z]+).*/] }.compact
162
+ outfile = File.join(File.dirname(options[:in_file]), 'pi_output_file.txt')
163
+ outputter = File.open(outfile,'w')
164
+ else
165
+ lines = ARGV
166
+ outputter = STDOUT
167
+ end
168
+ if options[:pi]
169
+ lines.each {|line| outputter.puts out(line, calc_PI(identify_potential_charges(line)) ) }
170
+ elsif options[:distribution]
171
+ lines.each do |line|
172
+ charge = charge_at_pH(identify_potential_charges(line), options[:ph])
173
+ charge_dist = distribution_from_charge(charge)
174
+ outputter.puts out(line + " @ pH #{options[:ph]}", charge_dist.join("; "))
175
+ end
176
+ end
177
+ if outfile
178
+ outputter.close
179
+ puts "OUTPUT in #{outfile}"
180
+ end
181
+ end