datafarming 1.4.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.md +1 -1
- data/README.md +10 -6
- data/datafarming.gemspec +11 -6
- data/exe/datafarming.rb +33 -27
- data/exe/generate_design.rb +306 -0
- data/lib/datafarming/freq_sets.rb +52 -243
- data/lib/datafarming/nolh_designs.rb +19105 -4483
- data/lib/datafarming/res_v_seqs.rb +57 -0
- data/lib/datafarming/scale_design.rb +29 -0
- data/lib/datafarming/screen_freq_sets.rb +143 -0
- data/lib/datafarming/version.rb +5 -0
- data/lib/datafarming.rb +8 -0
- metadata +42 -18
- data/DESCRIPTIONS.html +0 -1107
- data/DESCRIPTIONS.md +0 -45
- data/exe/augment_design.rb +0 -66
- data/exe/scaled_fde.rb +0 -169
- data/exe/scaled_rf_cubed.rb +0 -173
- data/exe/stack_nolhs.rb +0 -174
- data/lib/datafarming/factorial_generator.rb +0 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a222e42c364401edf4bb67e403cc9169ca3935c74d4e21d428336ad535349218
|
4
|
+
data.tar.gz: 929f66d57cb5997dbf1089f7436af8753a9cdf0f26e231d6e31b29c370f0a151
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 02b975dbe89ee318131bb13439926c0fc4b1a99a2db1c6e97bfce34f7caf3235cb97fb9b04ac37a89934273bb83b04b522d5e7fc8ece8ed951a8e80422062f7c
|
7
|
+
data.tar.gz: f9d61f4ec3b96a9cb8d51d826f05fdd8a44e191fd53b1a654368d9b4d6fca2dd80804b2061b418a942fc96255641c0c788ca13dd0c8b3b2f3d8cc70c9b938443
|
data/LICENSE.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
|
2
2
|
The MIT License (MIT)
|
3
3
|
|
4
|
-
Copyright (c) 2017-
|
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
@@ -1,8 +1,10 @@
|
|
1
1
|
### SETUP
|
2
2
|
|
3
|
-
This gem requires Ruby
|
4
|
-
|
5
|
-
-
|
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,14 @@ 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
|
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
|
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.
|
data/datafarming.gemspec
CHANGED
@@ -1,14 +1,17 @@
|
|
1
1
|
# -*- ruby -*-
|
2
|
-
|
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 =
|
7
|
-
s.date = "
|
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
|
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', '~>
|
23
|
+
s.add_runtime_dependency 'colorize', '~> 1'
|
21
24
|
s.add_runtime_dependency 'quickstats', '~> 2'
|
22
|
-
s.
|
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
|
-
'
|
5
|
+
'#### COMMAND-LINE TOOLS OVERVIEW',
|
6
6
|
'',
|
7
|
-
' Ruby is a powerful and concise
|
8
|
-
' are normally run from a command-line or terminal environment by
|
9
|
-
' ruby
|
10
|
-
' command-line arguments. However, scripts installed as gems do
|
11
|
-
' ruby command to be typed explicitly. For example, typing',
|
12
|
-
' stripheaderdups.rb my_file.txt
|
13
|
-
' and apply it to file
|
14
|
-
' that on Windows the
|
15
|
-
' stripheaderdups my_file.txt
|
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
|
17
|
+
' All scripts in this distribution are self documenting if run with a \'--help\'',
|
18
|
+
' or \'-h\' option. Quick descriptions follow.',
|
19
19
|
'',
|
20
|
-
'
|
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
|
-
'
|
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
|
-
' •
|
47
|
-
'
|
48
|
-
'
|
49
|
-
'
|
50
|
-
'
|
51
|
-
'
|
52
|
-
'
|
53
|
-
'
|
54
|
-
'
|
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
|
-
'
|
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
|
-
'
|
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,306 @@
|
|
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 = 0 # most have only one design after removing isomorphisms
|
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
|
+
v = 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
|
+
scale_factor = v.max
|
162
|
+
v.map { |x| (x / scale_factor).round(15) }
|
163
|
+
end.transpose
|
164
|
+
end
|
165
|
+
|
166
|
+
def fbd(options)
|
167
|
+
require 'datafarming/freq_sets'
|
168
|
+
freqs2cols(options, FBD::DESIGN_SETS)
|
169
|
+
end
|
170
|
+
|
171
|
+
def fbs(options)
|
172
|
+
require 'datafarming/screen_freq_sets'
|
173
|
+
freqs2cols(options, FBS::DESIGN_SETS)
|
174
|
+
end
|
175
|
+
|
176
|
+
def validate_opts(options)
|
177
|
+
required_options = [:design]
|
178
|
+
missing = required_options - options.keys
|
179
|
+
raise "Missing required options: #{missing}" unless missing.empty?
|
180
|
+
|
181
|
+
options[:design] = options[:design].to_sym
|
182
|
+
end
|
183
|
+
|
184
|
+
def validate(spec)
|
185
|
+
unknown = spec.keys - REQUIRED_SPECS - ELECTIVE_SPECS
|
186
|
+
raise "Unknown factor spec(s): #{unknown}" unless unknown.empty?
|
187
|
+
|
188
|
+
missing = REQUIRED_SPECS - spec.keys
|
189
|
+
raise "Factor spec #{spec} missing #{missing}" unless missing.empty?
|
190
|
+
end
|
191
|
+
|
192
|
+
def optional_specs(factor_spec, i)
|
193
|
+
YAML.safe_load("{#{factor_spec}}".gsub(':', ': '),
|
194
|
+
permitted_classes: [Symbol]).tap do |spec|
|
195
|
+
spec.transform_keys!(&:to_sym)
|
196
|
+
spec[:decimals] ||= nil
|
197
|
+
spec[:id] ||= "X_#{format('%03d', (i + 1))}"
|
198
|
+
validate(spec)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def parse_specs(options, remainder)
|
203
|
+
if remainder.size.positive?
|
204
|
+
options[:num_factors] = remainder.size
|
205
|
+
remainder.map.with_index { |factor_spec, i| optional_specs(factor_spec, i) }
|
206
|
+
elsif options.key? :num_factors
|
207
|
+
Array.new(options[:num_factors]) do |i|
|
208
|
+
{ "id": "X_#{format('%03d', (i + 1))}", "min": -1, "max": 1, "decimals": nil }
|
209
|
+
end
|
210
|
+
else
|
211
|
+
y_options, factor_specs = YAML.safe_load($stdin, permitted_classes: [Symbol])
|
212
|
+
y_options.each_key { |key| options[key] ||= y_options[key] }
|
213
|
+
factor_specs.each { |spec| validate(spec) }
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def parse_args
|
218
|
+
options = {
|
219
|
+
headers: true,
|
220
|
+
replicate: 1,
|
221
|
+
stack: 1,
|
222
|
+
center: false
|
223
|
+
}
|
224
|
+
|
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')
|
236
|
+
opts.on('-r', '--replicate=QTY', /[1-9][0-9]*/, Integer,
|
237
|
+
'Replicate QTY times', '(default: 1)')
|
238
|
+
opts.on('-s', '--stack=QTY', /[1-9][0-9]*/, Integer,
|
239
|
+
'Stack QTY times', '(default: 1)')
|
240
|
+
opts.on('--[no-]center', 'NOTE: Applies only to stacked designs',
|
241
|
+
'Stacking includes center pt','(default: no-center)')
|
242
|
+
opts.on('--[no-]headers', 'Output has column headers', '(default: headers)')
|
243
|
+
opts.on('-l', '--levels=QTY', Regexp.new("(#{LEVELS.join(')|(')})"),
|
244
|
+
Integer, 'NOTE: Applies only to NOLH designs',
|
245
|
+
'Number of levels in the base NOLH:',
|
246
|
+
" #{LEVELS.join ' '}")
|
247
|
+
opts.on('-o', '--output_yaml', 'Write design YAML to STDOUT')
|
248
|
+
opts.on('-i', '--input_yaml', 'Read design YAML from STDIN')
|
249
|
+
end.parse!(into: options)
|
250
|
+
|
251
|
+
raise 'Optional factor specs ignored when using YAML input' if options[:input_yaml] && remainder.size.positive?
|
252
|
+
|
253
|
+
specs = parse_specs(options, remainder)
|
254
|
+
validate_opts options
|
255
|
+
[options, specs]
|
256
|
+
end
|
257
|
+
|
258
|
+
def stack(design, num_stacks, centered)
|
259
|
+
return design if num_stacks < 2
|
260
|
+
raise "Stacking #{num_stacks} times exceeds number of design columns" if num_stacks > design[0].length
|
261
|
+
|
262
|
+
num_stacks -= 1
|
263
|
+
result = design.transpose
|
264
|
+
if num_stacks > 0
|
265
|
+
design_t = if centered
|
266
|
+
design.transpose
|
267
|
+
else
|
268
|
+
design.delete_if {|row| row.all? &:zero? }.transpose
|
269
|
+
end
|
270
|
+
num_stacks.times do
|
271
|
+
design_t.rotate!
|
272
|
+
result.map!.with_index { |line, i| line + design_t[i] }
|
273
|
+
end
|
274
|
+
end
|
275
|
+
result.transpose
|
276
|
+
end
|
277
|
+
|
278
|
+
|
279
|
+
ARGV << '--help' if ARGV.empty?
|
280
|
+
|
281
|
+
options, specs = parse_args
|
282
|
+
|
283
|
+
if options[:output_yaml]
|
284
|
+
options.delete :output_yaml
|
285
|
+
puts [options, specs].to_yaml
|
286
|
+
else
|
287
|
+
base_design = method(options[:design]).call(options)
|
288
|
+
separator = ','
|
289
|
+
puts Array.new(options[:num_factors]) { |i| specs[i][:id] }.join(separator) if options[:headers]
|
290
|
+
|
291
|
+
# stack
|
292
|
+
design = stack(base_design, options[:stack], options[:center])
|
293
|
+
.transpose.first(options[:num_factors]).transpose
|
294
|
+
|
295
|
+
# scale
|
296
|
+
scaler = specs.map do |spec|
|
297
|
+
Scaler.new(min: spec[:min], max: spec[:max], decimals: spec[:decimals])
|
298
|
+
end
|
299
|
+
|
300
|
+
# replicate
|
301
|
+
options[:replicate].times do
|
302
|
+
design.each do |line|
|
303
|
+
puts line.map.with_index { |v, i| scaler[i].scale(v) }.join(separator)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|