test_eigen.py 27.3 KB
Newer Older
1
# -*- coding: utf-8 -*-
Dean Moldovan's avatar
Dean Moldovan committed
2
import pytest
3
from pybind11_tests import ConstructorStats
Dean Moldovan's avatar
Dean Moldovan committed
4

5
6
np = pytest.importorskip("numpy")
m = pytest.importorskip("pybind11_tests.eigen")
7

Dean Moldovan's avatar
Dean Moldovan committed
8

9
10
11
12
13
ref = np.array([[ 0.,  3,  0,  0,  0, 11],
                [22,  0,  0,  0, 17, 11],
                [ 7,  5,  0,  1,  0, 11],
                [ 0,  0,  0,  0,  0, 11],
                [ 0,  0, 14,  0,  8, 11]])
Dean Moldovan's avatar
Dean Moldovan committed
14
15
16
17
18
19
20


def assert_equal_ref(mat):
    np.testing.assert_array_equal(mat, ref)


def assert_sparse_equal_ref(sparse_mat):
21
    assert_equal_ref(sparse_mat.toarray())
Dean Moldovan's avatar
Dean Moldovan committed
22
23
24


def test_fixed():
25
26
27
28
29
30
    assert_equal_ref(m.fixed_c())
    assert_equal_ref(m.fixed_r())
    assert_equal_ref(m.fixed_copy_r(m.fixed_r()))
    assert_equal_ref(m.fixed_copy_c(m.fixed_c()))
    assert_equal_ref(m.fixed_copy_r(m.fixed_c()))
    assert_equal_ref(m.fixed_copy_c(m.fixed_r()))
Dean Moldovan's avatar
Dean Moldovan committed
31
32
33


def test_dense():
34
35
36
37
38
39
    assert_equal_ref(m.dense_r())
    assert_equal_ref(m.dense_c())
    assert_equal_ref(m.dense_copy_r(m.dense_r()))
    assert_equal_ref(m.dense_copy_c(m.dense_c()))
    assert_equal_ref(m.dense_copy_r(m.dense_c()))
    assert_equal_ref(m.dense_copy_c(m.dense_r()))
40

Dean Moldovan's avatar
Dean Moldovan committed
41

42
def test_partially_fixed():
43
    ref2 = np.array([[0., 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]])
44
45
46
47
48
    np.testing.assert_array_equal(m.partial_copy_four_rm_r(ref2), ref2)
    np.testing.assert_array_equal(m.partial_copy_four_rm_c(ref2), ref2)
    np.testing.assert_array_equal(m.partial_copy_four_rm_r(ref2[:, 1]), ref2[:, [1]])
    np.testing.assert_array_equal(m.partial_copy_four_rm_c(ref2[0, :]), ref2[[0], :])
    np.testing.assert_array_equal(m.partial_copy_four_rm_r(ref2[:, (0, 2)]), ref2[:, (0, 2)])
49
    np.testing.assert_array_equal(
50
        m.partial_copy_four_rm_c(ref2[(3, 1, 2), :]), ref2[(3, 1, 2), :])
51

52
53
54
55
56
    np.testing.assert_array_equal(m.partial_copy_four_cm_r(ref2), ref2)
    np.testing.assert_array_equal(m.partial_copy_four_cm_c(ref2), ref2)
    np.testing.assert_array_equal(m.partial_copy_four_cm_r(ref2[:, 1]), ref2[:, [1]])
    np.testing.assert_array_equal(m.partial_copy_four_cm_c(ref2[0, :]), ref2[[0], :])
    np.testing.assert_array_equal(m.partial_copy_four_cm_r(ref2[:, (0, 2)]), ref2[:, (0, 2)])
57
    np.testing.assert_array_equal(
58
        m.partial_copy_four_cm_c(ref2[(3, 1, 2), :]), ref2[(3, 1, 2), :])
59

60
    # TypeError should be raise for a shape mismatch
61
62
    functions = [m.partial_copy_four_rm_r, m.partial_copy_four_rm_c,
                 m.partial_copy_four_cm_r, m.partial_copy_four_cm_c]
63
64
65
66
67
68
69
    matrix_with_wrong_shape = [[1, 2],
                               [3, 4]]
    for f in functions:
        with pytest.raises(TypeError) as excinfo:
            f(matrix_with_wrong_shape)
        assert "incompatible function arguments" in str(excinfo.value)

70
71
72
73
74

def test_mutator_descriptors():
    zr = np.arange(30, dtype='float32').reshape(5, 6)  # row-major
    zc = zr.reshape(6, 5).transpose()  # column-major

75
76
77
78
    m.fixed_mutator_r(zr)
    m.fixed_mutator_c(zc)
    m.fixed_mutator_a(zr)
    m.fixed_mutator_a(zc)
79
    with pytest.raises(TypeError) as excinfo:
80
        m.fixed_mutator_r(zc)
