{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Fermionic Quantization (and the Spin Statistics Theorem)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So far we've essentially discussed \"bosonic\" second quantization, where we assign a harmonic oscillator to each degree of freedom of some single particle Hilbert space, and the number operator of each oscillator keeps track of how many particles are in *that state*. It works out that this \"Fock space\" corresponds dimensionally to a tower of permutation symmetric tensor products of $0, 1, 2, 3, \\dots$ particles. In other words, the use of harmonic oscillators automatically imposes for us that condition that the second quantized particles be permutation symmetric. \n", "\n", "It's worth remember that although the creation and annihilation operators are not hermitian, they can be used as building blocks for hermitian operators. And if we have multiple oscillators, we can just tensor these creation and annihilation operators with identities so they act on the appropriate subspaces. \n", "\n", "For bosons, if we have multiple creation and annihilation operators, they should obey the following commutation relations:\n", "\n", "$$ [b_{i}, b_{j}] = [b^{\\dagger}_{i}, b^{\\dagger}_{j}] = 0 $$\n", "$$ [b_{i}, b^{\\dagger}_{j}] = \\delta^{i}_{j} $$\n", "\n", "Where \\\\( b^{\\dagger} \\\\) is a creation operator, \\\\( b \\\\) is an annihilation operator, and: the commutator is \\\\( [A, B] = AB - BA \\\\). \n", "\n", "In practice, if we work in finite dimensions, we can cap the maximum number of quanta in a given harmonic oscillator, and get finite dimensional \"creation\" and \"annihilation\" operators, which add and subtract quanta from each oscillator, although they don't satisfy perfectly the correct commutation relations. \n", "\n", "Okay, so here's the thing:\n", "\n", "If we want to second quantize fermionically, as opposed to bosonically, we have to make some changes. First, our oscillators can only have 0 or 1 excitations. In other words, at maximum only 1 fermion can be in a given state. This is the Pauli exclusion principle at work. The whole thing has to do with the fact while fermions and bosons are both \"indistinguishable\" (with respect to the tensor product) particles, fermions must always be in a permutation *antisymmetric* state, unlike bosons which must be in a permutation *symmetric* state. Look what happens if we try to antisymmetrize two identical states via $A, B \\rightarrow A \\otimes B - B \\otimes A$: $\\mid \\uparrow \\rangle \\mid \\uparrow \\rangle - \\mid \\uparrow \\rangle \\mid \\uparrow \\rangle = 0$: we get the zero vector. The Pauli exclusion principle is just a fancy way of saying that fermions, by nature, are permutation antisymmetric particles. If we permute them, then the whole tensor state is multiplied by -1. This just changes the overall phase, and not the probabilities: indeed, the particles are still indistinguishable, despite them all picking up a sign flip when they are swapped in the tensor product.\n", "\n", "For this reason, in terms of second quantization, it doesn't matter the order in which we create bosons, but it does matter for fermions: \\\\( f^{\\dagger}_{i} f^{\\dagger}_{j} \\mid 0 \\rangle = -f^{\\dagger}_{j} f^{\\dagger}_{i} \\mid 0 \\rangle \\\\), where \\\\( \\mid 0 \\rangle \\\\) is the fermion vacuum aka the state of all oscillators having 0 quanta.\n", "\n", "The upshot is that the commutation relations between fermions involve the anticommutator \\\\( \\{A, B\\} = AB + BA \\\\), instead of the commutator.\n", "\n", "$$ \\{f_{i}, f_{j}\\} = \\{f^{\\dagger}_{i}, f^{\\dagger}_{j}\\} = 0 $$\n", "$$ \\{f_{i}, f^{\\dagger}_{j}\\} = \\delta^{i}_{j} $$\n", "\n", "Luckily, with a trick, it's not hard to construct the right matrix representation for these operators. Indeed, it's kinda nice that the fermion excitations are capped at 1, since we can use $2$ x $2$ matrices for their creation and annihilation operators, and we can deal with a nice actually finite dimensional vector space. \n", "\n", "For example, suppose we have 5 fermions. The standard $2$ x $2$ matrices for creation and annihilation operators are just:\n", "\n", "$$ f^{\\dagger} = \\begin{pmatrix} 0 & 0 \\\\ 1 & 0 \\end{pmatrix} $$\n", "$$ f = \\begin{pmatrix} 0 & 1 \\\\ 0 & 0 \\end{pmatrix} $$\n", "\n", "But here's the catch: to get 5 annihilation operators with the correct commutation relations, preserving the antisymmetry of the fermions, we take:\n", "\n", "$$ f_{0} = f \\otimes I \\otimes I \\otimes I \\otimes I $$\n", "$$ f_{1} = Z \\otimes f \\otimes I \\otimes I \\otimes I $$\n", "$$ f_{2} = Z \\otimes Z \\otimes f \\otimes I \\otimes I $$\n", "$$ f_{3} = Z \\otimes Z \\otimes Z \\otimes f \\otimes I $$\n", "$$ f_{4} = Z \\otimes Z \\otimes Z \\otimes Z \\otimes f $$\n", "\n", "The idea is that there is a \"normal ordering\" for the fermions. When the operators are applied to the vacuum in the descending order, the $Z$'s in \\\\( f_{4} \\\\), don't matter since there are no excitations in those oscillators: we're in the vacuum after all. But when the operators are applied in the reverse order, they pick up a negative sign from the $Z$'s. You can check that this works:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import qutip as qt\n", "import numpy as np\n", "\n", "def anticommutator(a, b):\n", " return a*b + b*a\n", "\n", "#########################################################################################\n", "\n", "def fermion_operators(n):\n", " return [qt.tensor(*[qt.destroy(2) if i == j\\\n", " else (qt.sigmaz() if j < i\\\n", " else qt.identity(2))\\\n", " for j in range(n)])\\\n", " for i in range(n)]\n", "\n", "def test_fermion_operators(f):\n", " for i in range(len(f)):\n", " for j in range(len(f)):\n", " d = f[i].shape[0]\n", " test1 = anticommutator(f[i], f[j]).full()\n", " test2 = anticommutator(f[i], f[j].dag()).full()\n", " if not \\\n", " (np.isclose(test1, np.zeros((d,d))).all()\\\n", " and \\\n", " ((np.isclose(test2, np.zeros((d,d))).all() and i != j)\\\n", " or (np.isclose(test2, np.eye(d)).all() and i == j))):\n", " return False\n", " return True\n", "\n", "#########################################################################################\n", "\n", "n = 3\n", "IDn = qt.identity(2**n)\n", "IDn.dims = [[2]*n, [2]*n]\n", "\n", "f = fermion_operators(n)\n", "print(test_fermion_operators(f))\n", "\n", "N = sum([a.dag()*a for a in f]) # number operator\n", "I = qt.basis(2**n, 0) # vacuum state\n", "I.dims = [[2]*n, [1]*n]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Okay, so we have $n$ fermionic oscillators to play with. And we want to second quantize an $n$ dimensional single particle Hilbert space fermionically. So now we have to talk about what the tower of antisymmetric multi-particle states looks like.\n", "\n", "First, we observe that $sgn(\\sigma) = (-1)^{T(\\sigma)}$, where $\\sigma$ is some permutation: e.g., $132$ would be a permutation of $123$. $T(\\sigma)$ is the number of transpositions in the permutation. And $sgn(\\sigma)$ is known as the parity of the permutation.\n", "\n", "If we want to antisymmetrize a set of states, we basically want to sum over all permutations of the states, forming the tensor product of the pieces in each order, multiplying each tensor product by $-1$ for each swap in the permutation:\n", "\n", "$ \\sum_{\\sigma} (-1)^{T(\\sigma)} ( \\sigma_{i} \\otimes \\sigma_{j } \\otimes \\dots)$\n", "\n", "Using this, we can easily calculate the basis states for the permutation antisymmetric subspace of $n$ particles who each live in a $d$ dimensional Hilbert space.\n", "\n", "We take each set of the $d$ basis vectors, each set that has $n$ elements and contains no repeats, and antisymmeterize those basis vectors. We'll actually end up with $\\begin{pmatrix} d \\\\ n \\end{pmatrix}$ basis vectors. We can form a rectangular matrix out of them, which will let us translate between a state of $\\begin{pmatrix} d \\\\ n \\end{pmatrix}$ dimensions, and a state of $n$ antisymmeterized particle states. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import qutip as qt\n", "from qutip.qip.operations import swap\n", "import itertools\n", "\n", "def perm_parity(lst):\n", " parity = 1\n", " for i in range(0,len(lst)-1):\n", " if lst[i] != i:\n", " parity *= -1\n", " mn = min(range(i,len(lst)), key=lst.__getitem__)\n", " lst[i],lst[mn] = lst[mn],lst[i]\n", " return parity \n", "\n", "def antisymmetrize(*states):\n", " a = sum([perm_parity(list(perm))*qt.tensor(*[states[p] for p in perm])\\\n", " for perm in itertools.permutations(list(range(len(states))))])\n", " if a.norm() != 0:\n", " a = a.unit()\n", " return a\n", "\n", "def antisymmetric_basis(n, d=2):\n", " states = []\n", " for perm in itertools.combinations_with_replacement(list(range(d)), r=n):\n", " a = antisymmetrize(*[qt.basis(d, p) for p in perm])\n", " if a.norm() != 0:\n", " states.append(a)\n", " Q = qt.Qobj(np.array([state.full().T[0].tolist() for state in states]))\n", " Q.dims[1] = [d]*n\n", " return Q\n", " \n", "a = qt.rand_ket(2)\n", "print(a)\n", "b = qt.rand_ket(2)\n", "print(b)\n", "\n", "c = antisymmetrize(a,b)\n", "print(c)\n", "print(swap()*c)\n", "\n", "antisymmetric_basis(3, d=3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we put the two together.\n", "\n", "We tensor, say, $3$ fermionic oscillators, and construct the total number operator. Then we construct a permutation matrix that rearranges the number operator so that its eigenvalues are in order. We can also use this permutation matrix $P$ to rearrange a Fock state so that it's of the form: $H_{1} \\oplus H_{3} \\oplus H_{3} \\oplus H_{1}$. The dimensionality of these Hilbert spaces corresponds to the dimensionality of the antisymmetric subspace of the tensor product of $0, 1, 2$, and $3$ particles. So once we've picked apart out state, we can apply the linear map constructed above which sends:\n", "\n", "$H_{1} \\oplus H_{3} \\oplus H_{3} \\oplus H_{1} \\rightarrow 0 \\oplus A(H_{3}) \\oplus A(H_{3}, H_{3}) \\oplus A(H_{3}, H_{3}, H_{3})$, where $A()$ denotes the antisymmetric subspace of the tensor product of the particles." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import qutip as qt\n", "import numpy as np\n", "from itertools import product\n", "\n", "def construct_permutation(n): \n", " tensor_basis_labels = list(product([0,1], repeat=n))\n", " total_n_basis_labels = []\n", " for i in range(n+1):\n", " total_n_basis_labels.extend(\\\n", " list(filter(lambda x: sum(x) == i, list(product([0,1], repeat=n))))[::-1])\n", " P = np.zeros((2**n, 2**n))\n", " for i, label in enumerate(tensor_basis_labels):\n", " P[total_n_basis_labels.index(label)][i] = 1\n", " P = qt.Qobj(P)\n", " P.dims = [[2]*n, [2]*n]\n", " sums = [sum(label) for label in total_n_basis_labels]\n", " unique_sums = set(sums)\n", " dims = [sums.count(us) for us in unique_sums]\n", " return P, dims \n", "\n", "def extract_states(q, dims):\n", " v = q.full().T[0]\n", " running = 0\n", " blocks = []\n", " for d in dims:\n", " blocks.append(qt.Qobj(v[running:running+d]))\n", " running += d\n", " return blocks\n", "\n", "def osc_antisymmetrics(state):\n", " n = len(state.dims[0])\n", " P, dim = construct_permutation(n)\n", " extracted = extract_states(P*state, dim)\n", " finished = [extracted[0]]\n", " for i in range(1, len(extracted)):\n", " finished.append(antisymmetric_basis(i, d=n).dag()*extracted[i])\n", " return finished\n", " \n", "#########################################################################################\n", "\n", "n = 3\n", "IDn = qt.identity(2**n)\n", "IDn.dims = [[2]*n, [2]*n]\n", "\n", "f = fermion_operators(n)\n", "N = sum([a.dag()*a for a in f])\n", "\n", "print(N)\n", "P, dim = construct_permutation(n)\n", "N_ = P*N*P.dag()\n", "print(N_)\n", "\n", "state = qt.rand_ket(2**n)\n", "state.dims = [[2]*n, [1]*n]\n", "\n", "print(\"\\n Extracted sectors:\")\n", "print(extract_states(P*state, dim))\n", "print(\"\\nTower of antisymmetric states:\\n\")\n", "antisymmetric_tower = osc_antisymmetrics(state)\n", "print(antisymmetric_tower)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So there you have it: fermionic quantization.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "