Class: Chain

Inherits:
Object show all
Defined in:
lib/Chain.rb

Direct Known Subclasses

UnifiedChain

Constant Summary collapse

@@default_tail_theory =
nil

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeChain

Returns a new instance of Chain.

Parameters:

  • nodes

    An array of theories in order



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/Chain.rb', line 10

def initialize()
  @values, @nodes = {}, [] 
  
  # Create an array of possible ids for any theories added
  @uniq_theory_instance_ids = ('A'...'Z').to_a 
  
  # Create a result version of 'finish'

  # TODO  Drop @tail_theory as a instance variable
  #@tail_theory = Theory.new([tail_dependent],nil,[])
  @tail_theory = Chain.default_tail_theory
  
  # NOTE: Now the head and tail are using the same ids for their variables
  @chain_mapping = ChainMapping.new()
  
  # Get the value mappings for the chain
  tc = Parser.run('test_cases')
  real_method = Parser.run('runtime_method')
  
  # Add the tail to the nodes list
  #add_link(@tail_theory,{1=>real_method,2=>tc})
  extension_permutaions(@tail_theory,{1=>real_method,2=>tc})
  
end

Instance Attribute Details

#chain_mappingObject (readonly)

TODO This access is proably temporary



4
5
6
# File 'lib/Chain.rb', line 4

def chain_mapping
  @chain_mapping
end

Class Method Details

.default_tail_theoryObject



35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/Chain.rb', line 35

def self.default_tail_theory
  if @@default_tail_theory.nil?
    finish = Parser.run("if(runtime_method.all_pass?(test_cases))\nreturn true\nend")
    tail_theory = finish.write
    tail_theory.gsub!('runtime_method','var1')
    tail_theory.gsub!('test_cases','var2')
    tail_dependent = TheoryDependent.new(StringToTheory.run(tail_theory))
    @@default_tail_theory = Theory.new([tail_dependent],nil,[])
    @@default_tail_theory.copy
  else
    @@default_tail_theory.copy
  end    
end

Instance Method Details

#<<(node) ⇒ Object

TODO I want to drop allot of these quick methods



50
51
52
# File 'lib/Chain.rb', line 50

def <<(node)
  @nodes << node
end

#[](index) ⇒ Object



62
63
64
# File 'lib/Chain.rb', line 62

def [](index)
  return @nodes[index]
end

Returns any number of new chains after adding this link to the position specified.

TODO SHould I raise an error if it doesn’t connect to anything?



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'lib/Chain.rb', line 275

def add_link_to(theory,position,value_mapping)
  # TODO  Down and up are quite similar I should consider refactoring
  
  # Do through each of the dependents and then find a result with the same structure
  mappings = [@chain_mapping.copy]
  upward_links = @nodes[0...position]

  theory.copy.dependents.each do |dependent|
    each_result(position,upward_links) do |index,link,result|
      if result.same_structure?(dependent)
        new_mappings = []
        mappings.each do |x|
          new_mappings << x.apply_mapping_update(theory,dependent.theory_component_id,link,result.theory_component_id)
        end
        mappings = new_mappings
      end
    end
  end
  
  # Go down the rest of the chain looking for exisitng links with unmet dependents
  downward_links = @nodes[position...@nodes.length]
  theory.copy.results.each do |result|
    each_unmet(:dependents,position,downward_links) do |index,link,dependent|
      if dependent.same_structure?(result)
        new_mappings = []
        mappings.each do |x|
          new_mappings << x.apply_mapping_update(link,dependent.theory_component_id,theory,result.theory_component_id)
        end
        mappings = new_mappings
      end
    end
  end
  
  # Strip out any mappings that are identical to original they are 
  # links that haven't been connected with anything 
  # (don't include the head since it only has one thing to connect to)
  # => TODO Do I like this? It means I'm including some forced connections
  unless @nodes.length < 2
    
    # Identify the mappings that are the same
    mappings = mappings.select {|x| !@chain_mapping.same?(x) }
    # => TODO Should inlcude error/warning and exit when there are no new mappings
  end
  
  # Identify any orphan variables in the action and give them uniq global ids
  mappings.each do |mapping|
    theory.orphan_action_variables.each do |x|
      mapping.add_mapping(
        mapping.next_free_global_id,
        theory.theory_instance_id,
        x.theory_variable_id
      )
    end
  end
      
  # Create a new nodes array with the new theory in place
  updated_nodes = @nodes.copy
  updated_nodes.insert(position,theory)
  
  # Create a new chain for each of the possible mappings
  extended_chains = []
  mappings.each do |x|
    c = self.copy
    new_chain = c.create!(updated_nodes,x,@uniq_theory_instance_ids.copy,@values.copy)
    extended_chains << new_chain 
  end
  return extended_chains