81
82
    assert ('(arg0: numpy.ndarray[numpy.float32[5, 6],'
            ' flags.writeable, flags.c_contiguous]) -> None'
83
84
            in str(excinfo.value))
    with pytest.raises(TypeError) as excinfo:
85
        m.fixed_mutator_c(zr)
86
87
    assert ('(arg0: numpy.ndarray[numpy.float32[5, 6],'
            ' flags.writeable, flags.f_contiguous]) -> None'
88
89
            in str(excinfo.value))
    with pytest.raises(TypeError) as excinfo:
90
        m.fixed_mutator_a(np.array([[1, 2], [3, 4]], dtype='float32'))
91
    assert ('(arg0: numpy.ndarray[numpy.float32[5, 6], flags.writeable]) -> None'
92
93
94
            in str(excinfo.value))
    zr.flags.writeable = False
    with pytest.raises(TypeError):
95
        m.fixed_mutator_r(zr)
96
    with pytest.raises(TypeError):
97
        m.fixed_mutator_a(zr)
98

99

100
def test_cpp_casting():
101
102
    assert m.cpp_copy(m.fixed_r()) == 22.
    assert m.cpp_copy(m.fixed_c()) == 22.
103
    z = np.array([[5., 6], [7, 8]])
104
105
106
107
108
    assert m.cpp_copy(z) == 7.
    assert m.cpp_copy(m.get_cm_ref()) == 21.
    assert m.cpp_copy(m.get_rm_ref()) == 21.
    assert m.cpp_ref_c(m.get_cm_ref()) == 21.
    assert m.cpp_ref_r(m.get_rm_ref()) == 21.
109
    with pytest.raises(RuntimeError) as excinfo:
110
111
        # Can't reference m.fixed_c: it contains floats, m.cpp_ref_any wants doubles
        m.cpp_ref_any(m.fixed_c())
112
113
    assert 'Unable to cast Python instance' in str(excinfo.value)
    with pytest.raises(RuntimeError) as excinfo:
114
115
        # Can't reference m.fixed_r: it contains floats, m.cpp_ref_any wants doubles
        m.cpp_ref_any(m.fixed_r())
116
    assert 'Unable to cast Python instance' in str(excinfo.value)
117
    assert m.cpp_ref_any(m.ReturnTester.create()) == 1.
118

119
120
    assert m.cpp_ref_any(m.get_cm_ref()) == 21.
    assert m.cpp_ref_any(m.get_cm_ref()) == 21.
121
122
123
124
125


def test_pass_readonly_array():
    z = np.full((5, 6), 42.0)
    z.flags.writeable = False
126
127
128
129
    np.testing.assert_array_equal(z, m.fixed_copy_r(z))
    np.testing.assert_array_equal(m.fixed_r_const(), m.fixed_r())
    assert not m.fixed_r_const().flags.writeable
    np.testing.assert_array_equal(m.fixed_copy_r(m.fixed_r_const()), m.fixed_r_const())
130

Dean Moldovan's avatar
Dean Moldovan committed
131
132
133

def test_nonunit_stride_from_python():
    counting_mat = np.arange(9.0, dtype=np.float32).reshape((3, 3))
134
135
    second_row = counting_mat[1, :]
    second_col = counting_mat[:, 1]
136
137
138
139
140
141
    np.testing.assert_array_equal(m.double_row(second_row), 2.0 * second_row)
    np.testing.assert_array_equal(m.double_col(second_row), 2.0 * second_row)
    np.testing.assert_array_equal(m.double_complex(second_row), 2.0 * second_row)
    np.testing.assert_array_equal(m.double_row(second_col), 2.0 * second_col)
    np.testing.assert_array_equal(m.double_col(second_col), 2.0 * second_col)
    np.testing.assert_array_equal(m.double_complex(second_col), 2.0 * second_col)
Dean Moldovan's avatar
Dean Moldovan committed
142
143
144

    counting_3d = np.arange(27.0, dtype=np.float32).reshape((3, 3, 3))
    slices = [counting_3d[0, :, :], counting_3d[:, 0, :], counting_3d[:, :, 0]]
145
    for ref_mat in slices:
146
147
        np.testing.assert_array_equal(m.double_mat_cm(ref_mat), 2.0 * ref_mat)
        np.testing.assert_array_equal(m.double_mat_rm(ref_mat), 2.0 * ref_mat)
Dean Moldovan's avatar
Dean Moldovan committed
148

149
    # Mutator:
150
151
    m.double_threer(second_row)
    m.double_threec(second_col)
152
153
    np.testing.assert_array_equal(counting_mat, [[0., 2, 2], [6, 16, 10], [6, 14, 8]])

Dean Moldovan's avatar
Dean Moldovan committed
154

