<jupyter_start><jupyter_text>Chapter 1: Math Tools I (Vectors, Complex Numbers and Dirac Notation) The aim of this chapter is not to explain the basics of quantum information theory or quantum mechanics. It is rather directed towards introducing the language that both of these fields are built on, linear algebra. Both Chapter 1 and Chapter 2 attempt to establish the mathematical foundation necessary to keep up with this (fairly introductory) series. The module that will be used to demonstrate applications to Python is  Numpy.**Note:** While I do attempt to survey these topics briefly enough for one to grasp everything else mentioned in throughout the 8 Chapters, a solid understanding of both vector and matrix algebra is much advised.       1.1 Complex Numbers  A complex number is a number which takes the following form:    \begin{equation}  c = a + ib  \end{equation}  where $a$ and $b$ are real numbers and $i = \sqrt{-1}$. This type of number represents a real component ($Re(c) = a)$ and an imaginary component ($Im(c) = b$).     One can say that a complex number behaves exactly like a 2D vector, which raises interesting properties such as the ability to describe a complex number as a point in a 2D complex plane (_i.e_ a plane where the x-axis represents $a$, and the y-axis represents $ib$).     For example, let us say we have the number, $c = a+bi$. That could be represented by the following:        Another thing one can notice is that both real numbers and imaginary numbers are special cases of complex numbers. A real number is where $b = 0$ and an imaginary is where $a = 0$     Properties of a complex number    A complex number has two essential properties, Its length and its angle. Looking at the figure above, it should be easy to see that the length of a complex number is $|c| =  \sqrt{a^2 + b^2}$ (using the pythagorean theorem).    The angle formed with the x-axis (also called the argument) can be obtained using basic trigonometry. One would find that its value is $\theta = \tan^{-1}(b/a)$.      Operations on complex numbers    Let's say we have 2 complex numbers, $c_1 = a + ib $ and $c_2 = d + ie$, and we want to apply basic mathematical operations on these two numbers. One would do that via the following methods:     **Addition:**    $c_3 = c_1 + c_2$   $c_3 = a + ib + d + ie$   $c_3 = (a+d) + i(b+e)$    **Subtraction:**    $c_3 = c_1 - c_2$   $c_3 = a + ib - d - ie$   $c_3 = (a-d) + i(b-e)$     **Multiplitcation:**    $c_3 = c_1 * c_2$   $c_3 = (a + ib) * (d + ie)$   $c_3 = ad + iae + ibd -be$   $c_3 = (ad-be) + i(ae+bd)$     **Division:**    $c_3 = c_1 / c_2$   $c_3 = \frac{a + ib}{d + ie}$   $c_3 = \frac{a + ib}{d + ie} \frac{d - ie}{d - ie}$    $c_3 = \frac{(ad+be) + i(ae-bd)}{d^2 + e^2}$        Python Implementation<jupyter_code>#Importing numpy, a module very useful for all things math!
import numpy as np

#Python syntax for 1+2i
c1 = complex(1,2)

#Getting length and angle
length_c1 = abs(c1)
angle_c1 = np.angle(c1)

#Real and imaginary components

Re = c1.real
Im = c1.imag

print("The Length and Angle of " + str(c1), "is", str(length_c1), "and", str(angle_c1))
print('Re: ', Re)
print('Im: ', Im)
#Operation on 2 complex numbers

c1 = complex(1,2)
c2 = complex(3,4)

add = c1 + c2
sub = c1 - c2
mult = c1*c2
div = c1/c2

print("Add:", str(add))
print("Subtract:", str(sub))
print("Multiply:", str(mult))
print("Divide:", str(div))<jupyter_output>Add: (4+6j)
Subtract: (-2-2j)
Multiply: (-5+10j)
Divide: (0.44+0.08j)<jupyter_text>1.2 Complex Conjugation The concept of a complex conjugate is very simple, yet it seems to have a very important role in the usage of complex numbers. All the complex conjugate does is it transforms $i$ to $-i$. For example, if we have a complex number $c = a + ib$, its conjugate will be $c^* =  a-ib$. Visually, this can be described as flipping a point in the complex plane upon the x-axis, as shown bellow:  Looking at the Figure above, one can see that $|c| = |c^*|$ and $\phi_c = - \phi_{c^*}$.      Rules of Conjugation  **Product of complex number and its conjugate:**     $cc^* = (a + ib)(a - ib)$   $cc^* = (a^2 + iab - iab + b^2)$   $cc^* = a^2 + b^2$   $cc^* = |c|^2$    **Product of Conjugates:**    $(c_1 c_2)^* = c_1 ^* c_2 ^*$    **Sum of Conjugates:**    $(c_1 + c_2)^* = c_1 ^* + c_2 ^*$    **Conjugate of Conjugates:**    $c^{**} = c$       Python Implementation<jupyter_code>#One can get the conjugate of a complex function using np.conj(). Let's say c2 = c1*

c1 = complex(1,1)
c2 = np.conj(c1)
print('c1: ', str(c1))
print('c1*: ', str(c2))

#Checking to see if product of complex number and conjugate is is length^2

cc_conj = c1*c2

if abs(cc_conj) == int(abs(c1)**2):
    print("Math works! c*c = |c|^2")


