datafarming 2.1.0 → 2.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 420d0eef5a4e09793bbbcaea21a57047eb96ffa44aa28a96455d6d695002f556
4
- data.tar.gz: 9e6dba22e9c9734c6da2555d01d4d2ed4f7fa5f0876badacb155c6a8a8fd542f
3
+ metadata.gz: 8d7589dc50c37a95f61fcd663202a51b54f5dcab18b40f06fd82a7ca6335a0ae
4
+ data.tar.gz: be600740132b28b59ee3b0b11b45dffaa630023b2babc884bb732efdb0757d49
5
5
  SHA512:
6
- metadata.gz: 1849ba07886a55ed337fd699b62075de26a2050756d2f5a6a00394efb1e4ca4098a488336a8e0071a766793ece844f518adc777e9783f061da760f949f6b58f7
7
- data.tar.gz: 60eaa33d6d9d66f2fa12e4d7bce18f8c67ccffdce26964064ec414a2f68e11e9fff76c649b446436f25c29768848f8b53b11ea79d516727fd31469009d893253
6
+ metadata.gz: 7d734fadf28fdef038fbb3f4cc9ee0b0d836d0c388345a005ecd3d9609396b411e527e7f408832a69dade7414ae01c1d016fc16a475be9b6a91045390c88fa44
7
+ data.tar.gz: 51122d843649187c2e335244b532d1df783fb100c5f0588f9965e604d1570a4cd036b44694590d1368087e13f2eff4649261803d23943512ca9c8032a1afae7b
data/LICENSE.md CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  The MIT License (MIT)
3
3
 
4
- Copyright (c) 2017-2023 Paul J. Sanchez
4
+ Copyright (c) 2017-2024 Paul J. Sanchez
5
5
 
