Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Section: BLAS/LAPACK - Linear Equations

This program demonstrates solving linear algebra equations in Fortran.

Linear Equation Problem

In this notebook we will use Fortran to solve a system of linear equations of the form:

Ax=b\Large A \mathbf{x} = \mathbf{b}

where:

A=[a11a12a1na21a22a2nam1am2amn]\Large A = \left[ \begin{array}{cccc} a_{11} & a_{12} & \cdots & a_{1n} \\ a_{21} & a_{22} & \cdots & a_{2n} \\ \vdots & \vdots & \vdots & \vdots \\ a_{m1} & a_{m2} & \cdots & a_{mn} \end{array} \right]
x=[x1x2xn]\Large \mathbf{x} = \left[ \begin{array}{c} x_{1} \\ x_{2} \\ \vdots \\ x_{n} \end{array} \right]
b=[b1b2bn]\Large \mathbf{b} = \left[ \begin{array}{c} b_{1} \\ b_{2} \\ \vdots \\ b_{n} \end{array} \right]

The approach will be to have a main program that reads in as parameters the number of equations, the matrix A\mathbf{A} and the vector x\mathbf{x} as text files. The main program is called solve_equations.f90. It makes use of the LAPACK linear equation solver routine called DGESV. Information on this routine can be found at LAPACK - DGESV.

The main program makes use of a module called linalg_mod.f90, which contains subroutines to read and write matrices and arrays to and from text files.

Finally, there is a helper program called generate_array.f90 which has a subroutine that gets arguments from the command line and generates either of vector or matrix of specified size. Every element in the array or matrix is a random number.

Explanation of the Programs

The following explanation is given by “Code Copilot” within ChatGPT 4.

Explaining generate_array.f90

The Fortran program generate_array.f90 serves to generate either a vector or a matrix with random elements, depending on the command line arguments provided. It uses routines from a module named linalg_mod to handle array creation and output. Below is an explanation of the program structure and its key components:

Program Structure

  1. Module Usage

    • iso_fortran_env: Utilized for standard environment variables and constants, specifically error_unit for error messages and DP alias for REAL64, indicating double precision.

    • linalg_mod: Externally defined module used here for write_array and generate_random_array, which handle array operations.

  2. Variable Declarations

    • dimensions: An allocatable integer array to store dimensions provided via command-line arguments.

    • vector: An allocatable vector of type real(kind=DP).

    • matrix: An allocatable 2D array of type real(kind=DP).

  3. Main Execution Logic

    • It begins by fetching command-line arguments into dimensions.

    • Based on the size of dimensions, it either generates a 1D vector or a 2D matrix:

      • For a single dimension, a vector of random elements is created and written.

      • For two dimensions, a matrix is created and written.

    • If neither one nor two dimensions are provided, it outputs an error indicating that only rank 1 and 2 arrays are supported.

  4. Error Handling

    • The program checks for proper memory allocation and command-line argument processing, with appropriate error messages and stops if any issues occur.

Subroutine: get_arguments

  • Purpose: Fetches command-line arguments to determine the size of the array to be generated.

  • Process:

    • Determines the number of arguments (rank).

    • Allocates the dimensions array based on rank.

    • Reads each command argument into dimensions, converting from string to integer.

    • Handles errors in conversion and allocation, stopping the program if necessary.

Key Functionalities

  • Array Generation: Depending on the input dimensions, it generates either a vector or a matrix with random values.

  • Modular Design: Leverages external modules for specific functionalities, which simplifies the main program.

  • Flexibility: The ability to handle both vectors and matrices makes the program versatile for different kinds of numerical data generation tasks.

Usage

  • The program is executed with command-line arguments specifying the dimensions of the array to generate. For example:

    • ./generate_array 10 would generate a vector of size 10.

    • ./generate_array 3 4 would generate a 3x4 matrix.

Now, let’s move on to read and explain the solve_equations.f90 file.

Explaining solve_equations.f90

The Fortran program solve_equations.f90 is designed to solve a system of linear equations using LAPACK’s dgesv subroutine, which solves systems of linear equations ( AX = B ) by performing LU decomposition with partial pivoting. Here’s a breakdown of its key components and functionalities:

Program Structure

  1. Module Usage

    • iso_fortran_env: Standard intrinsic module providing access to the error_unit and DP (double precision kind type alias).

    • linalg_mod: Custom module providing read_array and write_array functions, used to handle input/output operations for matrices and vectors.

  2. Variable Declarations

    • nr_eqns: Number of equations in the system, derived from command-line arguments.

    • status, info: Variables for checking statuses of operations; info is used specifically with dgesv.

    • matrix_file_name, vector_file_name: File names for storing matrix ( A ) and vector ( B ).

    • b: Vector ( B ) in the equation ( AX = B ).

    • pivot: Integer array for pivot indices from the LU decomposition.

    • A: Matrix ( A ) of the system.

  3. Explicit Interface for dgesv

    • The subroutine’s interface is explicitly defined to ensure correct passing of arguments:

      • n, nrhs, a, lda, ipiv, b, ldb, and info describe the system dimensions, matrix and vector storage details, and output for status information.

  4. Main Execution Logic

    • Argument Reading: Fetches command-line arguments to determine the number of equations and file names for matrix ( A ) and vector ( B ).

    • Array Allocation: Allocates memory for ( A ), ( B ), and pivot indices.

    • File Reading: Reads matrix ( A ) and vector ( B ) from files specified by the user.

    • System Solving: Calls dgesv to solve the linear system, where ( A ) is modified to store the factors from the LU decomposition, and ( B ) is overwritten with the solution ( X ).

    • Output: Writes the solution vector ( X ) back to a file.

  5. Error Handling

    • Checks for successful allocation and reading operations, with appropriate error messages and program termination on failures.

Subroutine: get_arguments

  • Purpose: Fetches and validates command-line arguments to ensure correct program setup.

  • Parameters:

    • nr_eqns: Number of equations/system size.

    • matrix_file_name, vector_file_name: File paths for the matrix and vector data.

Key Functionalities

  • Linear System Solver: Uses dgesv for robust and efficient solving of linear systems, leveraging LAPACK’s optimized routines.

  • Modular File Handling: Employs modular functions from linalg_mod for input/output operations, promoting code reuse and organization.

  • Error Management: Extensive error checking ensures the program gracefully handles common issues like invalid input or memory allocation failures.

Usage

The program is intended to be executed with three command-line arguments specifying the number of equations, matrix file name, and vector file name. For example:

./solve_equations 3 matrix.dat vector.dat

This would solve a 3x3 system of equations using matrix and vector data from matrix.dat and vector.dat.

Next, let’s proceed to read and explain the linalg_mod.f90 file.

Explaining linalg_mod.f90

The Fortran module linalg_mod.f90 provides utility functions for reading, writing, and generating arrays. This module likely supports the functionality required by the other programs you uploaded (generate_array.f90 and solve_equations.f90). Here’s a breakdown of its contents and functionalities:

Module Structure and Functions

  1. Module linalg_mod

    • Utilizes iso_fortran_env for standard environment definitions, such as output_unit, error_unit, and DP (double precision floating-point kind).

  2. Functionality Exposed

    • read_array: Reads an array from a file whose dimensions and rank are defined by the inputs.

    • write_array: Writes an array to a file or standard output, handling both vectors and matrices.

    • generate_random_array: Fills an allocatable array with random numbers, based on the dimensions provided.

  3. Detailed Function Descriptions

    • read_array:

      • Takes a file name, an array (allocatable), and its expected dimensions.

      • Checks if the rank of the array matches the number of dimensions provided.

      • Depending on the rank, it calls read_vector or read_matrix to perform the read operation.

      • Errors are managed by stopping the program if the ranks or dimensions do not match expectations.

    • write_array:

      • Accepts an array and an optional file name for output.

      • Uses rank-specific procedures (write_vector or write_matrix) to handle the output based on the array’s rank.

      • If no file name is provided, the output is directed to the standard output (output_unit).

    • generate_random_array:

      • Designed to allocate and populate an array (either vector or matrix) with random numbers.

      • Checks the allocation status and initializes the array with random numbers using the intrinsic random_number procedure.

      • Manages errors related to mismatched ranks or failed allocations.

Subroutines for Vector and Matrix Handling

  • read_vector and write_vector:

    • Handle reading and writing operations for vectors, respectively.

  • read_matrix and write_matrix:

    • Handle reading and writing operations for matrices, respectively.

Key Functionalities and Error Handling

  • The module is equipped to handle errors related to input/output operations and array manipulations, which is critical for robust numerical software.

  • Error messages are detailed, including specific issues like mismatched array ranks or failed memory allocations, which aids in debugging and user feedback.

  • Flexibility to output to either a file or standard output enhances the module’s usability across different computational scenarios.