end

Returns the number of breaks in the chain - this is essential the number of unmet depements.



160
161
162
163
164
165
166
167
168
# File 'lib/Chain.rb', line 160

def broken_link_count
  collection = TheoryCollection.new(@nodes)
  return collection.dependents.inject(0) do |total,dependent| 
    unless @chain_mapping.connected_component_ids.include?(dependent.theory_component_id)
      total += 1
    end
    total
  end      
end

#collect(&block) ⇒ Object



95
96
97
# File 'lib/Chain.rb', line 95

def collect(&block)
  return @nodes.collect(&block) 
end

#complete?Boolean

Returns true if the chain can be connected from the head to the tail.

Returns:



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/Chain.rb', line 223

def complete?
  
  # Check that all the variables in all the theories have been globally mapped, as well as the head
  @nodes.each do |theory|
    # Find the global variable for each of the theory variables
    theory.all_theory_variables.each do |theory_variable|
      begin
        to_global_id(@chain_mapping,theory.theory_instance_id,theory_variable.theory_variable_id)
      rescue StandardError => e   
        StandardLogger.instance.error(e)       
        return false
      end
    end
  end
  return dependents_met?
  
end

#copyObject



82
83
84
85
# File 'lib/Chain.rb', line 82

def copy
  #return Chain.new(@nodes.copy)
  return Marshal.load(Marshal.dump(self))
end

#describe(tab = 0) ⇒ Object



103
104
105
# File 'lib/Chain.rb', line 103

def describe(tab=0)
  return @nodes.inject('') {|total,x| total += x.describe}
end

#duplicate_component_ids?Boolean

DEVELOPMENT Because I’m using Marshal to restore some of the theories I just need to check that none of the components have duplicate ids.

Returns:



151
152
153
154
155
# File 'lib/Chain.rb', line 151

def duplicate_component_ids?
  collection = TheoryCollection.new(@nodes)
  component_ids = collection.components.inject([]) {|total,x| total << x.theory_component_id }
  return (component_ids.length != component_ids.uniq.length)
end

#each(&block) ⇒ Object



58
59
60
# File 'lib/Chain.rb', line 58

def each(&block)
  @nodes.each(&block)
end

#each_theory_progression(initial_runtime_method, test_cases) ⇒ Object

Returns an array of implemented theories that have variable syntax that can

be mapped to literal syntax(runtime_method,0,...) and also contain
real values for each of the variables.  So the runtime method will 
include all statements added to it.

Raises:

  • (StandardError)


363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'lib/Chain.rb', line 363

