5
5
import numpy as np
6
6
import pyttb as ttb
7
7
import pytest
8
+ import scipy .sparse as sparse
8
9
9
10
@pytest .fixture ()
10
11
def sample_ttensor ():
@@ -41,6 +42,30 @@ def test_ttensor_initialization_from_data(sample_ttensor):
41
42
assert isinstance (ttensorInstance .core , ttb .tensor )
42
43
assert all ([isinstance (a_factor , np .ndarray ) for a_factor in ttensorInstance .u ])
43
44
45
+ # Negative Tests
46
+ non_array_factor = ttensorInstance .u + [1 ]
47
+ with pytest .raises (ValueError ):
48
+ ttb .ttensor .from_data (ttensorInstance .core , non_array_factor [1 :])
49
+
50
+ non_matrix_factor = ttensorInstance .u + [np .array ([1 ])]
51
+ with pytest .raises (ValueError ):
52
+ ttb .ttensor .from_data (ttensorInstance .core , non_matrix_factor [1 :])
53
+
54
+ too_few_factors = ttensorInstance .u .copy ()
55
+ too_few_factors .pop ()
56
+ with pytest .raises (ValueError ):
57
+ ttb .ttensor .from_data (ttensorInstance .core , too_few_factors )
58
+
59
+ wrong_shape_factor = ttensorInstance .u .copy ()
60
+ row , col = wrong_shape_factor [0 ].shape
61
+ wrong_shape_factor [0 ] = np .random .random ((row + 1 , col + 1 ))
62
+ with pytest .raises (ValueError ):
63
+ ttb .ttensor .from_data (ttensorInstance .core , wrong_shape_factor )
64
+
65
+ # Enforce error until sptensor core/other cores supported
66
+ with pytest .raises (ValueError ):
67
+ ttb .ttensor .from_data (ttb .sptensor .from_tensor_type (ttensorInstance .core ), ttensorInstance .u )
68
+
44
69
@pytest .mark .indevelopment
45
70
def test_ttensor_initialization_from_tensor_type (sample_ttensor ):
46
71
@@ -58,6 +83,17 @@ def test_ttensor_full(sample_ttensor):
58
83
# This sanity check only works for all 1's
59
84
assert tensor .double () == np .prod (ttensorInstance .core .shape )
60
85
86
+ # Negative tests
87
+ sparse_core = ttb .sptensor ()
88
+ sparse_core .shape = ttensorInstance .core .shape
89
+ sparse_u = [sparse .coo_matrix (np .zeros (factor .shape )) for factor in ttensorInstance .u ]
90
+ # We could probably make these properties to avoid this edge case but expect to eventually cover these alternate
91
+ # cores
92
+ ttensorInstance .core = sparse_core
93
+ ttensorInstance .u = sparse_u
94
+ with pytest .raises (ValueError ):
95
+ ttensorInstance .full ()
96
+
61
97
@pytest .mark .indevelopment
62
98
def test_ttensor_double (sample_ttensor ):
63
99
ttensorInstance = sample_ttensor
@@ -87,17 +123,45 @@ def test_sptensor__neg__(sample_ttensor):
87
123
assert ttensorInstance .isequal (ttensorInstance3 )
88
124
89
125
@pytest .mark .indevelopment
90
- def test_ttensor_innerproduct (sample_ttensor ):
126
+ def test_ttensor_innerproduct (sample_ttensor , random_ttensor ):
91
127
ttensorInstance = sample_ttensor
92
128
93
129
# TODO these are an overly simplistic edge case for ttensors that are a single float
94
130
95
131
# ttensor innerprod ttensor
96
132
assert ttensorInstance .innerprod (ttensorInstance ) == ttensorInstance .double ()** 2
133
+ core_dim = ttensorInstance .core .shape [0 ] + 1
134
+ ndim = ttensorInstance .ndims
135
+ large_core_ttensor = ttb .ttensor .from_data (
136
+ ttb .tensor .from_data (np .ones ((core_dim ,)* ndim )),
137
+ [np .ones ((1 , core_dim ))] * ndim
138
+ )
139
+ assert large_core_ttensor .innerprod (ttensorInstance ) == ttensorInstance .full ().innerprod (large_core_ttensor .full ())
97
140
98
141
# ttensor innerprod tensor
99
142
assert ttensorInstance .innerprod (ttensorInstance .full ()) == ttensorInstance .double () ** 2
100
143
144
+ # ttensr innerprod ktensor
145
+ ktensorInstance = ttb .ktensor .from_data (np .array ([8. ]), [np .array ([[1. ]])]* 3 )
146
+ assert ttensorInstance .innerprod (ktensorInstance ) == ttensorInstance .double () ** 2
147
+
148
+ # ttensor innerprod tensor (shape larger than core)
149
+ random_ttensor .innerprod (random_ttensor .full ())
150
+
151
+ # Negative Tests
152
+ ttensor_extra_factors = ttb .ttensor .from_tensor_type (ttensorInstance )
153
+ ttensor_extra_factors .u .extend (ttensorInstance .u )
154
+ with pytest .raises (ValueError ):
155
+ ttensorInstance .innerprod (ttensor_extra_factors )
156
+
157
+ tensor_extra_dim = ttb .tensor .from_data (np .ones (ttensorInstance .shape + (1 ,)))
158
+ with pytest .raises (ValueError ):
159
+ ttensorInstance .innerprod (tensor_extra_dim )
160
+
161
+ invalid_option = []
162
+ with pytest .raises (ValueError ):
163
+ ttensorInstance .innerprod (invalid_option )
164
+
101
165
@pytest .mark .indevelopment
102
166
def test_ttensor__mul__ (sample_ttensor ):
103
167
ttensorInstance = sample_ttensor
@@ -107,6 +171,10 @@ def test_ttensor__mul__(sample_ttensor):
107
171
assert (ttensorInstance * mul_factor ).double () == np .prod (ttensorInstance .core .shape ) * mul_factor
108
172
assert (ttensorInstance * float (2 )).double () == np .prod (ttensorInstance .core .shape ) * float (mul_factor )
109
173
174
+ # Negative tests
175
+ with pytest .raises (ValueError ):
176
+ _ = ttensorInstance * 'some_string'
177
+
110
178
@pytest .mark .indevelopment
111
179
def test_ttensor__rmul__ (sample_ttensor ):
112
180
ttensorInstance = sample_ttensor
@@ -116,6 +184,10 @@ def test_ttensor__rmul__(sample_ttensor):
116
184
assert (mul_factor * ttensorInstance ).double () == np .prod (ttensorInstance .core .shape ) * mul_factor
117
185
assert (float (2 ) * ttensorInstance ).double () == np .prod (ttensorInstance .core .shape ) * float (mul_factor )
118
186
187
+ # Negative tests
188
+ with pytest .raises (ValueError ):
189
+ _ = 'some_string' * ttensorInstance
190
+
119
191
@pytest .mark .indevelopment
120
192
def test_ttensor_ttv (sample_ttensor ):
121
193
ttensorInstance = sample_ttensor
@@ -124,6 +196,17 @@ def test_ttensor_ttv(sample_ttensor):
124
196
final_value = sample_ttensor .ttv (trivial_vectors )
125
197
assert final_value == np .prod (ttensorInstance .core .shape )
126
198
199
+ assert np .allclose (
200
+ ttensorInstance .ttv (trivial_vectors [0 ], 0 ).double (),
201
+ ttensorInstance .full ().ttv (trivial_vectors [0 ], 0 ).double ()
202
+ )
203
+
204
+ # Negative tests
205
+ wrong_shape_vector = trivial_vectors .copy ()
206
+ wrong_shape_vector [0 ] = np .array ([mul_factor , mul_factor ])
207
+ with pytest .raises (ValueError ):
208
+ sample_ttensor .ttv (wrong_shape_vector )
209
+
127
210
@pytest .mark .indevelopment
128
211
def test_ttensor_mttkrp (random_ttensor ):
129
212
ttensorInstance = random_ttensor
@@ -133,23 +216,32 @@ def test_ttensor_mttkrp(random_ttensor):
133
216
]
134
217
final_value = ttensorInstance .mttkrp (vectors , 2 )
135
218
full_value = ttensorInstance .full ().mttkrp (vectors , 2 )
136
- assert np .all ( np . isclose ( final_value , full_value ) ), (
219
+ assert np .allclose ( final_value , full_value ), (
137
220
f"TTensor value is: \n { final_value } \n \n "
138
221
f"Full value is: \n { full_value } "
139
222
)
140
223
141
224
@pytest .mark .indevelopment
142
- def test_ttensor_norm (random_ttensor ):
225
+ def test_ttensor_norm (sample_ttensor , random_ttensor ):
143
226
ttensorInstance = random_ttensor
144
227
assert np .isclose (ttensorInstance .norm (), ttensorInstance .full ().norm ())
145
228
229
+ # Core larger than full tensor
230
+ ttensorInstance = sample_ttensor
231
+ assert np .isclose (ttensorInstance .norm (), ttensorInstance .full ().norm ())
232
+
146
233
@pytest .mark .indevelopment
147
234
def test_ttensor_permute (random_ttensor ):
148
235
ttensorInstance = random_ttensor
149
236
original_order = np .arange (0 , len (ttensorInstance .core .shape ))
150
237
permuted_tensor = ttensorInstance .permute (original_order )
151
238
assert ttensorInstance .isequal (permuted_tensor )
152
239
240
+ # Negative Tests
241
+ with pytest .raises (ValueError ):
242
+ bad_permutation_order = np .arange (0 , len (ttensorInstance .core .shape ) + 1 )
243
+ ttensorInstance .permute (bad_permutation_order )
244
+
153
245
@pytest .mark .indevelopment
154
246
def test_ttensor_ttm (random_ttensor ):
155
247
ttensorInstance = random_ttensor
@@ -163,19 +255,91 @@ def test_ttensor_ttm(random_ttensor):
163
255
f"TTensor value is: \n { final_value } \n \n "
164
256
f"Full value is: \n { reverse_value } "
165
257
)
258
+ final_value = ttensorInstance .ttm (matrices ) # No dims
259
+ assert final_value .isequal (reverse_value )
260
+ final_value = ttensorInstance .ttm (matrices , list (range (len (matrices )))) # Dims as list
261
+ assert final_value .isequal (reverse_value )
262
+
263
+
264
+ single_tensor_result = ttensorInstance .ttm (matrices [0 ], 0 )
265
+ single_tensor_full_result = ttensorInstance .full ().ttm (matrices [0 ], 0 )
266
+ assert np .allclose (single_tensor_result .double (), single_tensor_full_result .double ()), (
267
+ f"TTensor value is: \n { single_tensor_result .full ()} \n \n "
268
+ f"Full value is: \n { single_tensor_full_result } "
269
+ )
270
+
271
+ transposed_matrices = [matrix .transpose () for matrix in matrices ]
272
+ transpose_value = ttensorInstance .ttm (transposed_matrices , np .arange (len (matrices )), transpose = True )
273
+ assert final_value .isequal (transpose_value )
274
+
275
+ # Negative Tests
276
+ big_wrong_size = 123
277
+ matrices [0 ] = np .random .random ((big_wrong_size , big_wrong_size ))
278
+ with pytest .raises (ValueError ):
279
+ _ = ttensorInstance .ttm (matrices , np .arange (len (matrices )))
280
+
166
281
167
282
@pytest .mark .indevelopment
168
283
def test_ttensor_reconstruct (random_ttensor ):
169
284
ttensorInstance = random_ttensor
170
285
# TODO: This slice drops the singleton dimension, should it? If so should ttensor squeeze during reconstruct?
171
286
full_slice = ttensorInstance .full ()[:, 1 , :]
172
287
ttensor_slice = ttensorInstance .reconstruct (1 , 1 )
173
- assert np .all ( np . isclose ( full_slice .double (), ttensor_slice .squeeze ().double () ))
288
+ assert np .allclose ( full_slice .double (), ttensor_slice .squeeze ().double ())
174
289
assert ttensorInstance .reconstruct ().isequal (ttensorInstance .full ())
290
+ sample_all_modes = [np .array ([0 ])] * len (ttensorInstance .shape )
291
+ sample_all_modes [- 1 ] = 0 # Make raw scalar
292
+ reconstruct_scalar = ttensorInstance .reconstruct (sample_all_modes ).full ().double ()
293
+ full_scalar = ttensorInstance .full ()[tuple (sample_all_modes )]
294
+ assert np .isclose (reconstruct_scalar , full_scalar )
295
+
296
+ scale = np .random .random (ttensorInstance .u [1 ].shape ).transpose ()
297
+ _ = ttensorInstance .reconstruct (scale , 1 )
298
+ # FIXME from the MATLAB docs wasn't totally clear how to validate this
299
+
300
+ # Negative Tests
301
+ with pytest .raises (ValueError ):
302
+ _ = ttensorInstance .reconstruct (1 , [0 , 1 ])
175
303
176
304
@pytest .mark .indevelopment
177
305
def test_ttensor_nvecs (random_ttensor ):
178
306
ttensorInstance = random_ttensor
179
- ttensor_eigvals = ttensorInstance .nvecs (0 , 2 )
180
- full_eigvals = ttensorInstance .full ().nvecs (0 , 2 )
307
+ n = 0
308
+ r = 2
309
+ ttensor_eigvals = ttensorInstance .nvecs (n , r )
310
+ full_eigvals = ttensorInstance .full ().nvecs (n , r )
311
+ assert np .allclose (ttensor_eigvals , full_eigvals )
312
+
313
+ # Test for eig vals larger than shape-1
314
+ n = 1
315
+ r = 2
316
+ full_eigvals = ttensorInstance .full ().nvecs (n , r )
317
+ with pytest .warns (Warning ) as record :
318
+ ttensor_eigvals = ttensorInstance .nvecs (n , r )
319
+ assert 'Greater than or equal to tensor.shape[n] - 1 eigenvectors requires cast to dense to solve' \
320
+ in str (record [0 ].message )
181
321
assert np .allclose (ttensor_eigvals , full_eigvals )
322
+
323
+ # Negative Tests
324
+ sparse_core = ttb .sptensor ()
325
+ sparse_core .shape = ttensorInstance .core .shape
326
+ ttensorInstance .core = sparse_core
327
+
328
+ # Sparse core
329
+ with pytest .raises (NotImplementedError ):
330
+ ttensorInstance .nvecs (0 , 1 )
331
+
332
+ # Sparse factors
333
+ sparse_u = [sparse .coo_matrix (np .zeros (factor .shape )) for factor in ttensorInstance .u ]
334
+ ttensorInstance .u = sparse_u
335
+ with pytest .raises (NotImplementedError ):
336
+ ttensorInstance .nvecs (0 , 1 )
337
+
338
+ @pytest .mark .indevelopment
339
+ def test_sptensor_isequal (sample_ttensor ):
340
+ ttensorInstance = sample_ttensor
341
+ # Negative Tests
342
+ assert not ttensorInstance .isequal (ttensorInstance .full ())
343
+ ttensor_extra_factors = ttb .ttensor .from_tensor_type (ttensorInstance )
344
+ ttensor_extra_factors .u .extend (ttensorInstance .u )
345
+ assert not ttensorInstance .isequal (ttensor_extra_factors )
0 commit comments