6
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -21,3 +21,5 @@ Gem installation only needs to be done once. If you explicitly downloaded the `
21
21
  Ruby is a powerful and concise object-oriented scripting language. Normally, Ruby scripts are run from a command-line or terminal environment by typing the `ruby` command followed by a script name, often followed by one or more command-line arguments. However, scripts installed as gems do not need the `ruby` command to be typed explicitly. For example, typing `stripheaderdups.rb my_file.txt` will invoke the `stripheaderdups.rb` script and apply it to file `my_file.txt` in your current working directory. Note that on Windows the `.rb` suffix is not needed to run your scripts, e.g., `stripheaderdups my_file.txt` will suffice.
22
22
 
23
23
  All scripts in this distribution are self documenting if run with a `--help`, or `-h` option. You can run the command `datafarming.rb` at any point after installing this gem to see descriptions of the various data farming utilities included in thie distribution.
24
+
25
+ After installing the gem and making sure that gems are on your PATH, run the command `datafarming.rb` for a summary/overview of the various data farming tools.
@@ -62,15 +62,15 @@ EXAMPLES = [
62
62
  " by whitespace.",
63
63
  '',
64
64
  'EXAMPLE 3:',
65
- "\n #{File.basename($PROGRAM_NAME)} -d nolh -k 5 --output_yaml > my_template.yaml",
65
+ "\n #{File.basename($PROGRAM_NAME)} -d nolh -k 5 --output_yaml > my_template.yml",
66
66
  "\n Generate a YAML template for a 5-factor NOLH design, and redirect it",
67
- " to the file 'my_template.yaml'. (The output file can be given any name",
67
+ " to the file 'my_template.yml'. (The output file can be given any name",
68
68
  ' of your choosing.) The resulting YAML file has an intuitive human',
69
69
  " readable format. It can be edited using your favorite programmer's",
70
70
  ' text editor (NOTE: word processors will not work!). After saving your',
71
71
  ' revisions, you can read it back in to generate the actual design using',
72
72
  " the '--input_yaml' option:\n",
73
- " #{File.basename($PROGRAM_NAME)} --input_yaml < my_template.yaml",
73
+ " #{File.basename($PROGRAM_NAME)} --input_yaml my_template.yml",
74
74
  ' '
75
75
  ].freeze
76
76
 
@@ -91,34 +91,40 @@ def examples(opts)
91
91
  exit
92
92
  end
93
93
 
94
+ def fatal_err(msg)
95
+ STDERR.puts msg
96
+ exit 1
97
+ end
98
+
94
99
  def resv(options)
95
100
  require 'datafarming/res_v_seqs'
96
- raise 'Too many factors for ResV designs' if options[:num_factors] > RESV::INDEX.size
97
-
98
- RESV.make_design options[:num_factors]
101
+ num_factors = options[:specs].size
102
+ fatal_err("Too many factors for ResV designs") if num_factors > RESV::INDEX.size
103
+ RESV.make_design num_factors
99
104
  end
100
105
 
101
106
  def ccd(options)
102
107
  require 'datafarming/res_v_seqs'
103
- raise 'Too many factors for CCD designs' if options[:num_factors] > RESV::INDEX.size
104
-
105
- RESV.make_design(options[:num_factors]) + RESV.star_pts(options[:num_factors])
108
+ num_factors = options[:specs].size
109
+ fatal_err("Too many factors for CCD designs") if num_factors > RESV::INDEX.size
110
+ RESV.make_design(num_factors) + RESV.star_pts(num_factors)
106
111
  end
107
112
 
108
113
  def rotatable(options)
109
114
  require 'datafarming/res_v_seqs'
110
- raise 'Too many factors for Rotatable designs' if options[:num_factors] > RESV::INDEX.size
111
-
112
- inv_len = 1.0 / Math.sqrt(options[:num_factors])
113
- RESV.make_design(options[:num_factors]).each do |line|
115
+ num_factors = options[:specs].size
116
+ fatal_err("Too many factors for Rotatable designs") if num_factors > RESV::INDEX.size
117
+ inv_len = 1.0 / Math.sqrt(num_factors)
118
+ RESV.make_design(num_factors).each do |line|
114
119
  line.map! { |value| value * inv_len }
115
- end + RESV.star_pts(options[:num_factors])
120
+ end + RESV.star_pts(num_factors)
116
121
  end
117
122
 
118
123
  def nolh(options)
119
124
  require 'datafarming/nolh_designs'
125
+ num_factors = options[:specs].size
120
126
  minimal_size =
121
- case options[:num_factors]
127
+ case num_factors
122
128
  when 1..7
123
129
  17
124
130
  when 8..11
@@ -132,32 +138,28 @@ def nolh(options)
132
138
  when 30..100
133
139
  512
134
140
  else
135
- raise "invalid number of factors: #{options[:num_factors]}"
141
+ fatal_err "invalid number of factors: #{num_factors}"
136
142
  end
137
-
138
143
  options[:levels] ||= minimal_size
139
- if options[:levels] < minimal_size
140
- raise "Latin hypercube with #{options[:levels]} levels is" \
141
- "too small for #{options[:num_factors]} factors."
142
- end
143
-
144
+ fatal_err("Latin hypercube with #{options[:levels]} levels is " \
145
+ "too small for #{num_factors} factors.") if options[:levels] < minimal_size
144
146
  NOLH::DESIGN_TABLE[options[:levels]]
145
147
  end
146
148
 
147
149
  def freqs2cols(options, design_set)
148
- raise "No design (yet) for #{options[:num_factors]} factors" unless
149
- design_set.key? options[:num_factors]
150
-
151
- design_num = -1 # the last one has largest min frequency
152
-
153
- ds = design_set[options[:num_factors]]
150
+ num_factors = options[:specs].size
151
+ fatal_err("No design (yet) for #{num_factors} factors") unless design_set.key? num_factors
152
+ design_num = 0 # most have only one design after removing isomorphisms
153
+ ds = design_set[num_factors]
154
154
  freqs = ds.freqs[design_num]
155
155
  Array.new(freqs.size) do |row|
156
156
  omega = freqs[row]
157
- Array.new(ds.nyq) do |i|
157
+ v = Array.new(ds.nyq) do |i|
158
158
  f = ((i * omega) % ds.nyq).to_f / ds.nyq
159
159
  Math.sin(f * TWO_PI)
160
160
  end
161
+ scale_factor = v.max
162
+ v.map { |x| (x / scale_factor).round(15) }
161
163
  end.transpose
162
164
  end
163
165
 
@@ -174,17 +176,15 @@ end
174
176
  def validate_opts(options)
175
177
  required_options = [:design]
176
178
  missing = required_options - options.keys
177
- raise "Missing required options: #{missing}" unless missing.empty?
178
-
179
+ fatal_err("Missing required options: --#{missing.join(', --')}") unless missing.empty?
179
180
  options[:design] = options[:design].to_sym
180
181
  end
181
182
 
182
183
  def validate(spec)
183
184
  unknown = spec.keys - REQUIRED_SPECS - ELECTIVE_SPECS
184
- raise "Unknown factor spec(s): #{unknown}" unless unknown.empty?
185
-
185
+ fatal_err("Unknown factor spec(s): #{unknown}") unless unknown.empty?
186
186
  missing = REQUIRED_SPECS - spec.keys
187
- raise "Factor spec #{spec} missing #{missing}" unless missing.empty?
187
+ fatal_err("Factor spec #{spec} missing #{missing}") unless missing.empty?
188
188
  end
189
189
 
190
190
  def optional_specs(factor_spec, i)
@@ -199,64 +199,89 @@ end
199
199
 
200
200
  def parse_specs(options, remainder)
201
201
  if remainder.size.positive?
202
- options[:num_factors] = remainder.size
203
- remainder.map.with_index { |factor_spec, i| optional_specs(factor_spec, i) }
204
- elsif options.key? :num_factors
205
- Array.new(options[:num_factors]) do |i|
206
- { "id": "X_#{format('%03d', (i + 1))}", "min": -1, "max": 1, "decimals": nil }
202
+ remainder.each.with_index do |factor_spec, i|
203
+ options[:specs] << optional_specs(factor_spec, i)
207
204
  end
208
- else
209
- y_options, factor_specs = YAML.safe_load($stdin, permitted_classes: [Symbol])
210
- y_options.each_key { |key| options[key] ||= y_options[key] }
211
- factor_specs.each { |spec| validate(spec) }
212
205
  end
213
206
  end
214
207
 
215
208
  def parse_args
216
- options = {
209
+ default_options = {
217
210
  headers: true,
218
211
  replicate: 1,
219
212
  stack: 1,
220
- center: false
213
+ center: false,
214
+ specs: []
221
215
  }
222
216
 
223
- remainder = OptionParser.new do |opts|
224
- opts.banner = HELP_MSG.join("\n")
225
- opts.on('-h', '--help', 'Prints help') { prog_help(opts) }
226
- opts.on('-v', '--verbose-help', 'Print more verbose help') { verbose(opts) }
227
- opts.on('-x', '--examples', 'Show some examples') { examples(opts) }
228
- opts.on('-d', '--design=TYPE', DESIGN_TYPES,
229
- 'REQUIRED: Type of design, one of:',
230
- " #{DESIGN_TYPES.join ' '}")
231
- opts.on('-k', '--num_factors=QTY', /[1-9][0-9]*/, Integer,
232
- 'Number of factors',
233
- 'Ignored if optional factor specs provided')
234
- opts.on('-r', '--replicate=QTY', /[1-9][0-9]*/, Integer,
235
- 'Replicate QTY times', '(default: 1)')
236
- opts.on('-s', '--stack=QTY', /[1-9][0-9]*/, Integer,
237
- 'Stack QTY times', '(default: 1)')
238
- opts.on('--[no-]center', 'NOTE: Applies only to stacked designs',
239
- 'Stacking includes center pt','(default: no-center)')
240
- opts.on('--[no-]headers', 'Output has column headers', '(default: headers)')
241
- opts.on('-l', '--levels=QTY', Regexp.new("(#{LEVELS.join(')|(')})"),
242
- Integer, 'NOTE: Applies only to NOLH designs',
243
- 'Number of levels in the base NOLH:',
244
- " #{LEVELS.join ' '}")
245
- opts.on('-o', '--output_yaml', 'Write design YAML to STDOUT')
246
- opts.on('-i', '--input_yaml', 'Read design YAML from STDIN')
247
- end.parse!(into: options)
248
-
249
- raise 'Optional factor specs ignored when using YAML input' if options[:input_yaml] && remainder.size.positive?
250
-
251
- specs = parse_specs(options, remainder)
217
+ yaml_input = false
218
+ if ARGV.size > 2 && ARGV.any? { |elt| elt.eql?("-i") || elt.eql?("--input_yaml") }
219
+ STDERR.puts 'NOTE: All other arguments ignored when using YAML input'
220
+ end
221
+
222
+ options = default_options
223
+
224
+ begin
225
+ remainder = OptionParser.new do |opts|
226
+ opts.banner = HELP_MSG.join("\n")
227
+ opts.on('-h', '--help', 'Prints help') { prog_help(opts) }
228
+ opts.on('-v', '--verbose-help', 'Print more verbose help') { verbose(opts) }
229
+ opts.on('-x', '--examples', 'Show some examples') { examples(opts) }
230
+ opts.on('-d', '--design TYPE', DESIGN_TYPES,
231
+ 'REQUIRED: Type of design, one of:',
232
+ " #{DESIGN_TYPES.join ' '}")
233
+ opts.on('-k', '--num_factors QTY', /[1-9][0-9]*/, Integer,
234
+ 'Number of factors',
235
+ 'Ignored if optional factor specs provided') do |k|
236
+ options[:specs] = Array.new(k) do |i|
237
+ {id: "X_#{format('%03d', (i + 1))}", :min => -1, :max => 1 }
238
+ end
239
+ k
240
+ end
241
+ opts.on('-r', '--replicate QTY', /[1-9][0-9]*/, Integer,
242
+ 'Replicate QTY times', '(default: 1)')
243
+ opts.on('-s', '--stack QTY', /[1-9][0-9]*/, Integer,
244
+ 'Stack QTY times', '(default: 1)')
245
+ opts.on('--[no-]center', 'NOTE: Applies only to stacked designs',
246
+ 'Stacking includes center pt','(default: no-center)')
247
+ opts.on('--[no-]headers', 'Output has column headers', '(default: headers)')
248
+ opts.on('-l', '--levels QTY', Regexp.new("(#{LEVELS.join(')|(')})"),
249
+ Integer, 'NOTE: Applies only to NOLH designs',
250
+ 'Number of levels in the base NOLH:',
251
+ " #{LEVELS.join ' '}")
252
+ opts.on('-o', '--output_yaml', 'Write design YAML to STDOUT')
253
+ opts.on("-i PATH", "--input_yaml", 'Read design YAML from PATH',
254
+ 'Mutually exclusive with all other options, prints',
255
+ 'warning to STDERR if other options are passed') do |path|
256
+ ARGV.clear
257
+ options = {specs: []}
258
+ yaml_input = true
259
+ YAML::safe_load_file(path, permitted_classes: [Symbol]).each do |k, v|
260
+ options.merge!({k.to_sym => v})
261
+ end
262
+ end
263
+ end.parse!(into: options)
264
+ rescue OptionParser::ParseError => error
265
+ fatal_err("#{error}\n(-h or --help will show valid options)")
266
+ end
267
+
268
+ fatal_err("Levels option only valid for NOLH designs") if options[:levels] && options[:design] != :nolh
269
+
270
+ remainder = [] if yaml_input
271
+ if remainder.size.positive? && options[:specs].size.positive?
272
+ STDERR.puts 'NOTE: Num_factors ignored when using optional factor specs'
273
+ options[:specs] = []
274
+ end
275
+ options.delete(:num_factors) if options.include?(:num_factors)
276
+
277
+ parse_specs(options, remainder)
252
278
  validate_opts options
253
- [options, specs]
279
+ options
254
280
  end
255
281
 
256
282
  def stack(design, num_stacks, centered)
257
283
  return design if num_stacks < 2
258
- raise "Stacking #{num_stacks} times exceeds number of design columns" if num_stacks > design[0].length
259
-
284
+ fatal_err("Stacking #{num_stacks} times exceeds number of design columns") if num_stacks > design[0].length
260
285
  num_stacks -= 1
261
286
  result = design.transpose
262
287
  if num_stacks > 0
@@ -273,25 +298,24 @@ def stack(design, num_stacks, centered)
273
298
  result.transpose
274
299
  end
275
300
 
276
-
277
301
  ARGV << '--help' if ARGV.empty?
278
302
 
279
- options, specs = parse_args
303
+ options = parse_args
280
304
 
281
305
  if options[:output_yaml]
282
306
  options.delete :output_yaml
283
- puts [options, specs].to_yaml
307
+ puts options.to_yaml
284
308
  else
285
309
  base_design = method(options[:design]).call(options)
286
310
  separator = ','
287
- puts Array.new(options[:num_factors]) { |i| specs[i][:id] }.join(separator) if options[:headers]
311
+ puts Array.new(options[:specs].size) { |i| options[:specs][i][:id] }.join(separator) if options[:headers]
288
312
 
289
313
  # stack
290
314
  design = stack(base_design, options[:stack], options[:center])
291
- .transpose.first(options[:num_factors]).transpose
315
+ .transpose.first(options[:specs].size).transpose
292
316
 
293
317
  # scale
294
- scaler = specs.map do |spec|
318
+ scaler = options[:specs].map do |spec|
295
319
  Scaler.new(min: spec[:min], max: spec[:max], decimals: spec[:decimals])
296
320
  end
297
321