def each_theory_progression(initial_runtime_method,test_cases)
  raise StandardError.new('Only complete theories can be watched') unless self.implementable?
  
  theories = []
  previous_runtime_method = initial_runtime_method.copy
  chain_validator = TheoryChainValidator.new
  progressive_chain do |chain,theory|
    initial_runtime_method.copy
    runtime_method = chain_validator.build_method_from_chain( chain.implement,
                                                              initial_runtime_method.copy,
                                                              test_cases)
    
    # Create the intrinsic mapping for the theory
    unified_chain = chain.unify_chain
    
    # * Go through each variable and find the intrinsic value
    theory_intrinsic_mapping = Mapping.new
    values = {:before=>{},:after=>{}}
    unified_chain.theory_variables.each do |var|
      intrinsic_value = global_id_intrinsic_value(var.theory_variable_id)
      theory_intrinsic_mapping[var.theory_variable_id] = intrinsic_value        
      
      # Add the actual varlue of the intrinsic value
      if intrinsic_value.kind_of?(IntrinsicRuntimeMethod)
        values[:before][var.theory_variable_id] = previous_runtime_method.copy
        values[:after][var.theory_variable_id] = previous_runtime_method.copy
        next
      end
      if intrinsic_value.kind_of?(IntrinsicTestCases)
        values[:before][var.theory_variable_id] = test_cases.copy
        values[:after][var.theory_variable_id] = test_cases.copy
        next
      else
        values[:before][var.theory_variable_id] = intrinsic_value.literal
        values[:after][var.theory_variable_id] = intrinsic_value.literal
      end
    end

    # Generate a unified version of the theory
    mapping = generate_theory_mapping(theory.theory_instance_id)
    unified_theory = theory.map_to(mapping)
          
    yield theory.map_to(theory_intrinsic_mapping,values), unified_theory
    
    # Save the previous method for the dependents
    previous_runtime_method = runtime_method
    
  end
  return theories
  
end

#empty?Boolean

Returns:



87
88
89
# File 'lib/Chain.rb', line 87

def empty?
  return @nodes.empty?
end

#extension_permutaions(theory, value_mapping = nil) ⇒ Object

Attempts to add a new link to form a complete chain between the head and tail. It starts by linking to the tail and then head.

Raises:

  • (StandardError)


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
# File 'lib/Chain.rb', line 189

def extension_permutaions(theory,value_mapping=nil)
  
  # Give the theory an instance id
  theory = theory.copy
  raise StandardError.new('Reached limit to chains length') if @uniq_theory_instance_ids.empty?
  theory.theory_instance_id = @uniq_theory_instance_ids.shift
  
  # Save the values for use later during the global mapping 
  @values[theory.theory_instance_id] = value_mapping
  
  # The first link added should be the tail
  if @nodes.empty?
    add_tail(theory,value_mapping)
    return
  end
  
  # The second link should be the head  
  # (this doesn't necessarily need to be be case but I'll keep this convention
  if @nodes.length == 1 
    return add_link_to(theory,0,value_mapping)
  end
  
  # Attempt to add a link to all the possible locations (head and tail are exempt)
  extended_chains = []
  (1...@nodes.length).each do |position|
    res = self.copy.add_link_to(theory,position,value_mapping)
    extended_chains += res
  end
  return extended_chains
  
end

#firstObject



66
67
68
# File 'lib/Chain.rb', line 66

def first
  return @nodes.first
end

Writes out the chain but highlights any of links in the chain weren’t connected to a dependent.



175
176
177
178
179
180
181
182
183
184
# File 'lib/Chain.rb', line 175

def highlight_broken_links
  if duplicate_component_ids?
    StandardLogger.instance.warning('You are using theories that share the same component_id')
  end
  unmet = []
  each_unmet(:dependents,0,@nodes) do |index,theory,dependent|
    unmet << dependent.theory_component_id  
  end
  return @nodes.inject('') {|msg,x| msg += x.highlight(unmet)}
end

#implementObject

Implements a new the chain where all the theories are implemented. It can only be implemented if the chain is complete e.g. all the dependents and results are matched up.

Raises:

  • (StandardError)


348
349
350
351
352
353
354
355
356
# File 'lib/Chain.rb', line 348

def implement
  raise StandardError.new('This chain is not complete') unless complete?

  # Generate a global value mapping - so global id to real value
  unifyied_chain = unify_chain

  return unifyied_chain.implement(intrinsic_mapping)
  
end

#implementable?Boolean

Return true if the chain can be implemented - so all the theories have literal values associated with them. This could be directly via when the theory was added to the chain or indirectly via connecting two theories.

Returns:



419
420
421
422
423
424
425
# File 'lib/Chain.rb', line 419

def implementable?
  self.implement
  return true
rescue StandardError => e
  StandardLogger.instance.info e 
  return false
end

#lastObject



70
71
72
# File 'lib/Chain.rb', line 70

