Class: Ontology

Inherits:
Object
  • Object
show all
Defined in:
lib/cirrocumulus/ontology.rb

Constant Summary collapse

@@inproc_agents =
{}
@@loaded_rules =
{}
@@loaded_triggers =
{}
@@ontology_names =
{}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(identifier) ⇒ Ontology

Infrastructure code



125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/cirrocumulus/ontology.rb', line 125

def initialize(identifier)
  @identifier = identifier
  @facts = FactsDatabase.new()
  @classes = []
  @last_saga_id = 0
  @sagas = []
  @last_tick_time = Time.now

  self.class.register_ontology_instance(self)
  @mutex = Mutex.new
  @rule_queue = RuleQueue.new(self)
end

Instance Attribute Details

#identifierObject (readonly)

Returns the value of attribute identifier.



120
121
122
# File 'lib/cirrocumulus/ontology.rb', line 120

def identifier
  @identifier
end

Class Method Details

.assert(identifier, data) ⇒ Object



61
62
63
64
# File 'lib/cirrocumulus/ontology.rb', line 61

def assert(identifier, data)
  instance = query_ontology_instance(identifier)
  instance.assert(data) if instance
end

.current_rulesetObject



81
82
83
# File 'lib/cirrocumulus/ontology.rb', line 81

def current_ruleset()
			return @@loaded_rules[name] ||= []
end

.dump_kb(identifier) ⇒ Object



71
72
73
74
# File 'lib/cirrocumulus/ontology.rb', line 71

def dump_kb(identifier)
  instance = query_ontology_instance(identifier)
  return instance.nil? ? [] : instance.dump_kb()
end

.dump_sagas(identifier) ⇒ Object



76
77
78
79
# File 'lib/cirrocumulus/ontology.rb', line 76

def dump_sagas(identifier)
  instance = query_ontology_instance(identifier)
  return instance.nil? ? [] : instance.dump_sagas()
end

.enable_consoleObject



89
90
91
92
# File 'lib/cirrocumulus/ontology.rb', line 89

def enable_console
	proxy = RemoteConsole.new
	DRb.start_service('druby://0.0.0.0:8112', proxy)
end

.list_ontology_instancesObject



49
50
51
# File 'lib/cirrocumulus/ontology.rb', line 49

def list_ontology_instances
	@@inproc_agents.each_key.map {|key| key.to_s}
end

.ontology(ontology_name) ⇒ Object



94
95
96
# File 'lib/cirrocumulus/ontology.rb', line 94

def ontology(ontology_name)
	@@ontology_names[name] = ontology_name
end

.query_ontology_instance(identifier) ⇒ Object



53
54
55
56
57
58
59
# File 'lib/cirrocumulus/ontology.rb', line 53

def query_ontology_instance(identifier)
			@@inproc_agents.each_key do |key|
return @@inproc_agents[key] if key == identifier
			end

			nil
end

.register_ontology_instance(instance) ⇒ Object



45
46
47
# File 'lib/cirrocumulus/ontology.rb', line 45

def register_ontology_instance(instance)
	@@inproc_agents[instance.identifier] = instance
end

.retract(identifier, data) ⇒ Object



66
67
68
69
# File 'lib/cirrocumulus/ontology.rb', line 66

def retract(identifier, data)
  instance = query_ontology_instance(identifier)
  instance.retract(data) if instance
end

.rule(name, predicate, options = {}, &block) ⇒ Object



98
99
100
101
102
103
104
# File 'lib/cirrocumulus/ontology.rb', line 98

def rule(name, predicate, options = {}, &block)
  return if predicate.empty?
  return if current_ruleset.count {|rule| rule.name == name} > 0

  distilled_predicate = predicate.map {|cond| cond.is_a?(KnowledgeClass) ? cond.to_params : cond}
			current_ruleset << RuleDescription.new(name, distilled_predicate, options, block)
end

.trigger(name, options, &block) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/cirrocumulus/ontology.rb', line 106

def trigger(name, options, &block)
  return unless options.has_key?(:for)
  return if triggers.find {|t| t.name == name}

  trigger_for = options[:for]
  trigger_action = options[:on] || :assert

  t = TriggerDescription.new(name, trigger_for, trigger_action, block)
  triggers << t

  p t
end

.triggersObject



85
86
87
# File 'lib/cirrocumulus/ontology.rb', line 85

def triggers
  return @@loaded_triggers[name] ||= []
end

Instance Method Details

#add_knowledge_class(klass) ⇒ Object



165
166
167
# File 'lib/cirrocumulus/ontology.rb', line 165

def add_knowledge_class(klass)
  @classes << klass
end

#agree(agent, action, options = {}) ⇒ Object

The action of agreeing to perform some action, possibly in the future.



275
276
277
278
279
280
# File 'lib/cirrocumulus/ontology.rb', line 275

def agree(agent, action, options = {})
  info "%25s | agree with %s to perform %s %s" % [identifier, agent, Sexpistol.new.to_sexp(action), print_message_options(options)]

  channel = ChannelFactory.retrieve(identifier, agent)
  channel.agree(identifier, action, options) if channel
end

#assert(fact, options = {}) ⇒ Object



169
170
171
172
173
# File 'lib/cirrocumulus/ontology.rb', line 169

def assert(fact, options = {})
	@mutex.synchronize do
		assert_nb(fact, options)
  end
end

#create_saga(saga_class) ⇒ Object

Inter-agent communications



240
241
242
243
244
245
246
# File 'lib/cirrocumulus/ontology.rb', line 240

def create_saga(saga_class)
  @last_saga_id += 1
  saga = saga_class.new(saga_class.name + '-' + @last_saga_id.to_s, self)
  @sagas << saga

  saga
end

#dump_kbObject



228
229
230
# File 'lib/cirrocumulus/ontology.rb', line 228

def dump_kb()
  @facts.enumerate.map {|fact| fact.data.to_s}
end

#dump_sagasObject



232
233
234
# File 'lib/cirrocumulus/ontology.rb', line 232

def dump_sagas()
  @sagas.map {|saga| saga.to_s}
end

#failure(agent, action, reason = true, options = {}) ⇒ Object

The action of telling another agent that an action was attempted but the attempt failed.



295
296
297
298
299
300
# File 'lib/cirrocumulus/ontology.rb', line 295

def failure(agent, action, reason = true , options = {})
  info "%25s | inform %s that action %s failed with reason %s %s" % [identifier, agent, Sexpistol.new.to_sexp(action), Sexpistol.new.to_sexp(reason), print_message_options(options)]

  channel = ChannelFactory.retrieve(identifier, agent)
  channel.failure(identifier, action, reason, options) if channel
end

#handle_agree(sender, action, options = {}) ⇒ Object



381
382
383
384
385
# File 'lib/cirrocumulus/ontology.rb', line 381

def handle_agree(sender, action, options = {})
  if !handle_saga_reply(sender, :agree, action, options)
    info "%25s | %s agreed to perform %s %s" % [identifier, sender, Sexpistol.new.to_sexp(action), print_message_options(options)]
  end
end

#handle_failure(sender, action, reason, options = {}) ⇒ Object



393
394
395
396
397
# File 'lib/cirrocumulus/ontology.rb', line 393

def handle_failure(sender, action, reason, options = {})
  if !handle_saga_reply(sender, :failure, [action, reason], options)
    info "%25s | %s failed to perform %s because %s %s" % [identifier, sender, Sexpistol.new.to_sexp(action), Sexpistol.new.to_sexp(reason), print_message_options(options)]
  end
end

#handle_inform(sender, proposition, options = {}) ⇒ Object

Handles incoming fact. By default, just adds this fact to KB or redirects its processing to corresponding saga



358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/cirrocumulus/ontology.rb', line 358

def handle_inform(sender, proposition, options = {})
  puts "%25s | received %s from %s %s" % [identifier, Sexpistol.new.to_sexp(proposition), sender, print_message_options(options)]

  if !handle_saga_reply(sender, :inform, proposition, options)
    @classes.each do |klass|
      if instance = klass.from_fact(proposition)
        matcher = PatternMatcher.new(@facts.enumerate)
        bindings = matcher.match(instance.to_template)
        if !bindings.empty?
          replace instance.to_template, matcher.pattern_matches?(proposition, instance.to_template)
        else
          assert(proposition)
        end

        return
      end
    end

    assert proposition, :origin => sender
  end
end

#handle_query(sender, expression, options = {}) ⇒ Object