155
def test_negative_stride_from_python(msg):
156
157
158
    """Eigen doesn't support (as of yet) negative strides. When a function takes an Eigen matrix by
    copy or const reference, we can pass a numpy array that has negative strides.  Otherwise, an
    exception will be thrown as Eigen will not be able to map the numpy array."""
159
160
161
162
163

    counting_mat = np.arange(9.0, dtype=np.float32).reshape((3, 3))
    counting_mat = counting_mat[::-1, ::-1]
    second_row = counting_mat[1, :]
    second_col = counting_mat[:, 1]
164
165
166
167
168
169
    np.testing.assert_array_equal(m.double_row(second_row), 2.0 * second_row)
    np.testing.assert_array_equal(m.double_col(second_row), 2.0 * second_row)
    np.testing.assert_array_equal(m.double_complex(second_row), 2.0 * second_row)
    np.testing.assert_array_equal(m.double_row(second_col), 2.0 * second_col)
    np.testing.assert_array_equal(m.double_col(second_col), 2.0 * second_col)
    np.testing.assert_array_equal(m.double_complex(second_col), 2.0 * second_col)
170
171
172
173

    counting_3d = np.arange(27.0, dtype=np.float32).reshape((3, 3, 3))
    counting_3d = counting_3d[::-1, ::-1, ::-1]
    slices = [counting_3d[0, :, :], counting_3d[:, 0, :], counting_3d[:, :, 0]]
174
    for ref_mat in slices:
175
176
        np.testing.assert_array_equal(m.double_mat_cm(ref_mat), 2.0 * ref_mat)
        np.testing.assert_array_equal(m.double_mat_rm(ref_mat), 2.0 * ref_mat)
177
178
179

    # Mutator:
    with pytest.raises(TypeError) as excinfo:
180
        m.double_threer(second_row)
181
    assert msg(excinfo.value) == """
182
        double_threer(): incompatible function arguments. The following argument types are supported:
183
            1. (arg0: numpy.ndarray[numpy.float32[1, 3], flags.writeable]) -> None
184

185
        Invoked with: """ + repr(np.array([ 5.,  4.,  3.], dtype='float32'))  # noqa: E501 line too long
186
187

    with pytest.raises(TypeError) as excinfo:
188
        m.double_threec(second_col)
189
    assert msg(excinfo.value) == """
190
        double_threec(): incompatible function arguments. The following argument types are supported:
191
            1. (arg0: numpy.ndarray[numpy.float32[3, 1], flags.writeable]) -> None
192

193
        Invoked with: """ + repr(np.array([ 7.,  4.,  1.], dtype='float32'))  # noqa: E501 line too long
194
195


Dean Moldovan's avatar
Dean Moldovan committed
196
def test_nonunit_stride_to_python():
197
198
    assert np.all(m.diagonal(ref) == ref.diagonal())
    assert np.all(m.diagonal_1(ref) == ref.diagonal(1))
Dean Moldovan's avatar
Dean Moldovan committed
199
    for i in range(-5, 7):
200
        assert np.all(m.diagonal_n(ref, i) == ref.diagonal(i)), "m.diagonal_n({})".format(i)
Dean Moldovan's avatar
Dean Moldovan committed
201

202
203
204
    assert np.all(m.block(ref, 2, 1, 3, 3) == ref[2:5, 1:4])
    assert np.all(m.block(ref, 1, 4, 4, 2) == ref[1:, 4:])
    assert np.all(m.block(ref, 1, 4, 3, 2) == ref[1:4, 4:])
Dean Moldovan's avatar
Dean Moldovan committed
205
206
207


def test_eigen_ref_to_python():
208
    chols = [m.cholesky1, m.cholesky2, m.cholesky3, m.cholesky4]
Dean Moldovan's avatar
Dean Moldovan committed
209
    for i, chol in enumerate(chols, start=1):
210
        mymat = chol(np.array([[1., 2, 4], [2, 13, 23], [4, 23, 77]]))
Dean Moldovan's avatar
Dean Moldovan committed
211
212
213
        assert np.all(mymat == np.array([[1, 0, 0], [2, 3, 0], [4, 5, 6]])), "cholesky{}".format(i)


214
215
216
217
218
219
220
221
222
223
224
225
226
def assign_both(a1, a2, r, c, v):
    a1[r, c] = v
    a2[r, c] = v


def array_copy_but_one(a, r, c, v):
    z = np.array(a, copy=True)
    z[r, c] = v
    return z


def test_eigen_return_references():
    """Tests various ways of returning references and non-referencing copies"""
227

228
    master = np.ones((10, 10))
