datafarming 1.3.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9356e4448e1a6aa818a5361cc04a9149d8e3d78c74ca20aaf2bb1c353e0cbd87
4
- data.tar.gz: 0e18063cfe4c92c66ed822debc6a20ef7c9a5b063e3c5769f0647d9109f98034
3
+ metadata.gz: 420d0eef5a4e09793bbbcaea21a57047eb96ffa44aa28a96455d6d695002f556
4
+ data.tar.gz: 9e6dba22e9c9734c6da2555d01d4d2ed4f7fa5f0876badacb155c6a8a8fd542f
5
5
  SHA512:
6
- metadata.gz: 82b4d9c0ae4d331b5612bbaa930aabe57fd67b4fdb8b1c3e19f7d2df3904289c6d7adb209fcccef8186ec979d3a876dcca8b093229a69570d5b7f291abb26c61
7
- data.tar.gz: b9972a61a075af31a8b59957fac9f308ddea8647ff2035d442c8285c609c01c3ef2a83c65857c11a95f568cbedf3fdee311cd6f8cc1dc7e712d94c103ba36931
6
+ metadata.gz: 1849ba07886a55ed337fd699b62075de26a2050756d2f5a6a00394efb1e4ca4098a488336a8e0071a766793ece844f518adc777e9783f061da760f949f6b58f7
7
+ data.tar.gz: 60eaa33d6d9d66f2fa12e4d7bce18f8c67ccffdce26964064ec414a2f68e11e9fff76c649b446436f25c29768848f8b53b11ea79d516727fd31469009d893253
data/LICENSE.md CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  The MIT License (MIT)
3
3
 
4
- Copyright (c) 2017-2018 Paul J. Sanchez
4
+ Copyright (c) 2017-2023 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
@@ -1,8 +1,10 @@
1
1
  ### SETUP
2
2
 
3
- This gem requires Ruby v2.3 or later to be installed on your system.
4
- - On MacOS, Ruby comes pre-installed. Note: You can use the homebrew package manager from [brew.sh](https://brew.sh) if you want to install a newer version of Ruby which is noticeably faster than the one that currently ships with MacOS.
5
- - Windows users should use one of the installer packages available from [rubyinstaller.org](http://rubyinstaller.org/). You will need to determine whether your have a 32 or 64 bit processor, and download the appropriate installer. We recommend Ruby v2.5.x or later, as it is substantially faster than prior versions. When running the installer, check the checkboxes to add the Ruby installation to your PATH and to associate `.rb` and `.rbw` files with Ruby. A terminal window will pop up during the installation—press "enter" to automatically install all of the required tools, and when prompted again (several minutes later) press "enter" again to end the installation.
3
+ This gem requires Ruby v3 or later to be installed on your system.
4
+
5
+ - On MacOS, Ruby comes pre-installed. Note: You can use the homebrew package manager from [brew.sh](https://brew.sh) if you want to install a newer version of Ruby which has numerous advantages over the one that currently ships with MacOS.
6
+
7
+ - Windows users should use one of the installer packages available from [rubyinstaller.org](http://rubyinstaller.org/). You will need to determine whether your have a 32 or 64 bit processor, and download the appropriate installer. When running the installer, check the checkboxes to add the Ruby installation to your PATH and to associate `.rb` and `.rbw` files with Ruby. A terminal window will pop up during the installation—press "enter" to automatically install all of the required tools, and when prompted again (several minutes later) press "enter" again to end the installation.
6
8
 
7
9
  You can check that Ruby is properly installed by opening `Terminal.app` (MacOS) or `CMD.EXE` (Windows) and typing `ruby --version` (note: two dashes). You should see a response telling you which version of Ruby you are running.
8
10
 
@@ -10,48 +12,12 @@ After your Ruby installation is confirmed you can install the data farming Ruby
10
12
 
11
13
  gem install datafarming
12
14
 
13
- This assumes that you have a network connection. Additional dependencies will be installed automatically. On MacOS or Linux systems, you may be required to use the `sudo` command to to authenticate the installation. In that case, type `sudo gem install datafarming` and enter your password when prompted.
15
+ This assumes that you have a network connection. Additional dependencies will be installed automatically. On MacOS or Linux systems, you may be required to use the `sudo` command to authenticate the installation. In that case, type `sudo gem install datafarming` and enter your password when prompted.
14
16
 
15
- Gem installation only needs to be done once. If you explicitly downloaded the `.gem` file rather than using a network installation, you can delete it after performing the installation.
17
+ Gem installation only needs to be done once. If you explicitly downloaded the `.gem` file rather than using a network installation, you can delete it after performing the installation. If a newer version of the gem is released, you can upgrade to it with `gem update`.
16
18
 
17
19
  ### USAGE
18
20
 
19
- Ruby is a powerful and concise object-oriented scripting language. Scripts are normally 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.
20
-
21
- All scripts in this distribution are self documenting if run with a `--help`, `-h`, or `-?` option. Quick descriptions follow.
22
-
23
- ### GENERAL FILE MANIPULATION SCRIPTS:
24
- - `blank2csv.rb` —
25
- converts whitespace-delimited files to Comma Separated Values (CSV) files
26
- - `cat.rb` —
27
- Unix “cat” program work-alike for Windows—for viewing or creating text files or concatenating multiple files, based on stdio
28
- - `convert_line_endings.rb` —
29
- converts text files (including CSV files) to your current operating system environment (e.g., DOS to Unix or Unix to DOS)
30
- - `csv2blank.rb` —
31
- converts CSV files to whitespace-delimited files
32
-
33
- ### DATA FARMING RUBY SCRIPTS:
34
- - `batchrunner.rb` —
35
- run a model interactively with replication
36
- - `rundesign_general.rb` —
37
- run control to replicate a designed experiment with a model
38
- - `pool_files.rb` —
39
- pool columns from separate files into a new file, useful for combining design file & output files or multiple output files
40
- - `scaled_fde.rb` —
41
- generate fully orthogonal quadratic designs based on discrete Fourier frequencies, with factor scaling
42
- - `scaled_rf_cubed.rb` —
43
- generate 2-level Resolution V fractional factorial designs and Central Composite Designs (CCDs), with factor scaling
44
- - `stack_nolhs.rb` —
45
- generate designs by reassigning columns from built-in NOLHs, with factor scaling, for up to 100 factors
46
- - `stripheaders.rb`, `stripheaderdups.rb` —
47
- used to remove headers from a file, or duplicate headers within a file, respectively
48
- - `augment_design.rb` —
49
- generates star points to augment a fractional factorial
50
- - `cross.rb` —
51
- creates a combinatorial design by crossing all combinations of any # of individual smaller designs
52
- - `mser.rb` —
53
- uses MSER truncation to remove initial transient effects for time-series output, reports truncated average and number of observations for each run to facilitate construction of a properly weighted confidence interval.
54
- - `mser_nolbm.rb` —
55
- uses MSER truncation to remove initial transient effects for time-series output, then calculates a 95% confidence interval on the remaining data using non-overlapping batch means.
56
- - `mser_olbm.rb` —
57
- uses MSER truncation to remove initial transient effects for time-series output, then calculates a 95% confidence interval on the remaining data using overlapping batch means.
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
+
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.
data/datafarming.gemspec CHANGED
@@ -1,14 +1,17 @@
1
1
  # -*- ruby -*-
2
- _VERSION = "1.3.0"
2
+
3
+ require 'date'
4
+ require_relative "lib/datafarming/version"
3
5
 
4
6
  Gem::Specification.new do |s|
5
7
  s.name = "datafarming"
6
- s.version = _VERSION
7
- s.date = "2018-08-17"
8
+ s.version = DataFarming::VERSION
9
+ s.date = "#{Date.today}"
8
10
  s.summary = "Useful scripts for data farming."
9
- s.homepage = "https://gitlab.nps.edu/pjsanche/datafarmingrubyscripts.git"
11
+ s.homepage = "https://bitbucket.org/paul_j_sanchez/datafarmingrubyscripts"
10
12
  s.email = "pjs@alum.mit.edu"
11
- s.description = "Ruby scripts for data farming, including pre- and post-processing, design generation and scaling, and run control."
13
+ s.description = "Ruby scripts for data farming, including pre- and" \
14
+ "post-processing, design generation and scaling, and run control."
12
15
  s.author = "Paul J Sanchez"
13
16
  s.files = `git ls-files -z`.split("\x0").reject do |f|
14
17
  f.match(%r{^(test/|features/|.gitignore)})
@@ -17,8 +20,10 @@ Gem::Specification.new do |s|
17
20
  s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
21
  s.require_paths = ["lib"]
19
22
  s.add_runtime_dependency 'fwt', '~> 1.2'
20
- s.add_runtime_dependency 'colorize', '~> 0.8'
23
+ s.add_runtime_dependency 'colorize', '~> 1'
21
24
  s.add_runtime_dependency 'quickstats', '~> 2'
22
- s.required_ruby_version = '>= 2.3'
25
+ s.add_runtime_dependency 'optparse', '~> 0.4'
26
+ s.add_runtime_dependency 'yaml', '~> 0.3'
27
+ s.required_ruby_version = '>= 2.6'
23
28
  s.license = 'MIT'
24
29
  end
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lines = [
4
+ '',
5
+ '#### COMMAND-LINE TOOLS OVERVIEW',
6
+ '',
7
+ ' Ruby is a powerful and concise object-oriented scripting language.',
8
+ ' Scripts are normally run from a command-line or terminal environment by',
9
+ ' typing the ruby command followed by a script name, often followed by one',
10
+ ' or more command-line arguments. However, scripts installed as gems do',
11
+ ' not need the ruby command to be typed explicitly. For example, typing',
12
+ ' \'stripheaderdups.rb my_file.txt\' will invoke the \'stripheaderdups.rb\'',
13
+ ' script and apply it to file \'my_file.txt\' in your current working directory.',
14
+ ' Note that on Windows the \'.rb\' suffix is not needed to run your scripts,',
15
+ ' e.g.,\'stripheaderdups my_file.txt\' will suffice.',
16
+ '',
17
+ ' All scripts in this distribution are self documenting if run with a \'--help\'',
18
+ ' or \'-h\' option. Quick descriptions follow.',
19
+ '',
20
+ '#### DATA MANIPULATION',
21
+ '',
22
+ ' • blank2csv.rb —',
23
+ ' convert whitespace-delimited files to Comma Separated Values (CSV) files',
24
+ ' • cat.rb —',
25
+ ' Unix cat program work-alike for Windows—for viewing or creating text files',
26
+ ' or concatenating multiple files, based on stdio',
27
+ ' • convert_line_endings.rb —',
28
+ ' convert text files (including CSV files) to your current operating system',
29
+ ' environment (e.g., DOS to Unix or Unix to DOS)',
30
+ ' • csv2blank.rb —',
31
+ ' convert CSV files to whitespace-delimited files',
32
+ ' • pool_files.rb —',
33
+ ' pool columns from separate files into a new file, useful for combining',
34
+ ' design file & output files or multiple output files',
35
+ ' • stripheaders.rb , stripheaderdups.rb —',
36
+ ' remove column headers from a file, or duplicate headers within a file,',
37
+ ' respectively',
38
+ '',
39
+ '#### DESIGN GENERATORS',
40
+ '',
41
+ ' • cross.rb —',
42
+ ' create a combinatorial design by crossing all combinations of any # of',
43
+ ' individual smaller designs',
44
+ ' • generate_design.rb —',
45
+ ' allowable design types are',
46
+ ' - resv : Resolution V fractional factorial at 2 levels per factor;',
47
+ ' - ccd : Central Composite Designs are resv augmented with "star',
48
+ ' points", yielding 3 levels per factor;',
49
+ ' - rotatable : CCDs with corner points scaled so that all non-central',
50
+ ' design points are equidistant from the center;',
51
+ ' - nolh : Nearly Orthogonal Latin Hypercubes are highly efficient',
52
+ ' space filling designs for main effects models;',
53
+ ' - fbd : Frequency-Based Designs are space filling and orthogonal',
54
+ ' for full 2nd order models, offering zero correlation',
55
+ ' between main effects, quadratics, and two-way interactions;',
56
+ ' - fbs : Frequency-Based Screening Designs are similar to FBDs, but',
57
+ ' allow partial correlation amongst the interactions which',
58
+ ' significantly reduces the number of design points;',
59
+ ' all of which generate designs with stacking, replication, and individualized',
60
+ ' factor scaling',
61
+ '',
62
+ '#### RUN CONTROL',
63
+ '',
64
+ ' • batchrunner.rb —',
65
+ ' run a model interactively with replication',
66
+ ' • rundesign_general.rb —',
67
+ ' run control to replicate a designed experiment applied to a model',
68
+ '',
69
+ '#### CONFIDENCE INTERVALS FOR TIME SERIES',
70
+ '',
71
+ ' • mser.rb —',
72
+ ' use MSER truncation to remove initial transient effects for time-series',
73
+ ' output, reporting truncated average and number of observations for each run',
74
+ ' to facilitate construction of a properly weighted confidence interval.',
75
+ ' • mser_nolbm.rb —',
76
+ ' use MSER truncation to remove initial transient effects for time-series',
77
+ ' output, then calculate a 95% confidence interval on the remaining data using',
78
+ ' non-overlapping batch means.',
79
+ ' • mser_olbm.rb —',
80
+ ' use MSER truncation to remove initial transient effects for time-series',
81
+ ' output, then calculate a 95% confidence interval on the remaining data using',
82
+ ' overlapping batch means.',
83
+ ''
84
+ ]
85
+
86
+ lines.each { |line| puts line }
@@ -0,0 +1,304 @@
1
+ #!/usr/bin/env ruby -w
2
+ # frozen_string_literal: true
3
+
4
+ # require 'colorize'
5
+ # String.disable_colorization false
6
+ # require 'datafarming/error_handling'
7
+
8
+ require 'optparse'
9
+ require 'yaml'
10
+
11
+ require 'datafarming/scale_design'
12
+
13
+ DESIGN_TYPES = %i[resv ccd rotatable nolh fbd fbs].freeze
14
+ REQUIRED_SPECS = %i[min max].freeze
15
+ ELECTIVE_SPECS = %i[id decimals].freeze
16
+ LEVELS = [17, 33, 65, 129, 257, 512].freeze
17
+ TWO_PI = 2.0 * Math::PI
18
+
19
+ HELP_MSG = [
20
+ 'Generate, scale, stack, and replicate a variety of statistical designs',
21
+ "of experiments. You are required to specify the desired '--design TYPE'.",
22
+ '',
23
+ "Usage: #{File.basename($PROGRAM_NAME)} [options] [optional factor specs]"
24
+ ].freeze
25
+
26
+ EXTENDED_HELP = [
27
+ "====================\n",
28
+ "Using the '-k' option will specify the number of factors for a standardized",
29
+ 'design, in which all factors are normalized to the range of [-1, 1].',
30
+ '',
31
+ 'Optional factor specifications can be used to create a design which scales',
32
+ 'the factors to individualized ranges. Specifications are then required for',
33
+ 'all factors, and the number of factors will be derived from the number of',
34
+ "specifications provided (thus overriding the '-k' option). Each factor's",
35
+ 'specification is separated from the others by whitespace.',
36
+ '',
37
+ 'Factor specifications contain two to four comma-separated components, and',
38
+ 'cannot include any whitespace. The format is:',
39
+ "\n id:STRING,min:NUMBER,max:NUMBER,decimals:INTEGER\n",
40
+ "Components can be arranged in any order. The components 'min' and 'max'",
41
+ 'are required, and define the range of experimentation for this factor.',
42
+ "Components 'id' and 'decimals' are optional. If 'id' is omitted, id",
43
+ "values will be generated for use with the '--headers' option. If 'decimals'",
44
+ 'is omitted, the factors values will be displayed with default precision.',
45
+ ' '
46
+ ].freeze
47
+
48
+ EXAMPLES = [
49
+ "\n====================\n",
50
+ 'EXAMPLE 1:',
51
+ "\n #{File.basename($PROGRAM_NAME)} --design fbd -k 3 --no-headers",
52
+ "\n Generate a standardized frequency-based design with three factors,",
53
+ ' without headers. All ranges are -1..1 and output has default precision.',
54
+ '',
55
+ 'EXAMPLE 2:',
56
+ "\n #{File.basename($PROGRAM_NAME)} -d nolh id:Fred,min:-10,max:10 decimals:5,max:100,min:0",
57
+ "\n Generate a NOLH for two factors. The first factor will have a column",
58
+ " header of 'Fred', and values will be scaled in the range -10..10. The",
59
+ " second column will have an automatically generated header 'X_002', and",
60
+ ' values will be in the range 0..100 with five decimal places of precision.',
61
+ " As described in '--verbose-help', factor specifications must be separated",
62
+ " by whitespace.",
63
+ '',
64
+ 'EXAMPLE 3:',
65
+ "\n #{File.basename($PROGRAM_NAME)} -d nolh -k 5 --output_yaml > my_template.yaml",
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",
68
+ ' of your choosing.) The resulting YAML file has an intuitive human',
69
+ " readable format. It can be edited using your favorite programmer's",
70
+ ' text editor (NOTE: word processors will not work!). After saving your',
71
+ ' revisions, you can read it back in to generate the actual design using',
72
+ " the '--input_yaml' option:\n",
73
+ " #{File.basename($PROGRAM_NAME)} --input_yaml < my_template.yaml",
74
+ ' '
75
+ ].freeze
76
+
77
+ def prog_help(opts)
78
+ puts opts
79
+ exit
80
+ end
81
+
82
+ def verbose(opts)
83
+ puts opts
84
+ puts EXTENDED_HELP.join("\n")
85
+ exit
86
+ end
87
+
88
+ def examples(opts)
89
+ puts opts
90
+ puts EXAMPLES.join("\n")
91
+ exit
92
+ end
93
+
94
+ def resv(options)
95
+ 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]
99
+ end
100
+
101
+ def ccd(options)
102
+ 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])
106
+ end
107
+
108
+ def rotatable(options)
109
+ 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|
114
+ line.map! { |value| value * inv_len }
115
+ end + RESV.star_pts(options[:num_factors])
116
+ end
117
+
118
+ def nolh(options)
119
+ require 'datafarming/nolh_designs'
120
+ minimal_size =
121
+ case options[:num_factors]
122
+ when 1..7
123
+ 17
124
+ when 8..11
125
+ 33
126
+ when 12..16
127
+ 65
128
+ when 17..22
129
+ 129
130
+ when 23..29
131
+ 257
132
+ when 30..100
133
+ 512
134
+ else
135
+ raise "invalid number of factors: #{options[:num_factors]}"
136
+ end
137
+
138
+ 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
+ NOLH::DESIGN_TABLE[options[:levels]]
145
+ end
146
+
147
+ 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]]
154
+ freqs = ds.freqs[design_num]
155
+ Array.new(freqs.size) do |row|
156
+ omega = freqs[row]
157
+ Array.new(ds.nyq) do |i|
158
+ f = ((i * omega) % ds.nyq).to_f / ds.nyq
159
+ Math.sin(f * TWO_PI)
160
+ end
161
+ end.transpose
162
+ end
163
+
164
+ def fbd(options)
165
+ require 'datafarming/freq_sets'
166
+ freqs2cols(options, FBD::DESIGN_SETS)
167
+ end
168
+
169
+ def fbs(options)
170
+ require 'datafarming/screen_freq_sets'
171
+ freqs2cols(options, FBS::DESIGN_SETS)
172
+ end
173
+
174
+ def validate_opts(options)
175
+ required_options = [:design]
176
+ missing = required_options - options.keys
177
+ raise "Missing required options: #{missing}" unless missing.empty?
178
+
179
+ options[:design] = options[:design].to_sym
180
+ end
181
+
182
+ def validate(spec)
183
+ unknown = spec.keys - REQUIRED_SPECS - ELECTIVE_SPECS
184
+ raise "Unknown factor spec(s): #{unknown}" unless unknown.empty?
185
+
186
+ missing = REQUIRED_SPECS - spec.keys
187
+ raise "Factor spec #{spec} missing #{missing}" unless missing.empty?
188
+ end
189
+
190
+ def optional_specs(factor_spec, i)
191
+ YAML.safe_load("{#{factor_spec}}".gsub(':', ': '),
192
+ permitted_classes: [Symbol]).tap do |spec|
193
+ spec.transform_keys!(&:to_sym)
194
+ spec[:decimals] ||= nil
195
+ spec[:id] ||= "X_#{format('%03d', (i + 1))}"
196
+ validate(spec)
197
+ end
198
+ end
199
+
200
+ def parse_specs(options, remainder)
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 }
207
+ 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
+ end
213
+ end
214
+
215
+ def parse_args
216
+ options = {
217
+ headers: true,
218
+ replicate: 1,
219
+ stack: 1,
220
+ center: false
221
+ }
222
+
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)
252
+ validate_opts options
253
+ [options, specs]
254
+ end
255
+
256
+ def stack(design, num_stacks, centered)
257
+ return design if num_stacks < 2
258
+ raise "Stacking #{num_stacks} times exceeds number of design columns" if num_stacks > design[0].length
259
+
260
+ num_stacks -= 1
261
+ result = design.transpose
262
+ if num_stacks > 0
263
+ design_t = if centered
264
+ design.transpose
265
+ else
266
+ design.delete_if {|row| row.all? &:zero? }.transpose
267
+ end
268
+ num_stacks.times do
269
+ design_t.rotate!
270
+ result.map!.with_index { |line, i| line + design_t[i] }
271
+ end
272
+ end
273
+ result.transpose
274
+ end
275
+
276
+
277
+ ARGV << '--help' if ARGV.empty?
278
+
279
+ options, specs = parse_args
280
+
281
+ if options[:output_yaml]
282
+ options.delete :output_yaml
283
+ puts [options, specs].to_yaml
284
+ else
285
+ base_design = method(options[:design]).call(options)
286
+ separator = ','
287
+ puts Array.new(options[:num_factors]) { |i| specs[i][:id] }.join(separator) if options[:headers]
288
+
289
+ # stack
290
+ design = stack(base_design, options[:stack], options[:center])
291
+ .transpose.first(options[:num_factors]).transpose
292
+
293
+ # scale
294
+ scaler = specs.map do |spec|
295
+ Scaler.new(min: spec[:min], max: spec[:max], decimals: spec[:decimals])
296
+ end
297
+
298
+ # replicate
299
+ options[:replicate].times do
300
+ design.each do |line|
301
+ puts line.map.with_index { |v, i| scaler[i].scale(v) }.join(separator)
302
+ end
303
+ end
304
+ end