flex-cartesian 1.0 → 1.1

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 (3) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +154 -104
  3. metadata +16 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b8d43af751cdba0a5c141bab0606576291395a5cc3e3a8cb673bacc86c2920b
4
- data.tar.gz: 96bfd43d1328e48d38665c2c4aa3b951cf24b932c510ab73ed704d7b7bf98c10
3
+ metadata.gz: b89541266e2ac802592bc652ddbf2edffe1aea4e47ab1d732e2623ad2cc5835b
4
+ data.tar.gz: d717084b70f95af0e3f5706a426731685cc0cc7ddcba038b15b662d2d3226b3b
5
5
  SHA512:
6
- metadata.gz: 6ff1dbb6deb67e9c9a997d8fb4cea7bb75f8fb4aa9ef8ade0a2452c413e19f97c8687f7816c68d47bef8711fe9cdec98159aae2d80ab468ca55512675f4fd5b3
7
- data.tar.gz: 16764e02f4010d4c6431779f8cb2f9b7e11f6f04b97ac0c7546f5b1431e287936b92207a527a9838ab614aeed635f290e268961c235fbc83553eef4e448c2358
6
+ metadata.gz: 26b83b8486d89c151ae8fbc23b7ed9c19f5417a582fd6843b9e654264f0e1deb2615686fe6872b053252c9059a843ca416c41a97ccbc546de3da32b0228b7c61
7
+ data.tar.gz: aa43ccd2936336564a8b14e70a6142167a17db9b93e646ea56445f25b477cd95c8a219fdeac383801c71ea4a07210c00fb1ecbd67d51f18e97afe7ad7b01c8da
data/README.md CHANGED
@@ -2,25 +2,23 @@
2
2
 
3
3
  **Ruby implementation of flexible and human-friendly operations on Cartesian products**
4
4
 
5
-
6
-
7
5
  ## Features
8
6
 
9
7
  ✅ Named dimensions with arbitrary keys
10
8
 
11
- ✅ Enumerate over Cartesian product with a single block argument
9
+ ✅ Enumerate over Cartesian space with a single block argument
12
10
 
13
- Functions over Cartesian vectors are decoupled from dimensionality
11
+ Actions on Cartesian are decoupled from dimensionality: `s.cartesian { |v| do_something(v) }`
14
12
 
15
- Define conditions on Cartesian combinations using `s.cond(:set) { |v| v.dim1 > v.dim2 } }` syntax
13
+ Conditions for Cartesian space: `s.cond(:set) { |v| v.dim1 > v.dim2 } }`
16
14
 
17
- Calculate over named dimensions using `s.cartesian { |v| puts "#{v.dim1} and #{v.dim2}" }` syntax
15
+ Calculation over named dimensions: `s.cartesian { |v| puts "#{v.dim1} and #{v.dim2}" }`
18
16
 
19
- Add functions over dimensions using `s.add_function { |v| v.dim1 + v.dim2 }` syntax
17
+ Functions on Cartesian space: `s.func(:add, :my_sum) { |v| v.dim1 + v.dim2 }`
20
18
 
21
19
  ✅ Lazy and eager evaluation
22
20
 
23
- ✅ Progress bars for large Cartesian combinations
21
+ ✅ Progress bars for large Cartesian spaces
24
22
 
25
23
  ✅ Export of Cartesian space to Markdown or CSV
26
24
 
@@ -30,7 +28,77 @@
30
28
 
31
29
  ✅ Structured and colorized terminal output
32
30
 
