"""
Implementation of the "Majorana stars" formalism for pure states of higher spin.
"""
import numpy as np
import qutip as qt
from itertools import *
from ..utils import *
from ..coordinates import *
[docs]def spin_poly(spin, projective=False,\
homogeneous=False,\
cartesian=False,\
spherical=False,\
normalized=False,\
for_integration=False):
"""
Converts a spin into its Majorana polynomial, which is defined as follows:
.. math::
p(z) = \\sum_{m=-j}^{m=j} (-1)^{j+m} \\sqrt{\\frac{(2j)!}{(j-m)!(j+m)!}} a_{j+m} z^{j-m}
Here, the :math:`a`'s run through the components of the spin in the :math:`\\mid j, m\\rangle` representation.
Note that :math:`\\frac{(2j)!}{(j-m)!(j+m)!}` amounts to: :math:`\\binom{2j}{j+m}`, a binomial coefficient.
By default, returns the coefficients of the Majorana polynomial as an np.ndarray.
If `projective=True`, returns a function which takes an (array of) extended complex coordinate(s)
as an argument, and which evaluates the polynomial at that/those point(s). Note that to evaluate the polynomial
at :math:`\\infty`, we flip the stereographic projection axis and evaluate the latter polynomial at 0
(and then complex conjugate). Insofar as pole flipping causes the highest degree term to become the lowest degree/constant term,
evaluating at :math:`\\infty` amounts to returning the first coefficient.
If `homogeneous=True`, returns a function which takes a spinor (as an nd.array or qt.Qobj)
and evaluates the (unnormalized) homogeneous Majorana polynomial:
.. math::
p(z, w) = \\sum_{m=-j}^{m=j} (-1)^{j+m} \\sqrt{\\frac{(2j)!}{(j-m)!(j+m)!}} a_{j+m} w^{j-m} z^{j+m}
If `cartesian=True`, returns a function with takes cartesian coordinates
and evaluates the Majorana polynomial by first converting the cartesian coordinates to extended complex coordinates.
If `spherical=True`, returns a function with takes spherical coordinates
and evaluates the Majorana polynomial by first converting the spherical coordinates to extended complex coordinates.
If `normalized=True`, returns the normalized versions of any of the above functions. Note that the normalized
versions are no longer analytic/holomorphic. Given a extended complex coordinate :math:`z = re^{i\\theta}` (or :math:`\\infty`),
the normalization factor is:
.. math::
\\frac{e^{-2ij\\theta}}{(1+r^2)^j}
If :math:`z=\\infty`, again since we flip the poles, we use :math:`z=0`.
If `for_integration=True`, returns normalized function that takes spherical coordinates, with an extra normalization
factor of :math:`\\sqrt{\\frac{2j+1}{4\\pi}}` so that the integral over the sphere gives a normalized amplitude.
Note that coordinates must be given in the form of [[:math:`\\theta`'s], [:math:`\\phi`'s]].
When normalized, evaluating the Majorana polynomial is equivalent to evaluating:
.. math::
\\langle -xyz \\mid \\psi \\rangle
Where :math:`\\mid xyz \\rangle` refers to the spin coherent state which has all its "stars" at
cartesian coordinates :math:`x, y, z`, and :math:`\\mid \\psi \\rangle` is the spin in the :math:`\\mid j, m\\rangle`
representation. In other words, evaluating a normalized Majorana function at :math:`x, y, z` is equivalent to evaluating:
.. code-block::
spin_coherent(j, -xyz).dag()*spin
Which is the inner product between the spin and the spin coherent state antipodal to :math:`x, y, z`
on the sphere. Since the Majorana stars are zeros of this function, we can interpret them
as picking out those directions for which there's 0 probability that all the angular momentum is concentrated
in the opposite direction. Insofar as we can think of each star as contributing a quantum of angular momentum :math:`\\frac{1}{2}`
in that direction, naturally there's no chance that *all* the angular momentum is concentrated opposite to any of those points.
By the fundamental theorem of algebra, knowing these points is equivalent to knowing the entire quantum state.
Parameters
----------
spin : qt.Qobj
Spin-j state.
projective : bool, optional
Whether to return Majorana polynomial as a function of an extended complex coordinate.
homogeneous : bool, optional
Whether to return Majorana polynomial as a function of a spinor.
cartesian : bool, optional
Whether to return Majorana polynomial as a function of cartesian coordinates on unit sphere.
spherical : bool, optional
Whether to return Majorana polynomial as a function of spherical coordinates.
normalize : bool, optional
Whether to normalize the above functions.
for_integration : bool, optional
Extra normalization for integration.
Returns
-------
poly : np.ndarray or func
Either 2j+1 Majorana polynomial coefficients or else one of the above functions.
"""
j = (spin.shape[0]-1)/2
v = components(spin)
poly = np.array(\
[v[int(m+j)]*\
(((-1)**(int(m+j)))*\
np.sqrt(factorial(2*j)/(factorial(j-m)*factorial(j+m))))
for m in np.arange(-j, j+1)])
if for_integration:
normalized = True
if projective or cartesian or spherical or for_integration:
def _poly_(z):
normalization = np.exp(-2j*j*np.angle(z))/(1+abs(z if z != np.inf else 0)**2)**j if normalized else 1
return normalization*(poly[0] if z == np.inf else \
sum([poly[int(j+m)]*z**(j-m) for m in np.arange(-j, j+1)]))
def __poly__(z):
return np.array([_poly_(z_) for z_ in z]) if isinstance(z, Iterable) else _poly_(z)
if projective:
return __poly__
if cartesian:
return lambda xyz: __poly__(xyz_c(xyz))
if spherical:
return lambda sph: __poly__(sph_c(sph))
if for_integration:
def __for_integration__(theta_phi):
return np.sqrt((2*j+1)/(4*pi))*__poly__(sph_c(theta_phi.T))
return __for_integration__
if homogeneous:
def _hompoly_(spinor):
z, w = components(spinor)
return sum([poly[int(j+m)]*(w**(j-m))*(z**(j+m)) for m in np.arange(-j, j+1)])
return lambda spinor: np.array([_hompoly_(spinor_) for spinor_ in spinor]) if isinstance(spinor, Iterable) else _hompoly_(spinor)
return poly
[docs]def poly_spin(poly):
"""
Converts a Majorana polynomial into a spin-j state.
Parameters
----------
poly : np.ndarray
2j+1 Majorana polynomial coefficients.
Returns
-------
spin : qt.Qobj
Spin-j state.
"""
j = (len(poly)-1)/2
return qt.Qobj(np.array(\
[poly[int(m+j)]/\
(((-1)**(int(m+j)))*\
np.sqrt(factorial(2*j)/(factorial(j-m)*factorial(j+m))))
for m in np.arange(-j, j+1)])).unit()
[docs]def poly_roots(poly):
"""
Takes a Majorana polynomial to its roots. We use numpy's polynomial solver.
The number of initial coefficients which are 0 are intepreted as the number of
roots at :math:`\\infty`. In other words, to the extent that the degree of
a Majorana polynomial corresponding to a spin-j state is less than 2j+1, we add
that many roots at :math:`\\infty`.
Parameters
----------
poly : np.ndarray
2j+1 Majorana polynomial coefficients.
Returns
-------
roots : list
Roots on the extended complex plane.
"""
return [np.inf]*np.flatnonzero(poly)[0] + [complex(root) for root in np.roots(poly)]
[docs]def roots_poly(roots):
"""
Takes a set of points on the extended complex plane and forms the polynomial
which has these points as roots. Roots at :math:`\\infty` turn into initial
zero coefficients.
Parameters
----------
roots : list
Roots on the extended complex plane.
Returns
-------
poly : np.ndarray
2j+1 Majorana polynomial coefficients.
"""
zeros = roots.count(0j)
if zeros == len(roots):
return np.array([1] + [0j]*len(roots))
poles = roots.count(np.inf)
if poles == len(roots):
return np.array([0j]*poles + [1])
roots = [root for root in roots if root != np.inf]
coeffs = np.array([((-1)**(-i))*sum([np.prod(terms)\
for terms in combinations(roots, i)])\
for i in range(len(roots)+1)])
return np.concatenate([np.zeros(poles), coeffs])
[docs]def spin_xyz(spin):
"""
Takes a spin-j state and returns the cartesian coordinates on the unit sphere
corresponding to its "Majorana stars." Each contributes a quantum of angular
momentum :math:`\\frac{1}{2}` to the overall spin.
Note: If given a spin-0 state, returns [[0,0,0]]. If given a state with 0 norm, returns a list of 2j 0-vectors.
Parameters
----------
spin : qt.Qobj
Spin-j state.
Returns
-------
xyz : np.ndarray
A array with shape (2j, 3) containing the 2j cartesian coordinates of the stars.
"""
if spin.shape[0] == 1:
return np.array([[0,0,0]])
elif spin.norm() == 0:
return np.array([[0,0,0] for i in range(spin.shape[0]-1)])
return np.array([c_xyz(root) for root in poly_roots(spin_poly(spin))])
[docs]def xyz_spin(xyz):
"""
Given the cartesian coordinates of a set of "Majorana stars," returns the
corresponding spin-j state, which is defined only up to complex phase.
Parameters
----------
xyz : np.ndarray
A array with shape (2j, 3) containing the 2j cartesian coordinates of the stars.
Returns
-------
spin : qt.Qobj
Spin-j state.
"""
return poly_spin(roots_poly([xyz_c(star) for star in xyz])).unit()
[docs]def spin_spinors(spin):
"""
Takes a spin-j state and returns its decomposition into 2j spinors.
Parameters
----------
spin : qt.Qobj
Spin-j state.
Returns
-------
spinors : list
2j spinors.
"""
return [c_spinor(c) for c in poly_roots(spin_poly(spin))]
[docs]def spinors_spin(spinors):
"""
Given 2j spinors returns the corresponding spin-j state (up to phase).
Parameters
----------
spinors : list
2j spinors.
Returns
-------
spin : qt.Qobj
Spin-j state.
"""
return poly_spin(roots_poly([spinor_c(spinor) for spinor in spinors]))
[docs]def spin_c(spin):
"""
Takes a spin-j state and returns its decomposition into 2j roots on the extended complex plane.
Parameters
----------
spin : qt.Qobj
Spin-j state.
Returns
-------
c : list
2j extended complex roots.
"""
return poly_roots(spin_poly(spin))
[docs]def c_spin(c):
"""
Takes 2j roots on the extended complex plane and returns the corresponding spin-j state (up to complex phase).
Parameters
----------
c : list
2j extended complex roots.
Returns
-------
spin : qt.Qobj
Spin-j state.
"""
return poly_spin(roots_poly(c))
[docs]def spin_sph(spin):
"""
Takes a spin-j state and returns its decomposition into 2j "stars" given in spherical coordinates.
Parameters
----------
spin : qt.Qobj
Spin-j state.
Returns
-------
sph : np.array
A array with shape (2j, 3) containing the 2j spherical coordinates of the stars.
"""
return np.array([c_sph(c) for c in poly_roots(spin_poly(spin))])
[docs]def sph_spin(sph):
"""
Takes 2j "stars" given in spherical coordinates and returns the corresponding spin-j state (up to complex phase).
Parameters
----------
sph : np.array
A array with shape (2j, 3) containing the 2j spherical coordinates of the stars.
Returns
-------
spin : qt.Qobj
Spin-j state.
"""
return poly_spin(roots_poly([sph_c(s) for s in sph]))