Usage

This module is utilized by other programs for managing arrays, particularly in numerical computations involving linear algebra. It abstracts away the details of file I/O and array initialization, allowing other parts of the software to focus on core computational logic.

Overall, linalg_mod.f90 serves as a foundational component for array management in Fortran programs, providing essential services such as reading, writing, and generating arrays with a focus on error handling and user feedback.

Program Code

The individual program files are listed below:

In file linalg_mod.f90

linalg_mod.f90
module linalg_mod
    use, intrinsic :: iso_fortran_env, only : output_unit, error_unit, DP => REAL64
    implicit none

    private

    public :: read_array, write_array, generate_random_array

contains

    subroutine read_array(file_name, array, dimensions)
        implicit none
        character(len=*), intent(in) :: file_name
        real(kind=DP), dimension(..), allocatable, intent(out) :: array
        integer, dimension(:), intent(in) :: dimensions

        if (rank(array) /= size(dimensions)) then
            write (unit=error_unit, fmt='(A)') '# error: array rank does not match dimensions'
            stop 3
        end if
        select rank(array)
            rank(1)
                call read_vector(file_name, dimensions(1), array)
            rank(2)
                call read_matrix(file_name, dimensions(1), dimensions(2), array)
            rank default
                write (unit=error_unit, fmt='(A, I0, A)') &
                    '# error: rank ', rank(array), ' not supported'
                stop 4
        end select
    end subroutine read_array

    subroutine write_array(array, file_name)
        implicit none
        real(kind=DP), dimension(..), intent(in) :: array
        character(len=*), intent(in), optional :: file_name

        select rank(array)
            rank(1)
                call write_vector(array, file_name)
            rank(2)
                call write_matrix(array, file_name)
            rank default
                write (unit=error_unit, fmt='(A, I0, A)') &
                    '# error: rank ', rank(array), ' not supported'
                stop 4
        end select
    end subroutine write_array

    subroutine read_vector(file_name, n, vector)
        implicit none
        character(len=*), intent(in) :: file_name
        integer, value :: n
        real(kind=DP), dimension(:), allocatable, intent(out) :: vector
        integer :: i, status, unit_nr
        character(len=1024) :: msg

        open (newunit=unit_nr, file=trim(file_name), access='sequential', &
            action='read', status='old', iostat=status, iomsg=msg)
        if (status /= 0) then
            write (unit=error_unit, fmt='(2A)') '# error: ', trim(msg)
            stop 1
        end if

        if (.not. allocated(vector)) then
            allocate (vector(n), stat=status)
            if (status /= 0) then
                write (unit=error_unit, fmt='(2A)') '# error: ', trim(msg)
                stop 3
            end if
        end if

        do i = 1, n
            read (unit=unit_nr, fmt=*, iostat= status, iomsg=msg) vector(i)
            if (status /= 0) then
                write (unit=error_unit, fmt='(2A)') '# error: ', trim(msg)
                stop 2
            end if
        end do

        close (unit=unit_nr)

    end subroutine read_vector

    subroutine read_matrix(file_name, nr_rows, nr_cols, matrix)
        implicit none
        character(len=*), intent(in) :: file_name
        integer, value :: nr_rows, nr_cols
        real(kind=DP), dimension(:, :), allocatable, intent(out) :: matrix
        integer :: i, status, unit_nr
        character(len=1024) :: msg

        open (newunit=unit_nr, file=trim(file_name), access='sequential', &
            action='read', status='old', iostat=status, iomsg=msg)
        if (status /= 0) then
            write (unit=error_unit, fmt='(2A)') '# error: ', trim(msg)
            stop 1
        end if

        if (.not. allocated(matrix)) then
            allocate (matrix(nr_rows, nr_cols), stat=status)
            if (status /= 0) then
                write (unit=error_unit, fmt='(2A)') '# error: ', trim(msg)
                stop 3
            end if
        end if

        do i = 1, nr_rows
            read (unit=unit_nr, fmt=*, iostat= status, iomsg=msg) matrix(i, :)
            if (status /= 0) then
                write (unit=error_unit, fmt='(2A)') '# error: ', trim(msg)
                stop 2
            end if
        end do

        close (unit=unit_nr)

    end subroutine read_matrix

    subroutine write_vector(vector, file_name)
        implicit none
        real(kind=DP), dimension(:), intent(in) :: vector
        character(len=*), intent(in), optional :: file_name
        integer :: unit_nr, i, status
        character(len=1024) :: msg

        if (present(file_name)) then
            open (newunit=unit_nr, file=trim(file_name), access='sequential', &
                action='write', status='replace', iostat=status, iomsg=msg)
            if (status /= 0) then
                write (unit=error_unit, fmt='(2A)') '# error: ', trim(msg)
                stop 1
            end if
        else
            unit_nr = output_unit
        end if

        do i = 1, size(vector)
            write (unit=unit_nr, fmt='(F27.15)') vector(i)
        end do

        if (present(file_name)) close (unit_nr)
    end subroutine write_vector

    subroutine write_matrix(matrix, file_name)
        implicit none
        real(kind=DP), dimension(:, :), intent(in) :: matrix
        character(len=*), intent(in), optional :: file_name
        integer :: unit_nr, i, status
        character(len=1024) :: msg

        if (present(file_name)) then
            open (newunit=unit_nr, file=trim(file_name), access='sequential', &
                action='write', status='replace', iostat=status, iomsg=msg)
            if (status /= 0) then
                write (unit=error_unit, fmt='(2A)') '# error: ', trim(msg)
                stop 1
            end if
        else
            unit_nr = output_unit
        end if

        do i = 1, size(matrix, 1)
            write (unit=unit_nr, fmt='(*(F27.15))') matrix(i, :)
        end do

        if (present(file_name)) close (unit_nr)
    end subroutine write_matrix

    subroutine generate_random_array(array, dimensions)
        implicit none
        real(kind=DP), dimension(..), allocatable, intent(out) :: array
        integer, dimension(:), intent(in) :: dimensions
        integer :: status

        if (rank(array) /= size(dimensions)) then
            write (unit=error_unit, fmt='(A)') '# error: array rank does not match dimensions'
            stop 3
        end if
        select rank(array)
            rank(1)
                if (.not. allocated(array)) then
                    allocate(array(dimensions(1)), stat=status)
                    if (status /= 0) then
                        write (unit=error_unit, fmt='(A, I0)') &
                            '# error: can not allocate vector of size ', dimensions(1)
                        stop 6
                    end if
                end if
                call random_number(array)
            rank(2)
                if (.not. allocated(array)) then
                    allocate(array(dimensions(1), dimensions(2)), stat=status)
                    if (status /= 0) then
                        write (unit=error_unit, fmt='(A,I0, A, I0)') &
                            '# error: can not allocate matrix of size ', &
                            dimensions(1), ' by ', dimensions(2)
                        stop 6
                    end if
                end if
                call random_number(array)
            rank default
                write (unit=error_unit, fmt='(A, I0, A)') &
                    '# error: rank ', rank(array), ' not supported'
                stop 4
        end select
    end subroutine generate_random_array

