datafarming 1.4.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: 760e46472df38776a825b576376f9ce407398647fc3bd5fd484c8787caa505bc
4
- data.tar.gz: 57a92791102a99a4d8780c4f2584a4cb21ca6a4ae60545a06f1a1675d5633e82
3
+ metadata.gz: 420d0eef5a4e09793bbbcaea21a57047eb96ffa44aa28a96455d6d695002f556
4
+ data.tar.gz: 9e6dba22e9c9734c6da2555d01d4d2ed4f7fa5f0876badacb155c6a8a8fd542f
5
5
  SHA512:
6
- metadata.gz: 76cd410243bdc003a488c8dae6cc5d15fd6723d5525ad28398be5febf2a34109e50e40359e2bc1782d369c2dabd89bb5a33b66769a6872c841e5e2c32b9830b9
7
- data.tar.gz: 70d896019b4b1e6d439ec1977ac7fd1ed6969bdb2d6b368f14a993d37dc187e88d1537a0d2c48b77086808b57b6445b558ee243174b619381a82c94d7e0a3e68
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-2021 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.5 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,12 +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
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.
20
22
 
21
- You can run the command `datafarming.rb` at any point after installing to see descriptions of the various data farming utilities included in this distribution.
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.4.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 = "2021-01-19"
8
+ s.version = DataFarming::VERSION
9
+ s.date = "#{Date.today}"
8
10
  s.summary = "Useful scripts for data farming."
9
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.5'
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
data/exe/datafarming.rb CHANGED
@@ -2,22 +2,22 @@
2
2
 
3
3
  lines = [
4
4
  '',
5
- ' #### COMMAND-LINE TOOLS',
5
+ '#### COMMAND-LINE TOOLS OVERVIEW',
6
6
  '',
7
- ' Ruby is a powerful and concise object-oriented scripting language. Scripts',
8
- ' are normally run from a command-line or terminal environment by typing the',
9
- ' ruby command followed by a script name, often followed by one or more',
10
- ' command-line arguments. However, scripts installed as gems do not need the',
11
- ' ruby command to be typed explicitly. For example, typing',
12
- ' stripheaderdups.rb my_file.txt will invoke the stripheaderdups.rb script',
13
- ' and apply it to file my_file.txt in your current working directory. Note',
14
- ' that on Windows the .rb suffix is not needed to run your scripts, e.g.,',
15
- ' stripheaderdups my_file.txt will suffice.',
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
16
  '',
17
- ' All scripts in this distribution are self documenting if run with a --help,',
18
- ' -h, or -? option. Quick descriptions follow.',
17
+ ' All scripts in this distribution are self documenting if run with a \'--help\'',
18
+ ' or \'-h\' option. Quick descriptions follow.',
19
19
  '',
20
- ' #### DATA MANIPULATION',
20
+ '#### DATA MANIPULATION',
21
21
  '',
22
22
  ' • blank2csv.rb —',
23
23
  ' convert whitespace-delimited files to Comma Separated Values (CSV) files',
@@ -36,31 +36,37 @@ lines = [
36
36
  ' remove column headers from a file, or duplicate headers within a file,',
37
37
  ' respectively',
38
38
  '',
39
- ' #### DESIGN GENERATORS',
39
+ '#### DESIGN GENERATORS',
40
40
  '',
41
- ' • augment_design.rb —',
42
- ' generate star points to augment an already existing factorial design',
43
41
  ' • cross.rb —',
44
42
  ' create a combinatorial design by crossing all combinations of any # of',
45
43
  ' individual smaller designs',
46
- ' • scaled_fde.rb —',
47
- ' generate fully orthogonal quadratic designs based on discrete Fourier',
48
- ' frequencies, with factor scaling',
49
- ' • scaled_rf_cubed.rb —',
50
- ' generate 2-level Resolution V fractional factorial designs and Central',
51
- ' Composite Designs (CCDs), with factor scaling',
52
- ' • stack_nolhs.rb —',
53
- ' generate designs by reassigning columns from pre-tabled NOLHs, with factor',
54
- ' scaling, for up to 100 factors',
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',
55
61
  '',
56
- ' #### RUN CONTROL',
62
+ '#### RUN CONTROL',
57
63
  '',
58
64
  ' • batchrunner.rb —',
59
65
  ' run a model interactively with replication',
60
66
  ' • rundesign_general.rb —',
61
67
  ' run control to replicate a designed experiment applied to a model',
62
68
  '',
63
- ' #### CONFIDENCE INTERVALS FOR TIME SERIES',
69
+ '#### CONFIDENCE INTERVALS FOR TIME SERIES',
64
70
  '',
65
71
  ' • mser.rb —',
66
72
  ' use MSER truncation to remove initial transient effects for time-series',
@@ -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