matrix_boost 0.1.1 → 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
  SHA256:
3
- metadata.gz: b309404421f2759c33ecc81ed7fcd0a97d4523189dbd0cddae4605708b1098cb
4
- data.tar.gz: fa48071d459c9a49faf9f3485e8501878e3bf0b75ad9e64539e6291265986acc
3
+ metadata.gz: edf1ea7cf2375607d1680ad931b9da4740bd94ed4751bb11076c7f76f8eb3466
4
+ data.tar.gz: 8b8fde75f0be426b15b1fb44493bc5cb8373985a718edb429686d558d7e63535
5
5
  SHA512:
6
- metadata.gz: d07a2ab0fe8daf2da74e8ca9420c29166d8df42afdce783b601d3235d362eb55e7cbd587b3b904c26f9c1dcbfc378c386bd4857bf93ec2ebbedb551ad93921fd
7
- data.tar.gz: 8245b8b399a23e7b0698df9485e63e2ae3e3ac12209732e615cfe74c39145d0d972a8c935fd294004684d106d93418d85978bd8aec8dc2398b83d4dd41292238
6
+ metadata.gz: b40d23ee4416f3f411cf9e68f597f468cfd8f60dec26a32053b108e60edb3be7a1c338cf7afb9a52fdc32e36b68798d39410daf7e99c746eb800d000cab0b7f1
7
+ data.tar.gz: f287eabc86984559b619340f54a6ac044223b680e86502ba4b3cde448c755dcd00ae2965fc158851f91d7f410e6e8c5860127048667ea72ce7b49e116adb4f2d
data/.gitignore CHANGED
@@ -10,3 +10,4 @@ Gemfile.lock
10
10
  ext/*.o
11
11
  ext/*.bundle
12
12
  ext/Makefile
13
+ *.gem
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [v0.2.0](https://github.com/Bajena/matrix_boost/tree/v0.2.0) (2020-04-18)
4
+
5
+ [Full Changelog](https://github.com/Bajena/matrix_boost/compare/v0.1.1...v0.2.0)
6
+
7
+ **Closed issues:**
8
+
9
+ - Add inversion boost [\#1](https://github.com/Bajena/matrix_boost/issues/1)
10
+
11
+ **Merged pull requests:**
12
+
13
+ - Add inversion [\#2](https://github.com/Bajena/matrix_boost/pull/2) ([Bajena](https://github.com/Bajena))
14
+
15
+ ## [v0.1.1](https://github.com/Bajena/matrix_boost/tree/v0.1.1) (2020-04-12)
16
+
17
+ [Full Changelog](https://github.com/Bajena/matrix_boost/compare/v0.1.0...v0.1.1)
18
+
3
19
  ## [v0.1.0](https://github.com/Bajena/matrix_boost/tree/v0.1.0) (2020-04-12)
4
20
 
5
21
  [Full Changelog](https://github.com/Bajena/matrix_boost/compare/8f26eddc7e648c0c68375b52b736535126bfd47f...v0.1.0)
data/README.md CHANGED
@@ -8,21 +8,34 @@ from Ruby's [https://ruby-doc.org/stdlib-2.5.1/libdoc/matrix/rdoc/Matrix.html](M
8
8
  ### Performance comparison
9
9
 
10
10
  ```bash
11
- irb(main):007:0> MatrixBoost.benchmark(dim: 3, n: 10000000)
11
+ bin/rake benchmark_multiply; bin/rake benchmark_inverse
12
+ Benchmark multiplication (dim = 4, n = 1000000)...
12
13
  user system total real
13
- Ruby matrix multiply: 81.385777 0.190162 81.575939 ( 81.751942)
14
- C matrix multiply: 47.041961 0.081723 47.123684 ( 47.206611)
15
- Ruby matrix multiply after monkey patch: 50.903808 0.222949 51.126757 ( 51.351058
14
+ Ruby matrix multiply: 22.627594 0.180447 22.808041 ( 23.473300)
15
+ C matrix multiply: 12.393963 0.124988 12.518951 ( 13.102763)
16
+ Ruby matrix multiply after monkey patch: 11.493074 0.073740 11.566814 ( 11.645836)
17
+ >Ruby slower (%): 1.825695 1.443715 NaN ( 1.791477)
18
+
19
+ Benchmark inversion (dim = 4, n = 1000000)...
20
+ user system total real
21
+ Ruby matrix inverse: 26.191386 0.067096 26.258482 ( 26.328944)
22
+ C matrix inverse: 4.911581 0.005300 4.916881 ( 4.920316)
23
+ Ruby matrix inverse after monkey patch: 5.180995 0.003944 5.184939 ( 5.187873)
24
+ >Ruby slower (%): 5.332577 12.659623 NaN ( 5.351068)
16
25
  ```
17
26
 
18
- as you can see Ruby's Matrix implementation is ~67% slower than the same operation
27
+ - Ruby's multiplication of 4-dimensional martices is **~79%** slower than the same operation
28
+ implemented in a C extension 🎉.
29
+ - Ruby's inversion of 4-dimensional matrices is **~435%** slower than the same operation
19
30
  implemented in a C extension 🎉.
20
31
 
21
32
  ### Matrix class core extension
22
33
  Even though this gem was created mainly for learning how to use C extensions in Ruby
23
34
  you should still be able to use it in your production code.
24
35
 
25
- You can either use `MatrixBoost.multiply(m1, m2)` or replace the original methods
36
+ You can install the gem by adding `gem "matrix_boost"` to your gemfile.
37
+
38
+ Then you can either use `MatrixBoost.multiply(m1, m2)` or `MatrixBoost.invert(m)` or replace the original methods
26
39
  from `Matrix` by calling `MatrixBoost.apply_core_extensions`.
27
40
 
28
41
  ### How do I play around?
data/Rakefile CHANGED
@@ -17,6 +17,55 @@ task :compile do
17
17
  puts "Done"
18
18
  end
19
19
 
20
+ task :benchmark_multiply do
21
+ require "matrix_boost"
22
+
23
+ dim = 4
24
+ n = 10000
25
+
26
+ m1 = Matrix.build(dim) { rand }
27
+ m2 = Matrix.build(dim) { rand }
28
+
29
+ puts "Benchmark multiplication (dim = #{dim}, n = #{n})..."
30
+
31
+ Benchmark.benchmark(Benchmark::CAPTION, 45, Benchmark::FORMAT, ">Ruby slower (%):") do |x|
32
+ r = x.report("Ruby matrix multiply:") { n.times { m1 * m2 } }
33
+ c = x.report("C matrix multiply:") { n.times { MatrixBoost.multiply(m1, m2) } }
34
+
35
+ MatrixBoost.apply_core_extensions
36
+ x.report("Ruby matrix multiply after monkey patch:") { n.times { m1 * m2 } }
37
+
38
+ [r / c]
39
+ end
40
+
41
+ puts "Done"
42
+ puts ""
43
+ end
44
+
45
+ task :benchmark_inverse do
46
+ require "matrix_boost"
47
+
48
+ dim = 4
49
+ n = 10000
50
+
51
+ puts "Benchmark inversion (dim = #{dim}, n = #{n})..."
52
+
53
+ m = Matrix.build(dim) { rand }
54
+
55
+ Benchmark.benchmark(Benchmark::CAPTION, 45, Benchmark::FORMAT, ">Ruby slower (%):") do |x|
56
+ r = x.report("Ruby matrix inverse:") { n.times { m.inverse } }
57
+ c = x.report("C matrix inverse:") { n.times { MatrixBoost.invert(m) } }
58
+
59
+ MatrixBoost.apply_core_extensions
60
+ x.report("Ruby matrix inverse after monkey patch:") { n.times { m.inverse } }
61
+
62
+ [r / c]
63
+ end
64
+
65
+ puts "Done"
66
+ puts ""
67
+ end
68
+
20
69
  GitHubChangelogGenerator::RakeTask.new :changelog do |config|
21
70
  config.user = "Bajena"
22
71
  config.project = "matrix_boost"
@@ -29,7 +29,7 @@ Matrix* rb_array_to_matrix(VALUE a) {
29
29
  Check_Type(first_row, T_ARRAY);
30
30
  long columns = RARRAY_LEN(first_row);
31
31
 
32
- Matrix *matrix = constructor(rows, columns);
32
+ Matrix *matrix = matrix_constructor(rows, columns);
33
33
  int row;
34
34
  int column;
35
35
 
@@ -51,14 +51,31 @@ static VALUE mul_matrix(VALUE self, VALUE m1, VALUE m2) {
51
51
  Matrix *m1c = rb_array_to_matrix(m1);
52
52
  Matrix *m2c = rb_array_to_matrix(m2);
53
53
 
54
- Matrix *multiplied = multiply(m1c, m2c);
54
+ Matrix *multiplied = matrix_multiply(m1c, m2c);
55
55
 
56
- destroy_matrix(m1c);
57
- destroy_matrix(m2c);
56
+ matrix_destroy(m1c);
57
+ matrix_destroy(m2c);
58
58
 
59
59
  if (multiplied) {
60
60
  VALUE result = matrix_to_rb_array(multiplied);
61
- destroy_matrix(multiplied);
61
+ matrix_destroy(multiplied);
62
+ return result;
63
+ } else {
64
+ return Qnil;
65
+ }
66
+ }
67
+
68
+ static VALUE inv_matrix(VALUE self, VALUE m) {
69
+ Check_Type(m, T_ARRAY);
70
+
71
+ Matrix *mc = rb_array_to_matrix(m);
72
+ Matrix *inverted = matrix_invert(mc);
73
+
74
+ matrix_destroy(mc);
75
+
76
+ if (inverted) {
77
+ VALUE result = matrix_to_rb_array(inverted);
78
+ matrix_destroy(inverted);
62
79
  return result;
63
80
  } else {
64
81
  return Qnil;
@@ -69,4 +86,5 @@ void Init_extension(void) {
69
86
  VALUE MatrixBoost = rb_define_module("MatrixBoost");
70
87
  VALUE NativeHelpers = rb_define_class_under(MatrixBoost, "NativeHelpers", rb_cObject);
71
88
  rb_define_singleton_method(NativeHelpers, "mul_matrix", mul_matrix, 2);
89
+ rb_define_singleton_method(NativeHelpers, "inv_matrix", inv_matrix, 1);
72
90
  }
@@ -21,10 +21,11 @@ rows |
21
21
  the matrix is an array of array pointers where each array pointer corresponds to a vector
22
22
  */