end module linalg_mod

In file generate_array.f90

generate_array.f90
program generate_array
    use, intrinsic :: iso_fortran_env, only : error_unit, DP => REAL64
    use :: linalg_mod, only : write_array, generate_random_array
    implicit none
    integer, dimension(:), allocatable :: dimensions
    real(kind=DP), dimension(:), allocatable :: vector
    real(Kind=DP), dimension(:, :), allocatable :: matrix

    call get_arguments(dimensions)
    if (size(dimensions) == 1) then
        call generate_random_array(vector, dimensions)
        call write_array(vector)
        deallocate (vector)
    else if (size(dimensions) == 2) then
        call generate_random_array(matrix, dimensions)
        call write_array(matrix)
        deallocate (matrix)
    else
        write (unit=error_unit, fmt='(A)') '# error: only rank 1 and 2 are supported'
    end if

contains

   subroutine get_arguments(dimensions)
        implicit none
        integer, dimension(:), allocatable, intent(out) :: dimensions
        integer :: rank, status, i
        character(len=1024) :: buffer, msg
        
        rank = command_argument_count()
        if (rank < 1) then
            write (unit=error_unit, fmt='(A)') '# error: rank should be at least 1'
            stop 10
        end if
        allocate (dimensions(rank), stat=status)
        if (status /= 0) then
            write (unit=error_unit, fmt='(A)') '# error: can not allocate dimensions'
            stop 11
        end if

        do i = 1, rank
            call get_command_argument(i, buffer)
            read (buffer, fmt=*, iostat=status, iomsg=msg) dimensions(i)
            if (status /= 0) then
                write (unit=error_unit, fmt='(2A)') '# error: ', trim(msg)
                stop 11
            end if
        end do
    end subroutine get_arguments