408
409
410
411
412
413
414
415
416
417
# File 'lib/cirrocumulus/ontology.rb', line 408

def handle_query(sender, expression, options = {})
  info "%25s | %s queries %s %s" % [identifier, sender, Sexpistol.new.to_sexp(expression), print_message_options(options)] unless handle_saga_reply(sender, :query, expression, options)

  matcher = PatternMatcher.new(@facts.enumerate)
  matches = matcher.find_matches_for_condition(expression).map {|data| data.data}
  info "%25s | (found #{matches.size} matching facts)" % [identifier]
  matches.each do |fact|
    inform(sender, fact, reply(options))
  end
end

#handle_query_if(sender, proposition, options = {}) ⇒ Object

Handles query-if to ontology. By default, it lookups the fact in KB and replies to the sender.



422
423
424
# File 'lib/cirrocumulus/ontology.rb', line 422

def handle_query_if(sender, proposition, options = {})
  info "%25s | %s queries if %s %s" % [identifier, sender, Sexpistol.new.to_sexp(proposition), print_message_options(options)] unless handle_saga_reply(sender, :query, expression, options)
end

#handle_refuse(sender, action, reason, options = {}) ⇒ Object



387
388
389
390
391
# File 'lib/cirrocumulus/ontology.rb', line 387

def handle_refuse(sender, action, reason, options = {})
  if !handle_saga_reply(sender, :refuse, [action, reason], options)
    info "%25s | %s refused to perform %s because %s %s" % [identifier, sender, Sexpistol.new.to_sexp(action), Sexpistol.new.to_sexp(reason), print_message_options(options)]
  end
end

#handle_request(sender, contents, options = {}) ⇒ Object

Abstract method to handle requests to this ontology.



402
403
404
405
406
# File 'lib/cirrocumulus/ontology.rb', line 402

def handle_request(sender, contents, options = {})
  if !handle_saga_reply(sender, :request, contents, options)
    info "%25s | %s requests to perform %s %s" % [identifier, sender, Sexpistol.new.to_sexp(contents), print_message_options(options)]
  end
end

#inform(agent, fact, options = {}) ⇒ Object

Inform another agent about a fact. Normally, it will be added to it’s KB.



261
262
263
264
265
266
# File 'lib/cirrocumulus/ontology.rb', line 261

def inform(agent, fact, options = {})
	info "%25s | inform %s about %s %s" % [identifier, agent, Sexpistol.new.to_sexp(fact), print_message_options(options)]

   channel = ChannelFactory.retrieve(identifier, agent)
   channel.inform(identifier, fact, options) if channel
end

#inform_and_wait(agent, fact, options = {}) ⇒ Object



268
269
270
# File 'lib/cirrocumulus/ontology.rb', line 268

def inform_and_wait(agent, fact, options = {})

end

#joinObject



160
161
162
163
# File 'lib/cirrocumulus/ontology.rb', line 160

def join
self.running = false
@thread.join()
end

#nameObject



138
139
140
# File 'lib/cirrocumulus/ontology.rb', line 138

def name
  @@ontology_names[self.class.name]
end

#query(agent, expression, options = {}) ⇒ Object



316
317
318
319
320
321
# File 'lib/cirrocumulus/ontology.rb', line 316

def query(agent, expression, options = {})
   info "%25s | query %s about %s %s" % [identifier, agent, Sexpistol.new.to_sexp(expression), print_message_options(options)]

   channel = ChannelFactory.retrieve(identifier, agent)
   channel.query(identifier, expression, options) if channel
end

#query_and_wait(agent, smth, options = {}) ⇒ Object



323
324
# File 'lib/cirrocumulus/ontology.rb', line 323

def query_and_wait(agent, smth, options = {})
end

#query_if(agent, fact, options = {}) ⇒ Object

Send ‘query-if’ to another agent. Normally, it will reply if the expression is true or false.



329
330
331
332
333
334
# File 'lib/cirrocumulus/ontology.rb', line 329

def query_if(agent, fact, options = {})
   info "%25s | query %s if %s %s" % [identifier, agent, Sexpistol.new.to_sexp(fact), print_message_options(options)]

   channel = ChannelFactory.retrieve(identifier, agent)
   channel.query_if(identifier, fact, options) if channel