23
23
 
24
- static double vector_multiply(double *col, double *row, int length);
24
+ static double matrix_vector_multiply(double *col, double *row, int length);
25
+ static int matrix_row_scalar_multiply(Matrix *m, int row, float factor);
25
26
 
26
27
  /* make a zero matrix of given dimensions */
27
- Matrix *constructor(int r, int c){
28
+ Matrix *matrix_constructor(int r, int c){
28
29
  unsigned int i;
29
30
  Matrix *m;
30
31
  if(r <= 0 || c <= 0){
@@ -41,7 +42,7 @@ Matrix *constructor(int r, int c){
41
42
  }
42
43
 
43
44
  /* free memory associated with the matrix */
44
- int destroy_matrix(Matrix *m){
45
+ int matrix_destroy(Matrix *m){
45
46
  unsigned int i;
46
47
  if(m == NULL)
47
48
  return FAIL;
@@ -52,8 +53,19 @@ int destroy_matrix(Matrix *m){
52
53
  return SUCC;
53
54
  }
54
55
 
56
+ /* enter 1s along the main diagonal */
57
+ Matrix *matrix_identity(int length){
58
+ unsigned int i, j;
59
+ Matrix *m;
60
+ m = matrix_constructor(length, length);
61
+ for(i = 0; i < length; i++){
62
+ j = i;
63
+ (m->numbers[i])[j] = 1;
64
+ }
65
+ return m;
66
+ }
55
67
  /* print the matrix */
56
- int print(Matrix *m){
68
+ int matrix_print(Matrix *m){
57
69
  unsigned int i, j;
58
70
  if(m == NULL)
59
71
  return FAIL;
@@ -66,12 +78,12 @@ int print(Matrix *m){
66
78
  return SUCC;
67
79
  }
68
80
 
69
- Matrix *transpose(Matrix *m){
81
+ Matrix *matrix_transpose(Matrix *m){
70
82
  Matrix *trans;
71
83
  unsigned int i, j;
72
84
  if(m == NULL)
73
85
  return NULL;
74
- trans = constructor(m->columns, m->rows);
86
+ trans = matrix_constructor(m->columns, m->rows);
75
87
  for(i = 0; i < trans->columns; i++){
76
88
  for(j = 0; j < trans->rows; j++)
77
89
  trans->numbers[i][j] = m->numbers[j][i];
@@ -80,26 +92,117 @@ Matrix *transpose(Matrix *m){
80
92
  }
81
93
 
82
94
  /* m1 x m2 */
83
- Matrix *multiply(Matrix *m1, Matrix *m2){
95
+ Matrix *matrix_multiply(Matrix *m1, Matrix *m2){
84
96
  Matrix *product, *trans;
85
97
  unsigned int i, j;
86
98
  if(m1 == NULL || m2 == NULL)
87
99
  return NULL;
88
100
  if(m1->columns != m2->rows)
89
101
  return NULL;
90
- trans = transpose(m1);
91
- product = constructor(m1->rows, m2->columns);
102
+ trans = matrix_transpose(m1);
103
+ product = matrix_constructor(m1->rows, m2->columns);
92
104
  for(i = 0; i < product->columns; i++){
93
105
  for(j = 0; j < product->rows; j++){
94
- product->numbers[i][j] = vector_multiply(trans->numbers[j], m2->numbers[i], m2->rows);
106
+ product->numbers[i][j] = matrix_vector_multiply(trans->numbers[j], m2->numbers[i], m2->rows);
95
107
  }
96
108
  }
97
- destroy_matrix(trans);
109
+ matrix_destroy(trans);
98
110
  return product;
99
111
  }
100
112
 
113
+ /* matrix m will become the matrix_identity so the caller must save their matrix themselves */
114
+ Matrix *matrix_invert(Matrix *m){
115
+ Matrix *invert;
116
+ unsigned int i, j, l;
117
+ double factor;
118
+ if(m == NULL)
119
+ return NULL;
120
+ if((m)->columns != (m)->rows)
121
+ return NULL;
122
+ invert = matrix_identity((m)->rows);
123
+
124
+ /* matrix_reduce each of the rows to get a lower triangle */
125
+ for(i = 0; i < (m)->columns; i++){
126
+ for(j = i + 1; j < (m)->rows; j++){
127
+ if((m)->numbers[i][i] == 0){
128
+ for(l=i+1; l < m->rows; l++){
129
+ if(m->numbers[l][l] != 0){
130
+ matrix_row_swap(m, i, l);
131
+ break;
132
+ }
133
+ }
134
+ continue;
135
+ }
136
+ factor = (m)->numbers[i][j]/((m)->numbers[i][i]);
137
+ matrix_reduce(invert, i, j, factor);
138
+ matrix_reduce((m), i, j, factor);
139
+ }
140
+ }
141
+ /* now finish the upper triangle */
142
+ for(i = (m)->columns - 1; i > 0; i--){
143
+ for(j = i-1; j>=0; j--){
144
+ if((m)->numbers[i][i] == 0)
145
+ continue;
146
+ if(j == -1)
147
+ break;
148
+ factor = (m)->numbers[i][j]/((m)->numbers[i][i]);
149
+ matrix_reduce(invert, i, j, factor);
150
+ matrix_reduce((m), i, j, factor);
151
+ }
152
+ }
153
+ /* scale everything to 1 */
154
+ for(i = 0; i < (m)->columns; i++){
155
+ if((m)->numbers[i][i]==0)
156
+ continue;
157
+ factor = 1/((m)->numbers[i][i]);
158
+ matrix_row_scalar_multiply(invert, i, factor);
159
+ matrix_row_scalar_multiply((m), i, factor);
160
+ }
161
+ return invert;
162
+ }
163
+
164
+ int matrix_row_swap(Matrix *m, int a, int b){
165
+ double temp;
166
+ unsigned int i;
167
+ if(m == NULL)
168
+ return FAIL;
169
+ if(m->rows <= a || m->rows <= b)
170
+ return FAIL;
171
+ for(i = 0; i < m->columns; i++){
172
+ temp = m->numbers[i][a];
173
+ m->numbers[i][a] = m->numbers[i][b];
174
+ m->numbers[i][b] = temp;
175
+ }
176
+ return SUCC;
177
+ }
178
+
179
+ static int matrix_row_scalar_multiply(Matrix *m, int row, float factor){
180
+ int i;
181
+ if(m == NULL)
182
+ return FAIL;
183
+ if(m->rows <= row)
184
+ return FAIL;
185
+ for(i = 0; i < m->columns; i++)
186
+ m->numbers[i][row] *= factor;
187
+ return SUCC;
188
+ }
189
+
190
+ /* matrix_reduce row b by factor*a */
191
+ int matrix_reduce(Matrix *m, int a, int b, float factor){
192
+ int i;
193
+ if(m == NULL)
194
+ return FAIL;
195
+ if(m->rows < a || m->rows < b)
196
+ return FAIL;
197
+ for(i = 0; i < m->columns; i++){
198
+ m->numbers[i][b] -= m->numbers[i][a]*factor;
199
+ }
200
+
201
+ return SUCC;
202
+ }
203
+
101
204
  /* v1 x v2 -- simply a helper function -- computes dot product between two vectors*/
102
- static double vector_multiply(double *col, double *row, int length){
205
+ static double matrix_vector_multiply(double *col, double *row, int length){
103
206
  double sum;
104
207
  unsigned int i;
105
208
  sum = 0;
@@ -4,6 +4,7 @@
4
4
  #ifdef __cplusplus
5
5
  extern "C" {
6
6
  #endif
7
+
7
8
  /* current representation of a matrix in my mind */
8
9
  typedef struct Matrix{
9
10
  int rows;
@@ -11,11 +12,15 @@ typedef struct Matrix{
11
12
  double **numbers;
12
13
  } Matrix;
13
14
 
14
- Matrix *constructor(int r, int c);
15
- int destroy_matrix(Matrix *m);
16
- int print(Matrix *m);
17
- Matrix *transpose(Matrix *m);
18
- Matrix *multiply(Matrix *m1, Matrix *m2);
15
+ Matrix *matrix_constructor(int r, int c);
16
+ Matrix *matrix_identity(int length);
17
+ int matrix_destroy(Matrix *m);
18
+ int matrix_print(Matrix *m);
19
+ Matrix *matrix_transpose(Matrix *m);
20
+ Matrix *matrix_multiply(Matrix *m1, Matrix *m2);
21
+ Matrix *matrix_invert(Matrix *m);
22
+ int matrix_reduce(Matrix *m, int a, int b, float factor);
23
+ int matrix_row_swap(Matrix *m, int a, int b);
19
24
 
20
25
  #ifdef __cplusplus
21
26
  }
@@ -11,26 +11,17 @@ module MatrixBoost
11
11
  Matrix[*NativeHelpers.mul_matrix(m1.to_a, m2.to_a)]
12
12
  end
13
13
 
14
- def benchmark(dim: 3, n: 100000)
15
- m1 = Matrix[[1, 2, 3], [4, 5, 6]]
16
- m2 = Matrix[[9,8],[7,6],[5,4]]
17
-
18
- m1 = Matrix.build(dim) { rand }
19
- m2 = Matrix.build(dim) { rand }
20
-
21
- Benchmark.benchmark(Benchmark::CAPTION, 45, Benchmark::FORMAT) do |x|
22
- x.report("Ruby matrix multiply:") { n.times { m1 * m2 } }
23
- x.report("C matrix multiply:") { n.times { multiply(m1, m2) } }
24
-
25
- apply_core_extensions
26
- x.report("Ruby matrix multiply after monkey patch:") { n.times { m1 * m2 } }
27
- end
14
+ # @param m [Matrix] Stdlib Matrix instance
15
+ # @return [Matrix] Inverted matrix
16
+ def invert(m)
17
+ Matrix[*NativeHelpers.inv_matrix(m.to_a)]
28
18
  end
29
19
 
30
20
  def apply_core_extensions
31
21
  require "matrix_boost/core_extensions"
32
22
 
33
23
  Matrix.prepend MatrixBoost::CoreExtensions::Multiply
24
+ Matrix.prepend MatrixBoost::CoreExtensions::Inverse
34
25
  end
35
26
  end
36
27
  end
@@ -15,5 +15,14 @@ module MatrixBoost
15
15
  end
16
16
  end
17
17
  end
18
+
19
+ module Inverse
20
+ # Overrides https://github.com/ruby/matrix/blob/master/lib/matrix.rb#L1175
21
+ def inverse
22
+ raise ErrDimensionMismatch unless square?
23
+
24
+ MatrixBoost.invert(self)
25
+ end
26
+ end
18
27
  end
19
28
  end
@@ -1,3 +1,3 @@
1
1
  module MatrixBoost
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: matrix_boost
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Bajena
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-04-12 00:00:00.000000000 Z
11
+ date: 2020-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake