Module cgc.ArbColors
Expand source code
from .Wavefunction import Wavefunction
from .LinAlg import expm, get_basis
import numba
import numpy as np
from scipy.fft import ifft2, fft2
class Nucleus(Wavefunction):
# Upon calling wilsonLine() or adjointWilsonLine(), these are properly defined
_wilsonLine = None
_adjointWilsonLine = None
# Some variables to keep track of what has been calculated/generated so far
# allowing us to avoid redundant computations
_wilsonLineExists = False
_adjointWilsonLineExists = False
def __init__(self, colorCharges, N, delta, mu, M=.5, g=1, Ny=100, rngSeed=None):
r"""
Dense object to be used in an instance of `cgc.Collision.Collision`.
Implements calculation of the Wilson Line using the generalized basis matrix set.
Parameters
----------
colorCharges : positive integer
The number of possible color charges; also the dimensionality of the special unitary group.
N : positive integer
The size of the square lattice to simulate.
delta : positive float
The distance between adjacent lattice sites.
mu : positive float
The scaling for the random gaussian distribution that generates the color charge density.
M : float (default=.5)
Infrared regulator parameter to regularize the Poisson equation for the gauge field.
g : float (default=1)
Parameter in the Poisson equation for the gauge field.
Ny : positive integer (default=100)
The longitudinal extent (in layers) of the nucleus object.
rngSeed : int (default=None)
Seed for the random number generator to initialize the color charge field
"""
super().__init__(colorCharges, N, delta, mu, M, g, rngSeed) # Super constructor
self._basis = get_basis(colorCharges)
self.Ny = Ny
# Modify the gaussian width to account for the multiple longitudinal layers
self.gaussianWidth = self.mu / self.delta / np.sqrt(self.Ny)
def colorChargeField(self, forceCalculate=False):
r"""
Generates the color charge density field according to a gaussian distribution. Differs
from super class implementation in that it generates the numerous fields according
to `Ny`. That is, the field \(\rho\) satisfies:
$$ \langle \rho_{a}^{(t)}(i^-,\vec i_{\perp}) \rho_{b}^{(t)}({j^-},\vec j_{\perp}) \rangle = g^2\mu_t^2 \frac{ 1 }{N_y \Delta^2} ~\delta_{ab}~\delta_{i_{\perp,1}\ j_{\perp,1}}~\delta_{i_{\perp,2} \ j_{\perp,2}} ~\delta_{i^- \ {j^-}} $$
If the field already exists, it is simply returned and no calculation is done.
Parameters
----------
forceCalculate : bool (default=False)
If the quantity has previously been calculated, the calculation will not be done
again unless this argument is set to True.
Returns
-------
colorChargeField : array(Ny, `colorCharges`**2 - 1, N, N)
"""
if self._colorChargeFieldExists and not forceCalculate:
return self._colorChargeField
# Randomly generate the intial color charge density using a gaussian distribution
self._colorChargeField = self.rng.normal(scale=self.gaussianWidth, size=(self.Ny, self.gluonDOF, self.N, self.N))
# Make sure we don't regenerate this field since it already exists on future calls
self._colorChargeFieldExists = True
return self._colorChargeField
def gaugeField(self, forceCalculate=False):
r"""
Calculates the gauge field for all longitudinal layers and charge distributions by solving the (modified)
Poisson equation involving the color charge field
$$g \frac{1 } {\partial_\perp^2 - m^2 } \rho_a(i^-, \vec {i}_\perp )$$
via Fourier method.
If the field already exists, it is simply returned and no calculation is done.
Parameters
----------
forceCalculate : bool (default=False)
If the quantity has previously been calculated, the calculation will not be done
again unless this argument is set to True.
Returns
-------
gaugeField : array(Ny, `colorCharges`**2 - 1, N, N)
"""
if self._gaugeFieldExists and not forceCalculate:
return self._gaugeField
# Make sure the charge field has already been generated (if not, this will generate it)
self.colorChargeField()
# Compute the fourier transform of the charge field
# Note that the normalization is set to 'backward', which for scipy means that the
# ifft2 is scaled by 1/n (where n = N^2)
chargeDensityFFTArr = fft2(self._colorChargeField, axes=(-2,-1), norm='backward')
# Absorb the numerator constants in the equation above into the charge density
chargeDensityFFTArr = -self.delta2 * self.g / 2 * chargeDensityFFTArr
# Calculate the individual elements of the gauge field in fourier space
# Note here that we have to solve the gauge field for each layer and for each gluon degree of freedom
# This method is defined at the bottom of this file; see there for more information
gaugeFieldFFTArr = _calculateGaugeFFTOpt(self.gluonDOF, self.N, self.Ny, self.poissonReg, chargeDensityFFTArr);
# Take the inverse fourier transform to get the actual gauge field
self._gaugeField = np.real(ifft2(gaugeFieldFFTArr, axes=(-2, -1), norm='backward'))
# Make sure this process isn't repeated unnecessarily by denoting that it has been done
self._gaugeFieldExists = True
return self._gaugeField
def wilsonLine(self, forceCalculate=False):
"""
Calculate the Wilson line using the gauge field and the appropriate basis matrices.
If the line already exists, it is simply returned and no calculation is done.
Parameters
----------
forceCalculate : bool (default=False)
If the quantity has previously been calculated, the calculation will not be done
again unless this argument is set to True.
Returns
-------
wilsonLine : array(N, N, `colorCharges`)
"""
if self._wilsonLineExists and not forceCalculate:
return self._wilsonLine
# Make sure the gauge field has already been calculated
self.gaugeField()
# We now combine all of the longitudinal layers into the single wilson line
# Optimized method is defined at the end of this file; see there for more information
self._wilsonLine = _calculateWilsonLineOpt(self.N, self.Ny, self.colorCharges, self._basis, self._gaugeField)
self._wilsonLineExists = True
return self._wilsonLine
def adjointWilsonLine(self, forceCalculate=False):
"""
Calculate the Wilson line in the adjoint representation.
If the line already exists, it is simply returned and no calculation is done.
Parameters
----------
forceCalculate : bool (default=False)
If the quantity has previously been calculated, the calculation will not be done
again unless this argument is set to True.
Returns
-------
adjointWilsonLine : array(`colorCharges`**2 - 1, `colorCharges`**2 - 1, N, N)
"""
if self._adjointWilsonLineExists and not forceCalculate:
return self._adjointWilsonLine
# Make sure the wilson line has already been calculated
if not self._wilsonLineExists:
self.wilsonLine()
# Calculation is optimized with numba, as with the previous calculations
# See bottom of the file for more information
self._adjointWilsonLine = _calculateAdjointWilsonLineOpt(self.gluonDOF, self.N, self._basis, self._wilsonLine)
self._adjointWilsonLineExists = True
return self._adjointWilsonLine
# Since we want to speed up the calculate, we define the calculation of the fourier elements of
# the gauge field using a numba-compiled method
# This has to be defined outside of the Nuclelus class since numbda doesn't play well with custom libraries
@numba.jit(nopython=True, cache=True)
def _calculateGaugeFFTOpt(gluonDOF, N, Ny, poissonReg, chargeDensityFFTArr):
r"""
Calculate the elements of the gauge field in fourier space.
This method is optimized using numba.
"""
gaugeFieldFFTArr = np.zeros_like(chargeDensityFFTArr, dtype='complex')
# Precompute for speed
two_pi_over_N = 2 * np.pi / N
for l in range(Ny):
for k in range(gluonDOF):
for i in range(N):
for j in range(N):
gaugeFieldFFTArr[l,k,i,j] = chargeDensityFFTArr[l,k,i,j]/(2 - np.cos(two_pi_over_N*i) - np.cos(two_pi_over_N*j) + poissonReg)
return gaugeFieldFFTArr
# Same deal as the above method, we have to define it outside the class so
# numba doesn't get confused
@numba.jit(nopython=True, cache=True)
def _calculateWilsonLineOpt(N, Ny, colorCharges, basis, gaugeField):
r"""
Calculate the elements of the wilson line.
This method is optimized using numba.
"""
wilsonLine = np.zeros((N, N, colorCharges, colorCharges), dtype='complex')
gluonDOF = colorCharges**2 - 1
# Slightly different ordering of indexing than in other places in the code,
# due to the fact that we have to sum of the gluonDOF and Ny axis
for i in range(N):
for j in range(N):
# Create the unit matrix for each point since we are multiplying
# the wilson line as we go (so we need to start with the identity)
for c in range(colorCharges):
wilsonLine[i,j,c,c] = 1
# The path ordered exponential becomes a product of exponentials for each layer
for l in range(Ny):
# Evaluate the argument of the exponential first
# We multiply the elements of the gauge field for each gluon degree of freedom
# by the respective basis matrix and sum them together
expArgument = np.zeros((colorCharges, colorCharges), dtype='complex') # Same shape as basis matrices
for k in range(gluonDOF):
expArgument = expArgument + gaugeField[l,k,i,j] * basis[k]
# Now actually calculate the exponential with our custom defined expm method
# that can properly interface with numba (scipy's can't)
exponential = np.ascontiguousarray(expm(-1.j * expArgument))
wilsonLine[i,j] = np.dot(wilsonLine[i,j], exponential)
return wilsonLine
@numba.jit(nopython=True, cache=True)
def _calculateAdjointWilsonLineOpt(gluonDOF, N, basis, wilsonLine):
r"""
Calculate the wilson line in the adjoint representation.
This method is optimized using numba
"""
# Wilson line is always real in adjoint representation, so need to dtype='complex' as with the others
adjointWilsonLine = np.zeros((gluonDOF, gluonDOF, N, N), dtype='double')
for a in range(gluonDOF):
for b in range(gluonDOF):
for i in range(N):
for j in range(N):
V = wilsonLine[i,j]
Vdag = np.conjugate(np.transpose(V))
adjointWilsonLine[a,b,i,j] = 2 * np.real(np.trace(np.dot(np.dot(basis[a], V), np.dot(basis[b], Vdag))))
return adjointWilsonLine
Classes
class Nucleus (colorCharges, N, delta, mu, M=0.5, g=1, Ny=100, rngSeed=None)
-
Dense object to be used in an instance of
Collision
.Implements calculation of the Wilson Line using the generalized basis matrix set.
Parameters
colorCharges
:positive integer
- The number of possible color charges; also the dimensionality of the special unitary group.
N
:positive integer
- The size of the square lattice to simulate.
delta
:positive float
- The distance between adjacent lattice sites.
mu
:positive float
- The scaling for the random gaussian distribution that generates the color charge density.
M
:float (default=.5)
- Infrared regulator parameter to regularize the Poisson equation for the gauge field.
g
:float (default=1)
- Parameter in the Poisson equation for the gauge field.
Ny
:positive integer (default=100)
- The longitudinal extent (in layers) of the nucleus object.
rngSeed
:int (default=None)
- Seed for the random number generator to initialize the color charge field
Expand source code
class Nucleus(Wavefunction): # Upon calling wilsonLine() or adjointWilsonLine(), these are properly defined _wilsonLine = None _adjointWilsonLine = None # Some variables to keep track of what has been calculated/generated so far # allowing us to avoid redundant computations _wilsonLineExists = False _adjointWilsonLineExists = False def __init__(self, colorCharges, N, delta, mu, M=.5, g=1, Ny=100, rngSeed=None): r""" Dense object to be used in an instance of `cgc.Collision.Collision`. Implements calculation of the Wilson Line using the generalized basis matrix set. Parameters ---------- colorCharges : positive integer The number of possible color charges; also the dimensionality of the special unitary group. N : positive integer The size of the square lattice to simulate. delta : positive float The distance between adjacent lattice sites. mu : positive float The scaling for the random gaussian distribution that generates the color charge density. M : float (default=.5) Infrared regulator parameter to regularize the Poisson equation for the gauge field. g : float (default=1) Parameter in the Poisson equation for the gauge field. Ny : positive integer (default=100) The longitudinal extent (in layers) of the nucleus object. rngSeed : int (default=None) Seed for the random number generator to initialize the color charge field """ super().__init__(colorCharges, N, delta, mu, M, g, rngSeed) # Super constructor self._basis = get_basis(colorCharges) self.Ny = Ny # Modify the gaussian width to account for the multiple longitudinal layers self.gaussianWidth = self.mu / self.delta / np.sqrt(self.Ny) def colorChargeField(self, forceCalculate=False): r""" Generates the color charge density field according to a gaussian distribution. Differs from super class implementation in that it generates the numerous fields according to `Ny`. That is, the field \(\rho\) satisfies: $$ \langle \rho_{a}^{(t)}(i^-,\vec i_{\perp}) \rho_{b}^{(t)}({j^-},\vec j_{\perp}) \rangle = g^2\mu_t^2 \frac{ 1 }{N_y \Delta^2} ~\delta_{ab}~\delta_{i_{\perp,1}\ j_{\perp,1}}~\delta_{i_{\perp,2} \ j_{\perp,2}} ~\delta_{i^- \ {j^-}} $$ If the field already exists, it is simply returned and no calculation is done. Parameters ---------- forceCalculate : bool (default=False) If the quantity has previously been calculated, the calculation will not be done again unless this argument is set to True. Returns ------- colorChargeField : array(Ny, `colorCharges`**2 - 1, N, N) """ if self._colorChargeFieldExists and not forceCalculate: return self._colorChargeField # Randomly generate the intial color charge density using a gaussian distribution self._colorChargeField = self.rng.normal(scale=self.gaussianWidth, size=(self.Ny, self.gluonDOF, self.N, self.N)) # Make sure we don't regenerate this field since it already exists on future calls self._colorChargeFieldExists = True return self._colorChargeField def gaugeField(self, forceCalculate=False): r""" Calculates the gauge field for all longitudinal layers and charge distributions by solving the (modified) Poisson equation involving the color charge field $$g \frac{1 } {\partial_\perp^2 - m^2 } \rho_a(i^-, \vec {i}_\perp )$$ via Fourier method. If the field already exists, it is simply returned and no calculation is done. Parameters ---------- forceCalculate : bool (default=False) If the quantity has previously been calculated, the calculation will not be done again unless this argument is set to True. Returns ------- gaugeField : array(Ny, `colorCharges`**2 - 1, N, N) """ if self._gaugeFieldExists and not forceCalculate: return self._gaugeField # Make sure the charge field has already been generated (if not, this will generate it) self.colorChargeField() # Compute the fourier transform of the charge field # Note that the normalization is set to 'backward', which for scipy means that the # ifft2 is scaled by 1/n (where n = N^2) chargeDensityFFTArr = fft2(self._colorChargeField, axes=(-2,-1), norm='backward') # Absorb the numerator constants in the equation above into the charge density chargeDensityFFTArr = -self.delta2 * self.g / 2 * chargeDensityFFTArr # Calculate the individual elements of the gauge field in fourier space # Note here that we have to solve the gauge field for each layer and for each gluon degree of freedom # This method is defined at the bottom of this file; see there for more information gaugeFieldFFTArr = _calculateGaugeFFTOpt(self.gluonDOF, self.N, self.Ny, self.poissonReg, chargeDensityFFTArr); # Take the inverse fourier transform to get the actual gauge field self._gaugeField = np.real(ifft2(gaugeFieldFFTArr, axes=(-2, -1), norm='backward')) # Make sure this process isn't repeated unnecessarily by denoting that it has been done self._gaugeFieldExists = True return self._gaugeField def wilsonLine(self, forceCalculate=False): """ Calculate the Wilson line using the gauge field and the appropriate basis matrices. If the line already exists, it is simply returned and no calculation is done. Parameters ---------- forceCalculate : bool (default=False) If the quantity has previously been calculated, the calculation will not be done again unless this argument is set to True. Returns ------- wilsonLine : array(N, N, `colorCharges`) """ if self._wilsonLineExists and not forceCalculate: return self._wilsonLine # Make sure the gauge field has already been calculated self.gaugeField() # We now combine all of the longitudinal layers into the single wilson line # Optimized method is defined at the end of this file; see there for more information self._wilsonLine = _calculateWilsonLineOpt(self.N, self.Ny, self.colorCharges, self._basis, self._gaugeField) self._wilsonLineExists = True return self._wilsonLine def adjointWilsonLine(self, forceCalculate=False): """ Calculate the Wilson line in the adjoint representation. If the line already exists, it is simply returned and no calculation is done. Parameters ---------- forceCalculate : bool (default=False) If the quantity has previously been calculated, the calculation will not be done again unless this argument is set to True. Returns ------- adjointWilsonLine : array(`colorCharges`**2 - 1, `colorCharges`**2 - 1, N, N) """ if self._adjointWilsonLineExists and not forceCalculate: return self._adjointWilsonLine # Make sure the wilson line has already been calculated if not self._wilsonLineExists: self.wilsonLine() # Calculation is optimized with numba, as with the previous calculations # See bottom of the file for more information self._adjointWilsonLine = _calculateAdjointWilsonLineOpt(self.gluonDOF, self.N, self._basis, self._wilsonLine) self._adjointWilsonLineExists = True return self._adjointWilsonLine
Ancestors
Methods
def adjointWilsonLine(self, forceCalculate=False)
-
Calculate the Wilson line in the adjoint representation.
If the line already exists, it is simply returned and no calculation is done.
Parameters
forceCalculate
:bool (default=False)
- If the quantity has previously been calculated, the calculation will not be done again unless this argument is set to True.
Returns
adjointWilsonLine
:array(
colorCharges**2 - 1,
colorCharges**2 - 1, N, N)
Expand source code
def adjointWilsonLine(self, forceCalculate=False): """ Calculate the Wilson line in the adjoint representation. If the line already exists, it is simply returned and no calculation is done. Parameters ---------- forceCalculate : bool (default=False) If the quantity has previously been calculated, the calculation will not be done again unless this argument is set to True. Returns ------- adjointWilsonLine : array(`colorCharges`**2 - 1, `colorCharges`**2 - 1, N, N) """ if self._adjointWilsonLineExists and not forceCalculate: return self._adjointWilsonLine # Make sure the wilson line has already been calculated if not self._wilsonLineExists: self.wilsonLine() # Calculation is optimized with numba, as with the previous calculations # See bottom of the file for more information self._adjointWilsonLine = _calculateAdjointWilsonLineOpt(self.gluonDOF, self.N, self._basis, self._wilsonLine) self._adjointWilsonLineExists = True return self._adjointWilsonLine
def colorChargeField(self, forceCalculate=False)
-
Generates the color charge density field according to a gaussian distribution. Differs from super class implementation in that it generates the numerous fields according to
Ny
. That is, the field \rho satisfies:\langle \rho_{a}^{(t)}(i^-,\vec i_{\perp}) \rho_{b}^{(t)}({j^-},\vec j_{\perp}) \rangle = g^2\mu_t^2 \frac{ 1 }{N_y \Delta^2} ~\delta_{ab}~\delta_{i_{\perp,1}\ j_{\perp,1}}~\delta_{i_{\perp,2} \ j_{\perp,2}} ~\delta_{i^- \ {j^-}}
If the field already exists, it is simply returned and no calculation is done.
Parameters
forceCalculate
:bool (default=False)
- If the quantity has previously been calculated, the calculation will not be done again unless this argument is set to True.
Returns
colorChargeField
:array(Ny,
colorCharges**2 - 1, N, N)
Expand source code
def colorChargeField(self, forceCalculate=False): r""" Generates the color charge density field according to a gaussian distribution. Differs from super class implementation in that it generates the numerous fields according to `Ny`. That is, the field \(\rho\) satisfies: $$ \langle \rho_{a}^{(t)}(i^-,\vec i_{\perp}) \rho_{b}^{(t)}({j^-},\vec j_{\perp}) \rangle = g^2\mu_t^2 \frac{ 1 }{N_y \Delta^2} ~\delta_{ab}~\delta_{i_{\perp,1}\ j_{\perp,1}}~\delta_{i_{\perp,2} \ j_{\perp,2}} ~\delta_{i^- \ {j^-}} $$ If the field already exists, it is simply returned and no calculation is done. Parameters ---------- forceCalculate : bool (default=False) If the quantity has previously been calculated, the calculation will not be done again unless this argument is set to True. Returns ------- colorChargeField : array(Ny, `colorCharges`**2 - 1, N, N) """ if self._colorChargeFieldExists and not forceCalculate: return self._colorChargeField # Randomly generate the intial color charge density using a gaussian distribution self._colorChargeField = self.rng.normal(scale=self.gaussianWidth, size=(self.Ny, self.gluonDOF, self.N, self.N)) # Make sure we don't regenerate this field since it already exists on future calls self._colorChargeFieldExists = True return self._colorChargeField
def gaugeField(self, forceCalculate=False)
-
Calculates the gauge field for all longitudinal layers and charge distributions by solving the (modified) Poisson equation involving the color charge field
g \frac{1 } {\partial_\perp^2 - m^2 } \rho_a(i^-, \vec {i}_\perp )
via Fourier method.
If the field already exists, it is simply returned and no calculation is done.
Parameters
forceCalculate
:bool (default=False)
- If the quantity has previously been calculated, the calculation will not be done again unless this argument is set to True.
Returns
gaugeField
:array(Ny,
colorCharges**2 - 1, N, N)
Expand source code
def gaugeField(self, forceCalculate=False): r""" Calculates the gauge field for all longitudinal layers and charge distributions by solving the (modified) Poisson equation involving the color charge field $$g \frac{1 } {\partial_\perp^2 - m^2 } \rho_a(i^-, \vec {i}_\perp )$$ via Fourier method. If the field already exists, it is simply returned and no calculation is done. Parameters ---------- forceCalculate : bool (default=False) If the quantity has previously been calculated, the calculation will not be done again unless this argument is set to True. Returns ------- gaugeField : array(Ny, `colorCharges`**2 - 1, N, N) """ if self._gaugeFieldExists and not forceCalculate: return self._gaugeField # Make sure the charge field has already been generated (if not, this will generate it) self.colorChargeField() # Compute the fourier transform of the charge field # Note that the normalization is set to 'backward', which for scipy means that the # ifft2 is scaled by 1/n (where n = N^2) chargeDensityFFTArr = fft2(self._colorChargeField, axes=(-2,-1), norm='backward') # Absorb the numerator constants in the equation above into the charge density chargeDensityFFTArr = -self.delta2 * self.g / 2 * chargeDensityFFTArr # Calculate the individual elements of the gauge field in fourier space # Note here that we have to solve the gauge field for each layer and for each gluon degree of freedom # This method is defined at the bottom of this file; see there for more information gaugeFieldFFTArr = _calculateGaugeFFTOpt(self.gluonDOF, self.N, self.Ny, self.poissonReg, chargeDensityFFTArr); # Take the inverse fourier transform to get the actual gauge field self._gaugeField = np.real(ifft2(gaugeFieldFFTArr, axes=(-2, -1), norm='backward')) # Make sure this process isn't repeated unnecessarily by denoting that it has been done self._gaugeFieldExists = True return self._gaugeField
def wilsonLine(self, forceCalculate=False)
-
Calculate the Wilson line using the gauge field and the appropriate basis matrices.
If the line already exists, it is simply returned and no calculation is done.
Parameters
forceCalculate
:bool (default=False)
- If the quantity has previously been calculated, the calculation will not be done again unless this argument is set to True.
Returns
wilsonLine
:array(N, N,
colorCharges)
Expand source code
def wilsonLine(self, forceCalculate=False): """ Calculate the Wilson line using the gauge field and the appropriate basis matrices. If the line already exists, it is simply returned and no calculation is done. Parameters ---------- forceCalculate : bool (default=False) If the quantity has previously been calculated, the calculation will not be done again unless this argument is set to True. Returns ------- wilsonLine : array(N, N, `colorCharges`) """ if self._wilsonLineExists and not forceCalculate: return self._wilsonLine # Make sure the gauge field has already been calculated self.gaugeField() # We now combine all of the longitudinal layers into the single wilson line # Optimized method is defined at the end of this file; see there for more information self._wilsonLine = _calculateWilsonLineOpt(self.N, self.Ny, self.colorCharges, self._basis, self._gaugeField) self._wilsonLineExists = True return self._wilsonLine