229
    a = m.ReturnTester()
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
    a_get1 = a.get()
    assert not a_get1.flags.owndata and a_get1.flags.writeable
    assign_both(a_get1, master, 3, 3, 5)
    a_get2 = a.get_ptr()
    assert not a_get2.flags.owndata and a_get2.flags.writeable
    assign_both(a_get1, master, 2, 3, 6)

    a_view1 = a.view()
    assert not a_view1.flags.owndata and not a_view1.flags.writeable
    with pytest.raises(ValueError):
        a_view1[2, 3] = 4
    a_view2 = a.view_ptr()
    assert not a_view2.flags.owndata and not a_view2.flags.writeable
    with pytest.raises(ValueError):
        a_view2[2, 3] = 4

    a_copy1 = a.copy_get()
    assert a_copy1.flags.owndata and a_copy1.flags.writeable
    np.testing.assert_array_equal(a_copy1, master)
    a_copy1[7, 7] = -44  # Shouldn't affect anything else
    c1want = array_copy_but_one(master, 7, 7, -44)
    a_copy2 = a.copy_view()
    assert a_copy2.flags.owndata and a_copy2.flags.writeable
    np.testing.assert_array_equal(a_copy2, master)
    a_copy2[4, 4] = -22  # Shouldn't affect anything else
    c2want = array_copy_but_one(master, 4, 4, -22)

    a_ref1 = a.ref()
    assert not a_ref1.flags.owndata and a_ref1.flags.writeable
    assign_both(a_ref1, master, 1, 1, 15)
    a_ref2 = a.ref_const()
    assert not a_ref2.flags.owndata and not a_ref2.flags.writeable
    with pytest.raises(ValueError):
        a_ref2[5, 5] = 33
    a_ref3 = a.ref_safe()
    assert not a_ref3.flags.owndata and a_ref3.flags.writeable
    assign_both(a_ref3, master, 0, 7, 99)
    a_ref4 = a.ref_const_safe()
    assert not a_ref4.flags.owndata and not a_ref4.flags.writeable
    with pytest.raises(ValueError):
        a_ref4[7, 0] = 987654321

    a_copy3 = a.copy_ref()
    assert a_copy3.flags.owndata and a_copy3.flags.writeable
    np.testing.assert_array_equal(a_copy3, master)
    a_copy3[8, 1] = 11
    c3want = array_copy_but_one(master, 8, 1, 11)
    a_copy4 = a.copy_ref_const()
    assert a_copy4.flags.owndata and a_copy4.flags.writeable
    np.testing.assert_array_equal(a_copy4, master)
    a_copy4[8, 4] = 88
    c4want = array_copy_but_one(master, 8, 4, 88)

    a_block1 = a.block(3, 3, 2, 2)
    assert not a_block1.flags.owndata and a_block1.flags.writeable
    a_block1[0, 0] = 55
    master[3, 3] = 55
    a_block2 = a.block_safe(2, 2, 3, 2)
    assert not a_block2.flags.owndata and a_block2.flags.writeable
    a_block2[2, 1] = -123
    master[4, 3] = -123
    a_block3 = a.block_const(6, 7, 4, 3)
    assert not a_block3.flags.owndata and not a_block3.flags.writeable
    with pytest.raises(ValueError):
        a_block3[2, 2] = -44444

    a_copy5 = a.copy_block(2, 2, 2, 3)
    assert a_copy5.flags.owndata and a_copy5.flags.writeable
    np.testing.assert_array_equal(a_copy5, master[2:4, 2:5])
    a_copy5[1, 1] = 777
    c5want = array_copy_but_one(master[2:4, 2:5], 1, 1, 777)

    a_corn1 = a.corners()
    assert not a_corn1.flags.owndata and a_corn1.flags.writeable
    a_corn1 *= 50
    a_corn1[1, 1] = 999
    master[0, 0] = 50
    master[0, 9] = 50
    master[9, 0] = 50
    master[9, 9] = 999
    a_corn2 = a.corners_const()
    assert not a_corn2.flags.owndata and not a_corn2.flags.writeable
    with pytest.raises(ValueError):
        a_corn2[1, 0] = 51

    # All of the changes made all the way along should be visible everywhere
    # now (except for the copies, of course)
    np.testing.assert_array_equal(a_get1, master)
    np.testing.assert_array_equal(a_get2, master)
    np.testing.assert_array_equal(a_view1, master)
    np.testing.assert_array_equal(a_view2, master)
    np.testing.assert_array_equal(a_ref1, master)
    np.testing.assert_array_equal(a_ref2, master)
    np.testing.assert_array_equal(a_ref3, master)
    np.testing.assert_array_equal(a_ref4, master)
    np.testing.assert_array_equal(a_block1, master[3:5, 3:5])
    np.testing.assert_array_equal(a_block2, master[2:5, 2:4])
    np.testing.assert_array_equal(a_block3, master[6:10, 7:10])
    np.testing.assert_array_equal(a_corn1, master[0::master.shape[0] - 1, 0::master.shape[1] - 1])
    np.testing.assert_array_equal(a_corn2, master[0::master.shape[0] - 1, 0::master.shape[1] - 1])

    np.testing.assert_array_equal(a_copy1, c1want)
    np.testing.assert_array_equal(a_copy2, c2want)
    np.testing.assert_array_equal(a_copy3, c3want)
    np.testing.assert_array_equal(a_copy4, c4want)
    np.testing.assert_array_equal(a_copy5, c5want)