31
+ ## Use Cases
32
+
33
+ `FlexCartesian` is especially useful in the following scenarios.
34
+
35
+ ### 1. Sweep Analysis of Performance
36
+
37
+ Systematically evaluate an application or algorithm across all combinations of parameters:
38
+
39
+ - Parameters: `threads`, `batch_size`, `backend`, etc
40
+ - Metrics: `throughput`, `latency`, `memory`
41
+ - Output: CSV or Markdown tables
42
+
43
+ ### 2. Hyperparameter Tuning for ML Models
44
+
45
+ Iterate over all combinations of hyperparameters:
46
+
47
+ - Examples: `learning_rate`, `max_depth`, `subsample`, `n_estimators`
48
+ - With constraints (e.g., `max_depth < 10 if learning_rate > 0.1`)
49
+ - With computed evaluation metrics like `accuracy`, `AUC`, etc
50
+
51
+ ### 3. Infrastructure and System Configuration
52
+
53
+ Generate all valid infrastructure configurations:
54
+
55
+ ```ruby
56
+ region: ["us-west", "eu-central"]
57
+ tier: ["basic", "pro"]
58
+ replicas: [1, 3, 5]
59
+ ```
60
+
61
+ With conditions like "basic tier cannot have more than one replica:
62
+ ```ruby
63
+ s.cond(:set) { |v| (v.tier == "basic" ? v.replicas == 1 : true) }
64
+ ```
65
+
66
+ ### 4. Mass Testing of CLI Commands
67
+ Generate and benchmark all valid CLI calls:
68
+
69
+ ```bash
70
+ myapp --threads=4 --batch=32 --backend=torch
71
+ ```
72
+
73
+ Capture runtime, output, errors, etc.
74
+
75
+ ### 5. Input Generation for UI/API Testing
76
+ Automatically cover input parameter spaces for:
77
+
78
+ - HTTP methods: ["GET", "POST"]
79
+ - User roles: ["guest", "user", "admin"]
80
+ - Language settings: ["en", "fr", "de"]
33
81
 
82
+ ### 6. Scientific and Engineering Simulations
83
+ Generate multidimensional experimental spaces for:
84
+
85
+ - Physics simulations
86
+ - Bioinformatics parameter sweeps
87
+ - Network behavior modeling, etc
88
+
89
+ ### 7. Structured Reporting and Visualization
90
+ Output Cartesian data as:
91
+
92
+ - Markdown (for GitHub rendering)
93
+ - CSV (for Excel, Google Sheets, and more advanced BI tools)
94
+ - Plain text (for CLI previews)
95
+
96
+ ### 8. Test Case Generation
97
+ Use it to drive automated test inputs for:
98
+
99
+ - RSpec shared examples
100
+ - Minitest table-driven tests
101
+ - PyTest parameterization
34
102
 
35
103
  ## Installation
36
104
 
@@ -98,25 +166,24 @@ s.cartesian(lazy: true).take(2).each { |v| do_something(v) }
98
166
 
99
167
  # 5. A function is a virtual dimension that is calculated based on a vector of base dimensions.
100
168
  # You can think of a function as a scalar field defined on Cartesian space.
101
- # 6. Functions are printed as virtual dimensions in .output method.
102
- # 7. However, functions remains virtual construct, and their values can't be referenced by name
103
- # (unlike regular dimensions). Also, functions do not add to .size of Cartesian space.
169
+ # 6. Functions are printed as virtual dimensions in `.output`.
170
+ # 7. Functions do not add to `.size` of Cartesian space.
104
171
 
105
172
  puts "\nAdd function 'triple'"
106
173
  puts "Note: function is visualized in .output as a new dimension"
107
- s.add_function(:triple) { |v| v.dim1 * 3 + (v.dim3 ? 1: 0) }
108
- # Note: however, function remains a virtual construct, and it cannot be referenced by name
174
+ s.func(:add, :triple) { |v| v.dim1 * 3 + (v.dim3 ? 1: 0) }
175
+ s.func(:run)
109
176
  s.output
110
177
 
111
178
  puts "\Add and then remove function 'test'"
112
- s.add_function(:test) { |v| v.dim3.to_i }
113
- s.remove_function(:test)
179
+ s.func(:add, :test) { |v| v.dim3.to_i }
180
+ s.func(:del, :test)
114
181
 
115
182
 
116
183
 
117
184
  # CONDITIONS ON CARTESIAN SPACE
118
185
 
119
- # 8. A condition is a logical restriction of allowed combitnations for Cartesian space.
186
+ # 8. A condition is a logical constraint for allowed combitnations of Cartesian space.
120
187
  # 9. Using conditions, you can take a slice of Cartesian space.
121
188
  # In particular, you can reflect semantical dependency of dimensional values.
122
189
 
@@ -140,10 +207,8 @@ puts "Restored size without conditions: #{s.size}"
140
207
 
141
208
  puts "\nPrint Cartesian space as plain table, all functions included"
142
209
  s.output
143
-
144
210
  puts "\nPrint Cartesian space as Markdown"
145
211
  s.output(format: :markdown)
146
-
147
212
  puts "\nPrint Cartesian space as CSV"
148
213
  s.output(format: :csv)
149
214
 
@@ -155,7 +220,6 @@ puts "\nImport Cartesian space from JSON (similar method for YAML)"
155
220
  File.write('example.json', JSON.pretty_generate(example))
156
221
  puts "\nNote: after import, all assigned functions will calculate again, and they appear in the output"