#Excercise: Test out other rules of conjugation<jupyter_output>c1:  (1+1j)
c1*:  (1-1j)
Math works! c*c = |c|^2<jupyter_text>1.3 Vector Space   Before continuing with the mathematical pre-requisites of basic Quantum Information Theory, allow me to be honest about something. Writing this chapter is very difficult for me. Not because of the high level of technicality required to write about such topics, but rather because I am sacrificing a lot of interesting and beautiful ideas in exchange of practicality and rapidity so that we can move on to the topic of interest on an "ASAP basis". The topics of vector spaces and abstract algebra are infinitely more deep and compelling than what I paint their picture to be, so as I said in the introduction, I highly advise looking at other resources. Ok, enough with my rant and back to let's get back to oversimplifying the math.There are several ways of defining a vector. One might introduce them as a Rank 1 Tensor, or as a quantity that has magnitude and direction, but for the sake of this chapter, we shall assume that you understand the basics and introduce it as an element of a vector space. A vector space is a collection of not only vectors, but a field of scalars and two operations, vector addition and scalar multiplication. The number of dimensions of a vector space can vary, and is based on the number of elements a vector in that space has. For instance, classically speaking, the position of a particle is usually described in a 3D vector-space with vector of form $\vec{r}(x,y,z)$, while relativistically speaking one would rather use what's called a four-vector to describe a particle's dynamics. A four-vector takes the following form: $\vec{R}(x,y,z,ct)$ or simply $\vec{R}(\vec{r},ct)$ (where $c$ is the speed of light and $t$ is time). This is what is meant by space-time. The reason I mention the relativistic case is to show that a vector space can also be a subspace of another vector space. In QIT, vector spaces will be used not to describe a particle's position, but rather the probabilities of qubits being in certain states. If this is not apparent now, it should be in Chapter 4. Operations**Vector Addition:** Vector addition is the proccess of adding the $i$th component of vectors (i.e $[x_1, x_2, x_3,...x_i] + [y_1,y_2,y_3,...y_i]$ = $[x_1 + y_1, x_2 + y_2,x_3 + y_3...x_i + y_i]$).  Given that u and v are vectors in vector space **V**,   1. $u + v$ is also a vector in **V**  2. $u + v = v + u$  3. $(u + v) + w = u + (w + v)$  4. There is a $\vec{0}$ vector in **V**, where $u + \vec{0} = u$  5. For every $u$ in **V**, there is another vector $-u$, where $u + (-u) = 0$    **Scalar Multiplication:** Scalar multiplication is the process of multiplying every item in a vector by a scalar.  (i.e $c*[x_1, x_2, x_3,...x_i]$ = $[c*x_1, c*x_2, c*x_3,...c*x_i]$).  Given that u and v are vectors in vector space **V** and c and d are  scalars in a field which is also in **V**,   1. $c*u$ is also a vector in **V**  2. $c(u + v) = cv + cu$  3. $c(du) = d(cu)$  4. $(c+d)u = cu + du$  5. $1(u) = u$   Linear combination   We just stated that in a vector space **V**, there are two operations that can be acted on vectors $u$ and $v$, vector addition (i.e $u+v$) and scalar multiplication (i.e $au$ or $bv$), where $a$ and $b$ are scalars. Any operation which takes the form $ua + bv$ is what is known as a linear combination. Linear combinations are very crucial to the topic of quantum information theory because it is essentially how one represents a superposition, but that will be further discussed later on.   Python Implementation<jupyter_code>#What not to do: represent a vector by a python list. 
#Let's see why:

print('List As a vector:')
notAVector1 = [1,2,3]
notAVector2 = [1,2,3]
scalar = 3

vecAdd = notAVector1 + notAVector2
scale = scalar*notAVector1
print("Doesn't satisfy the rules of vector addition: ", notAVector1, "+", notAVector2, "=", vecAdd)
print("Doesn't satisfy the rules of scalar multiplication: ", notAVector1, "*", scalar, "=", scale)


print('')

#What to do: Represent vectors as a 1D numpy array
#Let's see why:

print('1D Numpy array (np.array([vec])) as a vector:')
vector1 = np.array([1,2,3])
vector2 = np.array([1,2,3])
scalar  = 5
vecAdd = vector1 + vector2
scale = scalar*vector1

print("Satisfies the rules of vector addition: ", vector1, "+", vector2, "=", vecAdd)
print("Satisfies the rules of scalar multiplication: ", vector1, "*", scalar, "=", scale)


#Excercise: Use what we learned about vector spaces to build a python tool for complex numbers 
#(which can be represented by 2D vectors)<jupyter_output>List As a vector:
Doesn't satisfy the rules of vector addition:  [1, 2, 3] + [1, 2, 3] = [1, 2, 3, 1, 2, 3]
Doesn't satisfy the rules of scalar multiplication:  [1, 2, 3] * 3 = [1, 2, 3, 1, 2, 3, 1, 2, 3]