def assert_keeps_alive(cl, method, *args):
    cstats = ConstructorStats.get(cl)
    start_with = cstats.alive()
    a = cl()
    assert cstats.alive() == start_with + 1
    z = method(a, *args)
    assert cstats.alive() == start_with + 1
    del a
    # Here's the keep alive in action:
    assert cstats.alive() == start_with + 1
    del z
    # Keep alive should have expired:
    assert cstats.alive() == start_with


def test_eigen_keepalive():
354
355
    a = m.ReturnTester()
    cstats = ConstructorStats.get(m.ReturnTester)
356
357
358
359
360
361
362
363
364
    assert cstats.alive() == 1
    unsafe = [a.ref(), a.ref_const(), a.block(1, 2, 3, 4)]
    copies = [a.copy_get(), a.copy_view(), a.copy_ref(), a.copy_ref_const(),
              a.copy_block(4, 3, 2, 1)]
    del a
    assert cstats.alive() == 0
    del unsafe
    del copies

365
366
367
368
    for meth in [m.ReturnTester.get, m.ReturnTester.get_ptr, m.ReturnTester.view,
                 m.ReturnTester.view_ptr, m.ReturnTester.ref_safe, m.ReturnTester.ref_const_safe,
                 m.ReturnTester.corners, m.ReturnTester.corners_const]:
        assert_keeps_alive(m.ReturnTester, meth)
369

370
371
    for meth in [m.ReturnTester.block_safe, m.ReturnTester.block_const]:
        assert_keeps_alive(m.ReturnTester, meth, 4, 3, 2, 1)
372
373
374


def test_eigen_ref_mutators():
375
376
    """Tests Eigen's ability to mutate numpy values"""

377
378
379
    orig = np.array([[1., 2, 3], [4, 5, 6], [7, 8, 9]])
    zr = np.array(orig)
    zc = np.array(orig, order='F')
380
    m.add_rm(zr, 1, 0, 100)
381
    assert np.all(zr == np.array([[1., 2, 3], [104, 5, 6], [7, 8, 9]]))
382
    m.add_cm(zc, 1, 0, 200)
383
384
    assert np.all(zc == np.array([[1., 2, 3], [204, 5, 6], [7, 8, 9]]))

385
    m.add_any(zr, 1, 0, 20)
386
    assert np.all(zr == np.array([[1., 2, 3], [124, 5, 6], [7, 8, 9]]))
387
    m.add_any(zc, 1, 0, 10)
388
389
390
391
    assert np.all(zc == np.array([[1., 2, 3], [214, 5, 6], [7, 8, 9]]))

    # Can't reference a col-major array with a row-major Ref, and vice versa:
    with pytest.raises(TypeError):
392
        m.add_rm(zc, 1, 0, 1)
393
    with pytest.raises(TypeError):
394
        m.add_cm(zr, 1, 0, 1)
395
396

    # Overloads:
397
398
    m.add1(zr, 1, 0, -100)
    m.add2(zr, 1, 0, -20)
399
    assert np.all(zr == orig)
400
401
    m.add1(zc, 1, 0, -200)
    m.add2(zc, 1, 0, -10)
402
403
404
405
406
407
408
409
410
411
412
    assert np.all(zc == orig)

    # a non-contiguous slice (this won't work on either the row- or
    # column-contiguous refs, but should work for the any)
    cornersr = zr[0::2, 0::2]
    cornersc = zc[0::2, 0::2]

    assert np.all(cornersr == np.array([[1., 3], [7, 9]]))
    assert np.all(cornersc == np.array([[1., 3], [7, 9]]))

    with pytest.raises(TypeError):
413
        m.add_rm(cornersr, 0, 1, 25)
414
    with pytest.raises(TypeError):
415
        m.add_cm(cornersr, 0, 1, 25)
416
    with pytest.raises(TypeError):
417
        m.add_rm(cornersc, 0, 1, 25)
418
    with pytest.raises(TypeError):
419
420
421
        m.add_cm(cornersc, 0, 1, 25)
    m.add_any(cornersr, 0, 1, 25)
    m.add_any(cornersc, 0, 1, 44)
422
423
424
425
426
427
428
    assert np.all(zr == np.array([[1., 2, 28], [4, 5, 6], [7, 8, 9]]))
    assert np.all(zc == np.array([[1., 2, 47], [4, 5, 6], [7, 8, 9]]))

    # You shouldn't be allowed to pass a non-writeable array to a mutating Eigen method:
    zro = zr[0:4, 0:4]
    zro.flags.writeable = False
    with pytest.raises(TypeError):
429
        m.add_rm(zro, 0, 0, 0)
