%pip install qiskit

import math, cmath
from typing import List

Matrix = List[List[complex]]

def create_empty_matrix(rows, columns):
    # Create an empty matrix filled with 0s
    matrix = [[0] * columns for _ in range(rows)]

    return matrix


def matrix_add(a, b):
    # Get the size of the matrices
    rows = len(a)
    columns = len(a[0])

    # Create an empty matrix to store the result
    c = create_empty_matrix(rows, columns)

    # Perform element-wise addition of matrices
    for i in range(rows):
        for j in range(columns):
            c[i][j] = a[i][j] + b[i][j]

    return c


A = [[1, 2, 3],
     [4, 5, 6]]

B = [[7, 8, 9],
     [10, 11, 12]]

result = matrix_add(A, B)
print(result)
# Output: [[8, 10, 12],
#          [14, 16, 18]]


def scalar_mult(x, a):
    # Get the size of the matrix
    rows = len(a)
    columns = len(a[0])

    # Create a new matrix to store the result
    result = create_empty_matrix(rows, columns)

    # Perform scalar multiplication
    for i in range(rows):
        for j in range(columns):
            # Access element of the matrix
            element = a[i][j]

            # Compute the scalar multiplication and store it in the result matrix
            result[i][j] = x * element

    return result


x = 2
A = [[1, 2, 3],
     [4, 5, 6]]

result = scalar_mult(x, A)
print(result)
# Output: [[2, 4, 6],
#          [8, 10, 12]]


def matrix_mult(a, b):
    # Get the dimensions of matrices a and b
    rows_a = len(a)
    cols_a = len(a[0])
    rows_b = len(b)
    cols_b = len(b[0])

    # Check if the matrices can be multiplied
    if cols_a != rows_b:
        raise ValueError("Matrices cannot be multiplied. Invalid dimensions.")

    # Create a new matrix to store the result
    result = create_empty_matrix(rows_a, cols_b)

    # Perform matrix multiplication
    for i in range(rows_a):
        for j in range(cols_b):
            for k in range(cols_a):
                # Access elements from matrices a and b
                element_a = a[i][k]
                element_b = b[k][j]

                # Compute the product and accumulate it in the result matrix
                result[i][j] += element_a * element_b

    return result


A = [[1, 2, 3],
     [4, 5, 6]]

B = [[7, 8],
     [9, 10],
     [11, 12]]

result = matrix_mult(A, B)
print(result)
# Output: [[58, 64],
#          [139, 154]]


def determinant(a):
    # Get the dimension of the matrix
    n = len(a)

    # Check if the matrix is square
    if n != len(a[0]):
        raise ValueError("Matrix must be square.")

    # Base case: for a 1x1 matrix, return the single element as the determinant
    if n == 1:
        return a[0][0]

    # Recursive case: calculate the determinant using cofactor expansion
    det = 0

    for j in range(n):
        # Create a submatrix without the first row and the j-th column
        submatrix = [[a[i][col] for col in range(n) if col != j] for i in range(1, n)]

        # Calculate the determinant of the submatrix recursively
        sub_det = determinant(submatrix)

        # Calculate the cofactor by multiplying the element with (-1)^(1+j)
        cofactor = (-1) ** (1 + j) * sub_det

        # Accumulate the cofactors to compute the determinant
        det += a[0][j] * cofactor

    return det


def matrix_inverse(a):
    # Get the dimensions of the matrix
    n = len(a)

    # Check if the matrix is square
    if n != len(a[0]):
        raise ValueError("Matrix must be square.")

    # Calculate the determinant of the matrix
    det = determinant(a)

    # Check if the matrix is invertible (non-zero determinant)
    if det == 0:
        raise ValueError("Matrix is not invertible.")

    # Create an empty matrix to store the inverse
    inverse = create_empty_matrix(n, n)

    # Calculate the matrix of minors
    for i in range(n):
        for j in range(n):
            # Create a submatrix without the i-th row and j-th column
            submatrix = [[a[row][col] for col in range(n) if col != j] for row in range(n) if row != i]

            # Calculate the determinant of the submatrix
            minor = determinant(submatrix)

            # Calculate the cofactor by multiplying the determinant with (-1)^(i+j)
            cofactor = (-1) ** (i + j) * minor

            # Calculate the element of the inverse matrix
            inverse[j][i] = cofactor / det

    return inverse


A = [[1, 3, 3],
     [4, 5, 6],
     [7,8,9]]



result = matrix_inverse(A)
print(result)

def transpose(a):
    # Get the dimensions of the matrix
    rows = len(a)
    columns = len(a[0])

    # Create an empty matrix to store the transpose
    transposed = create_empty_matrix(columns, rows)

    # Fill in the transposed matrix with elements from the original matrix
    for i in range(rows):
        for j in range(columns):
            transposed[j][i] = a[i][j]

    return transposed


