art-decomp 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/.gitignore +2 -0
  2. data/LICENCE +661 -0
  3. data/README +9 -0
  4. data/Rakefile +46 -0
  5. data/VERSION +1 -0
  6. data/bin/ad-compare +22 -0
  7. data/bin/ad-console +11 -0
  8. data/bin/ad-inputs +24 -0
  9. data/bin/ad-validate +9 -0
  10. data/bin/art-decomp +8 -0
  11. data/lib/art-decomp/arch.rb +32 -0
  12. data/lib/art-decomp/b.rb +7 -0
  13. data/lib/art-decomp/bipainter.rb +142 -0
  14. data/lib/art-decomp/blanket.rb +82 -0
  15. data/lib/art-decomp/decomposer.rb +75 -0
  16. data/lib/art-decomp/decomposition.rb +91 -0
  17. data/lib/art-decomp/exceptions.rb +9 -0
  18. data/lib/art-decomp/executable.rb +94 -0
  19. data/lib/art-decomp/fsm.rb +128 -0
  20. data/lib/art-decomp/graph.rb +78 -0
  21. data/lib/art-decomp/kiss.rb +47 -0
  22. data/lib/art-decomp/logging.rb +52 -0
  23. data/lib/art-decomp/qu_generator/block_table.rb +42 -0
  24. data/lib/art-decomp/qu_generator/edge_labels.rb +24 -0
  25. data/lib/art-decomp/qv_generator/bipainting.rb +12 -0
  26. data/lib/art-decomp/qv_generator/graph_colouring.rb +16 -0
  27. data/lib/art-decomp/qv_generator/graph_merging.rb +19 -0
  28. data/lib/art-decomp/sep.rb +7 -0
  29. data/lib/art-decomp/uv_generator/braindead.rb +22 -0
  30. data/lib/art-decomp/uv_generator/relevance.rb +23 -0
  31. data/lib/art-decomp.rb +65 -0
  32. data/lib/core/enumerable.rb +24 -0
  33. data/lib/core/file.rb +11 -0
  34. data/lib/core/integer.rb +13 -0
  35. data/lib/core/set.rb +16 -0
  36. data/lib/core/string.rb +13 -0
  37. data/spec/art-decomp/arch_spec.rb +23 -0
  38. data/spec/art-decomp/b_spec.rb +27 -0
  39. data/spec/art-decomp/bipainter_spec.rb +22 -0
  40. data/spec/art-decomp/blanket_spec.rb +77 -0
  41. data/spec/art-decomp/decomposer_spec.rb +106 -0
  42. data/spec/art-decomp/decomposition_spec.rb +119 -0
  43. data/spec/art-decomp/executable_spec.rb +161 -0
  44. data/spec/art-decomp/fsm_spec.rb +146 -0
  45. data/spec/art-decomp/graph_spec.rb +69 -0
  46. data/spec/art-decomp/kiss_spec.rb +30 -0
  47. data/spec/art-decomp/logging_spec.rb +62 -0
  48. data/spec/art-decomp/qu_generator/block_table_spec.rb +37 -0
  49. data/spec/art-decomp/qu_generator/edge_labels_spec.rb +35 -0
  50. data/spec/art-decomp/qv_generator/bipainting_spec.rb +28 -0
  51. data/spec/art-decomp/qv_generator/graph_colouring_spec.rb +31 -0
  52. data/spec/art-decomp/qv_generator/graph_merging_spec.rb +30 -0
  53. data/spec/art-decomp/sep_spec.rb +13 -0
  54. data/spec/art-decomp/uv_generator/braindead_spec.rb +33 -0
  55. data/spec/art-decomp/uv_generator/relevance_spec.rb +61 -0
  56. data/spec/core/enumerable_spec.rb +20 -0
  57. data/spec/core/file_spec.rb +15 -0
  58. data/spec/core/integer_spec.rb +16 -0
  59. data/spec/core/set_spec.rb +26 -0
  60. data/spec/core/string_spec.rb +17 -0
  61. data/spec/fixtures/ex5 +36 -0
  62. data/spec/fixtures/fsm +24 -0
  63. data/spec/fixtures/fsm.exp +40 -0
  64. data/spec/fixtures/fsm.f +20 -0
  65. data/spec/fixtures/fsm.g +4 -0
  66. data/spec/fixtures/fsm.h +19 -0
  67. data/spec/fixtures/fsm.partially-exp +31 -0
  68. data/spec/fixtures/fsm.q +10 -0
  69. data/spec/fixtures/lion +15 -0
  70. data/spec/fixtures/lion.exp +15 -0
  71. data/spec/fixtures/lion.h +10 -0
  72. data/spec/fixtures/lion.to_kiss +11 -0
  73. data/spec/fixtures/mark1 +26 -0
  74. data/spec/fixtures/mc +14 -0
  75. data/spec/fixtures/mc.to_kiss +10 -0
  76. data/spec/fixtures/opus +26 -0
  77. data/spec/fixtures/opus.amb.h +22 -0
  78. data/spec/fixtures/opus.h +21 -0
  79. data/spec/fixtures/opus.to_kiss +22 -0
  80. data/spec/fixtures/s420 +142 -0
  81. data/spec/fixtures/s8 +24 -0
  82. data/spec/spec.opts +3 -0
  83. data/spec/spec_helper.rb +8 -0
  84. metadata +224 -0