157
222
  s.import('example.json').output
158
-
159
223
  puts "\nExport Cartesian space to YAML (similar method for JSON)"
160
224
  s.export('example.yaml', format: :yaml)
161
225
 
@@ -166,7 +230,6 @@ s.export('example.yaml', format: :yaml)
166
230
  puts "\nGet number of Cartesian combinations"
167
231
  puts "Note: .size counts only dimensions, it ignores virtual constructs (functions, conditions, etc.)"
168
232
  puts "Total size of Cartesian space: #{s.size}"
169
-
170
233
  puts "\nPartially converting Cartesian space to array:"
171
234
  array = s.to_a(limit: 3)
172
235
  puts array.inspect
@@ -218,62 +281,40 @@ These input parameters form the following dimensions.
218
281
  }
219
282
  ```
220
283
 
221
- Note that '//' isn't officially supported by JSON, and you may want to remove the comments if you experience parser errors.
284
+ Note that `//` isn't officially supported by JSON, and you may want to remove the comments if you experience parser errors.
222
285
  Let us build the code to run over these parameters.
223
286
 
224
287
  ```ruby
225
288
  require 'flex-cartesian'
226
289
 
227
- s = FlexCartesian.new(path: './ping_config.json') # file with the parameters as given above
290
+ s = FlexCartesian.new(path: './ping_parameters.json') # file with the parameters as given above
228
291
 
229
- result = {} # here we will store raw result of each ping and fetch target metrics from it
230
-
231
- # this function shows actual ping command
232
- s.func(:add, :command) do |v|
233
- "ping -c #{v.count} -s #{v.size} #{v.target}"
234
- end
292
+ result = {} # raw result of each ping
235
293
 
236
- # this function gets raw result of actual ping command
237
- s.func(:add, :raw_ping, hide: true) do |v|
238
- result[v.command] ||= `#{v.command} 2>&1`
239
- end
294
+ s.func(:add, :command) { |v| "ping -c #{v.count} -s #{v.size} #{v.target}" } # ping command
295
+ s.func(:add, :raw_ping, hide: true) { |v| result[v.command] ||= `#{v.command} 2>&1` } # capturing ping result
296
+ s.func(:add, :time) { |v| v.raw_ping[/min\/avg\/max\/(?:mdev|stddev) = [^\/]+\/([^\/]+)/, 1]&.to_f } # fetch ping time from result
297
+ s.func(:add, :min) { |v| v.raw_ping[/min\/avg\/max\/(?:mdev|stddev) = ([^\/]+)/, 1]&.to_f } # fetch min time from result
298
+ s.func(:add, :loss) { |v| v.raw_ping[/(\d+(?:\.\d+)?)% packet loss/, 1]&.to_f } # fetch ping loss from result
240
299
 
241
- # this function extracts ping time
242
- s.func(:add, :time) do |v|
243
- if v.raw_ping =~ /min\/avg\/max\/(?:mdev|stddev) = [^\/]+\/([^\/]+)/
244
- $1.to_f
245
- end
246
- end
300
+ s.func(:run) # Sweep analysis! Benchmark all possible combinations of parameters
247
301
 
248
- # this function extracts minimum ping time
249
- s.func(:add, :min) do |v|
250
- if v.raw_ping =~ /min\/avg\/max\/(?:mdev|stddev) = ([^\/]+)/
251
- $1.to_f
252
- end
253
- end
302
+ s.output(format: :csv, file: './result.csv') # save benchmark result as CSV
254
303
 
255
- # funally, this function extracts losses of ping
256
- s.func(:add, :loss) do |v|
257
- if v.raw_ping =~ /(\d+(?:\.\d+)?)% packet loss/
258
- $1.to_f
259
- end
260
- end
304
+ s.output(colorize: true) # for convenience, show result in terminal
305
+ ```
261
306
 
262
- # this is the spinal axis of FlexCartesian:
263
- # calculate all functions on the entire Cartesian space of parameters aka dimensions
264
- s.func(:run)
307
+ If you run the code, after a while it will generate benchmark results on the screen:
265
308
 
266
- # save benchmark results to CSV for convenient analysis in BI tools
267
- s.output(format: :csv, file: './benchmark.csv')
309
+ ![Ping Benchmark Example](doc/ping_benchmark_example.png)
268
310
 
269
- # for convenience, show tabular result on screen as well
270
- s.output(colorize: true)
271
- ```
311
+ Additionally, CSV version of this result is saved as `./benchmark.csv`
272
312
 