430
    with pytest.raises(TypeError):
431
        m.add_any(zro, 0, 0, 0)
432
    with pytest.raises(TypeError):
433
        m.add1(zro, 0, 0, 0)
434
    with pytest.raises(TypeError):
435
        m.add2(zro, 0, 0, 0)
436
437
438
439

    # integer array shouldn't be passable to a double-matrix-accepting mutating func:
    zi = np.array([[1, 2], [3, 4]])
    with pytest.raises(TypeError):
440
        m.add_rm(zi)
441
442
443
444
445


def test_numpy_ref_mutators():
    """Tests numpy mutating Eigen matrices (for returned Eigen::Ref<...>s)"""

446
447
448
449
450
451
    m.reset_refs()  # In case another test already changed it

    zc = m.get_cm_ref()
    zcro = m.get_cm_const_ref()
    zr = m.get_rm_ref()
    zrro = m.get_rm_const_ref()
452
453
454
455
456
457
458
459
460
461
462
463
464

    assert [zc[1, 2], zcro[1, 2], zr[1, 2], zrro[1, 2]] == [23] * 4

    assert not zc.flags.owndata and zc.flags.writeable
    assert not zr.flags.owndata and zr.flags.writeable
    assert not zcro.flags.owndata and not zcro.flags.writeable
    assert not zrro.flags.owndata and not zrro.flags.writeable

    zc[1, 2] = 99
    expect = np.array([[11., 12, 13], [21, 22, 99], [31, 32, 33]])
    # We should have just changed zc, of course, but also zcro and the original eigen matrix
    assert np.all(zc == expect)
    assert np.all(zcro == expect)
465
    assert np.all(m.get_cm_ref() == expect)
466
467
468
469

    zr[1, 2] = 99
    assert np.all(zr == expect)
    assert np.all(zrro == expect)
470
    assert np.all(m.get_rm_ref() == expect)
471
472
473
474
475
476
477
478
479

    # Make sure the readonly ones are numpy-readonly:
    with pytest.raises(ValueError):
        zcro[1, 2] = 6
    with pytest.raises(ValueError):
        zrro[1, 2] = 6

    # We should be able to explicitly copy like this (and since we're copying,
    # the const should drop away)
480
    y1 = np.array(m.get_cm_const_ref())
481
482
483
484
485
486
487
488
489
490
491
492

    assert y1.flags.owndata and y1.flags.writeable
    # We should get copies of the eigen data, which was modified above:
    assert y1[1, 2] == 99
    y1[1, 2] += 12
    assert y1[1, 2] == 111
    assert zc[1, 2] == 99  # Make sure we aren't referencing the original


def test_both_ref_mutators():
    """Tests a complex chain of nested eigen/numpy references"""

493
494
495
    m.reset_refs()  # In case another test already changed it

    z = m.get_cm_ref()  # numpy -> eigen
496
    z[0, 2] -= 3
497
    z2 = m.incr_matrix(z, 1)  # numpy -> eigen -> numpy -> eigen
498
    z2[1, 1] += 6
499
    z3 = m.incr_matrix(z, 2)  # (numpy -> eigen)^3
500
    z3[2, 2] += -5
501
    z4 = m.incr_matrix(z, 3)  # (numpy -> eigen)^4
502
    z4[1, 1] -= 1
503
    z5 = m.incr_matrix(z, 4)  # (numpy -> eigen)^5
504
505
506
507
508
509
510
511
512
    z5[0, 0] = 0
    assert np.all(z == z2)
    assert np.all(z == z3)
    assert np.all(z == z4)
    assert np.all(z == z5)
    expect = np.array([[0., 22, 20], [31, 37, 33], [41, 42, 38]])
    assert np.all(z == expect)

    y = np.array(range(100), dtype='float64').reshape(10, 10)
513
514
515
516
517
    y2 = m.incr_matrix_any(y, 10)  # np -> eigen -> np
    y3 = m.incr_matrix_any(y2[0::2, 0::2], -33)  # np -> eigen -> np slice -> np -> eigen -> np
    y4 = m.even_rows(y3)  # numpy -> eigen slice -> (... y3)
    y5 = m.even_cols(y4)  # numpy -> eigen slice -> (... y4)
    y6 = m.incr_matrix_any(y5, 1000)  # numpy -> eigen -> (... y5)
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540

    # Apply same mutations using just numpy:
    yexpect = np.array(range(100), dtype='float64').reshape(10, 10)
    yexpect += 10
    yexpect[0::2, 0::2] -= 33
    yexpect[0::4, 0::4] += 1000
    assert np.all(y6 == yexpect[0::4, 0::4])
    assert np.all(y5 == yexpect[0::4, 0::4])
    assert np.all(y4 == yexpect[0::4, 0::2])
    assert np.all(y3 == yexpect[0::2, 0::2])
    assert np.all(y2 == yexpect)
    assert np.all(y == yexpect)


