def conjugate_gradient_solve(sys_mat: Matrix, sys_vec: Vector, max_iter=100, max_error=1e-8) -> Vector: """ The conjugate gradient method is a iterative numeric method to solve systems of linear equations. The method starts with a vector full of zeroes as the first approximation to the solution and improves it in each iteration. In every iteration the error vector `error` is checked and only in the case where every value is less than the `max_error`, the solution is considered "good enough" and the solution vector returned. If after `max_iter` iterations a "good enough" solution hasn't been found, the function raises an `ArithmeticError`. :param sys_mat: system `Matrix` :param sys_vec: system `Vector` :param max_iter: `int` max number of iterations :param max_error: `float` max error accepted in the solution :return: solution `Vector` """ validate_system(sys_mat, sys_vec) solution = Vector(sys_vec.length) error = sys_vec - sys_mat.times_vector(solution) p = error.copy() def solution_good_enough(): for i in range(sys_vec.length): if math.fabs(error.value_at(i)) > max_error: return False return True for _ in range(max_iter): if solution_good_enough(): return solution m_times_p = sys_mat.times_vector(p) alpha = (error * error).sum / (p * m_times_p).sum solution += p.scaled(alpha) old_error = error.copy() error -= m_times_p.scaled(alpha) beta = (error * error).sum / (old_error * old_error).sum p = error + p.scaled(beta) raise ArithmeticError( f'Reached max number of iterations ({max_iter}) without ' + 'a good solution ')
def __assemble_system_matrix(self, size: int): matrix = Matrix(size, size) for bar in self.__bars: bar_matrix = bar.global_stiffness_matrix() dofs = self.__bar_dofs(bar) for row, row_dof in enumerate(dofs): for col, col_dof in enumerate(dofs): matrix.add_to_value(bar_matrix.value_at(row, col), row_dof, col_dof) self.__system_matrix = matrix
def doolitle_decomposition(matrix: Matrix) -> (Matrix, Matrix): """ Decomposes the matrix `matrix` into the product of a lower triangular and an upper triangular matrices: [A] = [L][U]. This function returns a tuple including the lower and upper triangular matrices, exactly in this order: ([L], [U]). :param matrix: `Matrix` :return: ([L], [U]) """ if not matrix.is_square: raise ValueError('Can\'t decompose a non-square matrix') size = matrix.rows_count (lower, upper) = (Matrix(size, size), Matrix(size, size)) for i in range(size): for j in range(size): val = matrix.value_at(i, j) if i <= j: _sum = 0 for k in range(i): l_ik = lower.value_at(i, k) u_kj = upper.value_at(k, j) _sum += (l_ik * u_kj) upper.set_value(val - _sum, i, j) if j <= i: _sum = 0 for k in range(j): l_ik = lower.value_at(i, k) u_kj = upper.value_at(k, j) _sum += (l_ik * u_kj) u_jj = upper.value_at(j, j) lower.set_value((val - _sum) / u_jj, i, j) return lower, upper
class ConjugateGradientTest(unittest.TestCase): sys_matrix = Matrix(4, 4).set_data([ 4.0, -2.0, 4.0, 2.0, -2.0, 10.0, -2.0, -7.0, 4.0, -2.0, 8.0, 4.0, 2.0, -7.0, 4.0, 7.0 ]) sys_vec = Vector(4).set_data([20, -16, 40, 28]) solution = Vector(4).set_data([1.0, 2.0, 3.0, 4.0]) def test_solve(self): actual = conjugate_gradient_solve( self.sys_matrix, self.sys_vec ) self.assertEqual(self.solution, actual)
def test_assemble_system_matrix(self, cholesky_mock): eal3 = 0.1118033989 c2_eal3 = .8 * eal3 s2_eal3 = .2 * eal3 cs_eal3 = .4 * eal3 expected_mat = Matrix(6, 6).set_data([ c2_eal3, cs_eal3, 0, 0, -c2_eal3, -cs_eal3, cs_eal3, .25 + s2_eal3, 0, -.25, -cs_eal3, -s2_eal3, 0, 0, .125, 0, -.125, 0, 0, -.25, 0, .25, 0, 0, -c2_eal3, -cs_eal3, -.125, 0, .125 + c2_eal3, cs_eal3, -cs_eal3, -s2_eal3, 0, 0, cs_eal3, s2_eal3 ]) self.structure.solve_structure() [actual_mat, _] = cholesky_mock.call_args[0] cholesky_mock.assert_called_once() self.assertEqual(expected_mat, actual_mat)
def test_system_matrix_constraints(self, cholesky_mock): self._set_external_constraints() eal3 = 0.1118033989 c2_eal3 = .8 * eal3 s2_eal3 = .2 * eal3 cs_eal3 = .4 * eal3 expected_mat = Matrix(6, 6).set_data([ 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, .125 + c2_eal3, cs_eal3, 0, 0, 0, 0, cs_eal3, s2_eal3 ]) self.structure.solve_structure() [actual_mat, _] = cholesky_mock.call_args[0] cholesky_mock.assert_called_once() self.assertEqual(expected_mat, actual_mat)
def global_stiffness_matrix(self) -> Matrix: """ Computes the bar's stiffness matrix in global coordinates. :return: global stiffness `Matrix` """ direction = self.geometry.direction_vector eal = self.young_mod * self.cross_section / self.length c = direction.cosine s = direction.sine c2_eal = (c**2) * eal s2_eal = (s**2) * eal sc_eal = (s * c) * eal return Matrix(4, 4).set_data([ c2_eal, sc_eal, -c2_eal, -sc_eal, sc_eal, s2_eal, -sc_eal, -s2_eal, -c2_eal, -sc_eal, c2_eal, sc_eal, -sc_eal, -s2_eal, sc_eal, s2_eal ])
def test_global_stiffness_matrix(self): expected = Matrix(4, 4).set_data( [4, 2, -4, -2, 2, 1, -2, -1, -4, -2, 4, 2, -2, -1, 2, 1]) actual = self.bar.global_stiffness_matrix() self.assertEqual(expected, actual)