273
- This code is 100% practical and illustrative. You can benchmark:
313
+ The PING benchmarking code above is 100% practical and illustrative.
314
+ You can modify it and benchmark virtually anything:
274
315
 
275
- - Local block devices using 'dd'
276
- - GPU-to-Storage connection using 'gdsio'
316
+ - Local block devices using `dd`
317
+ - GPU-to-Storage connection using `gdsio`
277
318
  - Local file systems using FS-based utilities
278
319
  - Local CPU RAM using RAM disk or specialized benchmarks for CPU RAM
279
320
  - Database performance using SQL client or non-SQL client utilities
@@ -285,15 +326,19 @@ In any use case, FlexCartesian will unfold complete landscape of the target perf
285
326
  As result, you will be able to spot optimal configurations, correlations, bottlenecks, and sweet spots.
286
327
  Moreover, you will make your conclusions in a justifiable way.
287
328
 
288
- Here is an example of how I used FlexCartesian to [analyze optimal performance/cost of YOLO](https://www.linkedin.com/pulse/comparing-gpu-a10-ampere-a1-shapes-object-oci-yuri-rassokhin-rseqf).
329
+ Here is example of using FlexCartesian for [performance/cost analysis of YOLO](https://www.linkedin.com/pulse/comparing-gpu-a10-ampere-a1-shapes-object-oci-yuri-rassokhin-rseqf).
330
+
331
+
289
332
 
290
333
  ## API Overview
291
334
 
292
335
  ### Initialization
293
336
  ```ruby
294
- FlexCartesian.new(dimensions_hash)
337
+ FlexCartesian.new(dimensions = nil, path: nil, format: :json)
295
338
  ```
296
- - `dimensions_hash`: a hash with named dimensions; each value can be an `Enumerable` (e.g., arrays, ranges).
339
+ - `dimensions_hash`: optional hash with named dimensions; each value can be an `Enumerable` (arrays, ranges, etc)
340
+ - `path`: optional path to file with stored dimensions, JSON and YAML supported
341
+ - `format`: optional format of `path` file, defaults to JSON
297
342
 
298
343
  Example:
299
344
  ```ruby
@@ -309,10 +354,11 @@ FlexCartesian.new(dimensions)
309
354
  ---
310
355
 
311
356
  ### Iterate Over All Combinations
357
+
358
+ Example:
312
359
  ```ruby
313
360
  # With block
314
361
  cartesian(dims = nil, lazy: false) { |vector| ... }
315
-
316
362
  # Without block: returns Enumerator
317
363
  cartesian(dims = nil, lazy: false)
318
364
  ```
@@ -326,20 +372,31 @@ s.cartesian { |v| puts "#{v.dim1} - #{v.dim2}" }
326
372
 
327
373
  ---
328
374
 
329
- ### Add / Remove Functions
375
+ ### Handling Functions
330
376
  ```ruby
331
- add_function(name, &block)
332
- remove_function(name)
377
+ func(command = :print, name = nil, hide: false, &block)
333
378
  ```
334
- - `name`: symbol the name of the virtual dimension (e.g. `:label`)
379
+ - `command`: symbol, one of the following
380
+ - `:add` to add function as a virtual dimension to Cartesian space
381
+ - `:del` to delete function from Cartesian space
382
+ - `:print` as defaut action, prints all the functions added to Cartesian space
383
+ - `:run` to calculate all the functions defined for Cartesian space
384
+ - `name`: symbol, name of the virtual dimension, e.g. `:my_function`
385
+ - `hide`: flag that hides or shows the function in .output; it is useful to hide intermediate calculations
335
386
  - `block`: a function that receives each vector and returns a computed value
336
387
 
337
388
  Functions show up in `.output` like additional (virtual) dimensions.
338
389
 
390
+ > Note: functions must be calculated excpliticy using `:run` command.
391
+ > Before the first calculation, a function has `nil` values in `.output`.
392
+ > Explicit :run is reequired to unambigously control points in the execution flow where high computational resource is to be consumed.
393
+ > Otherwise, automated recalculation of functions, perhaps, during `.output` would be a difficult-to-track computational burden.
394
+
339
395
  Example:
340
396
  ```ruby
341
397
  s = FlexCartesian.new( { dim1: [1, 2], dim2: ['A', 'B'] } )
342
- s.add_function(:increment) { |v| v.dim1 + 1 }
398
+ s.func(:add, :increment) { |v| v.dim1 + 1 }
399
+ s.func(:run)
343
400
 
344
401
  s.output(format: :markdown)
345
402
  # | dim1 | dim2 | increment |
@@ -349,7 +406,6 @@ s.output(format: :markdown)
349
406
  # ...
350
407
  ```
351
408
 
352
- > Note: functions are virtual — they are not part of the base dimensions, but they integrate seamlessly in output.
353
409
 
354
410
  ---
355
411
 
@@ -377,19 +433,20 @@ Displays a progress bar using `ruby-progressbar`.
377
433
 
378
434
  ---
379
435
 
380
- ### Print Table to Console
436
+ ### Print Cartesian
381
437
  ```ruby
382
- output(
383
- separator: " | ",
384
- colorize: false,
385
- align: false,
386
- format: :plain # or :markdown, :csv
387
- limit: nil
388
- )
438
+ output(separator: " | ", colorize: false, align: true, format: :plain, limit: nil, file: nil)
389
439
  ```
440
+ - `separator`: how to visually separate columns in the output
441
+ - `colorize`: whether to colorize output or not
442
+ - `align`: whether to align output by column or not
443
+ - `format`: one of `:plain`, `:markdown`, or `:csv`
444
+ - `limit`: break the output after the first `limit` Cartesian combinations
445
+ - `file`: print to `file`
446
+
390
447
  Prints all combinations in table form (plain/markdown/CSV).
391
448
  Markdown example:
392
- ```
449
+ ```markdown
393
450
  | dim1 | dim2 |
394
451
  |------|------|
395
452
  | 1 | "a" |
@@ -400,9 +457,10 @@ Markdown example:
400
457
 
401
458
  ### Import from JSON or YAML
402
459
  ```ruby
403
- import('file.json',
404
- format: :json) # or :yaml
460
+ import(path, format: :json)
405
461
  ```
462
+ - `path`: input file
463
+ - `format`: format to read, `:json` and `:yaml` supported
406
464
 
407
465
  Obsolete import methods:
408
466
  ```ruby
@@ -412,38 +470,30 @@ s.from_yaml("file.yaml")
412
470
 
413
471
  ---
414
472
 
415
- ### Export from JSON or YAML
416
- ```ruby
417
- export('file.json',
418
- format: :json) # or :yaml
419
- ```
420
-
421
- ---
422
-
423
- ### Print Cartesian Space
424
- Each yielded combination is a `Struct` extended with:
425
- ```ruby
426
- output(separator: " | ", colorize: false, align: true)
427
- ```
428
- Example:
473
+ ### Export to JSON or YAML
429
474
  ```ruby
430
- s.cartesian { |v| v.output(colorize: true, align: false) }
475
+ export(path, format: :json)
431
476
  ```
432
-
433
- ---
477
+ - `path`: output file
478
+ - `format`: format to export, `:json` and `:yaml` supported
434
479
 
435
480
  ### Conditions on Cartesian Space
436
481
  ```ruby
437
- cond(command = :print, # or :set, :unset, :clear
438
- index: nil, # index of a conditions to unset
439
- &block # defintiion of the condition to set
440
- )
482
+ cond(command = :print, index: nil, &block)
441
483
  ```
484
+ - `command`: one of the following
485
+ - `:set` to set the condition to Cartesian space
486
+ - `:unset` to remove the `index` condition from Cartesian space
487
+ - `:clear` to remove all conditions from Cartesian space
488
+ - `:print` default command, prints all the conditions on the Cartesian space
489
+ - `index`: index of the condition set to Cartesian space, it is used to remove specified condition
490
+ - `block`: definition of the condition, it should return `true` or `false` to avoid unpredictable behavior
491
+
442
492
  Example:
443
493
  ```ruby
444
494
  s.cond(:set) { |v| v.dim1 > v.dim3 }
445
495
  s.cond # defaults to s.cond(:print) and shows all the conditions in the form 'index | definition'
446
- s.cond(:unset, 0) # remove previously set condition
496
+ s.cond(:unset, 0) # remove the condition
447
497
  s.cond(:clear) # remove all conditions, if any
448
498
  ```
449
499
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flex-cartesian
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.0'
4
+ version: '1.1'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yury Rassokhin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-14 00:00:00.000000000 Z
11
+ date: 2025-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.13'
41
+ - !ruby/object:Gem::Dependency
42
+ name: progressbar
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.13'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.13'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: json
43
57
  requirement: !ruby/object:Gem::Requirement