lex-cognitive-rhythm 0.1.0
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.
- checksums.yaml +7 -0
- data/lib/legion/extensions/cognitive_rhythm/client.rb +15 -0
- data/lib/legion/extensions/cognitive_rhythm/helpers/constants.rb +38 -0
- data/lib/legion/extensions/cognitive_rhythm/helpers/rhythm.rb +89 -0
- data/lib/legion/extensions/cognitive_rhythm/helpers/rhythm_engine.rb +163 -0
- data/lib/legion/extensions/cognitive_rhythm/runners/cognitive_rhythm.rb +78 -0
- data/lib/legion/extensions/cognitive_rhythm/version.rb +9 -0
- metadata +67 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b10cd9e09140796135a50743e3221fb1898e54330483eb221a75cecc2ccbb4ba
|
|
4
|
+
data.tar.gz: 7e80c1170efa27c27c9ad8ef80a90f27799cff2215f10e320c5b966a6df217e6
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 38dd4deb2b004ce766ec60114adb12be669caa24159ccc558a990d318824e737682437d535a7d413d70fc6572de647bdc18ed9a3b212b7caeeeb21c5904f1f30
|
|
7
|
+
data.tar.gz: e95dbb8690f885fcf0a4bb95d58f1a39abcfcca132c1beaf96e385e7184f5ac56d4a07092d58072eaa0a961486edf939939250ecefc47af57499e9df3424adb1
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveRhythm
|
|
6
|
+
class Client
|
|
7
|
+
include Runners::CognitiveRhythm
|
|
8
|
+
|
|
9
|
+
def initialize(engine: nil)
|
|
10
|
+
@engine = engine || Helpers::RhythmEngine.new
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveRhythm
|
|
6
|
+
module Helpers
|
|
7
|
+
module Constants
|
|
8
|
+
ULTRADIAN_PERIOD = 5400
|
|
9
|
+
CIRCADIAN_PERIOD = 86_400
|
|
10
|
+
MIN_AMPLITUDE = 0.0
|
|
11
|
+
MAX_AMPLITUDE = 1.0
|
|
12
|
+
DEFAULT_PHASE_OFFSET = 0.0
|
|
13
|
+
MAX_RHYTHMS = 20
|
|
14
|
+
MAX_HISTORY = 200
|
|
15
|
+
|
|
16
|
+
RHYTHM_TYPES = %i[ultradian circadian custom].freeze
|
|
17
|
+
|
|
18
|
+
COGNITIVE_DIMENSIONS = %i[alertness creativity focus analytical social emotional].freeze
|
|
19
|
+
|
|
20
|
+
PHASE_LABELS = {
|
|
21
|
+
(0.0...0.25) => :rising,
|
|
22
|
+
(0.25...0.5) => :peak,
|
|
23
|
+
(0.5...0.75) => :falling,
|
|
24
|
+
(0.75..1.0) => :trough
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
AMPLITUDE_LABELS = {
|
|
28
|
+
(0.8..) => :high,
|
|
29
|
+
(0.6...0.8) => :moderate_high,
|
|
30
|
+
(0.4...0.6) => :moderate,
|
|
31
|
+
(0.2...0.4) => :moderate_low,
|
|
32
|
+
(..0.2) => :low
|
|
33
|
+
}.freeze
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveRhythm
|
|
6
|
+
module Helpers
|
|
7
|
+
class Rhythm
|
|
8
|
+
include Constants
|
|
9
|
+
|
|
10
|
+
attr_reader :id, :name, :rhythm_type, :dimension, :period,
|
|
11
|
+
:amplitude, :phase_offset, :created_at
|
|
12
|
+
|
|
13
|
+
def initialize(id:, name:, rhythm_type:, dimension:, period:,
|
|
14
|
+
amplitude: 0.5, phase_offset: 0.0)
|
|
15
|
+
@id = id
|
|
16
|
+
@name = name
|
|
17
|
+
@rhythm_type = rhythm_type
|
|
18
|
+
@dimension = dimension
|
|
19
|
+
@period = period
|
|
20
|
+
@amplitude = amplitude.to_f.clamp(MIN_AMPLITUDE, MAX_AMPLITUDE)
|
|
21
|
+
@phase_offset = phase_offset.to_f
|
|
22
|
+
@created_at = Time.now.utc
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def value_at(time)
|
|
26
|
+
elapsed = time.to_f - @created_at.to_f + @phase_offset
|
|
27
|
+
@amplitude * (0.5 + (0.5 * Math.sin(2 * Math::PI * (elapsed / @period))))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def current_value
|
|
31
|
+
value_at(Time.now.utc)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def phase_at(time)
|
|
35
|
+
elapsed = time.to_f - @created_at.to_f + @phase_offset
|
|
36
|
+
(elapsed % @period) / @period
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def current_phase
|
|
40
|
+
phase_at(Time.now.utc)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def phase_label
|
|
44
|
+
phase = current_phase
|
|
45
|
+
PHASE_LABELS.each { |range, label| return label if range.cover?(phase) }
|
|
46
|
+
:trough
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def amplitude_label
|
|
50
|
+
AMPLITUDE_LABELS.each { |range, label| return label if range.cover?(@amplitude) }
|
|
51
|
+
:low
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def peak?
|
|
55
|
+
phase_label == :peak
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def trough?
|
|
59
|
+
phase_label == :trough
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def rising?
|
|
63
|
+
phase_label == :rising
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def falling?
|
|
67
|
+
phase_label == :falling
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def to_h
|
|
71
|
+
{
|
|
72
|
+
id: @id,
|
|
73
|
+
name: @name,
|
|
74
|
+
rhythm_type: @rhythm_type,
|
|
75
|
+
dimension: @dimension,
|
|
76
|
+
period: @period,
|
|
77
|
+
amplitude: @amplitude,
|
|
78
|
+
phase_offset: @phase_offset,
|
|
79
|
+
current_value: current_value.round(4),
|
|
80
|
+
current_phase: current_phase.round(4),
|
|
81
|
+
phase_label: phase_label,
|
|
82
|
+
amplitude_label: amplitude_label
|
|
83
|
+
}
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveRhythm
|
|
6
|
+
module Helpers
|
|
7
|
+
class RhythmEngine
|
|
8
|
+
include Constants
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@rhythms = {}
|
|
12
|
+
@counter = 0
|
|
13
|
+
@history = []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def add_rhythm(name:, rhythm_type:, dimension:, period: nil, amplitude: 0.5, phase_offset: 0.0)
|
|
17
|
+
return nil if @rhythms.size >= MAX_RHYTHMS
|
|
18
|
+
|
|
19
|
+
resolved_period = resolve_period(rhythm_type, period)
|
|
20
|
+
return nil if resolved_period.nil?
|
|
21
|
+
|
|
22
|
+
@counter += 1
|
|
23
|
+
id = :"rhythm_#{@counter}"
|
|
24
|
+
rhythm = Rhythm.new(
|
|
25
|
+
id: id,
|
|
26
|
+
name: name,
|
|
27
|
+
rhythm_type: rhythm_type,
|
|
28
|
+
dimension: dimension,
|
|
29
|
+
period: resolved_period,
|
|
30
|
+
amplitude: amplitude,
|
|
31
|
+
phase_offset: phase_offset
|
|
32
|
+
)
|
|
33
|
+
@rhythms[id] = rhythm
|
|
34
|
+
rhythm
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def remove_rhythm(rhythm_id:)
|
|
38
|
+
removed = @rhythms.delete(rhythm_id)
|
|
39
|
+
removed ? { success: true, rhythm_id: rhythm_id } : { success: false, reason: :not_found }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def dimension_value(dimension:)
|
|
43
|
+
matching = @rhythms.values.select { |r| r.dimension == dimension }
|
|
44
|
+
return 0.0 if matching.empty?
|
|
45
|
+
|
|
46
|
+
matching.sum(&:current_value) / matching.size
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def current_state
|
|
50
|
+
COGNITIVE_DIMENSIONS.to_h do |dim|
|
|
51
|
+
[dim, dimension_value(dimension: dim).round(4)]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def optimal_for(dimension:)
|
|
56
|
+
matching = @rhythms.values.select { |r| r.dimension == dimension }
|
|
57
|
+
return false if matching.empty?
|
|
58
|
+
|
|
59
|
+
matching.any?(&:peak?)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def best_time_for(dimension:, within: 3600)
|
|
63
|
+
matching = @rhythms.values.select { |r| r.dimension == dimension }
|
|
64
|
+
return nil if matching.empty?
|
|
65
|
+
|
|
66
|
+
now = Time.now.utc.to_f
|
|
67
|
+
step = 60
|
|
68
|
+
steps = within / step
|
|
69
|
+
|
|
70
|
+
best_time = nil
|
|
71
|
+
best_value = -1.0
|
|
72
|
+
|
|
73
|
+
(0..steps).each do |i|
|
|
74
|
+
t = now + (i * step)
|
|
75
|
+
value = matching.sum { |r| r.value_at(t) } / matching.size
|
|
76
|
+
if value > best_value
|
|
77
|
+
best_value = value
|
|
78
|
+
best_time = t
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
best_time ? Time.at(best_time).utc : nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def synchronize(rhythm_ids:)
|
|
86
|
+
rhythms = rhythm_ids.map { |id| @rhythms[id] }.compact
|
|
87
|
+
return { success: false, reason: :not_found } if rhythms.empty?
|
|
88
|
+
|
|
89
|
+
reference_phase = rhythms.first.current_phase
|
|
90
|
+
rhythms.each do |r|
|
|
91
|
+
current = r.current_phase
|
|
92
|
+
offset_adjustment = (reference_phase - current) * r.period
|
|
93
|
+
instance_variable_set_phase_offset(r, r.phase_offset + offset_adjustment)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
{ success: true, synchronized: rhythms.map(&:id) }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def cognitive_profile
|
|
100
|
+
current = current_state
|
|
101
|
+
current.each_with_object({}) do |(dim, value), hash|
|
|
102
|
+
phase_label = dominant_phase_label(dim)
|
|
103
|
+
amplitude_label = dominant_amplitude_label(dim)
|
|
104
|
+
hash[dim] = {
|
|
105
|
+
value: value,
|
|
106
|
+
phase_label: phase_label,
|
|
107
|
+
amplitude_label: amplitude_label,
|
|
108
|
+
optimal: optimal_for(dimension: dim)
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def peak_dimensions
|
|
114
|
+
COGNITIVE_DIMENSIONS.select { |dim| optimal_for(dimension: dim) }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def trough_dimensions
|
|
118
|
+
COGNITIVE_DIMENSIONS.select do |dim|
|
|
119
|
+
matching = @rhythms.values.select { |r| r.dimension == dim }
|
|
120
|
+
matching.any?(&:trough?)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def to_h
|
|
125
|
+
{
|
|
126
|
+
rhythm_count: @rhythms.size,
|
|
127
|
+
rhythms: @rhythms.values.map(&:to_h),
|
|
128
|
+
current_state: current_state
|
|
129
|
+
}
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
def resolve_period(rhythm_type, period)
|
|
135
|
+
case rhythm_type
|
|
136
|
+
when :ultradian then ULTRADIAN_PERIOD
|
|
137
|
+
when :circadian then CIRCADIAN_PERIOD
|
|
138
|
+
when :custom then period
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def dominant_phase_label(dimension)
|
|
143
|
+
matching = @rhythms.values.select { |r| r.dimension == dimension }
|
|
144
|
+
return :none if matching.empty?
|
|
145
|
+
|
|
146
|
+
matching.max_by(&:current_value)&.phase_label || :none
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def dominant_amplitude_label(dimension)
|
|
150
|
+
matching = @rhythms.values.select { |r| r.dimension == dimension }
|
|
151
|
+
return :none if matching.empty?
|
|
152
|
+
|
|
153
|
+
matching.max_by(&:amplitude)&.amplitude_label || :none
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def instance_variable_set_phase_offset(rhythm, new_offset)
|
|
157
|
+
rhythm.instance_variable_set(:@phase_offset, new_offset.to_f)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveRhythm
|
|
6
|
+
module Runners
|
|
7
|
+
module CognitiveRhythm
|
|
8
|
+
include Helpers::Constants
|
|
9
|
+
include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
|
|
10
|
+
|
|
11
|
+
def add_cognitive_rhythm(name:, rhythm_type:, dimension:, period: nil,
|
|
12
|
+
amplitude: 0.5, phase_offset: 0.0, **)
|
|
13
|
+
rhythm = engine.add_rhythm(
|
|
14
|
+
name: name,
|
|
15
|
+
rhythm_type: rhythm_type,
|
|
16
|
+
dimension: dimension,
|
|
17
|
+
period: period,
|
|
18
|
+
amplitude: amplitude,
|
|
19
|
+
phase_offset: phase_offset
|
|
20
|
+
)
|
|
21
|
+
return { success: false, reason: :limit_reached_or_invalid } unless rhythm
|
|
22
|
+
|
|
23
|
+
{ success: true, rhythm_id: rhythm.id, name: rhythm.name, dimension: rhythm.dimension }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def remove_cognitive_rhythm(rhythm_id:, **)
|
|
27
|
+
result = engine.remove_rhythm(rhythm_id: rhythm_id)
|
|
28
|
+
result[:success] ? result : { success: false, reason: result[:reason] }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def current_rhythm_state(**)
|
|
32
|
+
{ success: true, state: engine.current_state }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def dimension_rhythm_value(dimension:, **)
|
|
36
|
+
value = engine.dimension_value(dimension: dimension)
|
|
37
|
+
{ success: true, dimension: dimension, value: value.round(4) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def optimal_for_task(dimension:, **)
|
|
41
|
+
{ success: true, dimension: dimension, optimal: engine.optimal_for(dimension: dimension) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def best_time_for_task(dimension:, within: 3600, **)
|
|
45
|
+
time = engine.best_time_for(dimension: dimension, within: within)
|
|
46
|
+
return { success: false, reason: :no_rhythms_for_dimension } unless time
|
|
47
|
+
|
|
48
|
+
{ success: true, dimension: dimension, best_time: time.iso8601, within_seconds: within }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def cognitive_rhythm_profile(**)
|
|
52
|
+
{ success: true, profile: engine.cognitive_profile }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def peak_cognitive_dimensions(**)
|
|
56
|
+
dims = engine.peak_dimensions
|
|
57
|
+
{ success: true, dimensions: dims, count: dims.size }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def trough_cognitive_dimensions(**)
|
|
61
|
+
dims = engine.trough_dimensions
|
|
62
|
+
{ success: true, dimensions: dims, count: dims.size }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def cognitive_rhythm_stats(**)
|
|
66
|
+
{ success: true }.merge(engine.to_h)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def engine
|
|
72
|
+
@engine ||= Helpers::RhythmEngine.new
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lex-cognitive-rhythm
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Esity
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: legion-gaia
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
description: Ultradian and circadian cognitive rhythm engine for LegionIO — models
|
|
27
|
+
attention peaks, energy troughs, and creative windows
|
|
28
|
+
email:
|
|
29
|
+
- matthewdiverson@gmail.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- lib/legion/extensions/cognitive_rhythm/client.rb
|
|
35
|
+
- lib/legion/extensions/cognitive_rhythm/helpers/constants.rb
|
|
36
|
+
- lib/legion/extensions/cognitive_rhythm/helpers/rhythm.rb
|
|
37
|
+
- lib/legion/extensions/cognitive_rhythm/helpers/rhythm_engine.rb
|
|
38
|
+
- lib/legion/extensions/cognitive_rhythm/runners/cognitive_rhythm.rb
|
|
39
|
+
- lib/legion/extensions/cognitive_rhythm/version.rb
|
|
40
|
+
homepage: https://github.com/LegionIO/lex-cognitive-rhythm
|
|
41
|
+
licenses:
|
|
42
|
+
- MIT
|
|
43
|
+
metadata:
|
|
44
|
+
homepage_uri: https://github.com/LegionIO/lex-cognitive-rhythm
|
|
45
|
+
source_code_uri: https://github.com/LegionIO/lex-cognitive-rhythm
|
|
46
|
+
documentation_uri: https://github.com/LegionIO/lex-cognitive-rhythm/blob/master/README.md
|
|
47
|
+
changelog_uri: https://github.com/LegionIO/lex-cognitive-rhythm/blob/master/CHANGELOG.md
|
|
48
|
+
bug_tracker_uri: https://github.com/LegionIO/lex-cognitive-rhythm/issues
|
|
49
|
+
rubygems_mfa_required: 'true'
|
|
50
|
+
rdoc_options: []
|
|
51
|
+
require_paths:
|
|
52
|
+
- lib
|
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
54
|
+
requirements:
|
|
55
|
+
- - ">="
|
|
56
|
+
- !ruby/object:Gem::Version
|
|
57
|
+
version: '3.4'
|
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - ">="
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '0'
|
|
63
|
+
requirements: []
|
|
64
|
+
rubygems_version: 3.6.9
|
|
65
|
+
specification_version: 4
|
|
66
|
+
summary: Cognitive rhythm modelling for LegionIO
|
|
67
|
+
test_files: []
|