@@ -0,0 +1,22 @@
1
+ module ArtDecomp describe Bipainter do
2
+
3
+ context 'when used for generating the Qv and G blankets' do
4
+
5
+ # FIXME: add more specs, targeting non-trivial cases
6
+
7
+ it 'should colour the two incompatibility graphs properly and return the resulting blankets' do
8
+ beta_q = Blanket[B[1,2], B[3,4], B[5,6]]
9
+ beta_v = Blanket[B[1,3,5], B[2,4,6]]
10
+ seps = Set[Sep[1,2], Sep[1,3], Sep[1,6], Sep[2,6], Sep[3,4], Sep[3,6], Sep[4,5], Sep[5,6]]
11
+
12
+ bipainter = Bipainter.new beta_q, beta_v, seps
13
+ bipainter.blankets.should == [Blanket[B[1,2], B[3,4,5,6]], Blanket[B[1], B[2,3,5], B[4,6]]]
14
+ end
15
+
16
+ it 'should raise a RuntimeError on non-disjoint beta_v' do
17
+ lambda { Bipainter.new Blanket[], Blanket[B[0,1], B[0,2]], Set[] }.should raise_error(RuntimeError, 'non-disjoint beta_v')
18
+ end
19
+
20
+ end
21
+
22
+ end end
@@ -0,0 +1,77 @@
1
+ # encoding: UTF-8
2
+
3
+ module ArtDecomp describe Blanket do
4
+
5
+ it 'should be easily instantiable, properly comparable and usable as a Set element' do
6
+ Blanket[B[], B[0], B[1,2]].should == Blanket.new([B[1,2], B[0]])
7
+ Blanket[B[], B[0], B[1,2]].hash.should == Blanket.new([B[1,2], B[0]]).hash
8
+ Blanket[B[], B[0], B[1,2]].should eql Blanket.new([B[1,2], B[0]])
9
+ end
10
+
11
+ it 'should expose its internal ints variable and it should be immutable' do
12
+ Blanket[B[1,2], B[3,4]].ints.should == Set[B[3,4], B[1,2]]
13
+ Blanket[B[1,2], B[3,4]].ints.should be_frozen
14
+ end
15
+
16
+ it 'should be properly instantiable from an Array' do
17
+ blanket = Blanket.from_array [:b, DontCare, :a, :c, :b, :a, :c]
18
+ blanket.should == Blanket[B[0,1,4], B[1,2,5], B[1,3,6]]
19
+ end
20
+
21
+ it 'should be inspectable and return a self-instanting form' do
22
+ Blanket[B[]].inspect.should == 'Blanket[]'
23
+ Blanket[B[1,2], B[0]].inspect.should == 'Blanket[B[0], B[1,2]]'
24
+ end
25
+
26
+ it 'should be multipliable by other Blankets' do
27
+ b1 = Blanket[B[1,2,3], B[4,5,6]]
28
+ b2 = Blanket[B[1,2], B[3,4], B[5,6]]
29
+ (b1 * b2).should == Blanket[B[1,2], B[3], B[4], B[5,6]]
30
+ end
31
+
32
+ it 'should properly expose the separations it provides/requires' do
33
+ Blanket[].seps.should == Set[]
34
+ Blanket[B[1]].seps.should == Set[]
35
+ Blanket[B[1], B[2], B[]].seps.should == Set[Sep[1,2]]
36
+ Blanket[B[1,2], B[3,4]].seps.should == Set[Sep[1,3], Sep[1,4], Sep[2,3], Sep[2,4]]
37
+ Blanket[B[1,2,3], B[2,3,4]].seps.should == Set[Sep[1,4]]
38
+ end
39
+
40
+ it 'should properly report its size' do
41
+ Blanket[].size.should == 0
42
+ Blanket[B[1]].size.should == 1
43
+ Blanket[B[1], B[2], B[]].size.should == 2
44
+ Blanket[B[1,2,3], B[2,3,4]].size.should == 2
45
+ end
46
+
47
+ it 'should return the Blanket’s ‘natural’ encoding, based on the provided subblock' do
48
+ blanket = Blanket[B[0,1,2], B[1,2,3], B[2,3,4]]
49
+ blanket.encoding(B[0,1,2]).should == '00'
50
+ blanket.encoding(B[1,2,3]).should == '01'
51
+ blanket.encoding(B[2,3,4]).should == '10'
52
+ blanket.encoding(B[2]).should == '--'
53
+ lambda { blanket.encoding(B[1,2]) }.should raise_error(AmbiguousEncodingQuery, 'ambiguous encoding query: block 1,2')
54
+ end
55
+
56
+ it 'should return the Blanket’s ‘natural’ encodings in an Array if so asked' do
57
+ blanket = Blanket[B[0,1,2], B[1,2,3], B[2,3,4]]
58
+ blanket.encodings(B[0,1,2]).should == ['00']
59
+ blanket.encodings(B[1,2,3]).should == ['01']
60
+ blanket.encodings(B[2,3,4]).should == ['10']
61
+ blanket.encodings(B[2]).should == ['--']
62
+ blanket.encodings(B[1,2]).should == ['00', '01']
63
+ end
64
+
65
+ it 'should report how many physical pins it uses' do
66
+ Blanket[B[]].pins.should == 0
67
+ Blanket[B[1]].pins.should == 0
68
+ Blanket[B[1,2]].pins.should == 0
69
+ Blanket[B[1],B[2]].pins.should == 1
70
+ Blanket[B[1],B[2],B[3]].pins.should == 2
71
+ Blanket[B[1,2],B[3,4],B[5,6],B[7]].pins.should == 2
72
+ Blanket[B[1],B[2],B[3],B[4],B[5]].pins.should == 3
73
+ Blanket[B[1],B[2],B[3],B[4],B[5],B[6],B[7],B[8]].pins.should == 3
74
+ Blanket[B[1],B[2],B[3],B[4],B[5],B[6],B[7],B[8],B[9]].pins.should == 4
75
+ end
76
+
77
+ end end
@@ -0,0 +1,106 @@
1
+ module ArtDecomp describe Decomposer do
2
+
3
+ it 'should instantiate itself and its components properly' do
4
+ fsm = mock FSM
5
+ archs = Set[Arch[5,1], Arch[4,2]]
6
+ uv1, uv2 = mock('UVGenerator class'), mock('UVGenerator class')
7
+ qu1, qu2 = mock('QuGenerator class'), mock('QuGenerator class')
8
+ qv1, qv2 = mock('QvGenerator class'), mock('QvGenerator class')
9
+ [uv1, uv2].each { |gen| gen.should_receive(:new).with fsm, archs }
10
+ [qu1, qu2, qv1, qv2].each { |gen| gen.should_receive(:new).with no_args }
11
+ Decomposer.new :fsm => fsm, :archs => archs, :uv_gens => [uv1, uv2], :qu_gens => [qu1, qu2], :qv_gens => [qv1, qv2]
12
+ end
13
+
14
+ context 'given that the three generators are working properly' do
15
+
16
+ class StubGenerator
17
+ def initialize sequences
18
+ @sequences = sequences
19
+ end
20
+ def elems *key, &block
21
+ @sequences[key].each &block
22
+ end
23
+ alias uv_pairs elems
24
+ alias blankets elems
25
+ end
26
+
27
+ it 'should poll the generators and yield the resulting decompositions one by one' do
28
+ fsm = mock FSM, :beta_q => mock(Blanket, :pins => 3, :size => 5), :input_count => 4
29
+
30
+ u_a, v_a = Set[0,1], Set[2] # for this U/V pair: two Qu generating one Qv/G pair each
31
+ qu_a1, qv_a1, g_a1 = mock(Blanket, :pins => 2, :size => 4), mock(Blanket, :pins => 3, :size => 5), mock(Blanket, :pins => 2)
32
+ qu_a2, qv_a2, g_a2 = mock(Blanket, :pins => 2, :size => 4), mock(Blanket, :pins => 3, :size => 5), mock(Blanket, :pins => 2)
33
+
34
+ u_b, v_b = Set[0], Set[1,2] # for this U/V pair: one Qu generating two Qv/G pairs
35
+ qu_b = mock Blanket, :pins => 2, :size => 4
36
+ qv_bA, g_bA = mock(Blanket, :pins => 3, :size => 5), mock(Blanket, :pins => 2)
37
+ qv_bB, g_bB = mock(Blanket, :pins => 3, :size => 5), mock(Blanket, :pins => 2)
38
+
39
+ uv_gen = mock UVGenerator, :new => StubGenerator.new({[] => [[fsm, u_a, v_a], [fsm, u_b, v_b]]})
40
+ qu_gen = mock QuGenerator, :new => StubGenerator.new({[fsm, u_a, v_a] => [qu_a1, qu_a2],
41
+ [fsm, u_b, v_b] => [qu_b]})
42
+ qv_gen = mock QvGenerator, :new => StubGenerator.new({[fsm, u_a, v_a, qu_a1] => [[qv_a1, g_a1]],
43
+ [fsm, u_a, v_a, qu_a2] => [[qv_a2, g_a2]],
44
+ [fsm, u_b, v_b, qu_b] => [[qv_bA, g_bA], [qv_bB, g_bB]]})
45
+
46
+ decomposer = Decomposer.new :archs => Set[Arch[5,1]], :fsm => fsm, :uv_gens => [uv_gen], :qu_gens => [qu_gen], :qv_gens => [qv_gen]
47
+ results = decomposer.decompositions.to_a
48
+ results.size.should == 4
49
+ results.first.should == Decomposition.new(fsm, u_a, v_a, qu_a1, qv_a1, g_a1)
50
+ results.last.should == Decomposition.new(fsm, u_b, v_b, qu_b, qv_bB, g_bB)
51
+ end
52
+
53
+ it 'should yield only sensible decompositions' do
54
+ fsm = mock FSM
55
+ uv_gen = mock UVGenerator, :uv_pairs => [[fsm, Set[0], Set[1]]]
56
+ qu_gen = mock QuGenerator, :blankets => [mock(Blanket)]
57
+ qv_gen = mock QvGenerator, :blankets => [[mock(Blanket), mock(Blanket)], [mock(Blanket), mock(Blanket)]]
58
+ dec1 = mock Decomposition, :sensible? => true
59
+ dec2 = mock Decomposition, :sensible? => false
60
+ Decomposition.should_receive(:new).exactly(2).times.and_return dec1, dec2
61
+ decomposer = Decomposer.new :fsm => fsm, :uv_gens => [mock('UVG', :new => uv_gen)], :qu_gens => [mock('QuG', :new => qu_gen)], :qv_gens => [mock('QvG', :new => qv_gen)]
62
+ decomposer.decompositions.to_a.should == [dec1]
63
+ end
64
+
65
+ it 'should skip re-computing elements it already computed once (unless told not to)' do
66
+ qu1, qu2, qv, g1, g2 = mock(Blanket), mock(Blanket), mock(Blanket), mock(Blanket), mock(Blanket)
67
+ fsm = mock FSM
68
+ uv_gen = mock UVGenerator, :uv_pairs => [[fsm, Set[0], Set[1]], [fsm, Set[0], Set[1]], [fsm, Set[1], Set[0]]]
69
+ qu_gen = mock QuGenerator, :blankets => [qu1, qu1, qu2]
70
+ qv_gen = mock QvGenerator, :blankets => [[qv, g1], [qv, g1], [qv, g2]]
71
+ dec = mock Decomposition, :sensible? => true
72
+ Decomposition.should_receive(:new).exactly(8).times.and_return dec
73
+ decomposer = Decomposer.new :fsm => fsm, :uv_gens => [mock('UVG', :new => uv_gen)], :qu_gens => [mock('QuG', :new => qu_gen)], :qv_gens => [mock('QvG', :new => qv_gen)]
74
+ decomposer.decompositions.to_a.size.should == 8
75
+ Decomposition.should_receive(:new).exactly(27).times.and_return dec
76
+ decomposer = Decomposer.new :fsm => fsm, :uv_gens => [mock('UVG', :new => uv_gen)], :qu_gens => [mock('QuG', :new => qu_gen)], :qv_gens => [mock('QvG', :new => qv_gen)]
77
+ decomposer.decompositions(:keep_seen => true).to_a.size.should == 27
78
+ end
79
+
80
+ it 'should compute shallow ((u & v).size == 1) non-disjoint decompositions (if told to)' do
81
+ qu, qv, g1, g2 = mock(Blanket), mock(Blanket), mock(Blanket, :pins => 1), mock(Blanket, :pins => 2)
82
+ fsm = mock FSM
83
+ uv_gen = mock UVGenerator, :uv_pairs => [[fsm, Set[0,1], Set[2,3]]]
84
+ qu_gen = mock QuGenerator, :blankets => [qu]
85
+ qv_gen = mock QvGenerator, :blankets => [[qv, g2], [qv, g1]]
86
+ dec = mock Decomposition, :sensible? => true
87
+ Decomposition.should_receive(:new).exactly(6).times.and_return dec
88
+ decomposer = Decomposer.new :fsm => fsm, :uv_gens => [mock('UVG', :new => uv_gen)], :qu_gens => [mock('QuG', :new => qu_gen)], :qv_gens => [mock('QvG', :new => qv_gen)]
89
+ decomposer.decompositions(:non_disjoint => true).to_a.size.should == 4
90
+ end
91
+
92
+ it 'should compute deep ((u & v).size > 1) non-disjoint decompositions (if told to)' do
93
+ qu, qv, g1, g2 = mock(Blanket), mock(Blanket), mock(Blanket, :pins => 1), mock(Blanket, :pins => 2)
94
+ fsm = mock FSM
95
+ uv_gen = mock UVGenerator, :uv_pairs => [[fsm, Set[0,1], Set[2,3]]]
96
+ qu_gen = mock QuGenerator, :blankets => [qu]
97
+ qv_gen = mock QvGenerator, :blankets => [[qv, g2], [qv, g1]]
98
+ dec = mock Decomposition, :sensible? => true
99
+ Decomposition.should_receive(:new).exactly(8).times.and_return dec
100
+ decomposer = Decomposer.new :fsm => fsm, :uv_gens => [mock('UVG', :new => uv_gen)], :qu_gens => [mock('QuG', :new => qu_gen)], :qv_gens => [mock('QvG', :new => qv_gen)]
101
+ decomposer.decompositions(:non_disjoint => true, :deep_ndj => true).to_a.size.should == 4
102
+ end
103
+
104
+ end
105
+
106
+ end end
@@ -0,0 +1,119 @@
1
+ # encoding: UTF-8
2
+
3
+ module ArtDecomp describe Decomposition do
4
+
5
+ it 'should instantiate, compare and hash properly' do
6
+ fsm = mock FSM
7
+ u, v = Set[0,1], Set[2]
8
+ qu, qv, g = mock(Blanket), mock(Blanket), mock(Blanket)
9
+ dec = Decomposition.new fsm, u, v, qu, qv, g
10
+ dec.should == Decomposition.new(fsm, u, v, qu, qv, g)
11
+ dec.should_not == Decomposition.new(fsm, v, u, qu, qv, g)
12
+ dec.should_not == Decomposition.new(fsm, u, v, qv, qu, g)
13
+ dec.hash.should == Decomposition.new(fsm, u, v, qu, qv, g).hash
14
+ dec.should eql Decomposition.new(fsm, u, v, qu, qv, g)
15
+ end
16
+
17
+ it 'should create its F, Q, G and H blocks’ KISS representations properly' do
18
+ fsm = FSM.from_kiss 'spec/fixtures/fsm'
19
+ qu = Blanket[B[0,4,5], B[1,2,3,13,14], B[6,7,8,9,10,11,12], B[15,16,17,18,19]]
20
+ qv = Blanket[B[0,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,19], B[1,2,18]]
21
+ g = Blanket[B[0,2,5,6,7,9,11,15,16,17,18,19], B[1,3,4,8,10,12,13,14]]
22
+ decomposition = Decomposition.new fsm, Set[0,1,3], Set[2], qu, qv, g
23
+ decomposition.f_kiss.should == File.read('spec/fixtures/fsm.f')
24
+ decomposition.q_kiss.should == File.read('spec/fixtures/fsm.q')
25
+ decomposition.g_kiss.should == File.read('spec/fixtures/fsm.g')
26
+ decomposition.h_kiss.should == File.read('spec/fixtures/fsm.h')
27
+ end
28
+
29
+ it 'should create the H block properly, even if the G blanket seems ambiguous' do
30
+ fsm = FSM.from_kiss 'spec/fixtures/opus'
31
+ qu = Blanket[B[0,1,11,14,15,16,17,18,19,20,30,33,34,35,36,37], B[2,5,7,14,21,24,26,33], B[3,4,10,13,14,22,23,29,32,33], B[6,8,9,12,14,25,27,28,31,33]]
32
+ qv = Blanket[B[0,2,6,8,10,13,14,15,19,21,25,27,29,32,33,34], B[1,3,4,5,7,9,12,14,20,22,23,24,26,28,31,33], B[11,14,16,17,18,30,33,35,36,37]]
33
+ g = Blanket[B[0,2,6,8,10,13,14,15,19,21,25,27,29,32,33,34], B[1,3,4,5,7,9,12,14,20,22,23,24,26,28,31,33], B[11,14,16,17,18], B[30,33,35,36,37]]
34
+ decomposition = Decomposition.new fsm.expand_x(Set[0]), Set[1,2,3,4], Set[0], qu, qv, g
35
+ decomposition.h_kiss.should == File.read('spec/fixtures/opus.amb.h')
36
+ end
37
+
38
+ it 'should properly recode don’t-care states to *' do
39
+ fsm = FSM.from_kiss 'spec/fixtures/opus'
40
+ qu = Blanket[B[0,1,2,3,4,20,21], B[0,5,6,7], B[0,8,9,10,11,12,13,14,15,16], B[0,17,18,19]]
41
+ qv = Blanket[B[0,1,2,6,7,15,16,17,18], B[0,3,4,5,8,9,10,11,12,13,14,19], B[0,20,21]]
42
+ g = Blanket[B[0,1], B[2,6,7,15,16,17,18], B[3,4,5,8,9,10,11,12,13,14,19], B[20,21]]
43
+ decomposition = Decomposition.new fsm, Set[0,1,3,4], Set[2], qu, qv, g
44
+ decomposition.h_kiss.should == File.read('spec/fixtures/opus.h')
45
+ end
46
+
47
+ it 'should properly report whether it’s valid' do
48
+ fsm = FSM.from_kiss 'spec/fixtures/fsm'
49
+ qu = Blanket[B[0,1,2,3,4,5,17], B[6,7,8,9,10,11,12,13,14,15,16,18,19]]
50
+ qv = Blanket[B[0,1,2,17,19], B[3,6,7,8,9,10,11,12], B[4,5,18], B[13,14,15,16]]
51
+ g = Blanket[B[0,1,4,6,17,18,19], B[2,7,9,11], B[3,5,8,10,12], B[13,14,15,16]]
52
+ Decomposition.new(fsm, Set[1,3], Set[0,2], qu, qv, g).should be_valid
53
+ Decomposition.new(fsm, Set[1,3], Set[0,2], qv, qu, g).should_not be_valid
54
+ Decomposition.new(fsm, Set[1,3], Set[0,2], qv, qv, g).should_not be_valid
55
+ end
56
+
57
+ it 'should report whether it’s disjoint' do
58
+ fsm = mock FSM
59
+ b = mock Blanket
60
+ Decomposition.new(fsm, Set[0], Set[1], b, b, b).should be_disjoint
61
+ Decomposition.new(fsm, Set[0,1], Set[1], b, b, b).should_not be_disjoint
62
+ Decomposition.new(fsm, Set[1], Set[0,1], b, b, b).should_not be_disjoint
63
+ end
64
+
65
+ context 'when assesing whether it’s usable in the future' do
66
+
67
+ before do
68
+ @fsm = mock FSM, :beta_q => mock(Blanket, :pins => 3), :input_count => 3, :output_count => 2
69
+ @b = mock Blanket
70
+ @b1 = mock Blanket, :pins => 1, :size => 2
71
+ @b2 = mock Blanket, :pins => 2, :size => 4
72
+ @b3 = mock Blanket, :pins => 3
73
+ @b4 = mock Blanket, :pins => 4
74
+ end
75
+
76
+ it 'should properly report whether it’s decomposable further' do
77
+ Decomposition.new(@fsm, Set[0], Set[1], @b1, @b, @b).should_not be_decomposable
78
+ Decomposition.new(@fsm, Set[0], Set[1], @b2, @b, @b).should be_decomposable
79
+ end
80
+
81
+ it 'should properly report whether it’s sensible, based on the target Archs and G and H blocks’ architectures' do
82
+ Decomposition.new(@fsm, Set[0], Set[1,2], @b2, @b2, @b2).should_not be_sensible Set[Arch[3,2]]
83
+ Decomposition.new(@fsm, Set[0], Set[1,2], @b2, @b2, @b2).should be_sensible Set[Arch[4,1]]
84
+ Decomposition.new(@fsm, Set[], Set[0,1,2], @b2, @b2, @b2).should_not be_sensible Set[Arch[4,1]]
85
+ Decomposition.new(@fsm, Set[0], Set[1,2], @b2, @b3, @b2).should_not be_sensible Set[Arch[4,1]]
86
+ Decomposition.new(@fsm, Set[0,1], Set[1,2], @b2, @b2, @b2).should_not be_sensible Set[Arch[4,1]]
87
+ Decomposition.new(@fsm, Set[0], Set[1,2], @b3, @b2, @b2).should_not be_sensible Set[Arch[4,1]]
88
+ Decomposition.new(@fsm, Set[0], Set[1,2], @b2, @b2, @b3).should_not be_sensible Set[Arch[4,1]]
89
+ end
90
+
91
+ it 'should properly report whether it’s final based on a Set of Archs' do
92
+ Decomposition.new(@fsm, Set[0], Set[1], @b1, @b3, @b3).should be_final Set[Arch[5,1]]
93
+ Decomposition.new(@fsm, Set[0], Set[1], @b1, @b3, @b3).should_not be_final Set[Arch[4,2]]
94
+ Decomposition.new(@fsm, Set[], Set[1], @b1, @b3, @b3).should be_final Set[Arch[4,2]]
95
+ Decomposition.new(@fsm, Set[], Set[1,2], @b1, @b3, @b3).should_not be_final Set[Arch[4,2]]
96
+ Decomposition.new(@fsm, Set[], Set[1], @b1, @b4, @b3).should_not be_final Set[Arch[4,2]]
97
+ Decomposition.new(@fsm, Set[0], Set[1,2], @b1, @b3, @b3).should be_final Set[Arch[5,1]]
98
+ Decomposition.new(@fsm, Set[0,1], Set[2], @b1, @b3, @b3).should_not be_final Set[Arch[5,1]]
99
+ Decomposition.new(@fsm, Set[0], Set[1,2], @b2, @b3, @b3).should_not be_final Set[Arch[5,1]]
100
+ Decomposition.new(@fsm, Set[0], Set[1,2], @b1, @b3, @b4).should_not be_final Set[Arch[5,1]]
101
+ end
102
+
103
+ it 'should properly report G cell count (if G fits the provided Archs)' do
104
+ Decomposition.new(@fsm, Set[0], Set[1,2], @b, @b2, @b2).g_cells(Set[Arch[5,1]]).should == 2
105
+ Decomposition.new(@fsm, Set[0], Set[1,2], @b, @b2, @b2).g_cells(Set[Arch[4,2]]).should == 1
106
+ Decomposition.new(@fsm, Set[0], Set[1,2], @b, @b2, @b2).g_cells(Set[Arch[3,2]]).should be_nil
107
+ Decomposition.new(@fsm, Set[0], Set[1,2], @b, @b2, @b3).g_cells(Set[Arch[4,2]]).should == 2
108
+ end
109
+
110
+ it 'should properly report H cell count (if G fits the provided Archs)' do
111
+ Decomposition.new(@fsm, Set[0], Set[1,2], @b1, @b1, @b2).h_cells(Set[Arch[5,1]]).should == 4
112
+ Decomposition.new(@fsm, Set[0], Set[1,2], @b1, @b1, @b2).h_cells(Set[Arch[4,2]]).should == 2
113
+ Decomposition.new(@fsm, Set[0], Set[1,2], @b1, @b1, @b2).h_cells(Set[Arch[3,1]]).should be_nil
114
+ Decomposition.new(@fsm, Set[0], Set[1,2], @b1, @b2, @b2).h_cells(Set[Arch[4,2]]).should == 3
115
+ end
116
+
117
+ end
118
+
119
+ end end
@@ -0,0 +1,161 @@
1
+ # encoding: UTF-8
2
+
3
+ module ArtDecomp describe Executable do
4
+
5
+ before do
6
+ @orig_stderr = $stderr
7
+ $stderr = StringIO.new
8
+ @fsm = 'spec/fixtures/fsm'
9
+ @dir = "#{Dir.tmpdir}/#{rand.to_s}"
10
+ @args = ['--archs', '5/1', '--outdir', @dir, @fsm]
11
+ end
12
+
13
+ after do
14
+ $stderr = @orig_stderr
15
+ FileUtils.rmtree @dir if Dir.exists? @dir
16
+ end
17
+
18
+ def stderr
19
+ $stderr.rewind
20
+ $stderr.read
21
+ end
22
+
23
+ it 'should require an FSM' do
24
+ lambda { Executable.new([]) }.should raise_error SystemExit
25
+ stderr.should =~ rex('no FSM given')
26
+ end
27
+
28
+ it 'should require that the FSM exists' do
29
+ lambda { Executable.new(['bogus']) }.should raise_error SystemExit
30
+ stderr.should =~ rex('FSM does not exist')
31
+ end
32
+
33
+ it 'should require at least one target Arch' do
34
+ lambda { Executable.new([@fsm]) }.should raise_error SystemExit
35
+ stderr.should =~ rex('no architecture given')
36
+ end
37
+
38
+ it 'should require that all architectures are parsable' do
39
+ args = ['--archs', '5/1', 'a/b', '--outdir', @dir, @fsm]
40
+ lambda { Executable.new(args) }.should raise_error SystemExit
41
+ stderr.should =~ rex('archs not in the form of inputs/outputs')
42
+ end
43
+
44
+ it 'should require output directory' do
45
+ lambda { Executable.new(['--archs', '5/1', '--', @fsm]) }.should raise_error SystemExit
46
+ stderr.should =~ rex('no output directory given')
47
+ end
48
+
49
+ it 'should require that the output directory does not exist' do
50
+ lambda { Executable.new(['--archs', '5/1', '--outdir', Dir.tmpdir, @fsm]) }.should raise_error SystemExit
51
+ stderr.should =~ rex('output directory exists')
52
+ end
53
+
54
+ it 'should require that the output directory is creatable' do
55
+ Dir.mkdir @dir, 0400
56
+ subdir = "#{@dir}/#{rand.to_s}"
57
+ lambda { Executable.new(['--archs', '5/1', '--outdir', subdir, @fsm]) }.should raise_error SystemExit
58
+ stderr.should =~ rex('output directory cannot be created')
59
+ end
60
+
61
+ it 'should create the output directory' do
62
+ Dir.exists?(@dir).should be_false
63
+ Executable.new @args
64
+ Dir.exists?(@dir).should be_true
65
+ end
66
+
67
+ it 'should validate that the specified UV generator exists' do
68
+ lambda { Executable.new(@args + ['--uv', 'bogus']) }.should raise_error SystemExit
69
+ stderr.should =~ rex('no such UV generator')
70
+ end
71
+
72
+ it 'should validate that the specified Qu generator exists' do
73
+ lambda { Executable.new(@args + ['--qu', 'bogus']) }.should raise_error SystemExit
74
+ stderr.should =~ rex('no such Qu generator')
75
+ end
76
+
77
+ it 'should validate that the specified Qv generator exists' do
78
+ lambda { Executable.new(@args + ['--qv', 'bogus']) }.should raise_error SystemExit
79
+ stderr.should =~ rex('no such Qv generator')
80
+ end
81
+
82
+ it 'should dump the resulting decompositions into a file' do
83
+ fsm = FSM.from_kiss 'spec/fixtures/fsm'
84
+ dec = Decomposition.new fsm, Set[0], Set[1], Blanket[B[0],B[1],B[2]], Blanket[], Blanket[]
85
+
86
+ decomposer = mock Decomposer, :decompositions => [dec, dec].each
87
+ Decomposer.should_receive(:new).with(:fsm => fsm, :archs => an_instance_of(Set), :uv_gens => [UVGenerator::Relevance], :qu_gens => [QuGenerator::EdgeLabels], :qv_gens => [QvGenerator::GraphColouring]).and_return decomposer
88
+
89
+ Executable.new(@args).run false
90
+ Marshal.load(File.read("#{@dir}/decompositions")).should == [dec, dec]
91
+ end
92
+
93
+ it 'should create files holding the resulting Decomposition objects and keep track of the best decomposition' do
94
+ dec0 = Decomposition.new FSM.from_kiss('spec/fixtures/fsm'), Set[0], Set[1], Blanket[B[0],B[1],B[2]], Blanket[], Blanket[]
95
+ dec1 = Decomposition.new FSM.from_kiss('spec/fixtures/fsm'), Set[1], Set[0], Blanket[B[0],B[1],B[2]], Blanket[], Blanket[]
96
+ Decomposer.should_receive(:new).and_return mock(Decomposer, :decompositions => [dec0, dec1].each)
97
+ ex = Executable.new @args
98
+ ex.best.should be_nil
99
+ ex.run
100
+ ex.best.should == 4
101
+ Marshal.load(File.read("#{@dir}/0.dec")).should == dec0
102
+ Marshal.load(File.read("#{@dir}/1.dec")).should == dec1
103
+ end
104
+
105
+ it 'should pass all of the requested generators and architectures to the Decomposer, and report on what it’s using when asked' do
106
+ fsm = mock FSM, :fsm_cells => nil, :implementable_in? => false, :stats => ''
107
+ FSM.should_receive(:from_kiss).with(@fsm).and_return fsm
108
+
109
+ decomposer = mock Decomposer, :decompositions => [].each
110
+ Decomposer.should_receive(:new).with(:fsm => fsm, :archs => Set[Arch[4,2], Arch[5,1]], :uv_gens => [UVGenerator::Braindead, UVGenerator::Braindead], :qu_gens => [QuGenerator::BlockTable, QuGenerator::EdgeLabels], :qv_gens => [QvGenerator::GraphMerging, QvGenerator::Bipainting]).and_return decomposer
111
+
112
+ args = ['--archs', '5/1', '4/2', '--uv', 'Braindead', 'Braindead', '--qu', 'BlockTable', 'EdgeLabels', '--qv', 'GraphMerging', 'Bipainting', '--outdir', @dir, @fsm]
113
+ ex = Executable.new args
114
+ ex.gens.should == 'Braindead+Braindead, BlockTable+EdgeLabels, GraphMerging+Bipainting'
115
+ ex.run
116
+ end
117
+
118
+ it 'should allow setting any of the generators to ‘all’' do
119
+ fsm = mock FSM, :fsm_cells => nil, :implementable_in? => false, :stats => ''
120
+ FSM.should_receive(:from_kiss).with(@fsm).and_return fsm
121
+
122
+ decomposer = mock Decomposer, :decompositions => [].each
123
+ Decomposer.should_receive(:new).with(:fsm => fsm, :archs => Set[Arch[5,1]], :uv_gens => [UVGenerator::Braindead, UVGenerator::Relevance], :qu_gens => [QuGenerator::BlockTable, QuGenerator::EdgeLabels], :qv_gens => [QvGenerator::Bipainting, QvGenerator::GraphColouring,QvGenerator::GraphMerging]).and_return decomposer
124
+
125
+ args = ['--archs', '5/1', '--uv', 'all', '--qu', 'all', '--qv', 'all', '--outdir', @dir, @fsm]
126
+ Executable.new(args).run
127
+ end
128
+
129
+ it 'should decompose iteratively according to the number of iterations and expose this number (along with archs and dir)' do
130
+ args = ['--archs', '2/1', '--iters', '2', '--outdir', @dir, 'spec/fixtures/lion']
131
+ dec = Decomposition.new FSM.from_kiss('spec/fixtures/lion'), Set[0], Set[1], Blanket[B[0],B[1],B[2]], Blanket[], Blanket[]
132
+ Decomposer.stub!(:new).and_return mock(Decomposer, :decompositions => [dec].each)
133
+ ex = Executable.new args
134
+ ex.archs.should == Set[Arch[2,1]]
135
+ ex.iters.should == 2
136
+ ex.dir.should == @dir
137
+ ex.run
138
+ File.exists?("#{@dir}/0.dec").should be_true
139
+ File.exists?("#{@dir}/0/0.dec").should be_true
140
+ File.exists?("#{@dir}/0/0/0.dec").should be_false
141
+ end
142
+
143
+ it 'should allow logging to the specified file/stream' do
144
+ log = Tempfile.new rand
145
+ Decomposer.should_receive(:new).and_return mock(Decomposer, :decompositions => [].each)
146
+ Executable.new(['--archs', '5/1', '4/2', '--debug', '--log', log.path, '--outdir', @dir, @fsm]).run
147
+ Logging.level.should == Logger::DEBUG
148
+ Logging.off
149
+ File.read(log.path).should =~ rex('FSM 4/2+10s → 5/1+4/2')
150
+ end
151
+
152
+ it 'should handle the s8 edge case with grace' do
153
+ log = Tempfile.new rand
154
+ Executable.new(['--archs', '2/1', '--log', log.path, '--outdir', @dir, 'spec/fixtures/s8']).run
155
+ Logging.off
156
+ File.read(log.path).should =~ rex('final best decomposition: 0 cells')
157
+ end
158
+
159
+ # FIXME: add specs for --non-disjoint and --deep-ndj
160
+
161
+ end end
@@ -0,0 +1,146 @@
1
+ # encoding: UTF-8
2
+
3
+ module ArtDecomp describe FSM do
4
+
5
+ context 'parsed from an example KISS file' do
6
+
7
+ before do
8
+ @fsm = FSM.from_kiss 'spec/fixtures/fsm'
9
+ @lion = FSM.from_kiss 'spec/fixtures/lion'
10
+ @mark1 = FSM.from_kiss 'spec/fixtures/mark1'
11
+ @mc = FSM.from_kiss 'spec/fixtures/mc'
12
+ @opus = FSM.from_kiss 'spec/fixtures/opus'
13
+ @s8 = FSM.from_kiss 'spec/fixtures/s8'
14
+ @s420 = FSM.from_kiss 'spec/fixtures/s420'
15
+ end
16
+
17
+ it 'should parse both KISS files and strings' do
18
+ @mc.should == FSM.from_kiss(File.read 'spec/fixtures/mc')
19
+ end
20
+
21
+ it 'should handle edge cases, like KISS files with arbitrary next-states' do
22
+ lambda { FSM.from_kiss 'spec/fixtures/ex5' }.should_not raise_error
23
+ end
24
+
25
+ it 'should properly report the number of inputs' do
26
+ @opus.input_count.should == 5
27
+ @lion.input_count.should == 2
28
+ @mc.input_count.should == 3
29
+ end
30
+
31
+ it 'should properly report the number of outputs' do
32
+ @opus.output_count.should == 6
33
+ @lion.output_count.should == 1
34
+ @mc.output_count.should == 5
35
+ end
36
+
37
+ it 'should properly generate the Q Blanket' do
38
+ @opus.beta_q.should == Blanket[B[0,1,2], B[0,3,4], B[0,5], B[0,6,7], B[0,8,9,10,11,12,13,14], B[0,15,16], B[0,17,18], B[0,19], B[0,20], B[0,21]]
39
+ @lion.beta_q.should == Blanket[B[0,1,2], B[3,4,5], B[6,7,8], B[9,10]]
40
+ @mc.beta_q.should == Blanket[B[0,1,2], B[3,4], B[5,6,7], B[8,9]]
41
+ end
42
+
43
+ it 'should properly generate the F Blanket' do
44
+ @opus.beta_f.should == Blanket[B[0,1], B[2,3,9], B[4,14], B[5,6], B[7,8,20,21], B[10,16], B[11,18], B[12,15], B[13,17], B[19]]
45
+ @lion.beta_f.should == Blanket[B[0,1,4], B[2], B[2,3,7], B[5,6,10], B[8,9]]
46
+ @mc.beta_f.should == Blanket[B[0,1], B[2], B[3], B[4], B[5], B[6,7], B[8], B[9]]
47
+ end
48
+
49
+ it 'should properly generate selected inputs Blankets' do
50
+ @mc.beta_x(Set[0]).should == Blanket[B[0,1,3,4,6,7,8,9], B[1,2,3,4,5,7,8,9]]
51
+ @mc.beta_x(Set[1,2]).should == Blanket[B[0,1,3,5,6,8], B[0,1,4,5,6,9], B[0,2,3,6,7,8], B[0,2,4,6,7,9]]
52
+ @mc.beta_x(Set[]).should == Blanket[B[0,1,2,3,4,5,6,7,8,9]]
53
+ end
54
+
55
+ it 'should properly generate its KISS representation' do
56
+ @opus.to_kiss.should == File.read('spec/fixtures/opus.to_kiss')
57
+ @lion.to_kiss.should == File.read('spec/fixtures/lion.to_kiss')
58
+ @mc.to_kiss.should == File.read('spec/fixtures/mc.to_kiss')
59
+ end
60
+
61
+ it 'should return given inputs’ encoding for the given row(s)' do
62
+ @opus.x_encoding(Set[2], B[0]).should == '1'
63
+ @opus.x_encoding(Set[0], B[0]).should == '-'
64
+ @opus.x_encoding(Set[0], B[7,8]).should == '0'
65
+ lambda { @opus.x_encoding Set[0], B[8,9] }.should raise_error(AmbiguousEncodingQuery, 'ambiguous encoding query: block 8,9')
66
+ @opus.x_encoding(Set[0,2], B[0]).should == '-1'
67
+ end
68
+
69
+ it 'should return output encoding for the given row(s)' do
70
+ @opus.y_encoding(B[0]).should == '110000'
71
+ @opus.y_encoding(B[0,1,2,3]).should == '110000'
72
+ lambda { @opus.y_encoding B[3,4] }.should raise_error(AmbiguousEncodingQuery, 'ambiguous encoding query: block 3,4')
73
+ @lion.y_encoding(B[2,3]).should == '1'
74
+ @mark1.y_encoding(B[0]).should == '-11---1-00------'
75
+ @mark1.y_encoding(B[0,16]).should == '-11---1-00100000'
76
+ end
77
+
78
+ it 'should return state encoding for the given row(s)' do
79
+ @opus.q_encoding(B[1]).should == 'init0'
80
+ @opus.q_encoding(B[1,2]).should == 'init0'
81
+ @opus.q_encoding(B[0,1,2]).should == 'init0'
82
+ @opus.q_encoding(B[0]).should == '-'
83
+ lambda { @opus.q_encoding B[2,3] }.should raise_error(AmbiguousEncodingQuery, 'ambiguous encoding query: block 2,3')
84
+ end
85
+
86
+ it 'should return the row(s) of a state matching next-state of given row(s)' do
87
+ @opus.state_rows_of_next_state_of(B[20,21]).should == B[0,8,9,10,11,12,13,14]
88
+ @mark1.state_rows_of_next_state_of(B[2]).should == B[0,22]
89
+ end
90
+
91
+ it 'should equal/not-equal other FSMs and hash properly' do
92
+ @lion.should_not == FSM.from_kiss('spec/fixtures/opus')
93
+ @lion.should == FSM.from_kiss('spec/fixtures/lion')
94
+ @lion.hash.should == FSM.from_kiss('spec/fixtures/lion').hash
95
+ @lion.should eql FSM.from_kiss('spec/fixtures/lion')
96
+ end
97
+
98
+ it 'should expand selected input columns and return a new FSM instance' do
99
+ @lion.expand_x(Set[0,1]).should == FSM.from_kiss('spec/fixtures/lion.exp')
100
+ @fsm.expand_x(Set[0,1,2,3]).should == FSM.from_kiss('spec/fixtures/fsm.exp')
101
+ @fsm.expand_x(Set[0,3]).should == FSM.from_kiss('spec/fixtures/fsm.partially-exp')
102
+ end
103
+
104
+ it 'should return self if asked to expand columns lacking don’t-cares' do
105
+ opus = FSM.from_kiss 'spec/fixtures/opus.to_kiss'
106
+ opus.expand_x(Set[2]).should equal opus
107
+ end
108
+
109
+ it 'should report its input/output/state counts' do
110
+ @fsm.stats.should == '4/2+10s'
111
+ @lion.stats.should == '2/1+4s'
112
+ @mark1.stats.should == '5/16+15s'
113
+ @mc.stats.should == '3/5+4s'
114
+ @opus.stats.should == '5/6+10s'
115
+ end
116
+
117
+ it 'should report whether its directly implementable in a given Set of Archs' do
118
+ @lion.should be_implementable_in Set[Arch[4,2]]
119
+ @mc.should_not be_implementable_in Set[Arch[4,2]]
120
+ @mc.should be_implementable_in Set[Arch[5,1]]
121
+ @s8.should be_implementable_in Set[Arch[2,1]]
122
+ end
123
+
124
+ it 'should report the cell count for a given Set of Archs (if it’s implementable in it)' do
125
+ @lion.fsm_cells(Set[Arch[4,1]]).should == 3
126
+ @lion.fsm_cells(Set[Arch[4,2]]).should == 2
127
+ @mc.fsm_cells(Set[Arch[4,2]]).should be_nil
128
+ @mc.fsm_cells(Set[Arch[5,1]]).should == 7
129
+ @s8.fsm_cells(Set[Arch[2,1]]).should == 0
130
+ @s8.fsm_cells(Set[Arch[3,2]]).should == 0
131
+ @s8.fsm_cells(Set[Arch[4,1]]).should == 0
132
+ end
133
+
134
+ it 'should report its input relevance, and drop irrelevant inputs' do
135
+ @fsm.input_relevance.should == [2, 1, 3, 0, nil, nil, nil, nil]
136
+ @lion.input_relevance.should == [0, nil, nil, 1]
137
+ @mark1.input_relevance.should == [nil, nil, nil, nil, 0, 3, 2, 4, 1]
138
+ @mc.input_relevance.should == [nil, nil, 2, 1, 0]
139
+ @opus.input_relevance.should == [nil, nil, nil, nil, 2, 3, 4, 0, 1]
140
+ @s8.input_relevance.should == [3, 2, 1, 0, nil, nil, nil]
141
+ @s420.input_relevance.should == [1, 0, 18, nil, nil, nil, nil, nil, 17, 16, 15, 14, 13]
142
+ end
143
+
144
+ end
145
+
146
+ end end