end

#query_if_and_wait(agent, fact, options = {}) ⇒ Object



336
337
# File 'lib/cirrocumulus/ontology.rb', line 336

def query_if_and_wait(agent, fact, options = {})
end

#refuse(agent, action, reason, options = {}) ⇒ Object

The action of refusing to perform a given action, and explaining the reason for the refusal.



285
286
287
288
289
290
# File 'lib/cirrocumulus/ontology.rb', line 285

def refuse(agent, action, reason, options = {})
  info "%25s | refuse %s to perform %s because %s %s" % [identifier, agent, Sexpistol.new.to_sexp(action), Sexpistol.new.to_sexp(reason), print_message_options(options)]

  channel = ChannelFactory.retrieve(identifier, agent)
  channel.refuse(identifier, action, reason, options) if channel
end

#replace(pattern, values, options = {}) ⇒ Object



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/cirrocumulus/ontology.rb', line 181

def replace(pattern, values, options = {})
  @mutex.synchronize do
   matcher = PatternMatcher.new(@facts.enumerate())
    data = matcher.match(pattern)

    if data.empty?
      new_fact = pattern.clone

      pattern.each_with_index do |item,i|
        if item.is_a?(Symbol) && item.to_s.upcase == item.to_s
          new_fact[i] = values.is_a?(Hash) ? values[item] : values
        end
      end

      assert_nb(new_fact, options, false)
    else
      data.each do |match_data|
        old_fact = pattern.clone
        new_fact = pattern.clone
        pattern.each_with_index do |item,i|
          if match_data.include? item
            old_fact[i] = match_data[item]
            new_fact[i] = values.is_a?(Hash) ? values[item] : values
          end
        end

        facts_are_same = true
        old_fact.each_with_index do |item, idx|
          new_item = new_fact[idx]
          facts_are_same = false if new_item != item
        end

        unless facts_are_same
          debug "replace #{pattern.inspect} for #{values.inspect}"

          retract_nb(old_fact, true)
          assert_nb(new_fact, {}, false)
        end
      end
    end
  end
end

#reply(options) ⇒ Object



248
249
250
251
252
253
254
255
256
# File 'lib/cirrocumulus/ontology.rb', line 248

def reply(options)
  if options.has_key?(:conversation_id)
    {:conversation_id => options[:conversation_id]}
  elsif options.has_key?(:reply_with)
    {:in_reply_to => options[:reply_with]}
  else
    {}
  end
end

#report_incident(subject, description) ⇒ Object



224
225
226
# File 'lib/cirrocumulus/ontology.rb', line 224

def report_incident(subject, description)

end

#request(agent, contents, options = {}) ⇒ Object

Send request to another agent.



305
306
307
308
309
310
# File 'lib/cirrocumulus/ontology.rb', line 305

def request(agent, contents, options = {})
	info "%25s | %s -> %s" % [identifier.to_s, Sexpistol.new.to_sexp(contents), agent.to_s]

	channel = ChannelFactory.retrieve(identifier, agent)
	channel.request(identifier, contents) if channel
end

#request_and_wait(agent, contents, options = {}) ⇒ Object



312
313
314
# File 'lib/cirrocumulus/ontology.rb', line 312

def request_and_wait(agent, contents, options = {})

end

#restore_stateObject

Custom code to restore previous state. Called at startup.



342
# File 'lib/cirrocumulus/ontology.rb', line 342

def restore_state; end

#retract(fact) ⇒ Object



175
176
177
178
179
# File 'lib/cirrocumulus/ontology.rb', line 175

def retract(fact)
	@mutex.synchronize do
		retract_nb(fact)
	end
end

#runObject



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/cirrocumulus/ontology.rb', line 142

def run()
	self.running = true

	@thread = Thread.new(self) do |ontology|
		while self.running do
			ontology.timeout_facts
       @rule_queue.run_queued_rules

       if Time.now - @last_tick_time > 1
         ontology.tick
         @last_tick_time = Time.now
       end

			sleep 0.1
		end
	end
end

#tickObject

Tick. Every second.



347
348
349
350
351
352
353
# File 'lib/cirrocumulus/ontology.rb', line 347

def tick
  @sagas.each do |saga|
    next if saga.is_finished?

    saga.tick
  end
end