def last
  return @nodes.last
end

#lengthObject



54
55
56
# File 'lib/Chain.rb', line 54

def length
  return @nodes.length
end

#popObject



91
92
93
# File 'lib/Chain.rb', line 91

def pop
  return @nodes.pop
end

#reverseObject



74
75
76
# File 'lib/Chain.rb', line 74

def reverse
  return Chain.new(@nodes.reverse)
end

#shiftObject



78
79
80
# File 'lib/Chain.rb', line 78

def shift
  return @nodes.shift
end

#solvable?(finish, theories) ⇒ Boolean

This uses the supplied theories and checks whether the dependents of any of theories that might be used are reachable.

NOTE: This is only a rough assessment of whether the dependents are available

it doesn't mean that it is solvable but does identify that it definitely
isn't.

Returns:



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/Chain.rb', line 248

def solvable?(finish,theories)
  
  # Get the finish and check all the dependents can be met by the theories
  all_results = theories.inject([]) {|total,x| total += x.results}
  return false unless all_results.any? {|x| x.same_structure?(finish)}
  
  # Find the theories that can solve the problem and check their dependents can be met
  # TODO  Should probably include the head theory in here too.
  collection = TheoryCollection.new(theories.copy)
  finishing_theories = []
  collection.each_with_result_structure(finish) do |t,r|
    finishing_theories.push(t) 
  end
  
  finishing_theories.each do |x|
  end
  res = finishing_theories.any? do |x|
    dependency_tree_met?(x,theories+[self.first.copy])
  end
  return res
end

#theories_sequenceObject



126
127
128
# File 'lib/Chain.rb', line 126

def theories_sequence
  @nodes.collect {|x| x.theory_id}
end

#theory_variablesObject

Returns an array of all the theory variables used in the chain



429
430
431
# File 'lib/Chain.rb', line 429

def theory_variables
  return TheoryCollection.new(@nodes).theory_variables
end

#unify_chainObject

Returns a new chain where they are all using the same respective theory variables.

TODO This seems to create implemented theories - I’m not sure this is what I want



135
136
137
138
139
140
141
142
143
144
145
# File 'lib/Chain.rb', line 135

def unify_chain
  unless complete?
    raise StandardError.new('Only complete chains can be unified')
  end
  unified_theories = @nodes.inject([]) do |total,theory|
    mapping = generate_theory_mapping(theory.theory_instance_id)
    mapped_theory = theory.map_to(mapping)
    total.push(mapped_theory)
  end
  return UnifiedChain.new(unified_theories)
end

#uniq_theory_variable_idsObject

Returns an array of all the unique theory variables used within the chain.

e.g. [0,6,7]



112
113
114
115
116
# File 'lib/Chain.rb', line 112

def uniq_theory_variable_ids
  return @nodes.inject([]) do |total,theory| 
    total = total | theory.all_theory_variables.collect {|x| x.theory_variable_id}
  end
end

#unmet_dependentsObject

Returns an array of all the depdendents in the chain that haven’t been met by the front of the chain.



436
437
438
439
440
441
442
443
444
445
# File 'lib/Chain.rb', line 436

def unmet_dependents
  results = []
  return results if self.empty?
  duplicate_chain = self.copy
  last_link = duplicate_chain.pop
  last_link.dependents.each do |dependent|
    results.push dependent unless dependent_met?(dependent,duplicate_chain.copy)
  end
  return results+duplicate_chain.unmet_dependents
end

Returns a hash of all the unmet dependents down the chain (excluding the head) and the link/theory that it belongs to.



532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
# File 'lib/Chain.rb', line 532

def unmet_dependents_and_link
  results = []
  return results if self.empty?
  duplicate_chain = self.copy
  
  # Remove the head link from the chain
  duplicate_chain.shift
  
  duplicate_chain.length.times do
    last_link = duplicate_chain.pop
    last_link.dependents.each do |d|
      next if dependent_met?(d,duplicate_chain.copy)
      results.push({:dependent=>d.copy,:theory=>last_link.copy}) 
    end      
  end
  return results    
end