1D Numpy array (np.array([vec])) as a vector:
Satisfies the rules of vector addition:  [1 2 3] + [1 2 3] = [2 4 6]
Satisfies the rules of scalar multiplication:  [1 2 3] * 5 = [ 5 10 15]<jupyter_text>1.4 Basis Set       In the preceding sections, a vector was described as a set of coordinates in an $n$-dimensional vector space. However, That is not the only way to represent a vector. Another notable way to represent vectors is by viewing its components as scalars that act on what is called a set of unit vectors.       For example, let us take the vector $\vec{v}(a,b,c)$. Using the basic laws of vector addition and scalar multiplication, we can show that $\vec{v}$ can be written as the following linear combination:      \begin{equation}   \vec{v}(a,b,c) = a(1,0,0) + b(0,1,0) + c(0,0,1)    \end{equation}      where (1,0,0), (0,1,0), (0,0,1) are unit vectors. In 3 dimensions, unit vectors conventionally denoted by $\hat{i}$, $\hat{j}$ and $\hat{k}$, but it could of course extent to any $n$-dimensional vector space, $V$. Formally, a set of unit vectors is defined as an orthonormal basis of $R^n$ (an $n$-dimensional vector space where components can be any real number), but roughly speaking, they could be defined as any subset of $n$ vectors that are all orthogonal to eachother (ortho) and all have a length of 1 (normal). In $R^3$ (i.e the example given above), the unit vectors look like the following:  When first learning about unit vectors, one usually does the mistake of thinking that they are special in the sense that any vector could be written in terms of them. However, the truth is that they are nothing but a special case of a broader set of vectors which all possess the same quality, the quality of being a basis set. In vector space **V**, a subset of vectors are considered basis vectors if they satisfy the following:       1. Any vector in V can be written as a linear combination of the vectors in the basis set       2. No vector in the basis set can be expressed as a linear combination of the other vectors in the basis set       The only special quality that unit vectors possess is their general convenience in representing other vectors. Finding General Unit VectorsLet's say you have an arbitrary n-dimensional vector, $\vec{v}$, and you want to find a vector that has the same direction as $\vec{v}$ but has a length of 1 (i.e its unit vector). You would have to do what is called normalizing the vector, which could be done by scalar multiplying $v$ with the reciprocal of its length, $|v|$. According to the pythagorean theorem, the length (or *norm*) of a vector can be calculated by the following, \begin{equation}|v| = \sqrt{\sum_{i = 1}^{n} v_i ^2}\end{equation}or the square root of the sum of the square of its components.So in a 3 dimensional case, the unit vector of $\vec{v}$ would be\begin{equation}\hat{v} = \frac{\vec{v}(v_1,v_2,v_3)}{\sqrt{v_1 ^2 + v_2 ^2 + v_3 ^2}}\end{equation}A more general approach to find the norm of a vector is using a vector operation called the inner product, which for real vectors (i.e vectors that are in subspace $R^n$), can be written as the sum of the products of $i$-th components of the two vectors, or\begin{equation}\vec{v}.\vec{u} = \sum_{i = 1}^{n} v_i* u_i\end{equation}Notice that the first equation used to calculate the norm is essentially the square root of the inner product of a vector and itself, ergo $|v| = \sqrt{v.v}$ Inner Product of Complex Vectors In quantum computing, the main purpose of vectors is to represent the state of a qubit (or many qubits). One important thing to note before beginning quantum computing is that, for reasons that one might find very elegant, the formulation of quantum mechanics and concequently quantum computing is based on complex numbers and complex vectors rather than real numbers and vectors. A complex vector $\vec{v} \in C^N$ can be represented by the following\begin{equation}v = (v_1, v_2, v_3, ..., v_{n})\end{equation}where $v_i \in C$ (are complex numbers). The inner product of two vectors was previously defined to be the sum of the product of their corrersponding $i$th components. However, with complex vectors, a more general definition must be given, which is the following: \begin{equation}\vec{u}.\vec{v} = \sum_{i=1}^{n} u^*_i v_i\end{equation}where $\vec{u}$ and $\vec{v}$ are vectors and $u^*_i$ is the complex conjugate of the $i$th component of $\vec{u}$. One can see that the inner product of real vectors definition stated above is equivelent to the new, general one presented now because $a^* = a$, given that $a \in R$. Also, the norm of a complex vector is now \begin{equation}|v| = \sqrt{\sum_{i = 1}^{n} v_i^*v_i} = \sqrt{\sum_{i = 1}^{n} |v_i|^2 }\end{equation}Using the first rule of conjugation described in Section 1.2. In the next section, a more elegant and useful notation for dealing with complex vectors will be presented. One that is used ubiquitously throughout fields such as quantum mechanics, quantum field theory and most important to us, quantum information theory.   Python Implementation<jupyter_code>#Finding the Unit Vector of a vector, v

v = np.array([1,2,3])

norm = 1/np.sqrt(1**2 + 2**2 + 3**2)

unitV = norm*v

#Getting the length of unitV. If the math is right, it should be 1
lengthUnitV = np.array([i**2 for i in unitV]).sum()

#Results
print(unitV, 'is the normalized unit vector of v')

if lengthUnitV == 1:
    print('Math works! Unit vector length is 1')
    
    
#Excercise: Create a function that finds the unit vector of any given n-dimensional vector 
#Hint: generalize what was done to obtain the normalizing factor, norm


#Inner Product. It's the same numpy function for any set of two vectors

#v and u ∈ R^3

v = np.array([5,1,3])
u = np.array([4,2,5])

inner_r3 = np.inner(u,v)

print('Inner product of', v, ' and ', u, ' is ', inner_r3)

#v and u ∈ C^4

v = np.array([2j, 2+3j, 3, 0])
u = np.array([1+1j, 2+4j, 1, 1j])

inner_c4 = np.inner(u,v)

print('Inner product of', v, ' and ', u, ' is ', inner_c4)


#Excercise: Create a function that gets the norm of a  complex vector. Bonus if you don't use np.inner()

def normalize(vector):
    return unitVector<jupyter_output>Inner product of [5 1 3]  and  [4 2 5]  is  37
Inner product of [0.+2.j 2.+3.j 3.+0.j 0.+0.j]  and  [1.+1.j 2.+4.j 1.+0.j 0.+1.j]  is  (-7+16j)<jupyter_text>1.5 Dirac Notation     Anyone who has taken a course in physics knows that vectors are a very powerful tool for representing systems. In quantum mechanics and quantum computing, a vector is used to represent the state of a system (i.e a wavefunction/statevector). The vector notation that is generally used to describe such quantities is usually an idiosyncratic notation called Dirac Notation (or Bracket Notation). One might ask, "Why use a different notation? The ones used in the earlier sections and generally throughout math and physics seem fine enough. What benifit do we get out of using this new notation?" The reason Dirac Notation is used is because as you'll see in the later chapters, quantum states get messy to describe. Filled with hermition adjoints, tensor products, and hefty integrals and sums, it eventually gets difficult to keep track of everything that's going on. Dirac Notation does a good job of making it simpler to do all the manipulations required while explicitly separating the state from its dual (something we'll speak about later on).    There are two main components in Dirac Notation, a bra vector $\langle u|$ and a ket vector $|v\rangle$. In simple terms, a ket is represented by a column matrix, i.e    \begin{equation}  |u\rangle = \begin{pmatrix}u_1 \\\ u_2 \\\ \vdots \\\ u_n \end{pmatrix}  \end{equation}    and a bra is what is called the Hermitian Conjugate of a ket. Hermitian Conjugation is the act of taking the transpose of the complex conjugate of a matrix. While we have spoken about complex conjugation, we haven't yet defined what a transpose is. A transpose is an operation on a matrix that applies the following transformation:    \begin{equation}  M_{ij} \rightarrow M_{ji}  \end{equation}    where $M$ is a matrix and $i$ and $j$ represent the row and column index, respectively. Given that a ket vector is a column matrix, one can notice that the transpose of a ket vector is essentially a row matrix ($M_{1j} \rightarrow  M_{j1}$). And since a Hermitian conjugate, denoted by $M^{\dagger}$ does the following,    \begin{equation}  M^{\dagger} = M^{T*}  \end{equation}  one can find that a bra vector, $\langle u|$, takes the following form:    \begin{equation}  \langle u| = \begin{pmatrix} u_1^* & u_2^* & \ldots & u_n^* \end{pmatrix}  \end{equation}    So, in simple terms, a bra vector is a row matrix that contains the complex conjugates of the components of a ket matrix (which represents the state of the quantum system).    So we defined what a bra is, and what a ket is, but what is a bra(c)ket? Say we have vectors $\vec{u}$ and vectors $\vec{v}$, where both $\vec{u}$ and $\vec{v} \in C^n$, the bracket would be evaluated as follows:    \begin{equation}  \langle u|v\rangle = \begin{pmatrix} u_1^* & u_2^* & \ldots & u_n^* \end{pmatrix} * \begin{pmatrix}v_1 \\\ v_2  \\\ \vdots \\\ v_n \end{pmatrix}  \end{equation}  According to [matrix multiplication](https://www.khanacademy.org/math/precalculus/x9e81a4f98389efdf:matrices/x9e81a4f98389efdf:multiplying-matrices-by-matrices/v/multiplying-a-matrix-by-a-matrix), this expands to   \begin{equation}  \langle u|v\rangle = u_1^*v_1 + u_2^*v_2 + \ldots + u_n^*v_n  \end{equation}    Or more generally,  \begin{equation}  \langle u|v\rangle = \sum_{i=1}^n u_i^*v_i  \end{equation}    This result is interesting because, as shown in Section 1.4, this is essentially the inner product of vectors $\vec{u}$ and $\vec{v}$. From that, we can derive that the norm of vector $\vec{u}$ could be described by     \begin{equation}  |\vec{u}| = \sqrt{\langle u|u\rangle}  \end{equation}     Ket Algebra    Given that a ket is essentially just a representation of a vector, all the vector axioms and identities presented earlier apply in the same way. That is,      Ket Addition      1.  \begin{equation}  |\phi\rangle + |\psi\rangle = |\psi\rangle + |\phi\rangle  \end{equation}        2.  \begin{equation}  (|\psi\rangle + |\phi\rangle) + |\omega\rangle = (|\psi\rangle + |\omega\rangle) + |\phi\rangle  \end{equation}      Scalar Multiplication       1.  \begin{equation}  (c_1 + c_2)|\psi\rangle = c_1|\psi\rangle + c_2|\psi\rangle  \end{equation}                    2.  \begin{equation}  c_1(c_2)|\psi\rangle = c_2(c_1)|\psi\rangle  \end{equation}                        3.  \begin{equation}  (c_1)(|\psi\rangle + |\phi\rangle) = c_1|\psi\rangle + c_1|\phi\rangle  \end{equation}                  4.         \begin{equation}  (1)|\psi\rangle = |\psi\rangle  \end{equation}                       5.  \begin{equation}  |\psi\rangle + 0 = |\psi\rangle  \end{equation}     Application to Quantum Computing  As we'll speak about in more detail throughout the later chapters, a quantum computer's state is essentially a linear combination of two vectors, the $|0\rangle$ vector (which represents the 0 state) and the $|1\rangle$ vector (which represents the 1 state). As we stated before, a linear combination can be expressed as follows:  \begin{equation} |\psi\rangle = c_1|0\rangle + c_2|1\rangle  \end{equation}  where $c_1$ and $c_2$ $\in C$. The two constants represent a quantity called the amplitude, which when squared, gives the probability of getting the state associated with it. In other words, $c_1^2$ is the probability of getting a 0 and $c_2^2$ is the probability of getting a 1. And since there's a 100% probability of getting either a 0 or a 1, then the following must be true  \begin{equation} |c_1|^2 + |c_2|^2 = 1 \end{equation} This is essentially the language of quantum information theory, kets and amplitudes. Anything that changes the amplitudes is called an operator, but that's a lesson for another chapter.  Python Implementation<jupyter_code>#Defining a bra and a ket using np.matrix


#In np.matrix(), the elements of the matrix are inputed as a string a ";" creates a new row and a " " creates a new column

print('Case 1, Real Vectors:')
#If the vectors are real, then the complex conjugate of the vector is the vector itself. So the hermitan conj is just the transpose

ket = np.matrix('1 ; 2 ; 3 ; 4')
bra = np.matrix('1 2 3 4')

bracket = bra*ket

print('|u> = \n ', ket)
print('')
print('<u| = ', bra)
print('')
print('<u|u> =', int(bracket))

print('')
print('Case 2, Complex Vectors:')

#To apply a hermitian operator automatically, one can use np.matrix()'s function, getH(). For example,

ket = np.matrix(' 1j; 0 ; 3j ; 2')
bra = ket.getH()

bracket = bra*ket

print('')
print('|u> = \n ', ket)
print('')
print('<u| = ', bra)
print('')
print('<u|u> =', int(abs(bracket)))


#Excercise: Create a function that takes in 2 np.array inputs,turn one into a bra and other into a ket and evaluate the inner product<jupyter_output>Case 1, Real Vectors:
|u> = 
  [[1]
 [2]
 [3]
 [4]]

<u| =  [[1 2 3 4]]

<u|u> = 30

Case 2, Complex Vectors:

|u> = 
  [[0.+1.j]
 [0.+0.j]
 [0.+3.j]
 [2.+0.j]]

<u| =  [[0.-1.j 0.-0.j 0.-3.j 2.-0.j]]

<u|u> = 14<jupyter_text>1.6 Inner Product      The inner product of two vectors is something that was discussed briefly throughout the five previous sections but there are a few crucial ideas which haven't been discussed yet. As mentioned before, an inner product can be represented by a bra-ket, $\langle u|v\rangle$. The result of an inner product will always be a complex number, and the inner product must follow the following identities:          1. Linearity:       \begin{equation}\langle\psi|(a|\phi\rangle + b|\omega\rangle)  = a\langle\psi|\phi\rangle + b\langle\psi|\omega\rangle\end{equation}              2. Symmetry:       \begin{equation}\langle\psi|\phi\rangle = \langle\phi|\psi\rangle^*\end{equation}               3. Positivity:       \begin{equation}\langle\psi|\psi\rangle \geq 0 (|\psi\rangle \neq 0)\end{equation}Any vector space that both satisfies the axioms stated in the first few sections and is equipped with the inner product operation that satisfies the three identities stated above is called a Hilbert Space. In quantum mechanics, the wavefunction is a vector in an infinite dimensional Hilbert Space and in quantum information theory, a qubit's statevector is a vector in a 2D hilbert space (which takes the form [$c_1 c_2$], where $c_1$ and $c_2$ are the amplitudes described in Section 1.5).The purpose of vectors in quantum mechanics is to represent the amplitudes of every possible state in the system. The square of this amplitude vector represents the probability distribution of every possible states. It should be obvious that the sum of all probabilities should always equal 1, meaning that the sum (or more specifically the norm) of the amplitude vector squared ($|\psi^2|$) must equal 1. Using dirac notation, this could be expressed as \begin{equation}\langle\psi|\psi\rangle = 1\end{equation}Very often, one would find that a system's amplitude vector (i.e wavefunction/statevector) does not satisfy the relationship above, which is where the process of normalization, which was described in Section 1.4, comes into play. To normalize a vector (i.e set its norm to 1), one must scalar multiply $|\psi\rangle$ with the normalizing constant $\frac{1}{\sqrt{\langle\psi|\psi\rangle}}$, i.e\begin{equation}|\psi\rangle \rightarrow \frac{1}{\sqrt{\langle\psi|\psi\rangle}} |\psi\rangle\end{equation}       Another notable result one can obtain is the result of a bra-ket being zero. For instance, when\begin{equation}\langle u|v\rangle = 0\end{equation}then vectors $|u\rangle$ and $|v\rangle$ are orthogonal, or form a 90$^\circ$ angle with eachother<jupyter_code>#Defining bra, ket, and bracket where |u> = (i,0,3i,2)

ket = np.matrix(' 1j; 0 ; 3j ; 2')
bra = ket.getH()

bracket = bra*ket

print("|u> unnormalized:\n ", ket)
print('Norm:', int(abs(bracket)))

print('')

#Normalizing it

norm_const = 1/np.sqrt(bracket)

ket*=norm_const
bra = ket.getH()

bracket = bra*ket

print("|u> normalized:\n ", ket)
print('Norm:', int(abs(bracket)))
print('\nIf this was a statevector, the net probability would be 1, so the system makes sense!')


#Excercise: Make a function that checks to see if two ket vectors are orthogonal<jupyter_output>|u> unnormalized:
  [[0.+1.j]
 [0.+0.j]
 [0.+3.j]
 [2.+0.j]]
Norm: 14

|u> normalized:
  [[0.        +0.26726124j]
 [0.        +0.j        ]
 [0.        +0.80178373j]
 [0.53452248+0.j        ]]
Norm: 1

If this was a statevector, the net probability would be 1, so the system makes sense!<jupyter_text>1.7 Outer Product      A curious reader might ask something such as "If a bra-ket gives the inner product of two vectors, what does a ket-bra give?" The answer is rather simple to find using, again, matrix multiplication. Say we have vectors $|u\rangle$ and $|v\rangle$, the ket-bra could be written out as follows      \begin{equation}   |u\rangle\langle v| =   \begin{pmatrix}u_1 \\\ u_2  \\\ \vdots \\\ u_n \end{pmatrix}* \begin{pmatrix} v_1^* & v_2^* & \ldots & v_n^* \end{pmatrix}   \end{equation}      The output of this matrix multiplication will be the following      \begin{equation}  |u\rangle\langle v| = \begin{pmatrix}v_1^*u_1 & v_2^*u_1 & \ldots & v_n^*u_1 \\\ v_1^*u_2 & v_2^*u_2 & \ldots & v_n^*u_2 \\\ \vdots & \vdots & \ddots &\vdots \\\ v_1^*u_n & v_2^*u_n & \ldots & v_n^*u_n\end{pmatrix}   \end{equation}      As you can clearly see, unlike a bra-ket, the result is a matrix. This matrix is what is known as the density matrix in quantum mechanics and is used as a projection operator. Let's say we have a density matrix $M = |v\rangle\langle u|$ that acts on state $\psi$. The transformation it does is      \begin{equation}   M|\psi\rangle = (|v\rangle\langle u|)|\psi\rangle = |v\rangle \langle u|\psi\rangle   \end{equation}         The outer product is also equivilent to the tensor product (denoted by $\vec{u}\otimes \vec{v}$) and will prove to be a very crucial operation later on, especially when studying how the state evolves.       Python Implementation<jupyter_code># |u><u|, where |u> = (i,i,1,i)

ket = np.matrix('1j ; 1j ; 1 ; 1j')
bra = ket.getH()

outer = ket*bra
print('|u><u| =')
print('')
print(outer)
print('')
print('The dimensions are',outer.shape, '(i.e n by n)')<jupyter_output>|u><u| =

[[1.+0.j 1.+0.j 0.+1.j 1.+0.j]
 [1.+0.j 1.+0.j 0.+1.j 1.+0.j]
 [0.-1.j 0.-1.j 1.+0.j 0.-1.j]
 [1.+0.j 1.+0.j 0.+1.j 1.+0.j]]

The dimensions are (4, 4) (i.e n by n)<jupyter_text>Dirac Notation Python ModuleHere, we will build a small Python Module based on the concepts from the past 3 sections. It allows one to use bras and kets smoothly in Python, where the initial input is a list. This isn't anything special and its sole purpose is to offer a summary for the things we've learned about Dirac Notation so far. This also answers all the excercise questions above.  Dirac Module<jupyter_code>#ket(): The user inputs a python list that is then converted into a numpy array, which is then reshaped to be a column
def ket(vec):
    ket = np.array(vec)
    return ket.reshape(-1,1)


#bra(): The user inputs a ket vector (np array) that is then transformed into a bra vector

def bra(vec):
    #Turning the vector into a ket in case it isn't
    Ket = ket(vec)
    #Getting The conjugates of the components of the ket and inserting them into a row vector (i.e turning it to a bra)
    ket_conj = [np.conj(i)[0] for i in Ket]
    #Reformatting it into an array 
    bra = np.array(ket_conj)
    return bra


#braket(): Gets the inner product by evaluating <u|v>

def braket(bra,ket):
    #Gets the sum of all the u*v values from i=1 to n, where n is the number of dimensions
    inner_prod = 0
    for i in range(len(bra)):
        inner_prod += bra[i]*ket[i]
    return inner_prod


#ketbra(): Gets the outer product by evaluating |u><v|
def ketbra(bra,ket):
    return ket*bra


#normalize(): normalizes a ket

def normalize(ket):
    #Gets the bra
    Bra = bra(ket)
    #Gets the inner product
    norm = braket(Bra,ket)
    #Returns the normalized vector
    return 1/np.sqrt(norm)*ket

#isOrthogonal(): Checks if 2 vectors are orthogonal
def isOrthogonal(ket1, ket2):
    #Turns ket1 into a bra
    bra1 = bra(ket1)
    #Gets the inner product of bra1 and ket2
    inner = braket(bra1,ket2)
    #If inner == 0, Returns True, else, returns false
    return bool(inner == 0)<jupyter_output><empty_output><jupyter_text>Testing Dirac Module<jupyter_code>#Defining Two vectors (as lists)
u = [1+1j, 0, 1,-3]
v = [2j, 0, 0, 3+1j]

#Turning them into bras and kets
k = ket(u)
b = bra(v)

#Getting <v|u> and |u><v|
inner = braket(b,k)
outer = ketbra(b,k)

#Normalizing |u>
u_norm = normalize(k)

#Checking if |u> and |v> are orthogonal

orthog_checker = isOrthogonal(k,b)


#Outputs:

print('|u> =\n',k)
print('')
print('<v| =', b)

print('')

print('<v|u> =', inner)
print('')
print('|u><v| =\n', outer)

print('')

print('Normalized |u> =\n ', u_norm)

print('')

print('Are |u> and |v> orthogonal? (T/F):', orthog_checker)<jupyter_output>|u> =
 [[ 1.+1.j]
 [ 0.+0.j]
 [ 1.+0.j]
 [-3.+0.j]]

<v| = [0.-2.j 0.-0.j 0.-0.j 3.-1.j]

<v|u> = [-7.+1.j]

|u><v| =
 [[ 2.-2.j  0.+0.j  0.+0.j  4.+2.j]
 [ 0.+0.j  0.+0.j  0.+0.j  0.+0.j]
 [ 0.-2.j  0.+0.j  0.+0.j  3.-1.j]
 [ 0.+6.j  0.+0.j  0.+0.j -9.+3.j]]

Normalized |u> =
  [[ 0.28867513+0.28867513j]
 [ 0.        +0.j        ]
 [ 0.28867513+0.j        ]
 [-0.8660254 +0.j        ]]

Are |u> and |v> orthogonal? (T/F): False<jupyter_text>1.8  Linearly Dependent and Independent Vectors      A set of vectors are said to be linearly independent if no vector in that set could be written as a linear combination of the other vectors. Let's say we have a vector-set **V** = {$v_1,v_2,v_3,...v_n$}, to be linearly independent, the only solution to the following equation      \begin{equation}  c_1 v_1  + c_2 v_2  + c_3 v_3  + \ldots c_n v_n  = \sum_{i=1}^n c_i v_i  = 0   \end{equation}      must be       \begin{equation}   c_1 = c_2 = c_3 = \ldots = c_n = 0   \end{equation}      If the equation above is satisfied when any $c$ value $\neq 0$, then the vectors are linearly dependent.      1.9 New Description of Basis Sets, Orthonormality and Dual Spaces       In Section 1.4, a rudimentary definition of a $\textit{basis set}$ was given. The two conditions for a vector set  {$|v_1\rangle,|v_2\rangle,|v_3\rangle, \ldots, |v_n\rangle$} to be a basis set of subspace **V**  were the following:       1. Any vector in V can be written as a linear combination of the vectors in the basis set    2. No vector in the basis set can be expressed as a linear combination of the other vectors in the basis set      Now that more advanced concepts have been laid out, a new, more formal definition can be introduced. That definition is that a basis set must      1. Any vector in subspace V can be described as a linear combilation of the vectors in the set. If this condition is applied, the set is said to span the space. This would be described notationally as follows:     \begin{equation} |\psi\rangle = \sum_{i=1}^n c_i |v_i\rangle \end{equation}where $|\psi\rangle$ is any vector in **V**    2. The vectors in the set are linearly independent (Section 1.8)        3. The vector set is complete, meaning that no additional basis set is required to describe any possible state of the system    In Section 1.4, a vector set was said to be orthonormal if their inner product is zero and the norm of each vector was 1. Using a function called the Kronecker-Delta, one can write a single expression that describes both of these conditions, namely a vector set {$v_1, v_2,\ldots, v_n$} is orthonormal if\begin{equation}\langle v_i|v_j\rangle = \delta_{ij}\end{equation}where $\delta_{ij}$ is the Kronecker-Delta function, which is defined as\begin{equation}\delta_{ij} =    \begin{cases}            1, &         \text{if } i=j,\\            0, &         \text{if } i\neq j.    \end{cases}\end{equation}where $i$ and $j$ are integers between 1 and $n$ that represent the index of the two vectors undergoing the inner-product. The reason this works is because when $i \neq j$, the inner product of two different vectors must be zero (orthogonal) and when $i=j$, then the inner product $\langle v_i|v_j\rangle$ represents the norm of $v_i$ which must be one (normalized) (i.e orthonormal)The concept of a dual space is fairly detailed, but for the sake of this text all you need to know is that a bra vector $\langle \psi|$ is the dual of the ket vector $|\psi\rangle$.  Python Implementation<jupyter_code>#Kronicker-Delta Function which takes a list of numpy arrays
import numpy as np

def kronDelta(vecSet):
    #Creating an empty list that will soon be filled with all kronicker-delta values for a vectorset
    delta_list = []
    #Setting condition: for every value of i and j, if i=j, 1, if i=/=j, 0
    for i in range(len(vecSet)):
        for j in range(len(vecSet)):
            if i==j:
                delta=1
            else:
                delta = 0
            #Appending every result of the KD function to the empty list defined above
            delta_list.append(delta)
    #Returning the list 
    return delta_list


#Orthonormal Checker:takes the inner product of every 2 vectors in the set and compares it to the value obtained by KD

def orthonormalCheck(vecSet):
    #Gets the KD list of vecSet
    kdResult = kronDelta(vecSet)
    #Empty list that will soon be filled with all inner product values
    innerProds = []
    #Finding the all inner products of vecSet
    for i in range(len(vecSet)):
        for j in range(len(vecSet)):
            inner = np.inner(vecSet[i],vecset[j])
            #Appending every inner product result to the empty list defined above
            innerProds.append(inner)
    #Condition: if the inner-products of vecSet equals the KD of vecSet, True, else, False
    if innerProds == kdResult:
        return True
    else:
        return False
    
#Testing our functions on the unit vectors of R^4. This should obviously yield True
    
vecset = [np.array([1,0,0,0]), np.array([0,1,0,0]), np.array([0,0,1,0]), np.array([0,0,0,1])]
    
KD = kronDelta(vecset)

isOrthonormal = orthonormalCheck(vecset)

print('Kronicker-Delta Function of Unit Vectors of R^4:', KD)
print('Are they orthonormal? (T/F): ', isOrthonormal)

#Challenge: Build a function that shows whether a vector set forms a basis set of C^n<jupyter_output>Kronicker-Delta Function of Unit Vectors of R^4: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
Are they orthonormal? (T/F):  True<jupyter_text>1.10 Statevectors   You just went through a crash course of basic vector algebra and probably want a quick answer as to how you will use all of this stuff before having to go through the other two prerequisite chapters. I understand your concern, so here is your quick introduction to quantum computing:A quantum computer is any device which, in one way or another, utilizes the laws of quantum mechanics to perform logical operations. As we will show in the next chapter, a key property of quantum mechanics is that the physical systems it describes are intrinsically random. In other words, they are not in a definite state, but rather in a linear combination of all possible states. This is what is known as superposition. As hinted throughout this section, one would use a vector called the statevector to describe the probability of finding a particle in any given state. The number of dimensions of the statevector is equal to the number of possible states a system can be in. This statevector is represented by a ket as follows:\begin{equation}|\Psi\rangle = \begin{pmatrix}\psi_1 \\\ \psi_2  \\\ \vdots \\\ \psi_n \end{pmatrix}\end{equation}Where $\psi_i$ is a complex number that represents the a quantity called the amplitude for the $i$th state (where $i$ is between 1 and $n$). The importance of the amplitude is that when squared, it yields the probability of the system being in state $i$. Another way to represent this is by treating the main statevector |$\Psi$\rangle as a linear combination of basis vectors in an n-dimensional Hilbert space (as you can see, around half of the concepts we learned came up in this one sentence... so read it a few times). This would look like this:\begin{equation}|\Psi\rangle = \psi_1 |1\rangle + \psi_2 |2\rangle + \ldots + \psi_n |n\rangle = \sum_{i=1}^n \psi_i|i\rangle\end{equation}      As stated above, $|\psi_1|^2$ is the probability of finding a system in state 1, $|\psi_2|^2$ is the probability of finding a particle in state 2 and so on. From there, it would be easy to deduce that the sum of all the amplitudes squared, or $\sum_{i=1}^n |\psi_i|^2$ must be equal to the total probability of the system, which should be 1. However, remember that according to Section 1.2, $|\psi_i|^2 = \psi_i^* \psi_i$, which means that the sum of all amplitudes multiplied by their corresponding conjugates must equal 1. Recall that this essentially means that the norm of the vector is 1, which, when written out in Dirac Notation, takes the following form:      \begin{equation}   \langle \Psi|\Psi\rangle = 1   \end{equation}    I know what you're thinking. "Ok, Adam... all this vector stuff sounds cool but what do you mean by state? State of what?". State could mean several things. It could mean position, momentum, or any property of the system. It is just a matter of giving the statevector context about that state (i.e defining what propery to apply it to). If one would want to study the amplitude distribution of a particle's position $x$, then they have to evaluate the following inner product:  \begin{equation} \langle x|\psi\rangle \end{equation}  And similarly for momentum $p$, $\langle p|\psi\rangle$. These inner products yield what is known as the wavefunction, which describe a system's amplitude as a function of the physical quantity (e.g $\psi(x)$ for position).   Qubit  The segment above was a (frankly poor) introduction to what a statevector and wavefunction mean in quantum mechanics. But how does that explain what a quantum computer does, or equally importantly, how quantum information is represented? In a classical computer, information is simply represented by two possible **definite** states: a 0 or a 1. Anything that is in one of those states is called a bit. The function of a classical computer is to manipulate a series of bits according to its given instruction in order to create either cool things like GTA 5, or uncool things like TikTok.  However, as described above, the principles of quantum mechanics are inherently random (non-deterministic), meaning that information (such as a bit) cannot be in a definite state, but rather in a linear combination of states defined by a statevector $|\psi\rangle$. In this case, given that there are only two possible states a, 1 or 0, a quantum bit's (or for short, qubit's) quantum state will be represented by a superposition (linear combination) of state $|0\rangle$ and state |1\rangle. Technically speaking, this would mean that a qubit is a two level quantum system represented by a ket in 2D Hilbert space:  \begin{equation} |\psi\rangle = \alpha |0\rangle + \beta |1\rangle \end{equation}  where $\alpha$ and $\beta$ are amplitudes that are $\in C$, and $|0\rangle$ and $|1\rangle$ are basis vectors which can be represented by the following matrices:  \begin{equation} |0\rangle = \begin{pmatrix}1 \\\ 0\end{pmatrix} and |1\rangle = \begin{pmatrix}0 \\\ 1\end{pmatrix} \end{equation}  If one were to expand $|0\rangle$ and $|1\rangle$ into their matrix representation in the superposition expression described above, then one would get the following:  \begin{equation} |\psi\rangle = \alpha \begin{pmatrix}1 \\\ 0\end{pmatrix} + \beta \begin{pmatrix}0 \\\ 1\end{pmatrix} \newline = \begin{pmatrix}\alpha \\\ 0\end{pmatrix} + \begin{pmatrix}0 \\\ \beta \end{pmatrix} \newline = \begin{pmatrix}\alpha \\\ \beta\end{pmatrix} \end{equation}   I can go on and on about this topic but will have to stop here to save some information for the coming chapters<jupyter_code>#Go nuts here
zero = bra(np.array([1,0]))
one = ket(np.array([0,1]))

ketbra(zero,one)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10888607776213094, 'fraction_numerical': 0.020693605747428217, 'mean_word_length': 3.7605953868878537, 'pattern_counts': {'":': 0, '<': 57, '<?xml version=': 0, '>': 72, 'https://': 1, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '20073', 'license': 'MIT', 'n_tokens_mistral': 13390, 'n_tokens_neox': 12657, 'n_words': 5959, 'path': 'Pre-requisites/Chapter 1 - Math Tools I (Vectors, Complex Numbers and Dirac Notation).ipynb', 'repo_name': 'pratjz/TEQS', 'size': '41897'}