caffe 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 87a24cb6239268eff1a716f0f770edf6b708d0f3
4
- data.tar.gz: 4e589a9b416d7191567d6d07bc7b127d6bded9d4
3
+ metadata.gz: 0a805f3a1f52051590a5a9f99655b5b0484e18c6
4
+ data.tar.gz: 460cb54ae69140bca4900bba9fa721f7658bae6e
5
5
  SHA512:
6
- metadata.gz: 768510fe5634d31bd847593679836ee2b123264693ce3698dd421c30129fa76b7c26194939669e4e55c904c5def2825b5941a9cafec8df0cc2f756a9d146ac15
7
- data.tar.gz: b3a6c3de6efe2190be72ac7637c81f300182b7c5b4417fedbfff28eaf42a5f02eab71dc00c17aadf752f104ad60ec1a6110f983d1c78f786a1b1e8f9629da2cb
6
+ metadata.gz: a1d5e15287db253830036f54bff7a20f6b07e86551ce8cf1d549b66428d130d57b731dfd62b305d843038f9564e0a9330204fff8bda19e29d163dd527160f45c
7
+ data.tar.gz: 4f104903a669231d236056855572c7060a5afed9c61713b20046578e4d1e4081b7d414253a72abb63fc0b62c280d7e93ee3dfc49f71236903d8c07c9aaaecae4
data/README.md CHANGED
@@ -45,21 +45,20 @@ Build flags can be passed to `extconf.rb` to specify the `caffe` path and other
45
45
 
46
46
  If `rake` is used to build the extension, use:
47
47
 
48
- ```shell
48
+ ```
49
49
  $ rake compile -- <build flags>
50
50
  ```
51
51
 
52
52
  If installing from `gem`, use:
53
53
 
54
- ```shell
54
+ ```
55
55
  $ gem install caffe -- <build flags>
56
56
  ```
57
57
 
58
58
  If installing from `bundler`, use:
59
59
 
60
- ```shell
60
+ ```
61
61
  $ bundle config build.caffe <build flags>
62
-
63
62
  $ bundle install
64
63
  ```
65
64
 
@@ -67,39 +66,108 @@ $ bundle install
67
66
 
68
67
  If you put all the headers and libs required in default search path (like `/usr/local` & `/usr`), and use the default setting (with GPU mode) then everything should be ok
69
68
 
70
- ## Build ##
69
+ ## Installation ##
71
70
 
72
- Now the project is not a gem project, so it can be built by `rake`
71
+ ```
72
+ $ gem install caffe -- <build flags>
73
+ ```
73
74
 
74
- First, use `bundler` to install all the dependencies:
75
+ Using bundler:
75
76
 
76
- ```shell
77
+ ```
78
+ $ bundle config build.caffe <build flags>
77
79
  $ bundle install
78
80
  ```
79
81
 
80
- Then, use `rake` to build:
82
+ Require everything with:
83
+
84
+ ```ruby
85
+ require 'caffe'
86
+ ```
87
+
88
+ ## Development ##
89
+
90
+ First clone this repository:
91
+
92
+ ```
93
+ $ git clone git://github.com/gyf1214/ruby-caffe
94
+ ```
95
+
96
+ Then build all prerequisites for gem & test (proto files & test net):
97
+
98
+ ```
99
+ $ rake build:pre
100
+ ```
101
+
102
+ When building proto files, the caffe path (which contains `proto/caffe.proto`) can be specified by `ENV['CAFFE']`, i.e.
103
+
104
+ ```
105
+ $ CAFFE=/path/to/caffe rake build:pre
106
+ ```
81
107
 
82
- ```shell
108
+ Or by default the path is `.`, so you can just copy / link your `caffe.proto` to `proto/`
109
+
110
+ Compile C++ extension with:
111
+
112
+ ```
83
113
  $ rake compile -- <build flags>
84
114
  ```
85
115
 
86
- Test with:
116
+ Build flags & other methods to link caffe are described above
87
117
 
88
- ```shell
118
+ Test the code with (which include the rubocop code style check):
119
+
120
+ ```
89
121
  $ rake test
90
122
  ```
91
123
 
