matrix_boost 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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