def test_nocopy_wrapper():
    # get_elem requires a column-contiguous matrix reference, but should be
    # callable with other types of matrix (via copying):
    int_matrix_colmajor = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], order='F')
    dbl_matrix_colmajor = np.array(int_matrix_colmajor, dtype='double', order='F', copy=True)
    int_matrix_rowmajor = np.array(int_matrix_colmajor, order='C', copy=True)
    dbl_matrix_rowmajor = np.array(int_matrix_rowmajor, dtype='double', order='C', copy=True)

    # All should be callable via get_elem:
541
542
543
544
    assert m.get_elem(int_matrix_colmajor) == 8
    assert m.get_elem(dbl_matrix_colmajor) == 8
    assert m.get_elem(int_matrix_rowmajor) == 8
    assert m.get_elem(dbl_matrix_rowmajor) == 8
545

546
    # All but the second should fail with m.get_elem_nocopy:
547
    with pytest.raises(TypeError) as excinfo:
548
        m.get_elem_nocopy(int_matrix_colmajor)
549
550
    assert ('get_elem_nocopy(): incompatible function arguments.' in str(excinfo.value) and
            ', flags.f_contiguous' in str(excinfo.value))
551
    assert m.get_elem_nocopy(dbl_matrix_colmajor) == 8
552
    with pytest.raises(TypeError) as excinfo:
553
        m.get_elem_nocopy(int_matrix_rowmajor)
554
555
556
    assert ('get_elem_nocopy(): incompatible function arguments.' in str(excinfo.value) and
            ', flags.f_contiguous' in str(excinfo.value))
    with pytest.raises(TypeError) as excinfo:
557
        m.get_elem_nocopy(dbl_matrix_rowmajor)
558
559
560
561
562
    assert ('get_elem_nocopy(): incompatible function arguments.' in str(excinfo.value) and
            ', flags.f_contiguous' in str(excinfo.value))

    # For the row-major test, we take a long matrix in row-major, so only the third is allowed:
    with pytest.raises(TypeError) as excinfo:
563
        m.get_elem_rm_nocopy(int_matrix_colmajor)
564
565
566
    assert ('get_elem_rm_nocopy(): incompatible function arguments.' in str(excinfo.value) and
            ', flags.c_contiguous' in str(excinfo.value))
    with pytest.raises(TypeError) as excinfo:
567
        m.get_elem_rm_nocopy(dbl_matrix_colmajor)
568
569
    assert ('get_elem_rm_nocopy(): incompatible function arguments.' in str(excinfo.value) and
            ', flags.c_contiguous' in str(excinfo.value))
570
    assert m.get_elem_rm_nocopy(int_matrix_rowmajor) == 8
571
    with pytest.raises(TypeError) as excinfo:
572
        m.get_elem_rm_nocopy(dbl_matrix_rowmajor)
573
574
575
576
    assert ('get_elem_rm_nocopy(): incompatible function arguments.' in str(excinfo.value) and
            ', flags.c_contiguous' in str(excinfo.value))


577
578
579
580
581
582
583
584
def test_eigen_ref_life_support():
    """Ensure the lifetime of temporary arrays created by the `Ref` caster

    The `Ref` caster sometimes creates a copy which needs to stay alive. This needs to
    happen both for directs casts (just the array) or indirectly (e.g. list of arrays).
    """

    a = np.full(shape=10, fill_value=8, dtype=np.int8)
585
    assert m.get_elem_direct(a) == 8
586
587

    list_of_a = [a]
588
    assert m.get_elem_indirect(list_of_a) == 8
589
590


Dean Moldovan's avatar
Dean Moldovan committed
591
def test_special_matrix_objects():
592
    assert np.all(m.incr_diag(7) == np.diag([1., 2, 3, 4, 5, 6, 7]))
Dean Moldovan's avatar
Dean Moldovan committed
593

594
    asymm = np.array([[ 1.,  2,  3,  4],
Dean Moldovan's avatar
Dean Moldovan committed
595
596
597
598
599
600
601
602
603
604
                      [ 5,  6,  7,  8],
                      [ 9, 10, 11, 12],
                      [13, 14, 15, 16]])
    symm_lower = np.array(asymm)
    symm_upper = np.array(asymm)
    for i in range(4):
        for j in range(i + 1, 4):
            symm_lower[i, j] = symm_lower[j, i]
            symm_upper[j, i] = symm_upper[i, j]

605
606
    assert np.all(m.symmetric_lower(asymm) == symm_lower)
    assert np.all(m.symmetric_upper(asymm) == symm_upper)
Dean Moldovan's avatar
Dean Moldovan committed
607
608
609


def test_dense_signature(doc):
610
    assert doc(m.double_col) == """
611
        double_col(arg0: numpy.ndarray[numpy.float32[m, 1]]) -> numpy.ndarray[numpy.float32[m, 1]]
612
    """