end program generate_array

In solve_equations.f90

solve_equations.f90
program main
    use, intrinsic :: iso_fortran_env, only : error_unit, DP => REAL64
    use :: linalg_mod, only : read_array, write_array
    implicit none
    ! Made info an integer because the LAPACK routine DGESV uses an explicit
    ! interface.  In the explicit interface, the argument info is an integer.
    integer :: nr_eqns, status, info
    character(len=1024) :: matrix_file_name, vector_file_name
    ! The following line no longer works because the LAPACK routine DGESV uses
    ! an explicit interface.  In the explicit interface, the argument info
    ! is an integer, but in the implicit interface, it is a character(len=1024).
    !character(len=1024) :: matrix_file_name, vector_file_name, info
    real(kind=DP), dimension(:), allocatable :: b
    integer, dimension(:), allocatable :: pivot
    real(kind=DP), dimension(:, :), allocatable :: A
    !external :: dgesv ! not needed in gfortran and throws an error

    ! Explicit interface for the LAPACK routine DGESV
    interface
        subroutine dgesv(n, nrhs, a, lda, ipiv, b, ldb, info)
            import :: DP
            implicit none
            ! .. Scalar Arguments ..
            integer, intent (Out)             :: info
            integer, intent (In)              :: lda, ldb, n, nrhs
            ! .. Array Arguments ..
            real (Kind=dp), intent (Inout)    :: a(lda, *), b(ldb, *)
            integer, Intent (Out)             :: ipiv(n)
        end subroutine dgesv
    end interface

    call get_arguments(nr_eqns, matrix_file_name, vector_file_name)

    allocate (A(nr_eqns, nr_eqns), stat=status)

    if (status /= 0) then
        write (unit=error_unit, fmt='(A)') '#error: can not allocate A'
        stop 15
    end if

    call read_array(matrix_file_name, A, [ nr_eqns, nr_eqns ])

    allocate (b(nr_eqns), stat=status)

    if (status /= 0) then
        write (unit=error_unit, fmt='(A)') '#error: can not allocate b'
        stop 15
    end if

    call read_array(vector_file_name, b, [ nr_eqns ])

    allocate (pivot(size(b)), stat=status)

    if (status /= 0) then
        write (unit=error_unit, fmt='(A, I0)') '# error: can not allocate pivot of size ', size(b)
        stop 12
    end if

    call dgesv(size(A, 2), 1, A, size(A, 1), pivot, b, size(b), info)
    
    call write_array(b)

    deallocate (A, b, pivot)

contains

   subroutine get_arguments(nr_eqns, matrix_file_name, vector_file_name)
        implicit none
        integer, intent(out) :: nr_eqns
        character(len=*), intent(out) :: matrix_file_name, vector_file_name
        integer :: status
        character(len=1024) :: buffer, msg
        
        if (command_argument_count() /= 3) then
            write (unit=error_unit, fmt='(A)') &
                '# error: argument nr_eqns, matrix_file_name, vector_file_name'
            stop 10
        end if

        call get_command_argument(1, buffer)
        read (buffer, fmt=*, iostat=status, iomsg=msg) nr_eqns
        if (status /= 0) then
            write (unit=error_unit, fmt='(2A)') '# error: ', trim(msg)
            stop 11
        end if

        call get_command_argument(2, matrix_file_name)
        call get_command_argument(3, vector_file_name)
    end subroutine get_arguments

end program main

The above programs are compiled and run using Fortran Package Manager (fpm):

Build the Program using FPM (Fortran Package Manager)

import os
root_dir = ""
root_dir = os.getcwd()

Since the code makes use of the LAPACK library, the following FPM configuration file (fpm.toml) was used:

fpm.toml
name = "Section_BLAS_LAPACK_Linear_Equations"

[build]
auto-executables = true
auto-tests = true
auto-examples = true
link = ["blas", "lapack"]

[install]
library = false

[[executable]]
name="solve_equations"
source-dir="app"
main="solve_equations.f90"
code_dir = root_dir + "/" + "Fortran_Code/Section_BLAS_LAPACK_Linear_Equations"
os.chdir(code_dir)

