# Use Hardware-Efficient Algorithm to Solve Systems of Complex-Valued Linear Equations

This example shows how to solve systems of complex-valued linear equations using hardware-efficient MATLAB® code in Simulink® using a systolic array. The model used in this example shows a hardware-efficient method of solving the system of simultaneous equations

where A is an m-by-n complex matrix, X is an n-by-p complex matrix, and B is an m-by-p complex matrix.

### Open Model

Open the `fi_complex_mldivide_systolic_array_model`

model.

```
model = 'fi_complex_mldivide_systolic_array_model';
open_system(model)
```

The enabled subsystems that send the data contain MATLAB Function blocks that keep track of which input to send next, and send the rows of matrices A and B to block **4x4 Complex CORDIC Matrix Left Divide** when the `ready`

signal is high. If you send data when `ready`

is high, you will not invalidate any data already in the pipeline.

The algorithm overwrites matrix A with an upper-triangular matrix R. The algorithm overwrites B with C = Q'B where Q is unitary and QR = A. The algorithm uses back-substitution on the upper-triangular matrix equation

To examine the algorithm, look Under the mask of block **4x4 Complex CORDIC Matrix Left Divide**.

### Define Parameters

Define complex matrices A and B in the base workspace. In this example, matrix A must be 4-by-4, and matrix B must be 4-by-p, where p is the number of right-hand sides.

```
rng('default');
A = complex(randn(4,4),randn(4,4));
B = complex(randn(4,1),randn(4,1));
```

The method uses the CORDIC algorithm, so you must also specify the number of iterations of the CORDIC kernel in the `NumberOfCORDICIterations`

parameter, or on the block parameters of the **4x4 Complex CORDIC Matrix Left Divide** block.

When A and B are double-precision floating-point data types, set the number of CORDIC iterations to the number of bits in the mantissa of a double. If the inputs are fixed point, then the number of CORDIC iterations must be less than the word length. The accuracy of the computation improves one bit for each iteration, up to the word length of the inputs. This model will work with fixed-point, double, and single data types.

NumberOfCORDICIterations = 52;

You must also instantiate variable `BackSubstitutePrototype`

to specify the data-type used in back-substitution, or enter a prototype value on the block parameters of the "4x4 Complex CORDIC Matrix Left Divide" block. In this case, matrices A and B are double, so set `BackSubstitutePrototype`

value to be a double. This variable is used by the `cast`

function to cast the back-substitute variables using the 'like' syntax.

BackSubstitutePrototype = 0;

### Simulate Model

Turn off expected warnings and simulate the model.

warning_state = warning('off','Coder:builtins:ConstantFoldingOverFlow'); sim(model)

After simulation, the model returns matrix X, which is the solution to the matrix equation

Verify the results by checking that AX-B is a small value.

err = norm(A*X - B)

err = 6.5534e-15

### QR Algorithm

The CORDIC algorithm is used to compute the QR decomposition as in the examples Perform QR Factorization Using CORDIC, and Implement Hardware-Efficient QR Decomposition Using CORDIC in a Systolic Array.

This example is of a 4-by-4 matrix A. You can tile the blocks inside this model to build up to any size matrix.

To see the QR algorithm, look under the mask for block **Q'B, R 4x4 Complex CORDIC Systolic Array**.

### Complex QR

Because the data in this example is complex, an additional step is required to zero out the imaginary parts of the pivots to make them real-valued. This is done by splitting the data into real and imaginary parts and using CORDIC to zero out the imaginary part as if it were two rows of a real matrix. This is equivalent to the complex multiplication

where

and

except that the computation is done using the CORDIC algorithm without squares or square-roots.

To see the algorithm for , look under the mask for block **Rotate first element to real**.

### Back-Substitute

Finally, to compute X, compute the reciprocals of the diagonal elements of R and back-substitute into the right-hand side C. The algorithm uses a CORDIC divide implementation to compute the reciprocals.

To see the back-substitute algorithm, look under the mask for block **Back Substitute 4x4 Complex CORDIC**.

### Matrix Inverse

It is usually unnecessary to explicitly compute the inverse of a matrix [3],[5]. However, if you want to do so, set B equal to the identity matrix I. Then, the solution of the equation

equals

A = complex(2*rand(4,4)-1,2*rand(4,4)-1); B = complex(eye(4)); NumberOfCORDICIterations = 52; BackSubstitutePrototype = 0;

Simulate the model

sim(model)

Verify that AX and XA are close to the identity matrix. There will be small differences due to round-off errors.

A*X

ans = 1.0000 + 0.0000i 0.0000 - 0.0000i 0.0000 + 0.0000i 0.0000 - 0.0000i 0.0000 - 0.0000i 1.0000 + 0.0000i -0.0000 + 0.0000i -0.0000 + 0.0000i -0.0000 + 0.0000i 0.0000 - 0.0000i 1.0000 + 0.0000i 0.0000 + 0.0000i 0.0000 + 0.0000i 0.0000 - 0.0000i 0.0000 - 0.0000i 1.0000 - 0.0000i

X*A

ans = 1.0000 - 0.0000i -0.0000 - 0.0000i -0.0000 + 0.0000i 0.0000 - 0.0000i 0.0000 + 0.0000i 1.0000 + 0.0000i 0.0000 - 0.0000i 0.0000 + 0.0000i 0.0000 + 0.0000i 0.0000 + 0.0000i 1.0000 - 0.0000i -0.0000 + 0.0000i 0.0000 - 0.0000i -0.0000 + 0.0000i -0.0000 + 0.0000i 1.0000 + 0.0000i

### Fractional Scaling

Normalizing to fractional types makes the computations easier in fixed-point. You can scale the matrices so that their data is in the range [-1, +1] and use fractional fixed-point types because the solution to the matrix equation

