musa-dsl 0.21.4 → 0.22.3

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/lib/musa-dsl.rb +1 -1
  3. data/lib/musa-dsl/core-ext.rb +1 -0
  4. data/lib/musa-dsl/core-ext/arrayfy.rb +9 -9
  5. data/lib/musa-dsl/core-ext/hashify.rb +42 -0
  6. data/lib/musa-dsl/core-ext/inspect-nice.rb +6 -1
  7. data/lib/musa-dsl/datasets/e.rb +22 -5
  8. data/lib/musa-dsl/datasets/gdv.rb +0 -1
  9. data/lib/musa-dsl/datasets/p.rb +28 -37
  10. data/lib/musa-dsl/datasets/pdv.rb +0 -1
  11. data/lib/musa-dsl/datasets/ps.rb +10 -78
  12. data/lib/musa-dsl/logger/logger.rb +4 -3
  13. data/lib/musa-dsl/matrix/matrix.rb +0 -57
  14. data/lib/musa-dsl/sequencer/base-sequencer-implementation-every.rb +87 -0
  15. data/lib/musa-dsl/sequencer/base-sequencer-implementation-move.rb +439 -0
  16. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +2 -2
  17. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb +210 -0
  18. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb +178 -0
  19. data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +150 -597
  20. data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +6 -8
  21. data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +1 -5
  22. data/lib/musa-dsl/sequencer/{base-sequencer-public.rb → base-sequencer.rb} +59 -5
  23. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +11 -2
  24. data/lib/musa-dsl/sequencer/sequencer.rb +1 -1
  25. data/lib/musa-dsl/series/base-series.rb +43 -78
  26. data/lib/musa-dsl/series/flattener-timed-serie.rb +61 -0
  27. data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +143 -0
  28. data/lib/musa-dsl/series/holder-serie.rb +1 -1
  29. data/lib/musa-dsl/series/main-serie-constructors.rb +32 -92
  30. data/lib/musa-dsl/series/main-serie-operations.rb +60 -215
  31. data/lib/musa-dsl/series/proxy-serie.rb +1 -1
  32. data/lib/musa-dsl/series/quantizer-serie.rb +558 -0
  33. data/lib/musa-dsl/series/queue-serie.rb +1 -1
  34. data/lib/musa-dsl/series/series.rb +8 -2
  35. data/lib/musa-dsl/series/union-timed-series.rb +109 -0
  36. data/musa-dsl.gemspec +2 -2
  37. metadata +12 -5
  38. data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +0 -216
  39. data/lib/musa-dsl/series/hash-serie-splitter.rb +0 -196
@@ -20,7 +20,7 @@ module Musa
20
20
  @target = target.instance
21
21
  end
22
22
 
23
- def _prototype
23
+ def _prototype!
24
24
  raise PrototypingSerieError, 'Cannot get prototype of a proxy serie'
25
25
  end
26
26
 