The files solve_equations.f90 and generate_array.f90 were placed into the “app” folder, while the file linalg_mod.f90 was placed into the “src” folder.

build_status = os.system("fpm build 2>/dev/null")

Run the Program using FPM (Fortran Package Manager)

Solve a Test Linear System of Two Equations

As our first run, we wish to solve the following set of linear equations:

2x+8y=20x+2y=4\begin{align*} 2x+8y & = 20 \\ x+2y & = 4 \end{align*}

The variables in the equations are converted into components of the x\mathbf{x} vector as shown below:

2x1+8x2=20x1+2x2=4\begin{align*} 2x_1+8x_2 & = 20 \\ x_1+2x_2 & = 4 \end{align*}

These equations are converted into matrix form as shown below:

[2812][x1x2]=[204]\left[ \begin{array}{cc} 2 & 8 \\ 1 & 2 \\ \end{array} \right] \left[ \begin{array}{c} x_1 \\ x_2 \\ \end{array} \right] = \left[ \begin{array}{c} 20 \\ 4 \\ \end{array} \right]

Therefore we have the following:

A=[2812]\mathbf{A} = \left[ \begin{array}{cc} 2 & 8 \\ 1 & 2 \end{array} \right]
x=[x1x2]\mathbf{x} = \left[ \begin{array}{c} x_1 \\ x_2 \end{array} \right]
b=[204]\mathbf{b} = \left[ \begin{array}{c} 20 \\ 4 \end{array} \right]

The matrix A\mathbf{A} and the vector b\mathbf{b} are written into text files as shown below:

%%writefile A_test1.txt
2 8
1 2
Overwriting A_test1.txt
%%writefile b_test1.txt
20
4
Overwriting b_test1.txt

The solve_equations program can now be run with the number of equations command line argument set to 2, and the files A_test1.txt and b_test1.txt

exec_status = \
    os.system("fpm run solve_equations 2>/dev/null -- 2 A_test1.txt b_test1.txt")
         -2.000000000000000
          3.000000000000000

The results are printed in scientfic notation and in the order of x1x_1, x2x_2.

We now wish to use Python’s Numpy library to test these results:

import numpy as np

A = np.genfromtxt("A_test1.txt")
b = np.genfromtxt("b_test1.txt")
x = np.linalg.solve(A, b)
print("x1 = {0:2.1f}".format(x[0]))
print("x2 = {0:2.1f}".format(x[1]))
x1 = -2.0
x2 = 3.0

We can see that the Fortran code and Numpy produce the same results.

Solve a Test Linear System of Equations of Arbitrary Size

The Fortran code can be used to solve an arbitrarily large system of equations. To test this functionality, we make use of the generate_array.f90 program to generate arrays or matrices of arbitrary size filled with random numbers.

As a start, we will use the generate_array.f90 to generate a matrix file A_test2.txt that contains a 10x10 matrix.

exec_status = os.system("fpm run generate_array 2>/dev/null -- 10 10 > A_test2.txt") 

The A\mathbf{A} matrix is shown below:

import pandas as pd
A = pd.read_table("A_test2.txt", 
    header=None, 
    sep='\s+')
A
Loading...

And now we generate the b\mathbf{b} vector:

exec_status = os.system("fpm run generate_array 2>/dev/null -- 10 > b_test2.txt") 

The b\mathbf{b} vector is shown below:

b = pd.read_table("b_test2.txt", 
    header=None, 
    sep='\s+')
b
Loading...

We now use the solve_equation Fortran code to solve this linear system of equations:

exec_status = \
    os.system("fpm run solve_equations 2>/dev/null -- 10 A_test2.txt b_test2.txt")
          0.606580074505919
          0.262848995025362
         -3.184160209474974
          1.687834826210936
         -0.841646660233418
          0.736318982199287
          0.406982190149781
          0.765997586104040
          2.013844506119154
         -1.038783919070161

And the results are compared to the output of Numpy:

A = np.genfromtxt("A_test2.txt")
b = np.genfromtxt("b_test2.txt")
x = np.linalg.solve(A, b)

for i in range(len(x)):
    print ("x{0:d} = {1:2.6f}".format(i+1, x[i]))
x1 = 0.606580
x2 = 0.262849
x3 = -3.184160
x4 = 1.687835
x5 = -0.841647
x6 = 0.736319
x7 = 0.406982
x8 = 0.765998
x9 = 2.013845
x10 = -1.038784

Again, we see that the results are the same.