lex-feature-binding 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/README.md +62 -0
- data/lib/legion/extensions/feature_binding/actors/decay.rb +41 -0
- data/lib/legion/extensions/feature_binding/client.rb +25 -0
- data/lib/legion/extensions/feature_binding/helpers/binding_field.rb +120 -0
- data/lib/legion/extensions/feature_binding/helpers/bound_object.rb +85 -0
- data/lib/legion/extensions/feature_binding/helpers/constants.rb +48 -0
- data/lib/legion/extensions/feature_binding/helpers/feature.rb +48 -0
- data/lib/legion/extensions/feature_binding/runners/feature_binding.rb +98 -0
- data/lib/legion/extensions/feature_binding/version.rb +9 -0
- data/lib/legion/extensions/feature_binding.rb +17 -0
- metadata +71 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d449f9894bc3dc97a55225d9f6a6878a05fc4a73fae9641bf9825119188e8862
|
|
4
|
+
data.tar.gz: a9459e1caf0817290a4c96ecd8c4b3afee36f8b308b1d00f828f30bc697515b1
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ac2f5a591b2c72076d2870b3601591569121b08b94d2400a32e8f44155b1f0eb7f2d406f440f1991a1d2e78c58406d0e7d9d278bdb3f0d253652fb49638b8d8d
|
|
7
|
+
data.tar.gz: 85d294af88ba119e6282609b25c2afe7e5100511aac34b6a12c8731f8278284e6ac56953a8d5e975b0d7e1b73d426e33c0992b5bb471cea8d371f8dead9759bc
|
data/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# lex-feature-binding
|
|
2
|
+
|
|
3
|
+
Feature binding modeling for the LegionIO brain-modeled cognitive architecture.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
Implements the cognitive binding problem — how separate features from different processing streams are combined into unified object percepts. Registers unbound feature sets across visual, auditory, semantic, spatial, temporal, and affective dimensions, then binds them into coherent bound objects. Detects binding conflicts when feature dimensions contradict each other, and manages a vividness-based object store with decay.
|
|
8
|
+
|
|
9
|
+
Based on Treisman's Feature Integration Theory.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
client = Legion::Extensions::FeatureBinding::Client.new
|
|
15
|
+
|
|
16
|
+
# Register incoming features from multiple sensory streams
|
|
17
|
+
client.register_features(
|
|
18
|
+
features: {
|
|
19
|
+
semantic: 'incoming HTTP request',
|
|
20
|
+
temporal: :recent,
|
|
21
|
+
affective: :neutral,
|
|
22
|
+
spatial: :network_boundary
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
# => { success: true, feature_set_id: "...", dimensions_present: [:semantic, :temporal, :affective, :spatial],
|
|
26
|
+
# ready_to_bind: true }
|
|
27
|
+
|
|
28
|
+
# Bind into a unified object percept
|
|
29
|
+
client.bind_features(feature_set_id: '...', binding_mode: :automatic)
|
|
30
|
+
# => { success: true, object_id: "...", coherence: 0.75, bound: true, conflicts: [] }
|
|
31
|
+
|
|
32
|
+
# Check for feature conflicts before binding
|
|
33
|
+
client.detect_conflicts(feature_set_id: '...')
|
|
34
|
+
# => { conflicts: [], conflict_count: 0, severity: :none }
|
|
35
|
+
|
|
36
|
+
# Attend to an object to boost its coherence
|
|
37
|
+
client.attend_to_object(object_id: '...')
|
|
38
|
+
# => { coherence_boost: 0.2, new_coherence: 0.95 }
|
|
39
|
+
|
|
40
|
+
# View current bound objects
|
|
41
|
+
client.bound_objects
|
|
42
|
+
# => { objects: [...sorted by vividness], count: 5 }
|
|
43
|
+
|
|
44
|
+
# Overall binding quality
|
|
45
|
+
client.binding_quality
|
|
46
|
+
# => { avg_coherence: 0.72, coherence_label: :coherent, conflict_rate: 0.05 }
|
|
47
|
+
|
|
48
|
+
# Periodic tick: decay vividness, archive faded objects
|
|
49
|
+
client.update_feature_binding
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Development
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
bundle install
|
|
56
|
+
bundle exec rspec
|
|
57
|
+
bundle exec rubocop
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
MIT
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/actors/every'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module FeatureBinding
|
|
8
|
+
module Actor
|
|
9
|
+
class Decay < Legion::Extensions::Actors::Every
|
|
10
|
+
def runner_class
|
|
11
|
+
Legion::Extensions::FeatureBinding::Runners::FeatureBinding
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def runner_function
|
|
15
|
+
'update_feature_binding'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def time
|
|
19
|
+
30
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def run_now?
|
|
23
|
+
false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def use_runner?
|
|
27
|
+
false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def check_subtask?
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def generate_task?
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/feature_binding/helpers/constants'
|
|
4
|
+
require 'legion/extensions/feature_binding/helpers/feature'
|
|
5
|
+
require 'legion/extensions/feature_binding/helpers/bound_object'
|
|
6
|
+
require 'legion/extensions/feature_binding/helpers/binding_field'
|
|
7
|
+
require 'legion/extensions/feature_binding/runners/feature_binding'
|
|
8
|
+
|
|
9
|
+
module Legion
|
|
10
|
+
module Extensions
|
|
11
|
+
module FeatureBinding
|
|
12
|
+
class Client
|
|
13
|
+
include Runners::FeatureBinding
|
|
14
|
+
|
|
15
|
+
def initialize(field: nil, **)
|
|
16
|
+
@field = field || Helpers::BindingField.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
attr_reader :field
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module FeatureBinding
|
|
6
|
+
module Helpers
|
|
7
|
+
class BindingField
|
|
8
|
+
include Constants
|
|
9
|
+
|
|
10
|
+
attr_reader :features, :objects, :binding_history
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@features = {}
|
|
14
|
+
@objects = {}
|
|
15
|
+
@binding_history = []
|
|
16
|
+
@object_counter = 0
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def register_feature(id:, dimension:, value:, source: :perception, salience: 0.5)
|
|
20
|
+
return @features[id] if @features.key?(id)
|
|
21
|
+
return nil if @features.size >= MAX_FEATURES
|
|
22
|
+
|
|
23
|
+
@features[id] = Feature.new(id: id, dimension: dimension, value: value, source: source, salience: salience)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def bind(feature_ids:, attention: false)
|
|
27
|
+
ids = Array(feature_ids).select { |fid| @features.key?(fid) }
|
|
28
|
+
return nil if ids.size < 2
|
|
29
|
+
return nil if @objects.size >= MAX_OBJECTS
|
|
30
|
+
|
|
31
|
+
@object_counter += 1
|
|
32
|
+
obj_id = :"object_#{@object_counter}"
|
|
33
|
+
strength = attention ? DEFAULT_BINDING_STRENGTH + ATTENTION_BOOST : DEFAULT_BINDING_STRENGTH
|
|
34
|
+
|
|
35
|
+
obj = BoundObject.new(id: obj_id, feature_ids: ids, binding_strength: strength)
|
|
36
|
+
obj.confirm if attention
|
|
37
|
+
@objects[obj_id] = obj
|
|
38
|
+
|
|
39
|
+
record_binding(obj_id, ids, attention)
|
|
40
|
+
obj
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def attend(object_id:)
|
|
44
|
+
obj = @objects[object_id]
|
|
45
|
+
return nil unless obj
|
|
46
|
+
|
|
47
|
+
obj.strengthen
|
|
48
|
+
obj.confirm unless obj.confirmed?
|
|
49
|
+
obj
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def unbind(object_id:)
|
|
53
|
+
obj = @objects.delete(object_id)
|
|
54
|
+
!obj.nil?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def features_of(object_id:)
|
|
58
|
+
obj = @objects[object_id]
|
|
59
|
+
return [] unless obj
|
|
60
|
+
|
|
61
|
+
obj.feature_ids.filter_map { |fid| @features[fid]&.to_h }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def objects_with_feature(feature_id:)
|
|
65
|
+
@objects.values.select { |o| o.includes_feature?(feature_id) }.map(&:to_h)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def illusory_conjunctions
|
|
69
|
+
@objects.values.select { |o| o.state == :illusory }.map(&:to_h)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def unbound_features
|
|
73
|
+
bound_ids = @objects.values.flat_map(&:feature_ids).uniq
|
|
74
|
+
@features.values.reject { |f| bound_ids.include?(f.id) }.map(&:to_h)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def search_by_dimension(dimension:, value: nil)
|
|
78
|
+
matches = @features.values.select { |f| f.dimension == dimension }
|
|
79
|
+
matches = matches.select { |f| f.value == value } if value
|
|
80
|
+
matches.map(&:to_h)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def decay_all
|
|
84
|
+
@features.each_value(&:decay)
|
|
85
|
+
@features.reject! { |_, f| f.faded? }
|
|
86
|
+
|
|
87
|
+
@objects.each_value(&:decay)
|
|
88
|
+
@objects.reject! { |_, o| o.dissolved? }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def feature_count
|
|
92
|
+
@features.size
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def object_count
|
|
96
|
+
@objects.size
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def to_h
|
|
100
|
+
{
|
|
101
|
+
features: @features.size,
|
|
102
|
+
objects: @objects.size,
|
|
103
|
+
confirmed_objects: @objects.values.count(&:confirmed?),
|
|
104
|
+
illusory_count: @objects.values.count { |o| o.state == :illusory },
|
|
105
|
+
unbound_features: unbound_features.size,
|
|
106
|
+
history_size: @binding_history.size
|
|
107
|
+
}
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def record_binding(obj_id, feature_ids, attended)
|
|
113
|
+
@binding_history << { object_id: obj_id, features: feature_ids, attended: attended, at: Time.now.utc }
|
|
114
|
+
@binding_history.shift while @binding_history.size > MAX_BINDING_HISTORY
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module FeatureBinding
|
|
6
|
+
module Helpers
|
|
7
|
+
class BoundObject
|
|
8
|
+
include Constants
|
|
9
|
+
|
|
10
|
+
attr_reader :id, :feature_ids, :bound_at, :confirmed_at
|
|
11
|
+
attr_accessor :binding_strength
|
|
12
|
+
|
|
13
|
+
def initialize(id:, feature_ids:, binding_strength: DEFAULT_BINDING_STRENGTH)
|
|
14
|
+
@id = id
|
|
15
|
+
@feature_ids = Array(feature_ids).uniq
|
|
16
|
+
@binding_strength = binding_strength.to_f.clamp(0.0, 1.0)
|
|
17
|
+
@bound_at = Time.now.utc
|
|
18
|
+
@confirmed_at = nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def confirm
|
|
22
|
+
@confirmed_at = Time.now.utc
|
|
23
|
+
@binding_strength = [@binding_strength + ATTENTION_BOOST, 1.0].min
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def confirmed?
|
|
27
|
+
!@confirmed_at.nil?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def strengthen(amount = ATTENTION_BOOST)
|
|
31
|
+
@binding_strength = [@binding_strength + amount, 1.0].min
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def decay
|
|
35
|
+
@binding_strength = [@binding_strength - BINDING_DECAY, 0.0].max
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def dissolved?
|
|
39
|
+
@binding_strength <= BINDING_STRENGTH_FLOOR
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def state
|
|
43
|
+
if @binding_strength >= BINDING_CONFIRMATION_THRESHOLD && confirmed?
|
|
44
|
+
:bound
|
|
45
|
+
elsif @binding_strength >= ILLUSORY_CONJUNCTION_THRESHOLD
|
|
46
|
+
:tentative
|
|
47
|
+
elsif @binding_strength > BINDING_STRENGTH_FLOOR
|
|
48
|
+
:illusory
|
|
49
|
+
else
|
|
50
|
+
:unbound
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def feature_count
|
|
55
|
+
@feature_ids.size
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def includes_feature?(feature_id)
|
|
59
|
+
@feature_ids.include?(feature_id)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def strength_label
|
|
63
|
+
STRENGTH_LABELS.each { |range, lbl| return lbl if range.cover?(@binding_strength) }
|
|
64
|
+
:dissolving
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def to_h
|
|
68
|
+
{
|
|
69
|
+
id: @id,
|
|
70
|
+
features: @feature_ids.dup,
|
|
71
|
+
feature_count: feature_count,
|
|
72
|
+
binding_strength: @binding_strength.round(4),
|
|
73
|
+
strength_label: strength_label,
|
|
74
|
+
state: state,
|
|
75
|
+
state_label: BINDING_STATE_LABELS[state],
|
|
76
|
+
confirmed: confirmed?,
|
|
77
|
+
bound_at: @bound_at,
|
|
78
|
+
confirmed_at: @confirmed_at
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module FeatureBinding
|
|
6
|
+
module Helpers
|
|
7
|
+
module Constants
|
|
8
|
+
MAX_FEATURES = 200
|
|
9
|
+
MAX_BINDINGS = 100
|
|
10
|
+
MAX_OBJECTS = 50
|
|
11
|
+
MAX_BINDING_HISTORY = 200
|
|
12
|
+
|
|
13
|
+
BINDING_STRENGTH_FLOOR = 0.05
|
|
14
|
+
BINDING_DECAY = 0.01
|
|
15
|
+
BINDING_ALPHA = 0.15
|
|
16
|
+
ATTENTION_BOOST = 0.3
|
|
17
|
+
DEFAULT_BINDING_STRENGTH = 0.3
|
|
18
|
+
|
|
19
|
+
ILLUSORY_CONJUNCTION_THRESHOLD = 0.3
|
|
20
|
+
BINDING_CONFIRMATION_THRESHOLD = 0.6
|
|
21
|
+
FEATURE_SALIENCE_FLOOR = 0.05
|
|
22
|
+
|
|
23
|
+
FEATURE_DIMENSIONS = %i[
|
|
24
|
+
shape color size motion texture location
|
|
25
|
+
pitch volume timbre temporal_pattern
|
|
26
|
+
semantic syntactic pragmatic
|
|
27
|
+
valence arousal familiarity
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
30
|
+
BINDING_STATE_LABELS = {
|
|
31
|
+
bound: 'features unified into coherent object',
|
|
32
|
+
tentative: 'binding forming, not yet confirmed',
|
|
33
|
+
illusory: 'incorrect binding (conjunction error)',
|
|
34
|
+
unbound: 'features floating in pre-attentive field'
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
STRENGTH_LABELS = {
|
|
38
|
+
(0.8..) => :solid,
|
|
39
|
+
(0.6...0.8) => :firm,
|
|
40
|
+
(0.4...0.6) => :forming,
|
|
41
|
+
(0.2...0.4) => :weak,
|
|
42
|
+
(..0.2) => :dissolving
|
|
43
|
+
}.freeze
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module FeatureBinding
|
|
6
|
+
module Helpers
|
|
7
|
+
class Feature
|
|
8
|
+
include Constants
|
|
9
|
+
|
|
10
|
+
attr_reader :id, :dimension, :value, :source, :detected_at
|
|
11
|
+
attr_accessor :salience
|
|
12
|
+
|
|
13
|
+
def initialize(id:, dimension:, value:, source: :perception, salience: 0.5)
|
|
14
|
+
@id = id
|
|
15
|
+
@dimension = dimension
|
|
16
|
+
@value = value
|
|
17
|
+
@source = source
|
|
18
|
+
@salience = salience.to_f.clamp(0.0, 1.0)
|
|
19
|
+
@detected_at = Time.now.utc
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def salient?
|
|
23
|
+
@salience > FEATURE_SALIENCE_FLOOR
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def decay
|
|
27
|
+
@salience = [@salience - BINDING_DECAY, 0.0].max
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def faded?
|
|
31
|
+
@salience <= FEATURE_SALIENCE_FLOOR
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_h
|
|
35
|
+
{
|
|
36
|
+
id: @id,
|
|
37
|
+
dimension: @dimension,
|
|
38
|
+
value: @value,
|
|
39
|
+
source: @source,
|
|
40
|
+
salience: @salience.round(4),
|
|
41
|
+
detected_at: @detected_at
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module FeatureBinding
|
|
6
|
+
module Runners
|
|
7
|
+
module FeatureBinding
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
9
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
|
|
11
|
+
def register_feature(id:, dimension:, value:, source: :perception, salience: 0.5, **)
|
|
12
|
+
Legion::Logging.debug "[feature_binding] register: id=#{id} dim=#{dimension} val=#{value}"
|
|
13
|
+
feature = field.register_feature(id: id, dimension: dimension, value: value, source: source,
|
|
14
|
+
salience: salience)
|
|
15
|
+
if feature
|
|
16
|
+
{ success: true, feature: feature.to_h, total_features: field.feature_count }
|
|
17
|
+
else
|
|
18
|
+
{ success: false, reason: :limit_reached }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def bind_features(feature_ids:, attention: false, **)
|
|
23
|
+
Legion::Logging.debug "[feature_binding] bind: features=#{feature_ids} attention=#{attention}"
|
|
24
|
+
obj = field.bind(feature_ids: feature_ids, attention: attention)
|
|
25
|
+
if obj
|
|
26
|
+
{ success: true, object: obj.to_h, total_objects: field.object_count }
|
|
27
|
+
else
|
|
28
|
+
{ success: false, reason: :insufficient_features }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def attend_object(object_id:, **)
|
|
33
|
+
Legion::Logging.debug "[feature_binding] attend: object=#{object_id}"
|
|
34
|
+
obj = field.attend(object_id: object_id.to_sym)
|
|
35
|
+
if obj
|
|
36
|
+
{ success: true, object: obj.to_h }
|
|
37
|
+
else
|
|
38
|
+
{ success: false, reason: :not_found }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def unbind_object(object_id:, **)
|
|
43
|
+
Legion::Logging.debug "[feature_binding] unbind: object=#{object_id}"
|
|
44
|
+
removed = field.unbind(object_id: object_id.to_sym)
|
|
45
|
+
{ success: removed }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def features_of_object(object_id:, **)
|
|
49
|
+
features = field.features_of(object_id: object_id.to_sym)
|
|
50
|
+
Legion::Logging.debug "[feature_binding] features_of: object=#{object_id} count=#{features.size}"
|
|
51
|
+
{ success: true, features: features, count: features.size }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def objects_with(feature_id:, **)
|
|
55
|
+
objects = field.objects_with_feature(feature_id: feature_id)
|
|
56
|
+
Legion::Logging.debug "[feature_binding] objects_with: feature=#{feature_id} count=#{objects.size}"
|
|
57
|
+
{ success: true, objects: objects, count: objects.size }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def illusory_conjunctions(**)
|
|
61
|
+
illusions = field.illusory_conjunctions
|
|
62
|
+
Legion::Logging.debug "[feature_binding] illusory: #{illusions.size}"
|
|
63
|
+
{ success: true, illusory: illusions, count: illusions.size }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def unbound_features(**)
|
|
67
|
+
unbound = field.unbound_features
|
|
68
|
+
Legion::Logging.debug "[feature_binding] unbound: #{unbound.size}"
|
|
69
|
+
{ success: true, unbound: unbound, count: unbound.size }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def search_features(dimension:, value: nil, **)
|
|
73
|
+
results = field.search_by_dimension(dimension: dimension, value: value)
|
|
74
|
+
Legion::Logging.debug "[feature_binding] search: dim=#{dimension} found=#{results.size}"
|
|
75
|
+
{ success: true, results: results, count: results.size }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def update_feature_binding(**)
|
|
79
|
+
Legion::Logging.debug '[feature_binding] tick'
|
|
80
|
+
field.decay_all
|
|
81
|
+
{ success: true, features: field.feature_count, objects: field.object_count }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def feature_binding_stats(**)
|
|
85
|
+
Legion::Logging.debug '[feature_binding] stats'
|
|
86
|
+
{ success: true, stats: field.to_h }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def field
|
|
92
|
+
@field ||= Helpers::BindingField.new
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/feature_binding/version'
|
|
4
|
+
require 'legion/extensions/feature_binding/helpers/constants'
|
|
5
|
+
require 'legion/extensions/feature_binding/helpers/feature'
|
|
6
|
+
require 'legion/extensions/feature_binding/helpers/bound_object'
|
|
7
|
+
require 'legion/extensions/feature_binding/helpers/binding_field'
|
|
8
|
+
require 'legion/extensions/feature_binding/runners/feature_binding'
|
|
9
|
+
require 'legion/extensions/feature_binding/client'
|
|
10
|
+
|
|
11
|
+
module Legion
|
|
12
|
+
module Extensions
|
|
13
|
+
module FeatureBinding
|
|
14
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lex-feature-binding
|
|
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: Treisman's Feature Integration Theory for LegionIO — attention as glue
|
|
27
|
+
binding features into unified percepts
|
|
28
|
+
email:
|
|
29
|
+
- matthewdiverson@gmail.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- README.md
|
|
35
|
+
- lib/legion/extensions/feature_binding.rb
|
|
36
|
+
- lib/legion/extensions/feature_binding/actors/decay.rb
|
|
37
|
+
- lib/legion/extensions/feature_binding/client.rb
|
|
38
|
+
- lib/legion/extensions/feature_binding/helpers/binding_field.rb
|
|
39
|
+
- lib/legion/extensions/feature_binding/helpers/bound_object.rb
|
|
40
|
+
- lib/legion/extensions/feature_binding/helpers/constants.rb
|
|
41
|
+
- lib/legion/extensions/feature_binding/helpers/feature.rb
|
|
42
|
+
- lib/legion/extensions/feature_binding/runners/feature_binding.rb
|
|
43
|
+
- lib/legion/extensions/feature_binding/version.rb
|
|
44
|
+
homepage: https://github.com/LegionIO/lex-feature-binding
|
|
45
|
+
licenses:
|
|
46
|
+
- MIT
|
|
47
|
+
metadata:
|
|
48
|
+
homepage_uri: https://github.com/LegionIO/lex-feature-binding
|
|
49
|
+
source_code_uri: https://github.com/LegionIO/lex-feature-binding
|
|
50
|
+
documentation_uri: https://github.com/LegionIO/lex-feature-binding/blob/master/README.md
|
|
51
|
+
changelog_uri: https://github.com/LegionIO/lex-feature-binding/blob/master/CHANGELOG.md
|
|
52
|
+
bug_tracker_uri: https://github.com/LegionIO/lex-feature-binding/issues
|
|
53
|
+
rubygems_mfa_required: 'true'
|
|
54
|
+
rdoc_options: []
|
|
55
|
+
require_paths:
|
|
56
|
+
- lib
|
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.4'
|
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '0'
|
|
67
|
+
requirements: []
|
|
68
|
+
rubygems_version: 3.6.9
|
|
69
|
+
specification_version: 4
|
|
70
|
+
summary: LegionIO feature binding extension
|
|
71
|
+
test_files: []
|