#unmet_dependents_idsObject



118
119
120
121
122
123
124
# File 'lib/Chain.rb', line 118

def unmet_dependents_ids
  collection = TheoryCollection.new(@nodes)
  return collection.dependents.inject([]) do |total,dependent|
    total << dependent.theory_component_id unless @chain_mapping.connected_component_ids.include?(dependent.theory_component_id)
    total
  end
end

#variables_creation(runtime_method, test_cases) ⇒ Object

TODO Maybe just move this method to the UnfiedChain class Returns a set containing the intrinsic statements that created the variables. It should look something like the following.

[:global_variable_id=>0,:intrinsic_statement=>runtime_method,:written_statement=>‘var1’,

{:global_variable_id=>3,:intrinsic_statement=>runtime_method.params[0],:written_statement=>'var1.params[var3]'},
...]

Currently the only way variables are created are for array access.

There should not be the case where there are duplicate intrinsic_statements but different global_variable_ids - for now at least.

Raises:

  • (StandardError)


460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
# File 'lib/Chain.rb', line 460

def variables_creation(runtime_method,test_cases)
  raise StandardError.new('This should only be called on complete chains') unless complete?
  res = []
  unify_chain.theory_variables.each do |var|
          
    intrinsic_value = global_id_intrinsic_value(var.theory_variable_id)
    if intrinsic_value.kind_of?(IntrinsicRuntimeMethod)
      res << {
        :global_variable_id=>var.theory_variable_id,
        :intrinsic_statement=>Parser.run('runtime_method'),
        :written_statement=>'var'+var.theory_variable_id.to_s,
        :variable_value=>runtime_method.copy
      }    
    elsif intrinsic_value.kind_of?(IntrinsicTestCases)
      res << {
        :global_variable_id=>var.theory_variable_id,
        :intrinsic_statement=>Parser.run('test_cases'),
        :written_statement=>'var'+var.theory_variable_id.to_s,
        :variable_value=>test_cases.copy
      }        
    else
      
      # TODO  This whole bit is just really rushed------------------ write tests! 

      # Find all the statements that include that variable 
      unified_components = unify_chain.components
      
      # TODO  This is an extremely clumsy approach
      # Collect all the written components matching the delcaration
      written_components = unified_components.collect {|x| x.write}
      written_components = written_components.select {|x| x.include?('var'+var.theory_variable_id.to_s)}
      
      # => DEV
      temp_component = written_components.first

      # => MATCH var0.params[var2] in runtime_method.add_statement_at(OpenStatement.new(TheoryStatement.new(If.new, Container.new(var0.params[var2], Equivalent.new, var1[var4][:params][var3]))),var0.statement_id)
      reg = eval('/[\w\d\.\[\]:]*\['+var.write+'\]/')
              
      # TODO  Should use the Parser to find the statement with the varx in
      #var_match = /\s.*\[var[\d]\]/
      #var_match = /^[\s|\(]+(.*\[var[\d]\])/
      var_match = reg
      usage_statements = written_components.inject([]) do |total,x|
        
        #unless x.write.match(/\s.*\[var[\d]\]/).nil?
        unless x.write.match(var_match).nil?
          match = x.write.match(var_match)[0]
          total << TheoryStatement.new(StringToTheory.run(match))
        end
        total
      end
      intrinsic_statements = usage_statements.collect {|x| x.map_to(intrinsic_mapping)}
      
      unless intrinsic_statements.collect {|x| x.write}.uniq.length == 1
        raise StandardError.new('Unable to create intrinsic variable create for variable '+var.theory_variable_id.to_s)
      end
      
      res << {
        :global_variable_id=>var.theory_variable_id,
        :intrinsic_statement=>intrinsic_statements.first,
        :written_statement=>usage_statements.first.write,
        :variable_value=>intrinsic_mapping[var.theory_variable_id].copy
      }
      
    end       
  end
  return res
end

#write(tab = 0) ⇒ Object



99
100
101
# File 'lib/Chain.rb', line 99

def write(tab=0)
  return @nodes.inject('') {|total,x| total += x.write}
end