lex-cognitive-pendulum 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/Gemfile +11 -0
- data/lex-cognitive-pendulum.gemspec +29 -0
- data/lib/legion/extensions/cognitive_pendulum/client.rb +24 -0
- data/lib/legion/extensions/cognitive_pendulum/helpers/constants.rb +45 -0
- data/lib/legion/extensions/cognitive_pendulum/helpers/pendulum.rb +96 -0
- data/lib/legion/extensions/cognitive_pendulum/helpers/pendulum_engine.rb +85 -0
- data/lib/legion/extensions/cognitive_pendulum/runners/cognitive_pendulum.rb +121 -0
- data/lib/legion/extensions/cognitive_pendulum/version.rb +9 -0
- data/lib/legion/extensions/cognitive_pendulum.rb +17 -0
- data/spec/legion/extensions/cognitive_pendulum/client_spec.rb +25 -0
- data/spec/legion/extensions/cognitive_pendulum/helpers/constants_spec.rb +103 -0
- data/spec/legion/extensions/cognitive_pendulum/helpers/pendulum_engine_spec.rb +198 -0
- data/spec/legion/extensions/cognitive_pendulum/helpers/pendulum_spec.rb +247 -0
- data/spec/legion/extensions/cognitive_pendulum/runners/cognitive_pendulum_spec.rb +250 -0
- data/spec/spec_helper.rb +20 -0
- metadata +76 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f99df9563513ea075e00bd072f2e4c2157e0bcebb7ccd533c824e45bf26ac574
|
|
4
|
+
data.tar.gz: c63527d035a0a23b9d51e0be50316fcf006713c2f1cff1227451a189009430c8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c97ffd26c6de16c060383d895d7377d383f1457d153006ffba08b021986558b38c954227e88e1a71993aa0cbc3676be90bda5f5640aa89ab4bc14955f3d855b4
|
|
7
|
+
data.tar.gz: e27db8176a304f1ec1de57cc1c25bd91db0e769929a8dfd4aa618a17b300e2978ccfcd6e9ed3548f97ab18a732081af9e5293fb225b34fa5c57b1124d549fd11
|
data/Gemfile
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/legion/extensions/cognitive_pendulum/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'lex-cognitive-pendulum'
|
|
7
|
+
spec.version = Legion::Extensions::CognitivePendulum::VERSION
|
|
8
|
+
spec.authors = ['Esity']
|
|
9
|
+
spec.email = ['matthewdiverson@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'LEX Cognitive Pendulum'
|
|
12
|
+
spec.description = 'Models oscillation between cognitive poles with amplitude, period, damping, and resonance detection'
|
|
13
|
+
spec.homepage = 'https://github.com/LegionIO/lex-cognitive-pendulum'
|
|
14
|
+
spec.license = 'MIT'
|
|
15
|
+
spec.required_ruby_version = '>= 3.4'
|
|
16
|
+
|
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
18
|
+
spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-cognitive-pendulum'
|
|
19
|
+
spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-cognitive-pendulum'
|
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-cognitive-pendulum'
|
|
21
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-cognitive-pendulum/issues'
|
|
22
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
23
|
+
|
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
25
|
+
Dir.glob('{lib,spec}/**/*') + %w[lex-cognitive-pendulum.gemspec Gemfile]
|
|
26
|
+
end
|
|
27
|
+
spec.require_paths = ['lib']
|
|
28
|
+
spec.add_development_dependency 'legion-gaia'
|
|
29
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/cognitive_pendulum/helpers/constants'
|
|
4
|
+
require 'legion/extensions/cognitive_pendulum/helpers/pendulum'
|
|
5
|
+
require 'legion/extensions/cognitive_pendulum/helpers/pendulum_engine'
|
|
6
|
+
require 'legion/extensions/cognitive_pendulum/runners/cognitive_pendulum'
|
|
7
|
+
|
|
8
|
+
module Legion
|
|
9
|
+
module Extensions
|
|
10
|
+
module CognitivePendulum
|
|
11
|
+
class Client
|
|
12
|
+
include Runners::CognitivePendulum
|
|
13
|
+
|
|
14
|
+
def initialize(**)
|
|
15
|
+
@pendulum_engine = Helpers::PendulumEngine.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
attr_reader :pendulum_engine
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitivePendulum
|
|
6
|
+
module Helpers
|
|
7
|
+
module Constants
|
|
8
|
+
POLE_PAIRS = {
|
|
9
|
+
certainty_doubt: %i[certainty doubt],
|
|
10
|
+
focus_diffusion: %i[focus diffusion],
|
|
11
|
+
analysis_intuition: %i[analysis intuition],
|
|
12
|
+
approach_avoidance: %i[approach avoidance],
|
|
13
|
+
convergent_divergent: %i[convergent divergent]
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
DAMPING_RATE = 0.01
|
|
17
|
+
|
|
18
|
+
MAX_PENDULUMS = 100
|
|
19
|
+
|
|
20
|
+
AMPLITUDE_LABELS = {
|
|
21
|
+
(0.0..0.2) => :minimal,
|
|
22
|
+
(0.2..0.4) => :low,
|
|
23
|
+
(0.4..0.6) => :moderate,
|
|
24
|
+
(0.6..0.8) => :high,
|
|
25
|
+
(0.8..1.0) => :maximal
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
module_function
|
|
29
|
+
|
|
30
|
+
def valid_pole_pair?(pole_pair)
|
|
31
|
+
POLE_PAIRS.key?(pole_pair)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def amplitude_label(amplitude)
|
|
35
|
+
clamped = amplitude.clamp(0.0, 1.0)
|
|
36
|
+
AMPLITUDE_LABELS.each do |range, label|
|
|
37
|
+
return label if range.cover?(clamped)
|
|
38
|
+
end
|
|
39
|
+
:maximal
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitivePendulum
|
|
6
|
+
module Helpers
|
|
7
|
+
class Pendulum
|
|
8
|
+
attr_reader :id, :pole_pair, :amplitude, :period, :damping, :current_position, :created_at, :swings
|
|
9
|
+
|
|
10
|
+
def initialize(pole_pair:, amplitude: 0.5, period: 10.0, damping: Constants::DAMPING_RATE)
|
|
11
|
+
raise ArgumentError, "unknown pole_pair: #{pole_pair}" unless Constants.valid_pole_pair?(pole_pair)
|
|
12
|
+
raise ArgumentError, 'amplitude must be 0.0..1.0' unless amplitude.between?(0.0, 1.0)
|
|
13
|
+
raise ArgumentError, 'period must be positive' unless period.positive?
|
|
14
|
+
raise ArgumentError, 'damping must be >= 0' unless damping >= 0.0
|
|
15
|
+
|
|
16
|
+
@id = SecureRandom.uuid
|
|
17
|
+
@pole_pair = pole_pair
|
|
18
|
+
@amplitude = amplitude.clamp(0.0, 1.0)
|
|
19
|
+
@period = period.to_f
|
|
20
|
+
@damping = damping.to_f
|
|
21
|
+
@current_position = 0.0
|
|
22
|
+
@created_at = Time.now.utc
|
|
23
|
+
@swings = 0
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def swing!(force: 0.0)
|
|
27
|
+
force_clamped = force.to_f.clamp(-1.0, 1.0)
|
|
28
|
+
@current_position = (@current_position + force_clamped).clamp(-1.0, 1.0)
|
|
29
|
+
@swings += 1
|
|
30
|
+
@current_position
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def damp!
|
|
34
|
+
@amplitude = (@amplitude * (1.0 - @damping)).round(10).clamp(0.0, 1.0)
|
|
35
|
+
@current_position = (@current_position * (1.0 - @damping)).round(10).clamp(-1.0, 1.0)
|
|
36
|
+
@amplitude
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def position_at(time)
|
|
40
|
+
elapsed = time.to_f
|
|
41
|
+
angular_frequency = (2.0 * Math::PI) / @period
|
|
42
|
+
decay = Math.exp(-@damping * elapsed)
|
|
43
|
+
(@amplitude * decay * Math.cos(angular_frequency * elapsed)).round(10).clamp(-1.0, 1.0)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def at_pole_a?
|
|
47
|
+
@current_position <= -0.5
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def at_pole_b?
|
|
51
|
+
@current_position >= 0.5
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def amplitude_label
|
|
55
|
+
Constants.amplitude_label(@amplitude)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def resonant_with?(frequency)
|
|
59
|
+
return false unless frequency.positive?
|
|
60
|
+
|
|
61
|
+
natural_frequency = 1.0 / @period
|
|
62
|
+
ratio = frequency / natural_frequency
|
|
63
|
+
(ratio - 1.0).abs <= 0.05
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def dominant_pole
|
|
67
|
+
poles = Constants::POLE_PAIRS.fetch(@pole_pair)
|
|
68
|
+
return :neutral if @current_position.abs < 0.1
|
|
69
|
+
|
|
70
|
+
@current_position.negative? ? poles[0] : poles[1]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def to_h
|
|
74
|
+
poles = Constants::POLE_PAIRS.fetch(@pole_pair)
|
|
75
|
+
{
|
|
76
|
+
id: @id,
|
|
77
|
+
pole_pair: @pole_pair,
|
|
78
|
+
pole_a: poles[0],
|
|
79
|
+
pole_b: poles[1],
|
|
80
|
+
amplitude: @amplitude.round(10),
|
|
81
|
+
amplitude_label: amplitude_label,
|
|
82
|
+
period: @period,
|
|
83
|
+
damping: @damping,
|
|
84
|
+
current_position: @current_position.round(10),
|
|
85
|
+
dominant_pole: dominant_pole,
|
|
86
|
+
at_pole_a: at_pole_a?,
|
|
87
|
+
at_pole_b: at_pole_b?,
|
|
88
|
+
swings: @swings,
|
|
89
|
+
created_at: @created_at
|
|
90
|
+
}
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitivePendulum
|
|
6
|
+
module Helpers
|
|
7
|
+
class PendulumEngine
|
|
8
|
+
attr_reader :pendulums
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@pendulums = {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create_pendulum(pole_pair:, amplitude: 0.5, period: 10.0, damping: Constants::DAMPING_RATE)
|
|
15
|
+
raise ArgumentError, "max pendulums (#{Constants::MAX_PENDULUMS}) reached" if @pendulums.size >= Constants::MAX_PENDULUMS
|
|
16
|
+
|
|
17
|
+
pendulum = Pendulum.new(
|
|
18
|
+
pole_pair: pole_pair,
|
|
19
|
+
amplitude: amplitude,
|
|
20
|
+
period: period,
|
|
21
|
+
damping: damping
|
|
22
|
+
)
|
|
23
|
+
@pendulums[pendulum.id] = pendulum
|
|
24
|
+
pendulum
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def swing(pendulum_id, force: 0.0)
|
|
28
|
+
p = @pendulums[pendulum_id]
|
|
29
|
+
return nil unless p
|
|
30
|
+
|
|
31
|
+
p.swing!(force: force)
|
|
32
|
+
p
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def damp_all!
|
|
36
|
+
@pendulums.each_value(&:damp!)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def check_resonance(frequency)
|
|
40
|
+
return [] unless frequency.positive?
|
|
41
|
+
|
|
42
|
+
@pendulums.values.select { |p| p.resonant_with?(frequency) }.map(&:id)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def dominant_pole(pendulum_id)
|
|
46
|
+
p = @pendulums[pendulum_id]
|
|
47
|
+
return nil unless p
|
|
48
|
+
|
|
49
|
+
p.dominant_pole
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def most_active(limit: 5)
|
|
53
|
+
@pendulums.values
|
|
54
|
+
.sort_by { |p| -p.amplitude }
|
|
55
|
+
.first(limit)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def most_damped(limit: 5)
|
|
59
|
+
@pendulums.values
|
|
60
|
+
.sort_by(&:amplitude)
|
|
61
|
+
.first(limit)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def pendulum_report
|
|
65
|
+
{
|
|
66
|
+
total: @pendulums.size,
|
|
67
|
+
max: Constants::MAX_PENDULUMS,
|
|
68
|
+
pole_pairs: @pendulums.values.group_by(&:pole_pair).transform_values(&:count),
|
|
69
|
+
most_active: most_active(limit: 3).map(&:to_h),
|
|
70
|
+
most_damped: most_damped(limit: 3).map(&:to_h)
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def get(pendulum_id)
|
|
75
|
+
@pendulums[pendulum_id]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def count
|
|
79
|
+
@pendulums.size
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitivePendulum
|
|
6
|
+
module Runners
|
|
7
|
+
module CognitivePendulum
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
9
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
|
|
11
|
+
def create_pendulum(pole_pair:, amplitude: 0.5, period: 10.0, damping: Helpers::Constants::DAMPING_RATE, **)
|
|
12
|
+
unless Helpers::Constants.valid_pole_pair?(pole_pair)
|
|
13
|
+
return { success: false, error: :invalid_pole_pair, valid_pairs: Helpers::Constants::POLE_PAIRS.keys }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
raise ArgumentError, 'amplitude must be 0.0..1.0' unless amplitude.between?(0.0, 1.0)
|
|
17
|
+
raise ArgumentError, 'period must be positive' unless period.positive?
|
|
18
|
+
|
|
19
|
+
if pendulum_engine.count >= Helpers::Constants::MAX_PENDULUMS
|
|
20
|
+
return { success: false, error: :max_pendulums_reached, max: Helpers::Constants::MAX_PENDULUMS }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
pendulum = pendulum_engine.create_pendulum(
|
|
24
|
+
pole_pair: pole_pair,
|
|
25
|
+
amplitude: amplitude,
|
|
26
|
+
period: period,
|
|
27
|
+
damping: damping
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
Legion::Logging.debug "[cognitive_pendulum] created pole_pair=#{pole_pair} id=#{pendulum.id[0..7]}"
|
|
31
|
+
{ success: true, pendulum_id: pendulum.id, pole_pair: pole_pair, amplitude: pendulum.amplitude }
|
|
32
|
+
rescue ArgumentError => e
|
|
33
|
+
{ success: false, error: :argument_error, message: e.message }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def swing(pendulum_id:, force: 0.0, **)
|
|
37
|
+
result = pendulum_engine.swing(pendulum_id, force: force)
|
|
38
|
+
unless result
|
|
39
|
+
Legion::Logging.debug "[cognitive_pendulum] swing failed: #{pendulum_id[0..7]} not found"
|
|
40
|
+
return { success: false, error: :not_found }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
Legion::Logging.debug "[cognitive_pendulum] swing id=#{pendulum_id[0..7]} position=#{result.current_position.round(4)}"
|
|
44
|
+
{ success: true, pendulum_id: pendulum_id, current_position: result.current_position, dominant_pole: result.dominant_pole }
|
|
45
|
+
rescue ArgumentError => e
|
|
46
|
+
{ success: false, error: :argument_error, message: e.message }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def damp_all(**)
|
|
50
|
+
pendulum_engine.damp_all!
|
|
51
|
+
count = pendulum_engine.count
|
|
52
|
+
Legion::Logging.debug "[cognitive_pendulum] damped all (#{count} pendulums)"
|
|
53
|
+
{ success: true, damped: count }
|
|
54
|
+
rescue ArgumentError => e
|
|
55
|
+
{ success: false, error: :argument_error, message: e.message }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def check_resonance(frequency:, **)
|
|
59
|
+
return { success: false, error: :invalid_frequency, message: 'frequency must be positive' } unless frequency.to_f.positive?
|
|
60
|
+
|
|
61
|
+
resonant_ids = pendulum_engine.check_resonance(frequency.to_f)
|
|
62
|
+
Legion::Logging.debug "[cognitive_pendulum] resonance check frequency=#{frequency} matches=#{resonant_ids.size}"
|
|
63
|
+
{ success: true, frequency: frequency, resonant_pendulum_ids: resonant_ids, count: resonant_ids.size }
|
|
64
|
+
rescue ArgumentError => e
|
|
65
|
+
{ success: false, error: :argument_error, message: e.message }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def get_dominant_pole(pendulum_id:, **)
|
|
69
|
+
pole = pendulum_engine.dominant_pole(pendulum_id)
|
|
70
|
+
if pole.nil?
|
|
71
|
+
Legion::Logging.debug "[cognitive_pendulum] dominant_pole failed: #{pendulum_id[0..7]} not found"
|
|
72
|
+
return { success: false, error: :not_found }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
Legion::Logging.debug "[cognitive_pendulum] dominant_pole id=#{pendulum_id[0..7]} pole=#{pole}"
|
|
76
|
+
{ success: true, pendulum_id: pendulum_id, dominant_pole: pole }
|
|
77
|
+
rescue ArgumentError => e
|
|
78
|
+
{ success: false, error: :argument_error, message: e.message }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def most_active(limit: 5, **)
|
|
82
|
+
pendulums = pendulum_engine.most_active(limit: limit)
|
|
83
|
+
Legion::Logging.debug "[cognitive_pendulum] most_active limit=#{limit} found=#{pendulums.size}"
|
|
84
|
+
{ success: true, pendulums: pendulums.map(&:to_h), count: pendulums.size }
|
|
85
|
+
rescue ArgumentError => e
|
|
86
|
+
{ success: false, error: :argument_error, message: e.message }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def most_damped(limit: 5, **)
|
|
90
|
+
pendulums = pendulum_engine.most_damped(limit: limit)
|
|
91
|
+
Legion::Logging.debug "[cognitive_pendulum] most_damped limit=#{limit} found=#{pendulums.size}"
|
|
92
|
+
{ success: true, pendulums: pendulums.map(&:to_h), count: pendulums.size }
|
|
93
|
+
rescue ArgumentError => e
|
|
94
|
+
{ success: false, error: :argument_error, message: e.message }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def pendulum_report(**)
|
|
98
|
+
report = pendulum_engine.pendulum_report
|
|
99
|
+
Legion::Logging.debug "[cognitive_pendulum] report total=#{report[:total]}"
|
|
100
|
+
{ success: true, report: report }
|
|
101
|
+
rescue ArgumentError => e
|
|
102
|
+
{ success: false, error: :argument_error, message: e.message }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def get_pendulum(pendulum_id:, **)
|
|
106
|
+
p = pendulum_engine.get(pendulum_id)
|
|
107
|
+
p ? { success: true, pendulum: p.to_h } : { success: false, error: :not_found }
|
|
108
|
+
rescue ArgumentError => e
|
|
109
|
+
{ success: false, error: :argument_error, message: e.message }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def pendulum_engine
|
|
115
|
+
@pendulum_engine ||= Helpers::PendulumEngine.new
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
require_relative 'cognitive_pendulum/version'
|
|
5
|
+
require_relative 'cognitive_pendulum/helpers/constants'
|
|
6
|
+
require_relative 'cognitive_pendulum/helpers/pendulum'
|
|
7
|
+
require_relative 'cognitive_pendulum/helpers/pendulum_engine'
|
|
8
|
+
require_relative 'cognitive_pendulum/runners/cognitive_pendulum'
|
|
9
|
+
require_relative 'cognitive_pendulum/client'
|
|
10
|
+
|
|
11
|
+
module Legion
|
|
12
|
+
module Extensions
|
|
13
|
+
module CognitivePendulum
|
|
14
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/cognitive_pendulum/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::CognitivePendulum::Client do
|
|
6
|
+
it 'responds to runner methods' do
|
|
7
|
+
client = described_class.new
|
|
8
|
+
expect(client).to respond_to(:create_pendulum)
|
|
9
|
+
expect(client).to respond_to(:swing)
|
|
10
|
+
expect(client).to respond_to(:damp_all)
|
|
11
|
+
expect(client).to respond_to(:check_resonance)
|
|
12
|
+
expect(client).to respond_to(:get_dominant_pole)
|
|
13
|
+
expect(client).to respond_to(:most_active)
|
|
14
|
+
expect(client).to respond_to(:most_damped)
|
|
15
|
+
expect(client).to respond_to(:pendulum_report)
|
|
16
|
+
expect(client).to respond_to(:get_pendulum)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'creates a fresh engine per instance' do
|
|
20
|
+
c1 = described_class.new
|
|
21
|
+
c2 = described_class.new
|
|
22
|
+
c1.create_pendulum(pole_pair: :certainty_doubt)
|
|
23
|
+
expect(c2.pendulum_report[:report][:total]).to eq(0)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::CognitivePendulum::Helpers::Constants do
|
|
4
|
+
describe 'POLE_PAIRS' do
|
|
5
|
+
it 'defines five pole pairs' do
|
|
6
|
+
expect(described_class::POLE_PAIRS.size).to eq(5)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it 'includes certainty_doubt' do
|
|
10
|
+
expect(described_class::POLE_PAIRS[:certainty_doubt]).to eq(%i[certainty doubt])
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'includes focus_diffusion' do
|
|
14
|
+
expect(described_class::POLE_PAIRS[:focus_diffusion]).to eq(%i[focus diffusion])
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'includes analysis_intuition' do
|
|
18
|
+
expect(described_class::POLE_PAIRS[:analysis_intuition]).to eq(%i[analysis intuition])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'includes approach_avoidance' do
|
|
22
|
+
expect(described_class::POLE_PAIRS[:approach_avoidance]).to eq(%i[approach avoidance])
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'includes convergent_divergent' do
|
|
26
|
+
expect(described_class::POLE_PAIRS[:convergent_divergent]).to eq(%i[convergent divergent])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'is frozen' do
|
|
30
|
+
expect(described_class::POLE_PAIRS).to be_frozen
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe 'DAMPING_RATE' do
|
|
35
|
+
it 'is 0.01' do
|
|
36
|
+
expect(described_class::DAMPING_RATE).to eq(0.01)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe 'MAX_PENDULUMS' do
|
|
41
|
+
it 'is 100' do
|
|
42
|
+
expect(described_class::MAX_PENDULUMS).to eq(100)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe 'AMPLITUDE_LABELS' do
|
|
47
|
+
it 'has 5 ranges' do
|
|
48
|
+
expect(described_class::AMPLITUDE_LABELS.size).to eq(5)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'is frozen' do
|
|
52
|
+
expect(described_class::AMPLITUDE_LABELS).to be_frozen
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe '.valid_pole_pair?' do
|
|
57
|
+
it 'returns true for known pole pairs' do
|
|
58
|
+
expect(described_class.valid_pole_pair?(:certainty_doubt)).to be true
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'returns false for unknown pole pairs' do
|
|
62
|
+
expect(described_class.valid_pole_pair?(:unknown)).to be false
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'returns false for nil' do
|
|
66
|
+
expect(described_class.valid_pole_pair?(nil)).to be false
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe '.amplitude_label' do
|
|
71
|
+
it 'returns :minimal for 0.0' do
|
|
72
|
+
expect(described_class.amplitude_label(0.0)).to eq(:minimal)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'returns :minimal for 0.1' do
|
|
76
|
+
expect(described_class.amplitude_label(0.1)).to eq(:minimal)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'returns :low for 0.3' do
|
|
80
|
+
expect(described_class.amplitude_label(0.3)).to eq(:low)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'returns :moderate for 0.5' do
|
|
84
|
+
expect(described_class.amplitude_label(0.5)).to eq(:moderate)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'returns :high for 0.7' do
|
|
88
|
+
expect(described_class.amplitude_label(0.7)).to eq(:high)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'returns :maximal for 1.0' do
|
|
92
|
+
expect(described_class.amplitude_label(1.0)).to eq(:maximal)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'clamps values above 1.0' do
|
|
96
|
+
expect(described_class.amplitude_label(1.5)).to eq(:maximal)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'clamps values below 0.0' do
|
|
100
|
+
expect(described_class.amplitude_label(-0.5)).to eq(:minimal)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|