mspire-simulator 0.1.2 → 0.2.0

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/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