Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Matrix Ruby Wrappers #56

Open
wants to merge 30 commits into
base: master
Choose a base branch
from
Open

Matrix Ruby Wrappers #56

wants to merge 30 commits into from

Conversation

rajithv
Copy link
Contributor

@rajithv rajithv commented Jun 21, 2016

No description provided.

VALUE cmatrix_dense_alloc(VALUE klass)
{
CDenseMatrix *mat_ptr = dense_matrix_new();
return Data_Wrap_Struct(klass, NULL, cmatrix_dense_free, mat_ptr);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does klass stand for here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was writing it by taking the example of cbasic_alloc. So assumed, the klass is something which is called when allocating the space bound defined in rb_define_alloc_func(c_dense_matrix, cmatrix_dense_alloc);

Shoud I remove it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rajithv
Copy link
Contributor Author

rajithv commented Jun 23, 2016

@abinashmeher999 @isuruf

Please check the following:

irb(main):003:0> A = SymEngine::DenseMatrix.new(arr)
=> #<SymEngine::DenseMatrix([1, 2]
[3, 4]
[5, 6]
)>
irb(main):004:0> A.to_s
=> "[1, 2]\n[3, 4]\n[5, 6]\n"

Also, does it need row count and column count?

The to_s output is not nice.. right? What are our options?

@zverok
Copy link
Collaborator

zverok commented Jun 23, 2016

There are two main things to consider:

  1. inspect: it should be as informative, as possible, but with reasonable limitation; for example, what we did in daru:
v = Daru::DataFrame.new(a: [1,2,3], b: [4,5,6])
# => #<Daru::DataFrame(3x2)>
#       a   b
#   0   1   4
#   1   2   5
#   2   3   6 

v = Daru::DataFrame.new(a: (1..100), b: (201..300))
# => #<Daru::DataFrame(100x2)>
#       a   b
#   0   1 201
#   1   2 202
#   2   3 203
#   3   4 204
#   4   5 205
#   5   6 206
#   6   7 207
#   7   8 208
#   8   9 209
#   9  10 210
#  10  11 211
#  11  12 212
#  12  13 213
#  13  14 214
#  14  15 215
# ... ... ... 

# ^ above is full output of #inspect of large DataFrame, including those "..."
  1. to_s: for "non-stringy" types it should be as short as possible (while saving some informativeness), consider it used in contexts like raise ArgumentError, "Expected blah, yet received #{wtf}" ← this uses #to_s to render some error message... And somebody would output it.

@rajithv
Copy link
Contributor Author

rajithv commented Jun 23, 2016

@zverok Thanks. I'll come up with a scheme considering the points you mentioned. I have a better idea now.

@isuruf mentioned that to_s is going to change in C++ layer soon, so I'll wait for that to happen. Until then, I'll show the number of rows and number of columns in inspect.


} else if (argc == 2) {

// SymEngine::DenseMatrix(no_rows, no_cols)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed in C++ layer, but not needed in Ruby right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@isuruf should I remove it?

I suppose this can be used to generate an empty Matrix of size RxC, where individual elements can be assigned with set. But again, there's no point in doing that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, for that use case, it's better to use zeros

@rajithv rajithv changed the title [WIP] Matrix Ruby Wrappers Matrix Ruby Wrappers Jul 7, 2016
@rajithv
Copy link
Contributor Author

rajithv commented Jul 7, 2016

@isuruf @abinashmeher999 @zverok ready for review.

@zverok I'm not too sure about the specs. Please advice if I'm not doing it right :)

@zverok
Copy link
Collaborator

zverok commented Jul 7, 2016

Looking at it!

@@ -0,0 +1,7 @@
module SymEngine
class MatrixBase
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it say class DenseMatrix here?

@zverok
Copy link
Collaborator

zverok commented Jul 7, 2016

I've left some comments.
Also, I am a bit concerned about the fact that only "happy path" is tested. What if I'll try to multiply DenseMatrix by string or nil? Would there be an informative exception? Would there be at least some exception (and not some nasty crash in the middle of C code). Testing behavior on failures and corner cases (for ex., behavior of empty matrices) could be tiresome, but without it we can't be sure code is actually working.

@@ -66,35 +66,6 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"or create a variable"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove these changes from this PR?

VALUE operand = rb_ary_shift(args);
char *s = rb_obj_classname(operand);

if (strcmp(s, "SymEngine::DenseMatrix") == 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can move this if else function to a new function, sympify_matrix

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use a C API call to check that operand is a DenseMatrix instead of string comparisons

expect(matA.rows). to eq (3)
expect(matA.cols). to eq (3)
end
end
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, you can completely omit those describes including only one example each, its texts are pretty informative and well-written 👍
Just less cognitive load would be to read it this way:

describe SymEngine::DenseMatrix do
  describe 'convert' do
    subject { SymEngine([[1, 2],[3, 4]]) }

    it { is_expected.to be_a SymEngine::DenseMatrix }
    its(:to_s) { is_expected.to eq "[1, 2]\n[3, 4]\n" }
  end

  describe 'methods without arguments' do # that descibes accurately why you group it
    subject { SymEngine([[4, 3],[3, 2]]) }

    # moving those two here as they are pretty basic & have no arguments
    its(:rows) { is_expected.to eq (2) }
    its(:cols) { is_expected.to eq (2) }

    its(:inv) { is_expected.to be_a SymEngine::DenseMatrix }
    its(:inv) { is_expected.to eq SymEngine([[-2, 3],[3, -4]]) }

    its(:FFLU) { is_expected.to be_a SymEngine::DenseMatrix }
    its(:FFLU) { is_expected.to eq SymEngine([[4, 3],[3, -1]]) }

    its(:LU) { is_expected.to eq [SymEngine([[1, 0],[SymEngine(3)/SymEngine(4), 1]]), 
                                  SymEngine([[4, 3],[0, SymEngine(-1)/SymEngine(4)]])] }

    its(:LDL) { is_expected.to eq [SymEngine([[1, 0],[SymEngine(3)/SymEngine(4), 1]]), 
                                  SymEngine([[4, 0],[0, SymEngine(-1)/SymEngine(4)]])] }

    its(:FFLDU) { is_expected.to eq [SymEngine([[4, 0],[3, 1]]), SymEngine([[4, 0],[0, 4]]), 
                                     SymEngine([[4, 3],[0, -1]])] }

    its(:det) { is_expected.to eq (-1)}  

  end

  describe 'methods with arguments' do

    let(:mat1) { SymEngine([[1, 2],[3, 4]]) }
    let(:mat2) { SymEngine([[4, 3],[2, 1]]) }
    let(:a) { SymEngine(4) }
    let(:matA) { SymEngine([[3, 1, 2],[5, 7, 5], [1, 2, 3]]) }
    let(:b) { SymEngine([[4],[4],[4]]) }

    it 'adds and multiplies' do
      expect(mat1 + mat2).to eq(SymEngine([[5, 5],[5, 5]]))
      expect(mat1 + a).to eq(SymEngine([[5, 6],[7, 8]]))
      expect(mat1 * mat2).to eq(SymEngine([[8, 5],[20, 13]]))
      expect(mat1 * a).to eq(SymEngine([[4, 8],[12, 16]]))
    end  

    it 'performs transpose and submatrix' do
      expect(mat1.transpose).to eq(SymEngine([[1, 3],[2, 4]]))
      expect(mat1.submatrix(0, 0, 1, 0, 1, 1)).to eq(SymEngine([[1],[3]]))
    end

    it 'solves Ax = b' do
      expect(matA.LU_solve(b)).to eq(SymEngine([[SymEngine(12)/SymEngine(29)],
                                             [SymEngine(-32)/SymEngine(29)],
                                             [SymEngine(56)/SymEngine(29)]
                                             ]))  
    end

    it 'gets and sets elements' do
      expect(mat1.set(0, 0, a)).to eq(SymEngine([[4, 2],[3, 4]]))
      expect(mat1.get(1, 0)).to eq(SymEngine(3))
    end
  end
end

WDYT?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants