miga-base 0.7.26.1 → 1.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.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/lib/miga/_data/aai-intax.blast.tsv.gz +0 -0
  3. data/lib/miga/_data/aai-intax.diamond.tsv.gz +0 -0
  4. data/lib/miga/_data/aai-novel.blast.tsv.gz +0 -0
  5. data/lib/miga/_data/aai-novel.diamond.tsv.gz +0 -0
  6. data/lib/miga/cli/action/classify_wf.rb +2 -2
  7. data/lib/miga/cli/action/derep_wf.rb +1 -1
  8. data/lib/miga/cli/action/doctor.rb +57 -14
  9. data/lib/miga/cli/action/doctor/base.rb +47 -23
  10. data/lib/miga/cli/action/init.rb +11 -7
  11. data/lib/miga/cli/action/init/files_helper.rb +1 -0
  12. data/lib/miga/cli/action/ncbi_get.rb +3 -3
  13. data/lib/miga/cli/action/tax_dist.rb +2 -2
  14. data/lib/miga/cli/action/wf.rb +5 -4
  15. data/lib/miga/common.rb +1 -0
  16. data/lib/miga/daemon.rb +11 -4
  17. data/lib/miga/dataset/result.rb +10 -6
  18. data/lib/miga/json.rb +5 -4
  19. data/lib/miga/metadata.rb +5 -1
  20. data/lib/miga/parallel.rb +36 -0
  21. data/lib/miga/project.rb +8 -8
  22. data/lib/miga/project/base.rb +4 -4
  23. data/lib/miga/project/result.rb +2 -2
  24. data/lib/miga/sqlite.rb +10 -2
  25. data/lib/miga/version.rb +23 -9
  26. data/scripts/aai_distances.bash +16 -18
  27. data/scripts/ani_distances.bash +16 -17
  28. data/scripts/assembly.bash +31 -16
  29. data/scripts/haai_distances.bash +3 -27
  30. data/scripts/miga.bash +6 -4
  31. data/scripts/p.bash +1 -1
  32. data/scripts/read_quality.bash +9 -18
  33. data/scripts/trimmed_fasta.bash +14 -30
  34. data/scripts/trimmed_reads.bash +36 -36
  35. data/test/parallel_test.rb +31 -0
  36. data/test/project_test.rb +2 -1
  37. data/test/remote_dataset_test.rb +1 -1
  38. data/utils/distance/commands.rb +1 -0
  39. data/utils/distance/database.rb +0 -1
  40. data/utils/distance/runner.rb +2 -4
  41. data/utils/enveomics/Manifest/Tasks/fasta.json +39 -3
  42. data/utils/enveomics/Manifest/Tasks/fastq.json +50 -2
  43. data/utils/enveomics/Manifest/Tasks/mapping.json +70 -0
  44. data/utils/enveomics/Manifest/Tasks/other.json +77 -0
  45. data/utils/enveomics/Manifest/Tasks/sequence-identity.json +138 -1
  46. data/utils/enveomics/Manifest/categories.json +13 -4
  47. data/utils/enveomics/Scripts/Aln.cat.rb +206 -148
  48. data/utils/enveomics/Scripts/FastA.N50.pl +33 -29
  49. data/utils/enveomics/Scripts/FastA.fragment.rb +69 -61
  50. data/utils/enveomics/Scripts/FastA.sample.rb +61 -46
  51. data/utils/enveomics/Scripts/FastA.toFastQ.rb +69 -0
  52. data/utils/enveomics/Scripts/FastQ.maskQual.rb +89 -0
  53. data/utils/enveomics/Scripts/FastQ.tag.rb +59 -52
  54. data/utils/enveomics/Scripts/SRA.download.bash +6 -8
  55. data/utils/enveomics/Scripts/Table.prefScore.R +60 -0
  56. data/utils/enveomics/Scripts/aai.rb +3 -2
  57. data/utils/enveomics/Scripts/anir.rb +137 -0
  58. data/utils/enveomics/Scripts/lib/enveomics_rb/anir.rb +293 -0
  59. data/utils/enveomics/Scripts/lib/enveomics_rb/bm_set.rb +175 -0
  60. data/utils/enveomics/Scripts/lib/enveomics_rb/enveomics.rb +17 -17
  61. data/utils/enveomics/Scripts/lib/enveomics_rb/errors.rb +17 -0
  62. data/utils/enveomics/Scripts/lib/enveomics_rb/gmm_em.rb +30 -0
  63. data/utils/enveomics/Scripts/lib/enveomics_rb/match.rb +63 -0
  64. data/utils/enveomics/Scripts/lib/enveomics_rb/rbm.rb +49 -0
  65. data/utils/enveomics/Scripts/lib/enveomics_rb/stats.rb +3 -0
  66. data/utils/enveomics/Scripts/lib/enveomics_rb/stats/rand.rb +31 -0
  67. data/utils/enveomics/Scripts/lib/enveomics_rb/stats/sample.rb +152 -0
  68. data/utils/enveomics/Scripts/lib/enveomics_rb/utils.rb +73 -0
  69. data/utils/enveomics/Scripts/rbm-legacy.rb +172 -0
  70. data/utils/enveomics/Scripts/rbm.rb +87 -133
  71. data/utils/enveomics/Scripts/sam.filter.rb +148 -0
  72. data/utils/enveomics/enveomics.R/DESCRIPTION +2 -2
  73. data/utils/enveomics/enveomics.R/NAMESPACE +1 -1
  74. data/utils/enveomics/enveomics.R/R/prefscore.R +79 -0
  75. data/utils/enveomics/enveomics.R/R/utils.R +30 -0
  76. data/utils/enveomics/enveomics.R/README.md +1 -0
  77. data/utils/enveomics/enveomics.R/man/cash-enve.GrowthCurve-method.Rd +0 -1
  78. data/utils/enveomics/enveomics.R/man/cash-enve.RecPlot2-method.Rd +0 -1
  79. data/utils/enveomics/enveomics.R/man/cash-enve.RecPlot2.Peak-method.Rd +0 -1
  80. data/utils/enveomics/enveomics.R/man/enve.__tribs.Rd +10 -2
  81. data/utils/enveomics/enveomics.R/man/enve.barplot.Rd +16 -4
  82. data/utils/enveomics/enveomics.R/man/enve.cliopts.Rd +13 -3
  83. data/utils/enveomics/enveomics.R/man/enve.df2dist.Rd +8 -2
  84. data/utils/enveomics/enveomics.R/man/enve.df2dist.group.Rd +8 -2
  85. data/utils/enveomics/enveomics.R/man/enve.df2dist.list.Rd +9 -2
  86. data/utils/enveomics/enveomics.R/man/enve.growthcurve.Rd +13 -5
  87. data/utils/enveomics/enveomics.R/man/enve.prefscore.Rd +50 -0
  88. data/utils/enveomics/enveomics.R/man/enve.prune.dist.Rd +9 -2
  89. data/utils/enveomics/enveomics.R/man/enve.recplot.Rd +23 -6
  90. data/utils/enveomics/enveomics.R/man/enve.recplot2.Rd +13 -4
  91. data/utils/enveomics/enveomics.R/man/enve.recplot2.compareIdentities.Rd +8 -2
  92. data/utils/enveomics/enveomics.R/man/enve.recplot2.extractWindows.Rd +7 -2
  93. data/utils/enveomics/enveomics.R/man/enve.recplot2.findPeaks.__mow_one.Rd +14 -3
  94. data/utils/enveomics/enveomics.R/man/enve.recplot2.findPeaks.em.Rd +10 -2
  95. data/utils/enveomics/enveomics.R/man/enve.recplot2.findPeaks.emauto.Rd +8 -2
  96. data/utils/enveomics/enveomics.R/man/enve.recplot2.findPeaks.mower.Rd +17 -9
  97. data/utils/enveomics/enveomics.R/man/enve.recplot2.windowDepthThreshold.Rd +6 -2
  98. data/utils/enveomics/enveomics.R/man/enve.selvector.Rd +23 -0
  99. data/utils/enveomics/enveomics.R/man/enve.tribs.Rd +14 -5
  100. data/utils/enveomics/enveomics.R/man/plot.enve.GrowthCurve.Rd +19 -4
  101. data/utils/enveomics/enveomics.R/man/plot.enve.TRIBS.Rd +11 -3
  102. data/utils/enveomics/enveomics.R/man/plot.enve.TRIBStest.Rd +11 -4
  103. data/utils/enveomics/enveomics.R/man/plot.enve.recplot2.Rd +26 -12
  104. data/utils/multitrim/Multitrim How-To.pdf +0 -0
  105. data/utils/multitrim/README.md +67 -0
  106. data/utils/multitrim/multitrim.py +1555 -0
  107. data/utils/multitrim/multitrim.yml +13 -0
  108. data/utils/requirements.txt +4 -3
  109. data/utils/subclade/pipeline.rb +2 -2
  110. metadata +35 -7
  111. data/utils/enveomics/Scripts/lib/enveomics_rb/stat.rb +0 -30
@@ -1,24 +1,24 @@
1
1
 
2
- #
3
- # @author: Luis M. Rodriguez-R
4
- # @license: artistic license 2.0
5
- #
2
+ require 'enveomics_rb/utils'
3
+ use 'optparse'
4
+ ARGV << '-h' if ARGV.empty?
6
5
 
7
- require "optparse"
8
- ARGV << "-h" if ARGV.size==0
6
+ module Enveomics
7
+ class << self
8
+ def opt_banner(opt, banner, usage = nil)
9
+ opt.version ||= $VERSION
10
+ usage ||= "#{opt.program_name}.rb [options]"
11
+ opt.banner = <<~BANNER
9
12
 
10
- def use(gems, mandatory=true)
11
- gems = [gems] unless gems.is_a? Array
12
- begin
13
- require "rubygems"
14
- while ! gems.empty?
15
- require gems.shift
13
+ [Enveomics Collection: #{opt.program_name} #{opt.version}]
14
+
15
+ #{banner}
16
+
17
+ Usage
18
+ #{usage}
19
+
20
+ BANNER
16
21
  end
17
- return true
18
- rescue LoadError
19
- abort "\nUnmet requirements, please install required gems:" +
20
- gems.map{ |gem| "\n gem install #{gem}" }.join + "\n\n" if mandatory
21
- return false
22
22
  end
23
23
  end
24
24
 
@@ -0,0 +1,17 @@
1
+
2
+ module Enveomics
3
+ class Error < RuntimeError
4
+ end
5
+
6
+ class CommandError < Error
7
+ end
8
+
9
+ class OptionError < Error
10
+ end
11
+
12
+ class UnimplementedError < Error
13
+ end
14
+
15
+ class ParseError < Error
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+
2
+ require 'enveomics_rb/stats/sample'
3
+
4
+ module Enveomics
5
+ # Calculate Gaussian Mixture Models by Expectation Maximization
6
+ class GmmEm
7
+ attr :sample
8
+ attr :components
9
+ attr :opts
10
+
11
+ # Initialize Enve::GmmEm object from numeric array +x+, +components+
12
+ # gaussian components (an Integer), and options hash +opts+ with supported
13
+ # Symbol keys:
14
+ # - ll_delta_converge: Maximum change in LL to consider convergence
15
+ # (by default: 1e-15)
16
+ # - max_iter: Maximum number of EM iterations (by default: 1_000)
17
+ # - init_mu: Initial components means as numeric array
18
+ # - init_sigma: Initial components standard deviation as numeric array
19
+ # - init_alpha: Initial components fractions as numeric array adding up to 1
20
+ def initialize(x, components = 2, opts = {})
21
+ @sample = Enve::Stats::Sample.new(x)
22
+ @opts = opts
23
+ @opts[:ll_delta_convergence] ||= 1e-15
24
+ @opts[:max_iter] ||= 1_000
25
+ end
26
+
27
+
28
+ end
29
+ end
30
+
@@ -0,0 +1,63 @@
1
+
2
+ module Enveomics
3
+ ##
4
+ # A simple object representing a sequence match from a search engine
5
+ # supporting tabular BLAST output
6
+ class Match
7
+ attr :row
8
+
9
+ ##
10
+ # Initialize Enveomics::Match object from a tabular blast line String +ln+
11
+ def initialize(ln)
12
+ @row = ln.chomp.split("\t")
13
+ end
14
+
15
+ def qry
16
+ row[0]
17
+ end
18
+
19
+ def sbj
20
+ row[1]
21
+ end
22
+
23
+ def id
24
+ @id ||= row[2].to_f
25
+ end
26
+
27
+ def len
28
+ @len ||= row[3].to_i
29
+ end
30
+
31
+ def evalue
32
+ @evalue ||= row[9].to_f
33
+ end
34
+
35
+ def score
36
+ @score ||= row[10].to_f
37
+ end
38
+
39
+ def qry_len
40
+ @qry_len ||= row[12].to_i
41
+ end
42
+
43
+ def sbj_len
44
+ @sbj_len ||= row[13].to_i
45
+ end
46
+
47
+ def qry_fract
48
+ return 0.0 unless qry_len.zero?
49
+ @fract ||= len.to_f / qry_len
50
+ end
51
+
52
+ alias fract qry_fract
53
+
54
+ def sbj_fract
55
+ return 0.0 unless sbj_len.zero?
56
+ @fract ||= len.to_f / sbj_len
57
+ end
58
+
59
+ def to_s
60
+ row.join("\t")
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,49 @@
1
+ require 'enveomics_rb/bm_set'
2
+
3
+ module Enveomics
4
+ class RBM
5
+ attr :seq1, :seq2, :bms1, :bms2
6
+
7
+ ##
8
+ # Initialize RBM object with sequence paths +seq1+ and +seq2+, and
9
+ # Enveomics::BMset options Hash +bm_opts+
10
+ def initialize(seq1, seq2, bm_opts = {})
11
+ @seq1 = seq1
12
+ @seq2 = seq2
13
+ @bms1 = Enveomics::BMset.new(seq1, seq2, bm_opts)
14
+ @bms2 = Enveomics::BMset.new(seq2, seq1, bm_opts)
15
+ @set = nil
16
+ end
17
+
18
+ ##
19
+ # Array of Reciprocal Best Enveomics::Match objects
20
+ def set
21
+ @set ||= reciprocate!
22
+ end
23
+
24
+ ##
25
+ # Number of reciprocal best matches found
26
+ def count
27
+ set.count
28
+ end
29
+
30
+ ##
31
+ # Find reciprocal best matches and return the subset of +bms1+ that
32
+ # is reciprocal with +bms2+
33
+ def reciprocate!
34
+ bms1.each.select do |bm|
35
+ bms2[bm.sbj] && bm.qry == bms2[bm.sbj].sbj
36
+ end
37
+ end
38
+
39
+ ##
40
+ # Enumerate RBMs and yield +blk+
41
+ def each(&blk)
42
+ if block_given?
43
+ set.each { |bm| blk.call(bm) }
44
+ else
45
+ to_enum(:each)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,3 @@
1
+ require 'enveomics_rb/stats/rand'
2
+ require 'enveomics_rb/stats/sample'
3
+
@@ -0,0 +1,31 @@
1
+
2
+ module Enveomics
3
+ module Stats
4
+ class << self
5
+ # Generates a random number from the +dist+ distribution with +params+
6
+ # parameters. This is simply a wrapper to the r_* functions below.
7
+ def rand(dist = :unif, *params)
8
+ send("r_#{dist}", *params)
9
+ end
10
+
11
+ # Generates a random number from the uniform distribution between +min+
12
+ # and +max+. By default generates random numbers between 0.0 and 1.0.
13
+ def r_unif(min = 0.0, max = 1.0)
14
+ min + (max - min) * Random::rand
15
+ end
16
+
17
+ # Generates a random number from the geometric distribution with support
18
+ # {0, 1, 2, ...} and probability of success +p+.
19
+ def r_geom(p)
20
+ (Math::log(1.0 - rand) / Math::log(1.0 - p) - 1.0).ceil
21
+ end
22
+
23
+ # Generates a random number from the shifted geometric distribution with
24
+ # support {1, 2, 3, ...} and probability of success +p+.
25
+ def r_sgeom(p)
26
+ (Math::log(1.0 - rand) / Math::log(1.0 - p)).ceil
27
+ end
28
+ end
29
+ end
30
+ end
31
+
@@ -0,0 +1,152 @@
1
+
2
+ module Enveomics
3
+ module Stats
4
+ # Descriptive statistics for a given sample
5
+ class Sample
6
+ attr :x
7
+ attr :opts
8
+
9
+ # Initialize Enveomics::Stats::Sample with numeric vector +x+ and options
10
+ # Hash +opts+ supporting the keys:
11
+ # - +effective_range+: Range where values fall (by default: range of +x+)
12
+ # - +histo_bin_size+: Width of histogram widths
13
+ # (by default: 1/50th of +effective_range+)
14
+ def initialize(x, opts = {})
15
+ raise 'Cannot initialize an empty sample' if x.empty?
16
+ @x = x.map(&:to_f)
17
+ @opts = opts
18
+ end
19
+
20
+ # Size of the sample
21
+ def n
22
+ x.size
23
+ end
24
+
25
+ # Estimates the sample mean
26
+ def mean
27
+ @mean ||= x.inject(:+) / n
28
+ end
29
+
30
+ # Estimates the mean of the square of the sample
31
+ def square_mean
32
+ @square_mean ||= x.map { |i| i**2 }.inject(:+) / n
33
+ end
34
+
35
+ # Estimates the unbiased sample variance
36
+ def var
37
+ @var ||= (square_mean - mean ** 2) * n / (n - 1)
38
+ end
39
+
40
+ # Estimates the unbiased sample standard deviation
41
+ def sd
42
+ @sd ||= var ** 0.5
43
+ end
44
+
45
+ # --- Higher moments ---
46
+
47
+ # Estimate sample skewness
48
+ def skewness
49
+ return 0.0 if n == 1
50
+ cubed_dev = x.inject(0.0) { |sum, i| sum + (i - mean) ** 3 }
51
+ cubed_dev / ((n - 1) * (sd ** 3))
52
+ end
53
+
54
+ # Estimate sample excess kurtosis
55
+ def kurtosis
56
+ return 0.0 if n == 1
57
+ quart_dev = x.inject(0.0) { |sum, i| sum + (i - mean)**4 }
58
+ quart_dev / ((n - 1) * (sd**4))
59
+ end
60
+
61
+ # --- Ranges ---
62
+
63
+ # Range effectively considered
64
+ def effective_range
65
+ @opts[:effective_range] ||= [nil, nil]
66
+ @opts[:effective_range][0] ||= x.min
67
+ @opts[:effective_range][1] ||= x.max
68
+ @opts[:effective_range]
69
+ end
70
+
71
+ # Size of the effective range
72
+ def effective_range_size
73
+ effective_range[1] - effective_range[0]
74
+ end
75
+
76
+ # --- Histograms ---
77
+
78
+ # Size of each histogram bin
79
+ def histo_bin_size
80
+ @opts[:histo_bin_size] ||= effective_range_size / 50.0
81
+ end
82
+
83
+ # Calculate histogram ranges without checking for cached value
84
+ #
85
+ # Use #histo_ranges instead
86
+ def calculate_histo_ranges
87
+ rng = [[effective_range[1], effective_range[1] - histo_bin_size]]
88
+ while rng[rng.size - 1][1] > effective_range[0]
89
+ rng << [rng[rng.size - 1][1], rng[rng.size - 1][1] - histo_bin_size]
90
+ end
91
+ rng
92
+ end
93
+
94
+ # Histogram ranges as an array of two-entry arrays where the fist entry
95
+ # is the closed-ended maximum value (inclusive) of the range and the
96
+ # second entry is the open-ended minimum value (non-inclusive) of the
97
+ # range. The array is sorted from maximum to minimum
98
+ #
99
+ # Something like: +[[100.0, 99.0], [99.0, 98.0], ...]+, representing the
100
+ # ranges: {[100, 99), [99, 98), ...}
101
+ #
102
+ # The bin width is determined by #hist_bin_size
103
+ def histo_ranges
104
+ @histo_ranges ||= calculate_histo_ranges
105
+ end
106
+
107
+ # Mid-points of the histogram ranges from #histo_ranges, returns
108
+ # and array of Float
109
+ def histo_mids
110
+ @histo_mids ||= histo_ranges.map { |x| (x[0] + x[1]) / 2 }
111
+ end
112
+
113
+ # Calculate the histogram counts withouth checking cached value
114
+ #
115
+ # Use #histo_count instead
116
+ def calculate_histo_counts
117
+ counts = []
118
+ xx = x.dup
119
+ histo_ranges.each do |i|
120
+ counts << xx.size - xx.delete_if { |j| j > i[1] }.size
121
+ end
122
+ counts
123
+ end
124
+
125
+ # Histogram counts in the ranges determined by #histo_ranges
126
+ def histo_counts
127
+ @histo_counts ||= calculate_histo_counts
128
+ end
129
+
130
+ # --- Bimodality coefficients ---
131
+
132
+ # Sarle's sample bimodality coefficient b
133
+ def sarle_bimodality
134
+ (skewness**2 + 1) /
135
+ (kurtosis + (3 * ((n - 1)**2)) / ((n - 2) * (n - 3)))
136
+ end
137
+
138
+ # de Michele & Accantino (2014) B index
139
+ # DOI: 10.1371%2Fjournal.pone.0091195
140
+ def dma_bimodality
141
+ (mean - dma_mu_M).abs
142
+ end
143
+
144
+ # µ_M index proposed by Michele & Accantino (2014)
145
+ # DOI: 10.1371%2Fjournal.pone.0091195
146
+ def dma_mu_M
147
+ histo_counts.each_with_index.map { |m, k| m * histo_mids[k] }.inject(:+) / n
148
+ end
149
+ end
150
+ end
151
+ end
152
+
@@ -0,0 +1,73 @@
1
+
2
+ require 'enveomics_rb/errors'
3
+ require 'zlib'
4
+
5
+ def use(gems, mandatory = true)
6
+ gems = [gems] unless gems.is_a? Array
7
+ begin
8
+ require 'rubygems'
9
+ while !gems.empty?
10
+ require gems.shift
11
+ end
12
+ return true
13
+ rescue LoadError
14
+ abort "\nUnmet requirements, please install required gems:" +
15
+ gems.map{ |gem| "\n gem install #{gem}" }.join + "\n\n" if mandatory
16
+ return false
17
+ end
18
+ end
19
+
20
+ def say(*msg)
21
+ return if $QUIET ||= false
22
+
23
+ o = '[%s] %s' % [Time.now, msg.join('')]
24
+ $stderr.puts(o)
25
+ end
26
+
27
+ ##
28
+ # Returns an open reading file handler for the file,
29
+ # supporting .gz and '-' for STDIN
30
+ def reader(file)
31
+ file == '-' ? $stdin :
32
+ file =~ /\.gz$/ ? Zlib::GzipReader.open(file) :
33
+ File.open(file, 'r')
34
+ end
35
+
36
+ ##
37
+ # Returns an open writing file handler for the file,
38
+ # supporting .gz and '-' for STDOUT
39
+ def writer(file)
40
+ file == '-' ? $stdout :
41
+ file =~ /\.gz$/ ? Zlib::GzipWriter.open(file) :
42
+ File.open(file, 'w')
43
+ end
44
+
45
+ ##
46
+ # Run a command +cmd+ that can be a ready-to-go string or an Array to escape
47
+ #
48
+ # Supported symbol key options in Hash +opts+:
49
+ # - wait: Boolean, should I wait for the command to complete? Default: true
50
+ # - stdout: Path to redirect the standard output
51
+ # - stderr: Path to redirect the standard error
52
+ # - mergeout: Send stderr to stdout
53
+ #
54
+ # Return the process ID. If wait is true (default), check for the exit
55
+ # status and throw an Enveomics::CommandError if non-zero
56
+ def run_cmd(cmd, opts = {})
57
+ opts[:wait] = true if opts[:wait].nil?
58
+ cmd = cmd.shelljoin if cmd.is_a? Array
59
+ cmd += " > #{opts[:stdout].shellescape}" if opts[:stdout]
60
+ cmd += " 2> #{opts[:stderr].shellescape}" if opts[:stderr]
61
+ cmd += ' 2>&1' if opts[:mergeout]
62
+ pid = spawn(cmd)
63
+ return pid unless opts[:wait]
64
+
65
+ Process.wait(pid)
66
+ unless $?.success?
67
+ raise Enveomics::CommandError.new(
68
+ "Command failed with status #{$?.exitstatus}:\n#{cmd}"
69
+ )
70
+ end
71
+ pid
72
+ end
73
+