is the same as the solution to

To convert to fractional scaling with no additional cost in generated code, you can use the `reinterpretcast`

function in MATLAB or the Data Type Conversion block in Simulink with **Input and output to have equal Stored Integer**.

### Run Exhaustive Test Points

You can run many test inputs through the model by making A and B three-dimensional arrays.

m = 4; n = 4; p = 1; n_test_inputs = 100;

Create the inputs such that the real and imaginary parts are in the range [-1, +1]

A = complex(2*rand(m,n,n_test_inputs)-1, 2*rand(m,n,n_test_inputs)-1); B = complex(2*rand(m,p,n_test_inputs)-1, 2*rand(m,p,n_test_inputs)-1);

In this example, matrices A and B contained real and imaginary parts already in the range [-1, +1], so set the types to be fractional.

data_word_length = 24; data_fraction_length = 23;

### QR Data Types

The growth in the elements of R in the real QR factorization is (see Perform QR Factorization Using CORDIC). The elements are complex in this example, so an additional growth factor of is needed because

Also, the CORDIC algorithm grows intermediate values by the following gain factor where is the number of CORDIC iterations before it is normalized out.

Therefore, an upperbound for growth in the QR algorithm for m-by-n complex matrix A is the product of all the growth factors:

It this example, A is 4-by-4, so the maximum growth factor in the QR algorithm is

Therefore, the number of additional integer bits to allow in the QR algorithm to avoid overflow when you have m=4 rows is:

qr_growth_bits = ceil(log2(1.6468*sqrt(2*m)))

qr_growth_bits = 3

### Cast Matrices A and B to Fixed Point

The model requires that the inputs be signed and the word length of B be the same as the word length of A.

Grow the data wordlength to accommodate the QR growth.

qr_input_word_length = data_word_length + qr_growth_bits

qr_input_word_length = 27

Cast A to fixed point and B to A's type.

T.A = fi([], 1, qr_input_word_length, data_fraction_length); T.B = T.A; A = cast(A,'like',T.A); B = cast(B,'like',T.B);

### Back-Substitute Data Type

If is an -by- invertible complex matrix, and is the solution of matrix equation , then using properties of vector and matrix norms (see, for example, reference [6]), it can be shown that

where and is the smallest singular value of . This bound is related to the condition number of .

In this example, is a complex vector with the real and imaginary parts bounded by 1. Therefore, .

Therefore, if you know the distribution of singular values for the matrices in your problem, then you can choose the number of additional integer bits required to avoid overflow in the back-substitute with a given probability (see, for example, reference [1]).

In this example, we compute the singular values for all the matrices in our test bench and choose the number of integer bits to avoid overflow based on that.

singular_values = zeros(n,n_test_inputs); A0 = double(A); for k = 1:n_test_inputs singular_values(:,k) = svd(A0(:,:,k)); end condition_numbers = singular_values(1,:)./singular_values(end,:); x_bound = sqrt(2*n)./singular_values(end,:);

The number of bits of growth to add is the base-2 logarithm of the maximum bound.

integer_bits_for_backsubstitute = ceil(log2(max(x_bound)))

integer_bits_for_backsubstitute = 7

Subtract the required integer bits for back-substitute from the wordlength, and an additional bit for the sign bit.

backsubstitute_fraction_length = T.A.WordLength - integer_bits_for_backsubstitute - 1

backsubstitute_fraction_length = 19

Therefore, the data type for the back-substitute is:

BackSubstitutePrototype = fi(0, 1, T.A.WordLength, backsubstitute_fraction_length)

BackSubstitutePrototype = 0 DataTypeMode: Fixed-point: binary point scaling Signedness: Signed WordLength: 27 FractionLength: 19

Set the number of CORDIC iterations to be one less than the fixed-point word length of A.

NumberOfCORDICIterations = T.A.WordLength - 1

NumberOfCORDICIterations = 26

Simulate the model with fixed-point inputs.

sim(model)

### Calculate and Plot the Errors

A measure of error is

norm(AX - B)

for each pair of inputs A and B.

norm_error = zeros(1,size(X,3)); B0 = double(B); X0 = double(X); for k = 1:size(X,3) norm_error(k) = norm(A0(:,:,k)*X0(:,:,k) - B0(:,:,k)); end

Plot the errors. The errors are low because of the data types chosen. The errors are typically higher when the condition number of matrix A is high, as the theory predicts.

figure(1) clf h1 = subplot(2,1,1); plot(norm_error) grid on title('Errors') ylabel('norm(AX - B)') h2 = subplot(2,1,2); plot(condition_numbers) grid on title('Condition numbers') ylabel('cond(A)') xlabel('Test points') linkaxes([h1,h2],'x')

Re-enable warnings

warning(warning_state);

### References

[1] Zizhong Chen and Jack J. Dongarra. "Condition Numbers of Gaussian Random Matrices". SIAM Journal on Matrix Analysis and Applications. 27.3 (July 2005), pp. 603-620.

[2] George E. Forsythe and Cleve B. Moler. Computer Solution of Linear Algebraic Systems. Englewood Cliffs, N.J.: Prentice-Hall, 1967.

[3] George E. Forsythe, M.A. Malcom and Cleve B. Moler. Computer Methods for Mathematical Computations. Englewood Cliffs, N.J.: Prentice-Hall, 1977.

[4] Cleve B. Moler. Cleve's Corner: What is the Condition Number of a Matrix?>, The MathWorks, Inc. 2017.

[5] Cleve B. Moler. Numerical Computing with MATLAB. SIAM, 2004. isbn: 978-0-898716-60-3.

[6] Gene H. Golub and Charles F. Van Loan. Matrix Computations. The Johns Hopkins University Press.

```
%#ok<*NOPTS,*NASGU>
```