613
    assert doc(m.double_row) == """
614
        double_row(arg0: numpy.ndarray[numpy.float32[1, n]]) -> numpy.ndarray[numpy.float32[1, n]]
615
    """
616
617
618
619
620
621
622
623
    assert doc(m.double_complex) == ("""
        double_complex(arg0: numpy.ndarray[numpy.complex64[m, 1]])"""
                                     """ -> numpy.ndarray[numpy.complex64[m, 1]]
    """)
    assert doc(m.double_mat_rm) == ("""
        double_mat_rm(arg0: numpy.ndarray[numpy.float32[m, n]])"""
                                    """ -> numpy.ndarray[numpy.float32[m, n]]
    """)
Dean Moldovan's avatar
Dean Moldovan committed
624
625


626
627
628
629
def test_named_arguments():
    a = np.array([[1.0, 2], [3, 4], [5, 6]])
    b = np.ones((2, 1))

630
631
632
    assert np.all(m.matrix_multiply(a, b) == np.array([[3.], [7], [11]]))
    assert np.all(m.matrix_multiply(A=a, B=b) == np.array([[3.], [7], [11]]))
    assert np.all(m.matrix_multiply(B=b, A=a) == np.array([[3.], [7], [11]]))
633
634

    with pytest.raises(ValueError) as excinfo:
635
        m.matrix_multiply(b, a)
636
637
638
    assert str(excinfo.value) == 'Nonconformable matrices!'

    with pytest.raises(ValueError) as excinfo:
639
        m.matrix_multiply(A=b, B=a)
640
641
642
    assert str(excinfo.value) == 'Nonconformable matrices!'

    with pytest.raises(ValueError) as excinfo:
643
        m.matrix_multiply(B=a, A=b)
644
645
646
    assert str(excinfo.value) == 'Nonconformable matrices!'


Dean Moldovan's avatar
Dean Moldovan committed
647
def test_sparse():
648
    pytest.importorskip("scipy")
649
650
651
652
653
654
    assert_sparse_equal_ref(m.sparse_r())
    assert_sparse_equal_ref(m.sparse_c())
    assert_sparse_equal_ref(m.sparse_copy_r(m.sparse_r()))
    assert_sparse_equal_ref(m.sparse_copy_c(m.sparse_c()))
    assert_sparse_equal_ref(m.sparse_copy_r(m.sparse_c()))
    assert_sparse_equal_ref(m.sparse_copy_c(m.sparse_r()))
Dean Moldovan's avatar
Dean Moldovan committed
655
656
657


def test_sparse_signature(doc):
658
    pytest.importorskip("scipy")
659
    assert doc(m.sparse_copy_r) == """
660
        sparse_copy_r(arg0: scipy.sparse.csr_matrix[numpy.float32]) -> scipy.sparse.csr_matrix[numpy.float32]
661
    """  # noqa: E501 line too long
662
    assert doc(m.sparse_copy_c) == """
663
        sparse_copy_c(arg0: scipy.sparse.csc_matrix[numpy.float32]) -> scipy.sparse.csc_matrix[numpy.float32]
664
    """  # noqa: E501 line too long
665
666
667


def test_issue738():
668
669
670
    """Ignore strides on a length-1 dimension (even if they would be incompatible length > 1)"""
    assert np.all(m.iss738_f1(np.array([[1., 2, 3]])) == np.array([[1., 102, 203]]))
    assert np.all(m.iss738_f1(np.array([[1.], [2], [3]])) == np.array([[1.], [12], [23]]))
671

672
673
    assert np.all(m.iss738_f2(np.array([[1., 2, 3]])) == np.array([[1., 102, 203]]))
    assert np.all(m.iss738_f2(np.array([[1.], [2], [3]])) == np.array([[1.], [12], [23]]))
674
675


676
677
678
679
680
681
682
683
684
def test_issue1105():
    """Issue 1105: 1xN or Nx1 input arrays weren't accepted for eigen
    compile-time row vectors or column vector"""
    assert m.iss1105_row(np.ones((1, 7)))
    assert m.iss1105_col(np.ones((7, 1)))

    # These should still fail (incompatible dimensions):
    with pytest.raises(TypeError) as excinfo:
        m.iss1105_row(np.ones((7, 1)))
685
    assert "incompatible function arguments" in str(excinfo.value)
686
687
    with pytest.raises(TypeError) as excinfo:
        m.iss1105_col(np.ones((1, 7)))
688
    assert "incompatible function arguments" in str(excinfo.value)
689
690


691
692
693
694
def test_custom_operator_new():
    """Using Eigen types as member variables requires a class-specific
    operator new with proper alignment"""

695
    o = m.CustomOperatorNew()
696
697
    np.testing.assert_allclose(o.a, 0.0)
    np.testing.assert_allclose(o.b.diagonal(), 1.0)