A = [[1, 2, 3],
     [4, 5, 6],
     [7, 8, 9]]

A_transposed = transpose(A)
print(A_transposed)
# Output: [[1, 4, 7],
#          [2, 5, 8],
#          [3, 6, 9]]


def conjugate(a):
    # Get the dimensions of the matrix
    rows = len(a)
    columns = len(a[0])

    # Create an empty matrix to store the conjugate
    conjugated = create_empty_matrix(rows, columns)

    # Fill in the conjugated matrix with conjugates of elements from the original matrix
    for i in range(rows):
        for j in range(columns):
            element = a[i][j]
            conjugated[i][j] = complex(element.real, -element.imag)

    return conjugated


A = [[1+2j, 2+3j],
     [3+4j, 4+5j]]

A_conjugated = conjugate(A)
print(A_conjugated)
# Output: [[1-2j, 2-3j],
#          [3-4j, 4-5j]]


def adjoint(a):
    # Get the dimensions of the matrix
    rows = len(a)
    columns = len(a[0])

    # Create an empty matrix to store the adjoint
    adjointed = create_empty_matrix(columns, rows)

    # Fill in the adjointed matrix with conjugates of elements from the original matrix
    for i in range(rows):
        for j in range(columns):
            element = a[i][j]
            adjointed[j][i] = complex(element.real, -element.imag)

    return adjointed


A = [[1+2j, 2+3j],
     [3+4j, 4+5j]]

A_adjointed = adjoint(A)
print(A_adjointed)
# Output: [[1-2j, 3-4j],
#          [2-3j, 4-5j]]


def inner_prod(v, w):
    # Check if the vectors have the same length
    if len(v) != len(w):
        raise ValueError("Vectors must have the same length")

    inner_product = 0
    for i in range(len(v)):
        inner_product += v[i] * w[i].conjugate()

    return inner_product


v = [1+2j, 3+4j, 5+6j]
w = [7+8j, 9+10j, 11+12j]

result = inner_prod(v, w)
print(result)



import math

def normalize(v):
    # Calculate the magnitude of the vector
    magnitude = math.sqrt(sum(abs(element)**2 for element in v))

    # Check if the magnitude is zero
    if magnitude == 0:
        raise ValueError("Cannot normalize a zero vector")

    # Divide each element of the vector by the magnitude
    normalized = [element / magnitude for element in v]

    return normalized


v = [1+2j, 3+4j, 5+6j]

normalized_v = normalize(v)
print(normalized_v)


def outer_prod(v, w):
    # Get the lengths of the vectors
    len_v = len(v)
    len_w = len(w)

    # Create an empty matrix to store the outer product
    matrix = [[0] * len_w for _ in range(len_v)]

    # Calculate the outer product
    for i in range(len_v):
        for j in range(len_w):
            matrix[i][j] = v[i] * w[j]

    return matrix


v = [1+2j, 3+4j, 5+6j]
w = [7+8j, 9+10j, 11+12j]

result = outer_prod(v, w)
print(result)



def tensor_prod(A, B):
    # Get the dimensions of matrices A and B
    rows_A, cols_A = len(A), len(A[0])
    rows_B, cols_B = len(B), len(B[0])

    # Calculate the dimensions of the resulting tensor product matrix
    rows_result = rows_A * rows_B
    cols_result = cols_A * cols_B

    # Create an empty matrix to store the tensor product
    result = [[0] * cols_result for _ in range(rows_result)]

    # Calculate the tensor product
    for i in range(rows_A):
        for j in range(cols_A):
            for m in range(rows_B):
                for n in range(cols_B):
                    result[i * rows_B + m][j * cols_B + n] = A[i][j] * B[m][n]

    return result


A = [[1+2j, 3+4j], [5+6j, 7+8j]]
B = [[9+10j, 11+12j], [13+14j, 15+16j]]

result = tensor_prod(A, B)
print(result)

import numpy as np

def eigenvalue_eigenvector(A):
    # Convert the matrix A to a numpy array
    A = np.array(A)

    # Calculate the eigenvalues and eigenvectors using numpy's eig function
    eigen_vals, eigen_vecs = np.linalg.eig(A)

    # Return the eigenvalues and eigenvectors as a tuple
    return eigen_vals, eigen_vecs


A = [[1, 2], [3, 4]]

eigen_vals, eigen_vecs = eigenvalue_eigenvector(A)
print("Eigenvalues:", eigen_vals)
print("Eigenvectors:")
for i, vec in enumerate(eigen_vecs.T):
    print("Eigenvalue:", eigen_vals[i])
    print("Eigenvector:", vec)