@@ -0,0 +1,558 @@
1
+ require_relative '../datasets/e'
2
+ require_relative '../core-ext/inspect-nice'
3
+
4
+ # TODO remove debugging puts, intermediate hash comments on :info and InspectNice
5
+ using Musa::Extension::InspectNice
6
+
7
+ module Musa
8
+ module Series
9
+ module SerieOperations
10
+ def quantize(reference: nil, step: nil,
11
+ value_attribute: nil,
12
+ stops: nil,
13
+ predictive: nil,
14
+ left_open: nil,
15
+ right_open: nil)
16
+
17
+ Series.QUANTIZE(self,
18
+ reference: reference,
19
+ step: step,
20
+ value_attribute: value_attribute,
21
+ stops: stops,
22
+ predictive: predictive,
23
+ left_open: left_open,
24
+ right_open: right_open)
25
+ end
26
+ end
27
+ end
28
+
29
+ module Series
30
+ extend self
31
+
32
+ def QUANTIZE(time_value_serie,
33
+ reference: nil, step: nil,
34
+ value_attribute: nil,
35
+ stops: nil,
36
+ predictive: nil,
37
+ left_open: nil,
38
+ right_open: nil)
39
+
40
+ reference ||= 0r
41
+ step ||= 1r
42
+ value_attribute ||= :value
43
+ stops ||= false
44
+ predictive ||= false
45
+
46
+ if predictive
47
+ raise ArgumentError, "Predictive quantization doesn't allow parameters 'left_open' or 'right_open'" if left_open || right_open
48
+
49
+ PredictiveQuantizer.new(reference, step, time_value_serie, value_attribute, stops)
50
+ else
51
+ # By default: left closed and right_open
52
+ # By default 2:
53
+ # if right_open is true and left_open is nil, left_open will be false
54
+ # if left_open is true and right_open is nil, right_open will be false
55
+
56
+ right_open = right_open.nil? ? !left_open : right_open
57
+ left_open = left_open.nil? ? !right_open : left_open
58
+
59
+ RawQuantizer.new(reference, step, time_value_serie, value_attribute, stops, left_open, right_open)
60
+ end
61
+ end
62
+
63
+ module QuantizerTools
64
+ private def get_time_value(n)
65
+ case n
66
+ when nil
67
+ time = value = nil
68
+ when Musa::Datasets::AbsTimed
69
+ time = n[:time].rationalize
70
+ value = n[@value_attribute].rationalize
71
+ when Array
72
+ time = n[0].rationalize
73
+ value = n[1].rationalize
74
+ else
75
+ raise RuntimeError, "Don't know how to process #{n}"
76
+ end
77
+
78
+ return time, value
79
+ end
80
+ end
81
+
82
+ private_constant :QuantizerTools
83
+
84
+ class RawQuantizer
85
+ include Serie
86
+ include QuantizerTools
87
+
88
+ attr_reader :source
89
+
90
+ attr_reader :points_history
91
+
92
+ def initialize(reference, step, source, value_attribute, stops, left_open, right_open)
93
+ @reference = reference
94
+ @step_size = step.abs
95
+
96
+ @source = source
97
+ @value_attribute = value_attribute
98
+
99
+ @stops = stops
100
+ @left_open = left_open
101
+ @right_open = right_open
102
+
103
+ _restart false
104
+
105
+ mark_regarding! source
106
+ end
107
+
108
+ def _restart(restart_sources = true)
109
+ @last_processed_q_value = nil
110
+ @last_processed_time = nil
111
+
112
+ @before_last_processed_q_value = nil
113
+
114
+ @points = []
115
+ @segments = []
116
+
117
+ @source.restart if restart_sources
118
+ end
119
+
120
+ def _next_value
121
+ if @stops
122
+ i = 2
123
+
124
+ loop do
125
+ while @segments.size < i && process2; end
126
+
127
+ first = @segments[0]
128
+ last = @segments[i - 1]
129
+
130
+ # puts "_next_value: first #{first || 'nil'} last #{last || 'nil'}"
131
+
132
+ break if first.nil?
133
+
134
+ if last.nil? || last[:stop] || first[:value] != last[:value]
135
+ # puts "_next_value: found segments:"
136
+
137
+ durations_to_sum = @segments.shift(i-1)
138
+
139
+ # durations_to_sum.each { |i| puts i.inspect }
140
+ #
141
+ # puts "_next_value: result #{{ time: first[:time],
142
+ # @value_attribute => first[:value],
143
+ # duration: durations_to_sum.sum { |_| _[:duration] } }}"
144
+
145
+ return { time: first[:time],
146
+ @value_attribute => first[:value],
147
+ duration: durations_to_sum.sum { |_| _[:duration] } }
148
+ .extend(Musa::Datasets::AbsTimed)
149
+ .extend(Musa::Datasets::AbsD)
150
+ else
151
+ i += 1
152
+ end
153
+ end
154
+
155
+ return nil
156
+
157
+ else
158
+ i = 2
159
+ # puts "\n\n"
160
+ loop do
161
+ while @segments.size < i && process2; end
162
+
163
+ first = @segments[0]
164
+ last = @segments[i - 1]
165
+
166
+ # puts "_next_value: first #{first || 'nil'} last #{last || 'nil'}"
167
+
168
+ break if first.nil?
169
+
170
+ if last.nil? || first[:value] != last[:value]
171
+
172
+ # puts "_next_value: found segments:"
173
+
174
+ durations_to_sum = @segments.shift(i-1)
175
+
176
+ # durations_to_sum.each { |i| puts i.inspect }
177
+ #
178
+ # puts "_next_value: result #{{ time: first[:time],
179
+ # @value_attribute => first[:value],
180
+ # duration: durations_to_sum.sum { |_| _[:duration] } }}"
181
+
182
+ return { time: first[:time],
183
+ @value_attribute => first[:value],
184
+ duration: durations_to_sum.sum { |_| _[:duration] } }
185
+ .extend(Musa::Datasets::AbsTimed)
186
+ .extend(Musa::Datasets::AbsD)
187
+ else
188
+ i += 1
189
+ end
190
+ end
191
+
192
+ return nil
193
+ end
194
+ end
195
+
196
+ private def process2
197
+ while (ready_count = count_ready_points) <= 2 &&
198
+ process(*get_time_value(@source.next_value), !@source.peek_next_value)
199
+ end
200
+
201
+ if ready_count >= 2
202
+ point = @points.shift
203
+
204
+ from_time = point[:time]
205
+ from_value = point[:value]
206
+
207
+ next_point = @points.first
208
+
209
+ to_time = next_point[:time]
210
+ to_value = next_point[:value]
211
+ to_point_is_last = next_point[:last]
212
+
213
+ sign = to_value <=> from_value # to_value > from_value => +1
214
+
215
+ # puts "process2: from_time #{from_time} from_value #{from_value} to_time #{to_time} to_value #{to_value} to_last #{to_point_is_last || 'nil'} sign #{sign}"
216
+
217
+ if sign == 0
218
+ if @segments.last && @segments.last[:time] == from_time
219
+
220
+ @segments.last[:duration] = to_time - from_time
221
+ @segments.last[:info] += "; edited on a as start"
222
+
223
+ else
224
+ @segments << { time: from_time,
225
+ value: from_value,
226
+ duration: to_time - from_time,
227
+ info: "added on a as start" }
228
+
229
+ end
230
+
231
+ if !to_point_is_last
232
+ @segments << { time: to_time,
233
+ value: from_value,
234
+ duration: 0,
235
+ stop: true,
236
+ info: "added on a as end stop" }
237
+ end
238
+ else
239
+ time_increment = to_time - from_time
240
+
241
+ step_value_increment = @step_size * sign
242
+
243
+ extra_steps = 0
244
+
245
+ if @right_open
246
+ loop_to_value = to_value - step_value_increment
247
+ else
248
+ loop_to_value = to_value
249
+ extra_steps += 1
250
+ end
251
+
252
+ if @left_open
253
+ loop_from_value = from_value + step_value_increment
254
+ extra_steps -= 1
255
+ else
256
+ loop_from_value = from_value
257
+ end
258
+
259
+ step_time_increment = time_increment / ((to_value - from_value).abs + extra_steps)
260
+
261
+ intermediate_point_time = from_time
262
+
263
+ # puts "process2: loop_from_value #{loop_from_value} loop_to_value #{loop_to_value} step_value_increment #{step_value_increment} step_time_increment #{step_time_increment}"
264
+
265
+ loop_from_value.step(loop_to_value, step_value_increment) do |value|
266
+ if @segments.last &&
267
+ @segments.last[:time] == intermediate_point_time &&
268
+ @segments.last[:value] == value
269
+
270
+ @segments.last[:duration] = step_time_increment
271
+ @segments.last[:info] += "; edited on b"
272
+
273
+ # puts "process2: editing #{@segments.last}"
274
+
275
+ else
276
+ @segments << v = { time: intermediate_point_time,
277
+ value: value,
278
+ duration: step_time_increment,
279
+ info: "added on b" }
280
+
281
+ # puts "process2: adding #{v.inspect}"
282
+ end
283
+
284
+ intermediate_point_time += step_time_increment
285
+ end
286
+ end
287
+
288
+ true
289
+ else
290
+ false
291
+ end
292
+ end
293
+
294
+ private def count_ready_points
295
+ @points.select { |_| _[:ready] }.size
296
+ end
297
+
298
+ private def process(time, value, last_time_value)
299
+ if time && value
300
+ raise RuntimeError, "time only can go forward" if @last_processed_time && time <= @last_processed_time
301
+
302
+ q_value = round_quantize(value)
303
+
304
+ # A continuation point time will be changed if new points of equal value arrive.
305
+ # For this reason this point is not ready to consume.
306
+ # A ready point is a well determined point that can be consumed.
307
+ # When we arrive to a well determined point all the previous points become also determined (ready).
308
+
309
+ if q_value == @last_processed_q_value && !last_time_value
310
+ if @points.size == 1 || @points.last[:ready]
311
+ # If @points.last is the first point of a segment the new point is a continuation point.
312
+ # The continuation point is used as a stop point.
313
+ @points << { time: time, value: q_value }
314
+ else
315
+ # @points.last is NOT the first point of a segment but a continuation point.
316
+ @points.last[:time] = time
317
+ end
318
+ else
319
+ @points.reverse_each do |point|
320
+ break if point[:ready]
321
+ point[:ready] = true
322
+ end
323
+
324
+ @points << { time: time, value: q_value, ready: true, last: last_time_value }
325
+ end
326
+
327
+ @last_processed_q_value = q_value
328
+ @last_processed_time = time
329
+
330
+ true
331
+ else
332
+ false
333
+ end
334
+ end
335
+
336
+ private def round_quantize(value)
337
+ round((value - @reference) / @step_size) * @step_size + @reference
338
+ end
339
+
340
+ private def round(value)
341
+ i = value.floor
342
+ value > (i + 1/2r) ? i + 1r : i.rationalize
343
+ end
344
+
345
+ def infinite?
346
+ !!@source.infinite?
347
+ end
348
+ end
349
+
350
+ private_constant :RawQuantizer
351
+
352
+ class PredictiveQuantizer
353
+ include Serie
354
+ include QuantizerTools
355
+
356
+ attr_reader :source
357
+
358
+ def initialize(reference, step, source, value_attribute, include_stops)
359
+ @reference = reference
360
+ @step_size = step
361
+
362
+ @source = source
363
+ @value_attribute = value_attribute
364
+
365
+ @include_stops = include_stops
366
+
367
+ @halfway_offset = step / 2r
368
+ @crossing_reference = reference - @halfway_offset
369
+
370
+ _restart false
371
+
372
+ mark_regarding! source
373
+ end
374
+
375
+ def _restart(restart_sources = true)
376
+ @source.restart if restart_sources
377
+
378
+ @last_time = nil
379
+ @crossings = []
380
+
381
+ @first = true
382
+ end
383
+
384
+ def infinite?
385
+ !!@source.infinite?
386
+ end
387
+
388
+ def _next_value
389
+ result = nil
390
+
391
+ first_time, first_value = get_time_value(@source.peek_next_value) if @first
392
+
393
+ while @crossings.size <= 2 && process_more; end
394
+
395
+ @crossings.delete_if { |c| c[:stop] && c[:stops].nil? }
396
+
397
+ if @crossings[0]
398
+ time = @crossings[0][:time]
399
+ value = @crossings[0][@value_attribute]
400
+
401
+ if @first
402
+ @first = false
403
+
404
+ if time > first_time
405
+ result = { time: first_time,
406
+ @value_attribute => round_to_nearest_quantize(first_value, value),
407
+ duration: time - first_time }
408
+ .extend(Musa::Datasets::AbsD)
409
+ .extend(Musa::Datasets::AbsTimed)
410
+ else
411
+ result = _next_value
412
+ end
413
+ else
414
+ if @crossings[1]
415
+ next_time = @crossings[1][:time]
416
+ result = { time: time,
417
+ @value_attribute => value,
418
+ duration: next_time - time }
419
+ .extend(Musa::Datasets::AbsD)
420
+ .extend(Musa::Datasets::AbsTimed)
421
+
422
+ @crossings.shift
423
+
424
+ else
425
+ if @last_time && @last_time > @crossings[0][:time]
426
+ result = { time: @crossings[0][:time],
427
+ @value_attribute => @crossings[0][@value_attribute],
428
+ duration: @last_time - @crossings[0][:time] }
429
+ .extend(Musa::Datasets::AbsD)
430
+ .extend(Musa::Datasets::AbsTimed)
431
+
432
+ @last_time = nil
433
+ end
434
+ end
435
+ end
436
+ else
437
+ if @first && @last_time && @last_time > first_time
438
+ result = { time: first_time,
439
+ value: round_to_nearest_quantize(first_value),
440
+ duration: @last_time - first_time }
441
+ .extend(Musa::Datasets::AbsD)
442
+ .extend(Musa::Datasets::AbsTimed)
443
+
444
+ @first = false
445
+ @last_time = false
446
+ end
447
+ end
448
+
449
+ return result
450
+ end
451
+
452
+ private def process_more
453
+ while (@crossings.size <= 2 || @crossings[-1][:stop]) && new_crossings = next_crossings
454
+ new_crossings.each do |c|
455
+ if @last_time.nil? || c[:time] > @last_time
456
+
457
+ if c[:stop] &&
458
+ @crossings.dig(-1, :stop) &&
459
+ @crossings.dig(-1, @value_attribute) == c[@value_attribute]
460
+
461
+ c[:stops] = (@crossings[-1][:stops] || 0) + 1
462
+
463
+ @crossings[-1] = c
464
+ else
465
+ @crossings << c
466
+ end
467
+ end
468
+ end
469
+ end
470
+
471
+ !!new_crossings
472
+ end
473
+
474
+ private def next_crossings
475
+ from_time, from_value = get_time_value(@source.next_value)
476
+
477
+ if from_time && from_value
478
+ raise RuntimeError, "time only can go forward" if @last_time && from_time <= @last_time
479
+
480
+ @last_time = from_time
481
+
482
+ to_time, to_value = get_time_value(@source.peek_next_value)
483
+
484
+ if to_time && to_value
485
+ crossings(from_time, from_value, to_time, to_value)
486
+ end
487
+ end
488
+ end
489
+
490
+ private def crossings(from_time, from_value, to_time, to_value)
491
+ sign = to_value >= from_value ? 1r : -1r
492
+
493
+ if sign == 1
494
+ from_step = ((from_value - @crossing_reference) / @step_size).ceil
495
+ last_step = ((to_value - @crossing_reference) / @step_size).floor
496
+ else
497
+ from_step = ((from_value - @crossing_reference) / @step_size).floor
498
+ last_step = ((to_value - @crossing_reference) / @step_size).ceil
499
+ end
500
+
501
+ delta_value = to_value - from_value
502
+ delta_time = to_time - from_time
503
+
504
+ crossings =
505
+ from_step.step(last_step, sign).collect do |i|
506
+ value = @crossing_reference + i * @step_size
507
+
508
+ { time: from_time + (delta_time / delta_value) * (value - from_value),
509
+ @value_attribute => value + sign * @halfway_offset }
510
+ end
511
+
512
+ if @include_stops
513
+ first_crossing_time = crossings.dig(0, :time)
514
+ last_crossing_time = crossings.dig(-1, :time)
515
+
516
+ if first_crossing_time.nil? || from_time < first_crossing_time
517
+ stop_before = [ { time: from_time,
518
+ @value_attribute =>
519
+ round_to_nearest_quantize(from_value,
520
+ crossings.dig(0, @value_attribute)),
521
+ stop: true } ]
522
+ else
523
+ stop_before = []
524
+ end
525
+
526
+ if last_crossing_time.nil? || to_time > last_crossing_time
527
+ stop_after = [ { time: to_time,
528
+ @value_attribute =>
529
+ round_to_nearest_quantize(to_value,
530
+ crossings.dig(-1, @value_attribute)),
531
+ stop: true } ]
532
+ else
533
+ stop_after = []
534
+ end
535
+
536
+ stop_before + crossings + stop_after
537
+ else
538
+ crossings
539
+ end
540
+ end
541
+
542
+ private def round_to_nearest_quantize(value, nearest = nil)
543
+ v = (value - @reference) / @step_size
544
+
545
+ if nearest
546
+ a = v.floor * @step_size + @reference
547
+ b = v.ceil * @step_size + @reference
548
+
549
+ (nearest - a).abs < (nearest - b).abs ? a : b
550
+ else
551
+ v.round * @step_size + @reference
552
+ end
553
+ end
554
+ end
555
+
556
+ private_constant :PredictiveQuantizer
557
+ end
558
+ end