def test_orthogonal(): # Test shape, orthogonality, determinant, python2 error, MathArray if six.PY2: with raises(NotImplementedError, match='This feature requires newer versions of ' 'numpy and scipy than are available.'): matrices = OrthogonalMatrices(unitdet=False) matrices.gen_sample() with raises(NotImplementedError, match='This feature requires newer versions of ' 'numpy and scipy than are available.'): matrices = OrthogonalMatrices(unitdet=True) matrices.gen_sample() else: # These are the doctests matrices = OrthogonalMatrices() assert matrices.gen_sample().shape == (2, 2) matrices = OrthogonalMatrices(dimension=4) assert matrices.gen_sample().shape == (4, 4) matrices = OrthogonalMatrices(unitdet=True) assert within_tolerance(np.linalg.det(matrices.gen_sample()), 1, 1e-14) matrices = OrthogonalMatrices(unitdet=False) m = matrices.gen_sample() assert (within_tolerance(np.linalg.det(m), 1, 1e-14) or within_tolerance(np.linalg.det(m), -1, 1e-14)) matrices = OrthogonalMatrices(unitdet=True) m = matrices.gen_sample() assert within_tolerance(m * np.conjugate(np.transpose(m)), MathArray(np.eye(2)), 1e-14) matrices = OrthogonalMatrices(unitdet=False) m = matrices.gen_sample() assert within_tolerance(m * np.conjugate(np.transpose(m)), MathArray(np.eye(2)), 1e-14) shapes = tuple(range(2, 5)) dets = (True, False) # More general testing for shape in shapes: for det in dets: matrices = matrices = OrthogonalMatrices(dimension=shape, unitdet=det) m = matrices.gen_sample() assert m.shape == (shape, shape) assert np.array_equal(np.conj(m), m) assert within_tolerance(m * np.transpose(m), MathArray(np.eye(shape)), 1e-14) assert (approx(np.abs(np.linalg.det(m)), 1) or approx(np.abs(np.linalg.det(m)), -1)) if det: assert approx(np.linalg.det(m), 1) assert isinstance(m, MathArray)
def test_general_matrices(): # Test shape, real/complex, norm, triangular options, MathArray shapes = product(tuple(range(2, 5)), tuple(range(2, 5))) norms = ([2, 6], [3, 4], [4, 12], [1 - 1e-12, 1 + 1e-12]) triangles = (None, 'upper', 'lower') for shape in shapes: for norm in norms: for triangle in triangles: matrices = RealMatrices(shape=shape, norm=norm, triangular=triangle) m = matrices.gen_sample() assert m.shape == shape assert norm[0] <= np.linalg.norm(m) <= norm[1] assert np.array_equal(np.conj(m), m) assert isinstance(m, MathArray) if triangle is None: assert not within_tolerance(m, MathArray(np.triu(m)), 0) assert not within_tolerance(m, MathArray(np.tril(m)), 0) elif triangle == "upper": assert within_tolerance(m, MathArray(np.triu(m)), 0) elif triangle == "lower": assert within_tolerance(m, MathArray(np.tril(m)), 0) matrices = ComplexMatrices(shape=shape, norm=norm, triangular=triangle) m = matrices.gen_sample() assert m.shape == shape assert norm[0] <= np.linalg.norm(m) <= norm[1] assert not np.array_equal(np.conj(m), m) assert isinstance(m, MathArray) if triangle is None: assert not within_tolerance(m, MathArray(np.triu(m)), 0) assert not within_tolerance(m, MathArray(np.tril(m)), 0) elif triangle == "upper": assert within_tolerance(m, MathArray(np.triu(m)), 0) elif triangle == "lower": assert within_tolerance(m, MathArray(np.tril(m)), 0)
def _within_tolerance(x, y): return within_tolerance(x, y, self.config['tolerance'])
def raw_check(self, answer, cleaned_input): """Perform the numerical check of student_input vs answer""" var_samples = gen_symbols_samples(self.config['variables'], self.config['samples'], self.config['sample_from'], self.functions, {}) func_samples = gen_symbols_samples(self.random_funcs.keys(), self.config['samples'], self.random_funcs, self.functions, {}) # Make a copy of the functions and variables lists # We'll add the sampled functions/variables in funcscope = self.functions.copy() varscope = self.constants.copy() num_failures = 0 for i in range(self.config['samples']): # Update the functions and variables listings with this sample funcscope.update(func_samples[i]) varscope.update(var_samples[i]) # Evaluate integrals. Error handling here is in two parts because # 1. custom error messages we've added # 2. scipy's warnings re-raised as error messages try: expected_re, expected_im, _ = self.evaluate_int( answer['integrand'], answer['lower'], answer['upper'], answer['integration_variable'], varscope=varscope, funcscope=funcscope) except IntegrationError as e: msg = "Integration Error with author's stored answer: {}" raise ConfigError(msg.format(e.message)) student_re, student_im, used_funcs = self.evaluate_int( cleaned_input['integrand'], cleaned_input['lower'], cleaned_input['upper'], cleaned_input['integration_variable'], varscope=varscope, funcscope=funcscope) # scipy raises integration warnings when things go wrong, # except they aren't really warnings, they're just printed to stdout # so we use quad's full_output option to catch the messages, and then raise errors. # The 4th component only exists when its warning message is non-empty if len(student_re) == 4: raise IntegrationError(student_re[3]) if len(expected_re) == 4: raise ConfigError(expected_re[3]) if len(student_im) == 4: raise IntegrationError(student_im[3]) if len(expected_im) == 4: raise ConfigError(expected_im[3]) self.log( self.debug_appendix_template.format( samplenum=i, variables=varscope, student_re_eval=student_re[0], student_re_error=student_re[1], student_re_neval=student_re[2]['neval'], student_im_eval=student_im[0], student_im_error=student_im[1], student_im_neval=student_im[2]['neval'], author_re_eval=expected_re[0], author_re_error=expected_re[1], author_re_neval=expected_re[2]['neval'], author_im_eval=expected_im[0], author_im_error=expected_im[1], author_im_neval=expected_im[2]['neval'], )) # Check if expressions agree expected = expected_re[0] + (expected_im[0] or 0) * 1j student = student_re[0] + (student_im[0] or 0) * 1j if not within_tolerance(expected, student, self.config['tolerance']): num_failures += 1 if num_failures > self.config["failable_evals"]: return { 'ok': False, 'grade_decimal': 0, 'msg': '' }, used_funcs # This response appears to agree with the expected answer return {'ok': True, 'grade_decimal': 1, 'msg': ''}, used_funcs
def test_square_matrices(): # Test shape, real/complex, norm, symmetry, traceless, det, MathArray shapes = tuple(range(2, 5)) norms = ([2, 6], [1 - 1e-12, 1 + 1e-12]) symmetries = (None, 'diagonal', 'symmetric', 'antisymmetric', 'hermitian', 'antihermitian') traceless_opts = (True, False) dets = (None, 0, 1) complexes = (True, False) for det, traceless, symmetry in product(dets, traceless_opts, symmetries): # Handle the cases that don't work if det == 0 and traceless: with raises(ConfigError, match='Unable to generate zero determinant traceless matrices'): SquareMatrices(traceless=traceless, symmetry=symmetry, determinant=det) continue # Continue with further cases for shape, norm, comp in product(shapes, norms, complexes): # Check for matrices that don't exist args = {'dimension': shape, 'norm': norm, 'traceless': traceless, 'symmetry': symmetry, 'determinant': det, 'complex': comp} if det == 1: if traceless and shape == 2: if symmetry == 'diagonal' and not comp: with raises(ConfigError, match='No real, traceless, unit-determinant, diagonal 2x2 matrix exists'): SquareMatrices(**args) continue elif symmetry == 'symmetric' and not comp: with raises(ConfigError, match='No real, traceless, unit-determinant, symmetric 2x2 matrix exists'): SquareMatrices(**args) continue elif symmetry == 'hermitian': with raises(ConfigError, match='No traceless, unit-determinant, Hermitian 2x2 matrix exists'): SquareMatrices(**args) continue elif shape % 2 == 1: # Odd dimension if symmetry == 'antisymmetric': with raises(ConfigError, match='No unit-determinant antisymmetric matrix exists in odd dimensions'): SquareMatrices(**args) continue elif symmetry == 'antihermitian': with raises(ConfigError, match='No unit-determinant antihermitian matrix exists in odd dimensions'): SquareMatrices(**args) continue if det == 0 and symmetry == 'antisymmetric': if comp: with raises(ConfigError, match='Unable to generate complex zero determinant antisymmetric matrices'): SquareMatrices(**args) continue if shape % 2 == 0: # Even dimension with raises(ConfigError, match='Unable to generate real zero determinant antisymmetric matrices in even dimensions'): SquareMatrices(**args) continue # Matrix exists, so let's test matrices = SquareMatrices(**args) if symmetry in ('hermitian', 'antihermitian'): comp = True m = matrices.gen_sample() # MathArray assert isinstance(m, MathArray) # Shape assert m.shape == (shape, shape) # Norm if det != 1: computed_norm = np.linalg.norm(m) assert norm[0] <= computed_norm or abs(computed_norm - norm[0]) < 1e-12 assert computed_norm <= norm[1] or abs(computed_norm - norm[1]) < 1e-12 # Complex if not comp: assert np.array_equal(np.conj(m), m) # Trace if traceless: assert within_tolerance(m.trace(), 0, 5e-13) # Determinant if det == 0: assert within_tolerance(np.abs(np.linalg.det(m)), 0, 1e-12) elif det == 1: assert within_tolerance(np.abs(np.linalg.det(m)), 1, 1e-12) # Symmetries if symmetry == 'diagonal': assert np.array_equal(np.diag(np.diag(m)), m) elif symmetry == 'symmetric': assert np.array_equal(m, m.T) elif symmetry == 'antisymmetric': assert np.array_equal(m, -m.T) elif symmetry == 'hermitian': assert np.array_equal(m, np.conj(m.T)) elif symmetry == 'antihermitian': assert np.array_equal(m, -np.conj(m.T))