92
- or after compilation:
124
+ Build the gem with:
125
+
126
+ ```
127
+ $ rake build
128
+ ```
129
+
130
+ Which will run all tests and build the gem in `pkg/caffe-<version>.gem`
131
+
132
+ Install the gem locally with:
133
+
134
+ ```
135
+ $ rake install
136
+ ```
137
+
138
+ ### Other rake tasks ###
139
+
140
+ ```
141
+ $ rake rubocop
142
+ ```
93
143
 
94
- ```shell
144
+ Run the rubocop code style check, you can also run auto correct with `rake rubocop:auto_correct`
145
+
146
+ ```
95
147
  $ rake spec
96
148
  ```
97
149
 
98
- require the lib with:
150
+ Run rspec only, without checking the dependencies, note that `build:pre` & `compile` must be completed before
151
+
152
+ ```
153
+ $ rake build:proto
154
+ $ rake build:test
155
+ ```
156
+
157
+ The two tasks build proto files and trained model for testing respectively. `ENV['CAFFE']` can be specified when building proto
99
158
 
100
- ```ruby
101
- require './lib/caffe'
102
159
  ```
160
+ $ rake clean
161
+ $ rake clobber
162
+ ```
163
+
164
+ The first cleans the temporary files and the second cleans all generated files
165
+
166
+ ```
167
+ $ rake release[remote]
168
+ ```
169
+
170
+ Create a version tag, and push to both git remote & [rubygems.org](https://rubygems.org)
103
171
 
104
172
  ## Author ##
105
173
 
@@ -2,6 +2,7 @@
2
2
  #include "common.hpp"
3
3
  #include "blob.hpp"
4
4
  #include "net.hpp"
5
+ #include "solver.hpp"
5
6
 
6
7
  extern "C"
7
8
  void Init_caffe() {
@@ -10,4 +11,5 @@ void Init_caffe() {
10
11
  Init_common();
11
12
  Init_blob();
12
13
  Init_net();
14
+ Init_solver();
13
15
  }
@@ -36,7 +36,7 @@ static Object getBlobByName(Object self, String name) {
36
36
  static Object forward(Object self) {
37
37
  Net *net = from_ruby<Net *>(self);
38
38
  float loss = .0;
39
- net -> Forward(NULL);
39
+ net -> Forward(&loss);
40
40
  return to_ruby(loss);
41
41
  }
42
42
 
@@ -51,5 +51,7 @@ void Init_net() {
51
51
  .define_method("blob", &getBlobByName)
52
52
  .define_method("reshape!", &Net::Reshape)
53
53
  .define_method("load_trained!", &Net::CopyTrainedLayersFromBinaryProto)
54
- .define_method("forward!", &forward);
54
+ .define_method("forward!", &forward)
55
+ .define_method("forward_backward!", &Net::ForwardBackward)
56
+ .define_method("share_trained!", &Net::ShareTrainedLayersWith);
55
57
  }
@@ -0,0 +1,61 @@
1
+ #include "solver.hpp"
2
+ #include "net.hpp"
3
+ #include "util.hpp"
4
+ #include <iostream>
5
+ #include <rice/Data_Type.hpp>
6
+ #include <rice/Constructor.hpp>
7
+ #include <rice/String.hpp>
8
+
9
+ using namespace Rice;
10
+
11
+ struct SolverConstructor {
12
+ static void construct(Object self, String path) {
13
+ caffe::SolverParameter param;
14
+ caffe::ReadSolverParamsFromTextFileOrDie(path.str(), &param);
15
+ Solver *solver = SolverRegistry::CreateSolver(param);
16
+ DATA_PTR(self.value()) = solver;
17
+ }
18
+ };
19
+
20
+ static Object getNet(Object self) {
21
+ Solver *solver = from_ruby<Solver *>(self);
22
+ Net *net = solver -> net().get();
23
+
24
+ if (net) {
25
+ return objectNoGC(net);
26
+ } else {
27
+ return Qnil;
28
+ }
29
+ }
30
+
31
+ static Object getTestNets(Object self) {
32
+ Solver *solver = from_ruby<Solver *>(self);
33
+ const std::vector<boost::shared_ptr<Net> > &nets = solver -> test_nets();
34
+ return mapArray(nets.begin(), nets.end(),
35
+ sharedToObj<Net, boost::shared_ptr<Net> >);
36
+ }
37
+
38
+ static void restore(Object self, String path) {
39
+ Solver *solver = from_ruby<Solver *>(self);
40
+ solver -> Restore(path.c_str());
41
+ }
42
+
43
+ static void solve(Object self) {
44
+ Solver *solver = from_ruby<Solver *>(self);
45
+ solver -> Solve(NULL);
46
+ }
47
+
48
+ void Init_solver() {
49
+ Module rb_mCaffe = define_module("Caffe");
50
+
51
+ Data_Type<Solver> rb_cSolver = rb_mCaffe
52
+ .define_class<Solver>("Solver")
53
+ .define_constructor(SolverConstructor())
54
+ .define_method("net", getNet)
55
+ .define_method("test_nets", getTestNets)
56
+ .define_method("iter", &Solver::iter)
57
+ .define_method("step!", &Solver::Step)
58
+ .define_method("snapshot", &Solver::Snapshot)
59
+ .define_method("restore!", restore)
60
+ .define_method("solve!", solve);
61
+ }
@@ -0,0 +1,11 @@
1
+ #ifndef __SOLVER
2
+ #define __SOLVER
3
+
4
+ #include <caffe/caffe.hpp>
5
+
6
+ typedef caffe::Solver<float> Solver;
7
+ typedef caffe::SolverRegistry<float> SolverRegistry;
8
+
9
+ void Init_solver(void);
10
+
11
+ #endif
@@ -1,6 +1,7 @@
1
1
  #ifndef __UTIL
2
2
  #define __UTIL
3
3
 
4
+ #include <caffe/caffe.hpp>
4
5
  #include <rice/Data_Type.hpp>
5
6
  #include <rice/Array.hpp>
6
7
  #include <vector>
@@ -17,6 +18,11 @@ Rice::Data_Object<T> objectNoGC(T *obj) {
17
18
  EmptyFreeFunction<T>::free);
18
19
  }
19
20
 
21
+ template<typename T, typename U>
22
+ Rice::Data_Object<T> sharedToObj(U obj) {
23
+ return objectNoGC(obj.get());
24
+ }
25
+
20
26
  template<typename Iter, typename Func>
21
27
  Rice::Array mapArray(Iter begin, Iter end, Func func) {
22
28
  Rice::Array ret;
@@ -1,3 +1,3 @@
1
1
  module Caffe
2
- VERSION = '0.1.0'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
@@ -22,5 +22,8 @@ RSpec.describe Caffe do
22
22
  expect(Caffe.solver_count).to eq(2)
23
23
  expect(Caffe.solver_rank).to eq(1)
24
24
  expect(Caffe.multiprocess).to eq(true)
25
+ Caffe.solver_count = 1
26
+ Caffe.solver_rank = 0
27
+ Caffe.multiprocess = false
25
28
  end
26
29
  end
@@ -12,6 +12,21 @@ layer {
12
12
  }
13
13
  }
14
14
 
15
+ layer {
16
+ name: "label"
17
+ type: "Input"
18
+ top: "label"
19
+ include {
20
+ phase: TRAIN
21
+ }
22
+ input_param {
23
+ shape: {
24
+ dim: 1
25
+ dim: 1
26
+ }
27
+ }
28
+ }
29
+
15
30
  layer {
16
31
  name: "ip1"
17
32
  type: "InnerProduct"
@@ -68,4 +83,18 @@ layer {
68
83
  type: "Softmax"
69
84
  bottom: "ip2"
70
85
  top: "prob"
86
+ include {
87
+ phase: TEST
88
+ }
89
+ }
90
+
91
+ layer {
92
+ name: "loss"
93
+ type: "SoftmaxWithLoss"
94
+ bottom: "ip2"
95
+ bottom: "label"
96
+ top: "loss"
97
+ include {
98
+ phase: TRAIN
99
+ }
71
100
  }
@@ -1,14 +1,14 @@
1
- net: "test_train.prototxt"
1
+ net: "spec/net/test_train.prototxt"
2
2
  test_iter: 100
3
- test_interval: 800
3
+ test_interval: 80
4
4
  base_lr: 0.1
5
5
  momentum: 0.9
6
6
  weight_decay: 0.0005
7
7
  lr_policy: "inv"
8
8
  gamma: 0.0001
9
9
  power: 0.75
10
- display: 800
11
- max_iter: 25600
12
- snapshot: 25600
13
- snapshot_prefix: "test"
10
+ display: 80
11
+ max_iter: 2560
12
+ snapshot: 2560
13
+ snapshot_prefix: "spec/net/test"
14
14
  solver_mode: CPU
@@ -6,7 +6,7 @@ layer {
6
6
  top: "data"
7
7
  top: "label"
8
8
  data_param {
9
- source: "test_data"
9
+ source: "spec/net/test_data"
10
10
  batch_size: 256
11
11
  backend: LMDB
12
12
  }
@@ -1,43 +1,68 @@
1
1
  RSpec.describe Caffe::Net do
2
2
  before :example do
3
3
  Caffe.mode = Caffe::CPU
4
- path = File.expand_path '../net/test.prototxt', __FILE__
5
- @net = Caffe::Net.new path, Caffe::TEST
4
+ @path = File.expand_path '../net/test.prototxt', __FILE__
6
5
  end
7
6
 
8
- it 'can get the input' do
9
- expect(@net.inputs).to be_an(Array)
10
- expect(@net.inputs.size).to eq(1)
11
- input = @net.inputs[0]
12
- expect(input).to be_a(Caffe::Blob)
13
- expect(input.shape).to eq([1, 32])
14
- end
7
+ shared_examples :net do
8
+ before :example do
9
+ @net = Caffe::Net.new @path, @phase
10
+ end
15
11
 
16
- it 'can get blob by name' do
17
- blob = @net.blob('ip1')
18
- expect(blob).to be_a(Caffe::Blob)
19
- expect(blob.shape).to eq([1, 100])
12
+ it 'can get the input' do
13
+ expect(@net.inputs).to be_an(Array)
14
+ expect([1, 2]).to include(@net.inputs.size)
15
+ input = @net.inputs[0]
16
+ expect(input).to be_a(Caffe::Blob)
17
+ expect(input.shape).to eq([1, 32])
18
+ if @net.inputs.size == 2
19
+ input = @net.inputs[1]
20
+ expect(input).to be_a(Caffe::Blob)
21
+ expect(input.shape).to eq([1, 1])
22
+ end
23
+ end
20
24
 
21
- blob = @net.blob('prob')
22
- expect(blob.shape).to eq([1, 2])
23
- end
25
+ it 'can get blob by name' do
26
+ blob = @net.blob('ip1')
27
+ expect(blob).to be_a(Caffe::Blob)
28
+ expect(blob.shape).to eq([1, 100])
24
29
 
25
- it 'can get output' do
26
- expect(@net.outputs).to be_an(Array)
27
- expect(@net.outputs.size).to eq(1)
28
- output = @net.outputs[0]
29
- expect(output).to be_a(Caffe::Blob)
30
- expect(output.shape).to eq([1, 2])
31
- end
30
+ blob = @net.blob('ip2')
31
+ expect(blob.shape).to eq([1, 2])
32
+ end
33
+
34
+ it 'can get output' do
35
+ expect(@net.outputs).to be_an(Array)
36
+ expect(@net.outputs.size).to eq(1)
37
+ output = @net.outputs[0]
38
+ expect(output).to be_a(Caffe::Blob)
39
+ expect([[1, 2], []]).to include(output.shape)
40
+ end
32
41
 
33
- it 'can reshape according to the input size' do
34
- input = @net.inputs[0]
35
- input.shape = [64, 32]
36
- @net.reshape!
37
- expect(@net.outputs[0].shape).to eq([64, 2])
42
+ it 'can reshape according to the input size' do
43
+ @net.inputs.each do |input|
44
+ shape = input.shape
45
+ shape[0] = 64
46
+ input.shape = shape
47
+ end
48
+ @net.reshape!
49
+ expect(@net.blob('ip2').shape).to eq([64, 2])
50
+ end
51
+
52
+ def input_data
53
+ data = Array.new 32 do
54
+ Random.rand 2
55
+ end
56
+ @net.inputs[0].copy_from! data
57
+
58
+ num = data.inject(0) do |i, x|
59
+ 2 * i + x
60
+ end
61
+ num % 1024 > 1024 / 2 ? 1 : 0
62
+ end
38
63
  end
39
64
 
40
- context 'trained net' do
65
+ shared_examples :trained do
41
66
  before :example do
42
67
  path = File.expand_path '../net/test.caffemodel', __FILE__
43
68
  @net.load_trained! path
@@ -47,25 +72,98 @@ RSpec.describe Caffe::Net do
47
72
  input = @net.inputs[0]
48
73
  expect(input.shape).to eq([1, 32])
49
74
  end
75
+ end
50
76
 
51
- it 'can forward' do
52
- data = Array.new 32 do
53
- Random.rand 2
54
- end
55
- input = @net.inputs[0]
56
- input.copy_from! data
77
+ shared_examples :shared_net do
78
+ before :example do
79
+ @src = @net
80
+ @net = Caffe::Net.new @path, Caffe::TEST
81
+ @net.share_trained! @src
82
+ end
57
83
 
84
+ it 'can forward and return the same result' do
85
+ input_data
86
+ @src.inputs[0].copy_from! @net.inputs[0].to_a
58
87
  expect(@net.forward!).to eq(0.0)
59
- output = @net.outputs[0]
60
- expect(output[0][0] + output[0][1]).to be_within(1e-6).of(1.0)
88
+ @src.forward!
61
89
 
62
- label = output[0][1] > output[0][0] ? 1 : 0
63
- num = data.inject(0) do |i, x|
64
- 2 * i + x
90
+ expect(@net.blob('ip1').to_a).to eq(@src.blob('ip1').to_a)
91
+ expect(@net.blob('ip2').to_a).to eq(@src.blob('ip2').to_a)
92
+ end
93
+ end
94
+
95
+ context 'test net' do
96
+ before :example do
97
+ @phase = Caffe::TEST
98
+ end
99
+ include_examples :net
100
+
101
+ context 'shared with another' do
102
+ include_examples :shared_net
103
+ end
104
+
105
+ context 'trained' do
106
+ include_examples :trained
107
+
108
+ context 'share with another' do
109
+ include_examples :shared_net
110
+ end
111
+
112
+ it 'can forward' do
113
+ expected = input_data
114
+
115
+ expect(@net.forward!).to eq(0.0)
116
+ output = @net.outputs[0]
117
+ expect(output[0][0] + output[0][1]).to be_within(1e-6).of(1.0)
118
+
119
+ label = output[0][1] > output[0][0] ? 1 : 0
120
+ expect(label).to eq(expected)
121
+ end
122
+
123
+ it 'can forward then backward' do
124
+ expect(@net.forward_backward!).to eq(0.0)
65
125
  end
66
- expected = num % 1024 > 1024 / 2 ? 1 : 0
126
+ end
127
+ end
128
+
129
+ context 'train net' do
130
+ before :example do
131
+ @mode = Caffe::TRAIN
132
+ end
133
+ include_examples :net
134
+
135
+ context 'share with another' do
136
+ include_examples :shared_net
137
+ end
138
+
139
+ context 'trained' do
140
+ include_examples :trained
67
141
 
68
- expect(label).to eq(expected)
142
+ context 'share with another' do
143
+ include_examples :shared_net
144
+ end
145
+
146
+ it 'can forward' do
147
+ expected = input_data
148
+ @net.inputs[1][0][0] = expected
149
+
150
+ loss = @net.forward!
151
+ expect(loss).not_to eq(0.0)
152
+ expect(loss).to be_within(1e-2).of(0.0)
153
+ end
154
+
155
+ it 'can forward then backward' do
156
+ ip2 = @net.blob 'ip2'
157
+ expect(ip2.diff[0][0]).to eq(0.0)
158
+ expect(ip2.diff[0][1]).to eq(0.0)
159
+
160
+ loss = @net.forward_backward!
161
+ expect(loss).not_to eq(0.0)
162
+ expect(loss).to be_within(1e-2).of(0.0)
163
+
164
+ expect(ip2.diff[0][0]).not_to eq(0.0)
165
+ expect(ip2.diff[0][1]).not_to eq(0.0)
166
+ end
69
167
  end
70
168
  end
71
169
  end
@@ -0,0 +1,66 @@
1
+ RSpec.describe Caffe::Solver do
2
+ before :example do
3
+ path = File.expand_path '../net/test_solver.prototxt', __FILE__
4
+ @solver = Caffe::Solver.new path
5
+ end
6
+
7
+ it '#net returns the train net' do
8
+ net = @solver.net
9
+ expect(net).to be_a(Caffe::Net)
10
+ expect(net.inputs.size).to eq(0)
11
+ expect(net.outputs.size).to eq(1)
12
+ end
13
+
14
+ it '#test_nets returns an array of test nets' do
15
+ nets = @solver.test_nets
16
+ expect(nets).to be_an(Array)
17
+ expect(nets.size).to eq(1)
18
+ net = nets[0]
19
+ expect(net).to be_a(Caffe::Net)
20
+ expect(net.outputs.size).to eq(2)
21
+ end
22
+
23
+ it '#iter returns the current iteration' do
24
+ expect(@solver.iter).to eq(0)
25
+ end
26
+
27
+ it '#step! steps the iteration' do
28
+ net = @solver.net
29
+ loss = net.forward!
30
+ @solver.step! 100
31
+ expect(@solver.iter).to eq(100)
32
+ expect(net.forward!).to be < loss
33
+ end
34
+
35
+ def snapshot_path
36
+ state = File.expand_path "../net/test_iter_#{@solver.iter}.solverstate",
37
+ __FILE__
38
+ model = File.expand_path "../net/test_iter_#{@solver.iter}.caffemodel",
39
+ __FILE__
40
+ [state, model]
41
+ end
42
+
43
+ it '#snapshot & #restore! can save & load the current state' do
44
+ state, model = snapshot_path
45
+ begin
46
+ @solver.snapshot
47
+ expect(File.exist?(state)).to be true
48
+ @solver.restore! state
49
+ ensure
50
+ File.unlink state
51
+ File.unlink model
52
+ end
53
+ end
54
+
55
+ it '#solve! solves the net' do
56
+ begin
57
+ @solver.solve!
58
+ net = @solver.net
59
+ expect(net.forward!).to be_within(1e-2).of(0.0)
60
+ ensure
61
+ state, model = snapshot_path
62
+ File.unlink state
63
+ File.unlink model
64
+ end
65
+ end
66
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: caffe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiny Tiny
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-02-17 00:00:00.000000000 Z
11
+ date: 2017-02-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rice
@@ -101,6 +101,8 @@ files:
101
101
  - ext/caffe/mkmf_cxx.rb
102
102
  - ext/caffe/net.cc
103
103
  - ext/caffe/net.hpp
104
+ - ext/caffe/solver.cc
105
+ - ext/caffe/solver.hpp
104
106
  - ext/caffe/util.hpp
105
107
  - lib/caffe.rb
106
108
  - lib/caffe/blob.rb
@@ -110,11 +112,11 @@ files:
110
112
  - spec/blob_spec.rb
111
113
  - spec/common_spec.rb
112
114
  - spec/net/gen_data.rb
113
- - spec/net/test.caffemodel
114
115
  - spec/net/test.prototxt
115
116
  - spec/net/test_solver.prototxt
116
117
  - spec/net/test_train.prototxt
117
118
  - spec/net_spec.rb
119
+ - spec/solver_spec.rb
118
120
  - spec/spec_helper.rb
119
121
  homepage: https://github.com/gyf1214/ruby-caffe
120
122
  licenses:
@@ -144,9 +146,9 @@ test_files:
144
146
  - spec/blob_spec.rb
145
147
  - spec/common_spec.rb
146
148
  - spec/net/gen_data.rb
147
- - spec/net/test.caffemodel
148
149
  - spec/net/test.prototxt
149
150
  - spec/net/test_solver.prototxt
150
151
  - spec/net/test_train.prototxt
151
152
  - spec/net_spec.rb
153
+ - spec/solver_spec.rb
152
154
  - spec/spec_helper.rb
Binary file