"""cosmomodels.py - Cosmological Model simulations
Provides classes for modelling cosmological inflationary scenarios.
Especially important classes are:
* :class:`FOCanonicalTwoStage` - drives first order calculation
* :class:`SOCanonicalThreeStage` - drives second order calculation
* :class:`CanonicalFirstOrder` - the class containing derivatives for first order calculation
* :class:`CanonicalRampedSecondOrder` - the class containing derivatives and ramped \
source term for second order calculation.
The :func:`make_wrapper_model` function takes a filename and returns a model instance
corresponding to the one stored in the file. This allows easier access to the
results than through a direct inspection of the HDF5 results file.
"""
#Author: Ian Huston
#For license and copyright information see LICENSE.txt which was distributed with this file.
from __future__ import division
#system modules
import numpy as np
import os.path
import datetime
from scipy import interpolate
import tables
import logging
#local modules from pyflation
from configuration import _debug
import cmpotentials
import rk4
import analysis
#Start logging
root_log_name = logging.getLogger().name
module_logger = logging.getLogger(root_log_name + "." + __name__)
#Profiling decorator when not using profiling.
if not "profile" in __builtins__:
[docs] def profile(f):
return f
[docs]class ModelError(StandardError):
"""Generic error for model simulating. Attributes include current results stack."""
pass
[docs]class CosmologicalModel(object):
"""Generic class for cosmological model simulations.
Contains run() method which chooses a solver and runs the simulation.
All cosmological model classes are subclassed from this one.
"""
solverlist = ["rkdriver_tsix"]
def __init__(self, ystart=None, simtstart=0.0, tstart=0.0, tstartindex=None,
tend=83.0, tstep_wanted=0.01, solver="rkdriver_tsix",
potential_func=None, pot_params=None, nfields=1, **kwargs):
"""Initialize model variables, some with default values.
Parameters
----------
ystart : array_like
initial values for y variables
simtstart : float, optional
initial overall time for simulation to start, default is 0.0.
tstart : array, optional
individual start times for each k mode, default is 0.0
tstartindex : array, optional
individual start time indices for each k mode, default is 0.0.
tend : float, optional
overall end time for simulation, default is 83.0
tstep_wanted : float, optional
size of time step to use in evolution, default is 0.01
solver : string, optional
the name of the rk4 driver function to use, default is "rkdriver_tsix"
potential_func : string, optional
the name of the potential function in cmpotentials,
default is msqphisq
pot_params : dict, optional
contains modifications to the default parameters in the potential,
default is empty dictionary.
nfields : int, optional
the number of fields in the model, default is 1.
"""
#Start logging
self._log = logging.getLogger('%s.%s' % (__name__, self.__class__.__name__))
self.ystart = ystart
self.k = getattr(self, "k", None) #so we can test whether k is set
if tstartindex is None:
self.tstartindex = np.array([0])
else:
self.tstartindex = tstartindex
if np.all(tstart < tend):
self.tstart, self.tend = tstart, tend
elif np.all(tstart==tend):
raise ValueError, "End time is the same as start time!"
else:
raise ValueError, "Ending time is before starting time!"
if np.all(simtstart < tend):
self.simtstart = simtstart
elif simtstart == tend:
raise ValueError("End time is same a simulation start time!")
else:
raise ValueError("End time is before simulation start time!")
self.tstep_wanted = tstep_wanted
if solver in self.solverlist:
self.solver = solver
else:
raise ValueError, "Solver not recognized!"
#Change potentials to be right function
if potential_func is None:
potential_func = "msqphisq"
self.potentials = cmpotentials.__getattribute__(potential_func)
self.potential_func = potential_func
#Set potential parameters to default to empty dictionary.
if pot_params is None:
pot_params = {}
self.pot_params = pot_params
#Set self.pot_params to argument
if not isinstance(pot_params, dict) and pot_params is not None:
raise ModelError("Need to provide pot_params as a dictionary of parameters.")
else:
self.pot_params = pot_params
#Set the number of fields using keyword argument, defaults to 1.
if nfields < 1:
raise ValueError("Cannot have zero or negative number of fields.")
else:
self.nfields = nfields
self.tresult = None #Will hold last time result
self.yresult = None #Will hold array of last y results
[docs] def derivs(self, yarray, t):
"""Return an array of derivatives of the dependent variables yarray at timestep t"""
pass
[docs] def potentials(self, y, pot_params=None):
"""Return a 4-tuple of potential, 1st, 2nd and 3rd derivs given y."""
pass
[docs] def findH(self,potential,y):
"""Return value of comoving Hubble variable given potential and y."""
pass
[docs] def run(self, saveresults=True):
"""Execute a simulation run using the parameters already provided."""
if self.solver not in self.solverlist:
raise ModelError("Unknown solver!")
if self.solver in ["rkdriver_tsix"]:
#set_trace()
#Loosely estimate number of steps based on requested step size
if not hasattr(self, "tstartindex"):
raise ModelError("Need to specify initial starting indices!")
if _debug:
self._log.debug("Starting simulation with %s.", self.solver)
solver = rk4.__getattribute__(self.solver)
try:
self.tresult, self.yresult = solver(ystart=self.ystart,
simtstart=self.simtstart,
tsix=self.tstartindex,
tend=self.tend,
allks=self.k,
h=self.tstep_wanted,
derivs=self.derivs)
except StandardError:
self._log.exception("Error running %s!", self.solver)
raise
#Aggregrate results and calling parameters into results list
self.lastparams = self.callingparams()
if saveresults:
try:
fname = self.saveallresults()
self._log.info("Results saved in " + fname)
except IOError, er:
self._log.error("Error trying to save results! Results NOT saved.\n" + er)
return
[docs] def callingparams(self):
"""Returns list of parameters to save with results."""
#Form dictionary of inputs
params = {"tstart":self.tstart,
"tend":self.tend,
"tstep_wanted":self.tstep_wanted,
"solver":self.solver,
"classname":self.__class__.__name__,
"datetime":datetime.datetime.now().strftime("%Y%m%d%H%M%S"),
"nfields":self.nfields
}
return params
[docs] def gethf5paramsdict(self):
"""Describes the fields required to save the calling parameters."""
params = {
"solver" : tables.StringCol(50),
"classname" : tables.StringCol(255),
"tstart" : tables.Float64Col(np.shape(self.tstart)),
"simtstart" : tables.Float64Col(),
"tend" : tables.Float64Col(),
"tstep_wanted" : tables.Float64Col(),
"datetime" : tables.Float64Col(),
"nfields" : tables.IntCol()
}
return params
[docs] def saveallresults(self, filename=None, filetype="hf5", yresultshape=None, **kwargs):
"""Saves results already calculated into a file."""
now = self.lastparams["datetime"]
if not filename:
filename = os.path.join(os.getcwd(), "run" + now + "." + filetype)
self._log.info("Filename set to " + filename)
if os.path.isdir(os.path.dirname(filename)):
if os.path.isfile(filename):
if _debug:
self._log.debug("File already exists! Using append data mode.")
filemode = "a"
else:
if _debug:
self._log.debug("File does not exist, using write mode.")
filemode = "w" #Writing to new file
else:
raise IOError("Directory %s does not exist" % os.path.dirname(filename))
if yresultshape is None:
yresultshape = list(self.yresult.shape)
yresultshape[0] = 0
#Check whether we should store ks and set group name accordingly
if self.k is None:
grpname = "bgresults"
else:
grpname = "results"
if filetype is "hf5":
try:
if filemode == "w":
rf = self.createhdf5structure(filename, grpname, yresultshape, **kwargs)
elif filemode == "a":
rf = tables.openFile(filename, filemode)
self.saveresultsinhdf5(rf, grpname)
except IOError:
raise
else:
raise NotImplementedError("Saving results in format %s is not implemented." % filetype)
return filename
[docs] def createhdf5structure(self, filename, grpname="results", yresultshape=None, hdf5complevel=2, hdf5complib="blosc"):
"""Create a new hdf5 file with the structure capable of holding results.
Parameters
----------
filename : string
Path including filename of file to create
grpname : string, optional
Name of the HDF5 group to create, default is "results"
yresultshape : tuple, optional
Shape of yresult variable to store
hdf5complevel : integer, optional
Compression level to use with PyTables, default 2.
hdf5complib : string, optional
Compression library to use with PyTables, default "blosc".
Returns
-------
rf : file handle
Handle of file created
"""
try:
rf = tables.openFile(filename, "w")
# Select which compression library to use in configuration
filters = tables.Filters(complevel=hdf5complevel, complib=hdf5complib)
#Create groups required
resgroup = rf.createGroup(rf.root, grpname, "Results of simulation")
tresarr = rf.createEArray(resgroup, "tresult",
tables.Float64Atom(),
(0,), #Shape of a single atom
filters=filters,
expectedrows=8194)
paramstab = rf.createTable(resgroup, "parameters",
self.gethf5paramsdict(),
filters=filters)
#Add in potential parameters pot_params as a table
potparamsshape = {"name": tables.StringCol(255),
"value": tables.Float64Col()}
potparamstab = rf.createTable(resgroup, "pot_params",
potparamsshape, filters=filters)
#Need to check if results are k dependent
if grpname is "results":
if hasattr(self, "bgmodel"):
#Store bg results:
bggrp = rf.createGroup(rf.root, "bgresults", "Background results")
bgtrarr = rf.createArray(bggrp, "tresult", self.bgmodel.tresult)
bgyarr = rf.createArray(bggrp, "yresult", self.bgmodel.yresult)
#Save results
yresarr = rf.createEArray(resgroup, "yresult", tables.ComplexAtom(itemsize=16), yresultshape, filters=filters, expectedrows=8194)
karr = rf.createArray(resgroup, "k", self.k)
ystartarr = rf.createArray(resgroup, "ystart", self.ystart)
if hasattr(self, "bgystart"):
bgystartarr = rf.createArray(resgroup, "bgystart", self.bgystart)
if hasattr(self, "foystart"):
foystarr = rf.createArray(resgroup, "foystart", self.foystart)
fotstarr = rf.createArray(resgroup, "fotstart", self.fotstart)
fotsxarr = rf.createArray(resgroup, "fotstartindex", self.fotstartindex)
else:
#Only make bg results array
yresarr = rf.createEArray(resgroup, "yresult", tables.Float64Atom(), yresultshape, filters=filters, expectedrows=8300)
except IOError:
raise
return rf
[docs] def saveresultsinhdf5(self, rf, grpname="results"):
"""Save simulation results in a HDF5 format file with filename.
Parameters
----------
rf : filelike
File to save results in
grpname : string, optional
Name of the HDF5 group to create in the file
"""
try:
#Get tables and array handles
resgrp = rf.getNode(rf.root, grpname)
#Now save data
#Save parameters
paramstab = resgrp.parameters
paramstabrow = paramstab.row
params = self.callingparams()
for key in params:
paramstabrow[key] = params[key]
paramstabrow.append() #Add to table
paramstab.flush()
#Save potential parameters
potparamstab = resgrp.pot_params
potparamsrow = potparamstab.row
for key in self.pot_params:
potparamsrow["name"] = key
potparamsrow["value"] = self.pot_params[key]
potparamsrow.append()
potparamstab.flush()
#Get yresult array handle
yresarr = resgrp.yresult
yresarr.append(self.yresult)
#Save tresults
tresarr = resgrp.tresult
tresarr.append(self.tresult)
#Flush saved results to file
rf.flush()
#Close file
rf.close()
#Log success
if _debug:
self._log.debug("Successfully wrote results to file " + rf.filename)
except IOError:
raise
[docs]class TestModel(CosmologicalModel):
"""Test class defining a very simple function"""
def __init__(self, ystart=np.array([1.0,1.0]), tstart=0.0, tend=1.0, tstep_wanted=0.01):
CosmologicalModel.__init__(self, ystart, tstart, tend, tstep_wanted)
[docs] def derivs(self, y, t, **kwargs):
"""Very simple set of ODEs"""
dydx = np.zeros(2)
dydx[0] = y[1]
dydx[1] = y[0]
return dydx
[docs]class BasicBgModel(CosmologicalModel):
"""Basic model with background equations
Array of dependent variables y is given by:
y[0] - phi_0 : Background inflaton
y[1] - dphi_0/deta : First deriv of \phi
y[2] - a : Scale Factor
"""
def __init__(self, ystart=np.array([0.1,0.1,0.1]), tstart=0.0, tend=120.0,
tstep_wanted=0.02, solver="rkdriver_tsix"):
CosmologicalModel.__init__(self, ystart, tstart, tend, tstep_wanted, solver=solver)
#Mass of inflaton in Planck masses
self.mass = 1.0
[docs] def potentials(self, y, pot_params=None):
"""Return value of potential at y, along with first and second derivs."""
#Use inflaton mass
mass2 = self.mass**2
#potential U = 1/2 m^2 \phi^2
U = 0.5*(mass2)*(y[0]**2)
#deriv of potential wrt \phi
dUdphi = (mass2)*y[0]
#2nd deriv
d2Udphi2 = mass2
#3rd deriv
d3Udphi3 = 0
return U, dUdphi, d2Udphi2, d3Udphi3
[docs] def derivs(self, y, t, **kwargs):
"""Basic background equations of motion.
dydx[0] = dy[0]/d\eta etc"""
#get potential from function
U, dUdphi, d2Udphi2 = self.potentials(y)[0:3]
#factor in eom [1/3 a^2 U_0]^{1/2}
Ufactor = np.sqrt((1.0/3.0)*(y[2]**2)*U)
#Set derivatives
dydx = np.zeros(3)
#d\phi_0/d\eta = y_1
dydx[0] = y[1]
#dy_1/d\eta = -2
dydx[1] = -2*Ufactor*y[1] - (y[2]**2)*dUdphi
#da/d\eta = [1/3 a^2 U_0]^{1/2}*a
dydx[2] = Ufactor*y[2]
return dydx
[docs]class PhiModels(CosmologicalModel):
"""Parent class for models implementing the scheme in Malik 06[astro-ph/0610864]"""
def __init__(self, *args, **kwargs):
"""Call superclass init method."""
super(PhiModels, self).__init__(*args, **kwargs)
[docs] def findH(self, U, y):
"""Return value of Hubble variable, H at y for given potential."""
phidot = y[self.phidots_ix]
#Expression for H
H = np.sqrt(U/(3.0-0.5*(np.sum(phidot**2))))
return H
[docs] def potentials(self, y, pot_params=None):
"""Return value of potential at y, along with first and second derivs."""
pass
[docs] def findinflend(self):
"""Find the efold time where inflation ends,
i.e. the hubble flow parameter epsilon >1.
Returns tuple of endefold and endindex (in tresult)."""
self.epsilon = self.getepsilon()
if not any(self.epsilon>1):
raise ModelError("Inflation did not end during specified number of efoldings. Increase tend and try again!")
endindex = np.where(self.epsilon>=1)[0][0]
#Interpolate results to find more accurate endpoint
tck = interpolate.splrep(self.tresult[:endindex], self.epsilon[:endindex])
t2 = np.linspace(self.tresult[endindex-1], self.tresult[endindex], 100)
y2 = interpolate.splev(t2, tck)
endindex2 = np.where(y2>1)[0][0]
#Return efold of more accurate endpoint
endefold = t2[endindex2]
return endefold, endindex
[docs] def getepsilon(self):
"""Return an array of epsilon = -\dot{H}/H values for each timestep."""
#Find Hdot
if len(self.yresult.shape) == 3:
phidots = self.yresult[:,self.phidots_ix,0]
else:
phidots = self.yresult[:,self.phidots_ix]
#Make sure to do sum across only phidot axis (1 in this case)
epsilon = 0.5*np.sum(phidots**2, axis=1)
return epsilon
[docs]class CanonicalBackground(PhiModels):
"""Basic model with background equations in terms of n
Array of dependent variables y is given by:
y[0] - \phi_0 : Background inflaton
y[1] - d\phi_0/dn : First deriv of \phi
y[2] - H: Hubble parameter
"""
def __init__(self, *args, **kwargs):
"""Initialize variables and call superclass"""
super(CanonicalBackground, self).__init__(*args, **kwargs)
#Set field indices. These can be used to select only certain parts of
#the y variable, e.g. y[self.bg_ix] is the array of background values.
self.H_ix = self.nfields*2
self.bg_ix = slice(0,self.nfields*2+1)
self.phis_ix = slice(0,self.nfields*2,2)
self.phidots_ix = slice(1,self.nfields*2,2)
#Set initial H value if None
if np.all(self.ystart[self.H_ix] == 0.0):
U = self.potentials(self.ystart, self.pot_params)[0]
self.ystart[self.H_ix] = self.findH(U, self.ystart)
[docs] def derivs(self, y, t, **kwargs):
"""Basic background equations of motion.
dydx[0] = dy[0]/dn etc"""
#get potential from function
U, dUdphi = self.potentials(y, self.pot_params)[0:2]
#Set derivatives
dydx = np.zeros_like(y)
#d\phi_0/dn = y_1
dydx[self.phis_ix] = y[self.phidots_ix]
#dphi^prime/dn
dydx[self.phidots_ix] = -(U*y[self.phidots_ix] + dUdphi[...,np.newaxis])/(y[self.H_ix]**2)
#dH/dn
dydx[self.H_ix] = -0.5*(np.sum(y[self.phidots_ix]**2, axis=0))*y[self.H_ix]
return dydx
[docs]class CanonicalFirstOrder(PhiModels):
"""First order model using efold as time variable with multiple fields.
nfields holds the number of fields and the yresult variable is then laid
out as follows:
yresult[0:nfields*2] : background fields and derivatives
yresult[nfields*2] : Hubble variable H
yresult[nfields*2 + 1:] : perturbation fields and derivatives
"""
def __init__(self, k=None, ainit=None, *args, **kwargs):
"""Initialize variables and call superclass"""
super(CanonicalFirstOrder, self).__init__(*args, **kwargs)
if ainit is None:
#Don't know value of ainit yet so scale it to 1
self.ainit = 1
else:
self.ainit = ainit
#Let k roam for a start if not given
if k is None:
self.k = 10**(np.arange(10.0)-8)
else:
self.k = k
#Set the field indices to use
self.setfieldindices()
#Initial conditions for each of the variables.
if self.ystart is None:
self.ystart= np.array([18.0,-0.1]*self.nfields + [0.0] + [1.0,0.0]*self.nfields)
#Set initial H value if None
if np.all(self.ystart[self.H_ix] == 0.0):
U = self.potentials(self.ystart, self.pot_params)[0]
self.ystart[self.H_ix] = self.findH(U, self.ystart)
[docs] def setfieldindices(self):
"""Set field indices. These can be used to select only certain parts of
the y variable, e.g. y[self.bg_ix] is the array of background values."""
self.H_ix = self.nfields * 2
self.bg_ix = slice(0, self.nfields * 2 + 1)
self.phis_ix = slice(0, self.nfields * 2, 2)
self.phidots_ix = slice(1, self.nfields * 2, 2)
self.pert_ix = slice(self.nfields * 2 + 1, None)
self.dps_ix = slice(self.nfields * 2 + 1, None, 2)
self.dpdots_ix = slice(self.nfields * 2 + 2, None, 2)
return
[docs] def derivs(self, y, t, **kwargs):
"""Return derivatives of fields in y at time t."""
#If k not given select all
if "k" not in kwargs or kwargs["k"] is None:
k = self.k
else:
k = kwargs["k"]
#Set up variables
phidots = y[self.phidots_ix]
lenk = len(k)
#Get a
a = self.ainit*np.exp(t)
H = y[self.H_ix]
nfields = self.nfields
#get potential from function
U, dUdphi, d2Udphi2 = self.potentials(y[self.bg_ix,0], self.pot_params)[0:3]
#Set derivatives taking care of k type
if type(k) is np.ndarray or type(k) is list:
dydx = np.zeros((2*nfields**2 + 2*nfields + 1,lenk), dtype=y.dtype)
innerterm = np.zeros((nfields,nfields,lenk), dtype=y.dtype)
else:
dydx = np.zeros(2*nfields**2 + 2*nfields + 1, dtype=y.dtype)
innerterm = np.zeros((nfields,nfields), y.dtype)
#d\phi_0/dn = y_1
dydx[self.phis_ix] = phidots
#dphi^prime/dn
dydx[self.phidots_ix] = -(U*phidots+ dUdphi[...,np.newaxis])/(H**2)
#dH/dn Do sum over fields not ks so use axis=0
dydx[self.H_ix] = -0.5*(np.sum(phidots**2, axis=0))*H
#d\delta \phi_I / dn
dydx[self.dps_ix] = y[self.dpdots_ix]
#Set up delta phis in nfields*nfields array
dpmodes = y[self.dps_ix].reshape((nfields, nfields, lenk))
#This for loop runs over i,j and does the inner summation over l
for i in range(nfields):
for j in range(nfields):
#Inner loop over fields
for l in range(nfields):
innerterm[i,j] += (d2Udphi2[i,l] + (phidots[i]*dUdphi[l]
+ dUdphi[i]*phidots[l]
+ phidots[i]*phidots[l]*U))*dpmodes[l,j]
#Reshape this term so that it is nfields**2 long
innerterm = innerterm.reshape((nfields**2,lenk))
#d\deltaphi_1^prime/dn
dydx[self.dpdots_ix] = -(U * y[self.dpdots_ix]/H**2 + (k/(a*H))**2 * y[self.dps_ix]
+ innerterm/H**2)
return dydx
[docs]class CanonicalSecondOrder(PhiModels):
"""Second order model using efold as time variable.
y[0] - \delta\varphi_2 : Second order perturbation [Real Part]
y[1] - \delta\varphi_2^\prime : Derivative of second order perturbation [Real Part]
y[2] - \delta\varphi_2 : Second order perturbation [Imag Part]
y[3] - \delta\varphi_2^\prime : Derivative of second order perturbation [Imag Part]
"""
def __init__(self, k=None, ainit=None, *args, **kwargs):
"""Initialize variables and call superclass"""
super(CanonicalSecondOrder, self).__init__(*args, **kwargs)
if ainit is None:
#Don't know value of ainit yet so scale it to 1
self.ainit = 1
else:
self.ainit = ainit
#Let k roam for a start if not given
if k is None:
self.k = 10**(np.arange(10.0)-8)
else:
self.k = k
#Initial conditions for each of the variables.
if self.ystart is None:
self.ystart = np.array([0.0,0.0,0.0,0.0])
[docs] def derivs(self, y, t, **kwargs):
"""Equation of motion for second order perturbations including source term"""
if _debug:
self._log.debug("args: %s", str(kwargs))
#If k not given select all
if "k" not in kwargs or kwargs["k"] is None:
k = self.k
nokix = True
kix = np.arange(len(k))
else:
k = kwargs["k"]
kix = kwargs["kix"]
if kix is None:
raise ModelError("Need to specify kix in order to calculate 2nd order perturbation!")
fotix = np.int(np.around((t - self.second_stage.simtstart)/self.second_stage.tstep_wanted))
#debug logging
if _debug:
self._log.debug("t=%f, fo.tresult[tix]=%f, fotix=%f", t, self.second_stage.tresult[fotix], fotix)
#Get first order results for this time step
if nokix:
fovars = self.second_stage.yresult[fotix].copy()
src = self.source[fotix]
else:
fovars = self.second_stage.yresult[fotix].copy()[:,kix]
src = self.source[fotix][kix]
phi, phidot, H = fovars[0:3]
epsilon = self.second_stage.bgepsilon[fotix]
#Get source terms
srcreal, srcimag = src.real, src.imag
#get potential from function
U, dU, d2U, d3U = self.potentials(fovars, self.pot_params)[0:4]
#Set derivatives taking care of k type
if type(k) is np.ndarray or type(k) is list:
dydx = np.zeros((4,len(k)))
else:
dydx = np.zeros(4)
#Get a
a = self.ainit*np.exp(t)
#Real parts
#d\deltaphi_2/dn = y[1]
dydx[0] = y[1]
#d\deltaphi_2^prime/dn #
dydx[1] = (-(3 - epsilon)*y[1] - ((k/(a*H))**2)*y[0]
-(d2U/H**2 - 3*(phidot**2))*y[0] - srcreal)
#Complex \deltaphi_2
dydx[2] = y[3]
#Complex derivative
dydx[3] = (-(3 - epsilon)*y[3] - ((k/(a*H))**2)*y[2]
-(d2U/H**2 - 3*(phidot**2))*y[2] - srcimag)
return dydx
[docs]class CanonicalHomogeneousSecondOrder(PhiModels):
"""Second order homogeneous model using efold as time variable.
y[0] - \delta\varphi_2 : Second order perturbation [Real Part]
y[1] - \delta\varphi_2^\prime : Derivative of second order perturbation [Real Part]
y[2] - \delta\varphi_2 : Second order perturbation [Imag Part]
y[3] - \delta\varphi_2^\prime : Derivative of second order perturbation [Imag Part]
"""
def __init__(self, k=None, ainit=None, *args, **kwargs):
"""Initialize variables and call superclass"""
super(CanonicalHomogeneousSecondOrder, self).__init__(*args, **kwargs)
if ainit is None:
#Don't know value of ainit yet so scale it to 1
self.ainit = 1
else:
self.ainit = ainit
#Let k roam for a start if not given
if k is None:
self.k = 10**(np.arange(10.0)-8)
else:
self.k = k
#Initial conditions for each of the variables.
if self.ystart is None:
self.ystart = np.array([0.0,0.0,0.0,0.0])
[docs] def derivs(self, y, t, **kwargs):
"""Equation of motion for second order perturbations including source term"""
if _debug:
self._log.debug("args: %s", str(kwargs))
#If k not given select all
if "k" not in kwargs or kwargs["k"] is None:
k = self.k
kix = np.arange(len(k))
else:
k = kwargs["k"]
kix = kwargs["kix"]
if kix is None:
raise ModelError("Need to specify kix in order to calculate 2nd order perturbation!")
#Need t index to use first order data
if kwargs["tix"] is None:
raise ModelError("Need to specify tix in order to calculate 2nd order perturbation!")
else:
tix = kwargs["tix"]
#debug logging
if _debug:
self._log.debug("tix=%f, t=%f, fo.tresult[tix]=%f", tix, t, self.second_stage.tresult[tix])
#Get first order results for this time step
fovars = self.second_stage.yresult[tix].copy()[:,kix]
phi, phidot, H = fovars[0:3]
epsilon = self.second_stage.bgepsilon[tix]
#get potential from function
U, dU, d2U, d3U = self.potentials(fovars, self.pot_params)[0:4]
#Set derivatives taking care of k type
if type(k) is np.ndarray or type(k) is list:
dydx = np.zeros((4,len(k)))
else:
dydx = np.zeros(4)
#Get a
a = self.ainit*np.exp(t)
#Real parts
#d\deltaphi_2/dn = y[1]
dydx[0] = y[1]
#d\deltaphi_2^prime/dn #
dydx[1] = (-(3 - epsilon)*y[1] - ((k/(a*H))**2)*y[0]
-(d2U/H**2 - 3*(phidot**2))*y[0] )
#Complex \deltaphi_2
dydx[2] = y[3]
#Complex derivative
dydx[3] = (-(3 - epsilon)*y[3] - ((k/(a*H))**2)*y[2]
-(d2U/H**2 - 3*(phidot**2))*y[2] )
return dydx
[docs]class CanonicalRampedSecondOrder(PhiModels):
"""Second order model using efold as time variable.
y[0] - \delta\varphi_2 : Second order perturbation [Real Part]
y[1] - \delta\varphi_2^\prime : Derivative of second order perturbation [Real Part]
y[2] - \delta\varphi_2 : Second order perturbation [Imag Part]
y[3] - \delta\varphi_2^\prime : Derivative of second order perturbation [Imag Part]
"""
def __init__(self, k=None, ainit=None, *args, **kwargs):
"""Initialize variables and call superclass"""
super(CanonicalRampedSecondOrder, self).__init__(*args, **kwargs)
if ainit is None:
#Don't know value of ainit yet so scale it to 1
self.ainit = 1
else:
self.ainit = ainit
#Let k roam for a start if not given
if k is None:
self.k = 10**(np.arange(10.0)-8)
else:
self.k = k
#Initial conditions for each of the variables.
if self.ystart is None:
self.ystart = np.array([0.0,0.0,0.0,0.0])
#Ramp arguments in form
# Ramp = (tanh(a*(t - t_ic - b) + c))/d if abs(t-t_ic+b) < e
if "rampargs" not in kwargs:
self.rampargs = {"a": 15.0,
"b": 0.3,
"c": 1,
"d": 2,
"e": 1}
else:
self.rampargs = kwargs["rampargs"]
[docs] def derivs(self, y, t, **kwargs):
"""Equation of motion for second order perturbations including source term"""
#if _debug:
# self._log.debug("args: %s", str(kwargs))
#If k not given select all
if "k" not in kwargs or kwargs["k"] is None:
k = self.k
nokix = True
kix = np.arange(len(k))
else:
k = kwargs["k"]
kix = kwargs["kix"]
if kix is None:
raise ModelError("Need to specify kix in order to calculate 2nd order perturbation!")
fotix = np.int(np.around((t - self.second_stage.simtstart)/self.second_stage.tstep_wanted))
#debug logging
if _debug:
self._log.debug("t=%f, fo.tresult[tix]=%f, fotix=%f", t, self.second_stage.tresult[fotix], fotix)
#Get first order results for this time step
if nokix:
fovars = self.second_stage.yresult[fotix].copy()
src = self.source[fotix].copy()
else:
fovars = self.second_stage.yresult[fotix].copy()[:,kix]
src = self.source[fotix][kix].copy()
phi, phidot, H = fovars[0:3]
epsilon = self.second_stage.bgepsilon[fotix]
#Get source terms and multiply by ramp
if nokix:
tanharg = t - self.tstart - self.rampargs["b"]
else:
tanharg = t-self.tstart[kix] - self.rampargs["b"]
#When absolute value of tanharg is less than e then multiply source by ramp for those values.
if np.any(abs(tanharg)<self.rampargs["e"]):
#Calculate the ramp
ramp = (np.tanh(self.rampargs["a"]*tanharg) + self.rampargs["c"])/self.rampargs["d"]
#Get the second order timestep value
sotix = t / self.tstep_wanted
#Compare with tstartindex values. Set the ramp to zero for any that are equal
ramp[self.tstartindex==sotix] = 0
#Scale the source term by the ramp value.
needramp = abs(tanharg)<self.rampargs["e"]
if _debug:
self._log.debug("Limits of indices which need ramp are %s.", np.where(needramp)[0][[0,-1]])
src[needramp] = ramp[needramp]*src[needramp]
#Split source into real and imaginary parts.
srcreal, srcimag = src.real, src.imag
#get potential from function
U, dU, d2U, d3U = self.potentials(fovars, self.pot_params)[0:4]
#Set derivatives taking care of k type
if type(k) is np.ndarray or type(k) is list:
dydx = np.zeros((4,len(k)))
else:
dydx = np.zeros(4)
#Get a
a = self.ainit*np.exp(t)
#Real parts
#d\deltaphi_2/dn = y[1]
dydx[0] = y[1]
#d\deltaphi_2^prime/dn #
dydx[1] = (-(3 - epsilon)*y[1] - ((k/(a*H))**2)*y[0]
-(d2U/H**2 - 3*(phidot**2))*y[0] - srcreal)
#Complex \deltaphi_2
dydx[2] = y[3]
#Complex derivative
dydx[3] = (-(3 - epsilon)*y[3] - ((k/(a*H))**2)*y[2]
-(d2U/H**2 - 3*(phidot**2))*y[2] - srcimag)
return dydx
[docs]class MultiStageDriver(CosmologicalModel):
"""Parent of all multi (2 or 3) stage models. Contains methods to determine ns, k crossing and outlines
methods to find Pr that are implemented in children."""
def __init__(self, *args, **kwargs):
"""Initialize super class instance."""
super(MultiStageDriver, self).__init__(*args, **kwargs)
#Set constant factor for 1st order initial conditions
if "cq" in kwargs:
self.cq = kwargs["cq"]
else:
self.cq = 50 #Default value as in Salopek et al.
[docs] def find_efolds_after_inflation(self, Hend, Hreh=None):
"""Calculate the number of efolds after inflation given the reheating
temperature and assuming standard calculation of radiation and matter phases.
Parameters
----------
Hend : scalar, value of Hubble parameter at end of inflation
Hreh : scalar (default=Hend), value of Hubble parameter at end of reheating
Returns
-------
N : scalar, number of efolds after the end of inflation until today.
N = ln (a_today/a_end) where a_end is scale factor at end of inflation.
References
----------
See Huston, arXiv: 1006.5321,
Liddle and Lyth, Cambridge University Press 2000, or
Peiris and Easther, JCAP 0807 (2008) 024, arXiv:0805.2154,
for more details on calculation of post-inflation expansion.
"""
if Hreh is None:
Hreh = Hend #Instantaneous reheating
N_after = 72.3 + 2.0/3.0*np.log(Hend) - 1.0/6.0*np.log(Hreh)
return N_after
[docs] def finda_end(self, Hend, Hreh=None, a_0=1):
"""Given the Hubble parameter at the end of inflation and at the end of reheating
calculate the scale factor at the end of inflation.
This function assumes by default that the scale factor = 1 today and should be used with
caution. A more correct approach is to call find_efolds_after_inflation directly
and to use the result as required.
Parameters
----------
Hend : scalar
value of Hubble parameter at end of inflation.
Hreh : scalar, optional
value of Hubble parameter at end of reheating, default is Hend.
a_0 : scalar, optional
value of scale factor today, default is 1.
Returns
-------
a_end : scalar
scale factor at the end of inflation.
"""
N_after = self.find_efolds_after_inflation(Hend, Hreh)
a_end = a_0*np.exp(-N_after)
return a_end
[docs] def finda_0(self, Hend, Hreh=None, a_end=None):
"""Given the Hubble parameter at the end of inflation and at the end of reheating,
and the scale factor at the end of inflation, calculate the scale factor today.
Parameters
----------
Hend : scalar
value of Hubble parameter at end of inflation
Hreh : scalar, optional
value of Hubble parameter at end of reheating, default is Hend.
a_end : scalar, optional
value of scale factor at the end of inflation, default is calculated
from results.
Returns
-------
a_0 : scalar
scale factor today
"""
if a_end is None:
try:
a_end = self.ainit*np.exp(self.tresult[-1])
except TypeError:
raise ModelError("Simulation has not been run yet.")
N_after = self.find_efolds_after_inflation(Hend, Hreh)
a_0 = a_end*np.exp(N_after)
return a_0
[docs] def findkcrossing(self, k, t, H, factor=None):
"""Given k, time variable and Hubble parameter, find when mode k crosses the horizon.
Parameters
----------
k : float
Single k value to compute crossing time with
t : array
Array of time values
H : array
Array of values of the Hubble parameter
factor : float, optional
coefficient of crossing k = a*H*factor
Returns
-------
kcrindex, kcrefold : tuple
Tuple containing k cross index (in t variable) and the efold number
e.g. t[kcrindex]
"""
#threshold
err = 1.0e-26
if factor is None:
factor = self.cq #time before horizon crossing
#get aHs
if len(H.shape) > 1:
#Use only one dimensional H
H = H[:,0]
aH = self.ainit*np.exp(t)*H
try:
kcrindex = np.where(np.sign(k - (factor*aH))<0)[0][0]
except IndexError, ex:
raise ModelError("k mode " + str(k) + " crosses horizon after end of inflation!")
kcrefold = t[kcrindex]
return kcrindex, kcrefold
[docs] def findallkcrossings(self, t, H):
"""Iterate over findkcrossing to get full list
Parameters
----------
t : array
Array of t values to calculate over
H : array
Array of Hubble parameter values, should be the same shape as t
Returns
-------
kcrossings : array
Array of (kcrindex, kcrefold) pairs of index (in to t) and efold number
at which each k in self.k crosses the horizon (k=a*H).
"""
return np.array([self.findkcrossing(onek, t, H) for onek in self.k])
[docs] def findHorizoncrossings(self, factor=1):
"""Find horizon crossing time indices and efolds for all ks
Parameters
----------
factor : float
Value of coefficient to calculate crossing time, k=a*H*factor
Returns
-------
hcrossings : array
Array of (kcrindex, kcrefold) pairs of time index
and efold number pairs
"""
return np.array([self.findkcrossing(onek, self.tresult, oneH, factor) for onek, oneH in zip(self.k, np.rollaxis(self.yresult[:,2,:], -1,0))])
@property
[docs] def deltaphi(self, recompute=False):
"""Return the value of deltaphi for this model, recomputing if necessary."""
pass
@property
[docs] def Pr(self):
"""The power spectrum of comoving curvature perturbation.
This is the unscaled spectrum P_R calculated for all timesteps and ks.
Calculated using the pyflation.analysis package.
"""
return analysis.Pr(self)
@property
[docs] def Pzeta(self):
"""The power spectrum of the curvature perturbation on uniform energy
density hypersurfaces.
Calculated using the pyflation.analysis package.
"""
return analysis.Pzeta(self)
@property
[docs] def scaled_Pr(self):
"""The power spectrum of comoving curvature perturbation.
Calculated using the pyflation.analysis package.
"""
return analysis.scaled_Pr(self)
@property
[docs] def scaled_Pzeta(self):
"""The power spectrum of comoving curvature perturbation.
Calculated using the pyflation.analysis package.
"""
return analysis.scaled_Pzeta(self)
[docs] def getfoystart(self):
"""Return model dependent setting of ystart"""
pass
[docs] def callingparams(self):
"""Returns list of parameters to save with results."""
#Test whether k has been set
try:
self.k
except (NameError, AttributeError):
self.k=None
#Form dictionary of inputs
params = {"tstart":self.tstart,
"ainit":self.ainit,
"potential_func":self.potentials.__name__,
"tend":self.tend,
"tstep_wanted":self.tstep_wanted,
"solver":self.solver,
"classname":self.__class__.__name__,
"datetime":datetime.datetime.now().strftime("%Y%m%d%H%M%S"),
"nfields":self.nfields
}
return params
[docs] def gethf5paramsdict(self):
"""Describes the fields required to save the calling parameters."""
params = {
"solver" : tables.StringCol(50),
"classname" : tables.StringCol(255),
"tstart" : tables.Float64Col(np.shape(self.tstart)),
"simtstart" : tables.Float64Col(),
"ainit" : tables.Float64Col(),
"potential_func" : tables.StringCol(255),
"tend" : tables.Float64Col(),
"tstep_wanted" : tables.Float64Col(),
"datetime" : tables.Float64Col(),
"nfields" : tables.IntCol()
}
return params
[docs]class FOCanonicalTwoStage(MultiStageDriver):
"""Uses a background and firstorder class to run a full (first-order) simulation.
Main additional functionality is in determining initial conditions.
Variables finally stored are as in first order class.
"""
def __init__(self, bgystart=None, tstart=0.0, tstartindex=None, tend=83.0, tstep_wanted=0.01,
k=None, ainit=None, solver="rkdriver_tsix", bgclass=None, foclass=None,
potential_func=None, pot_params=None, simtstart=0, nfields=1, **kwargs):
"""Initialize model and ensure initial conditions are sane."""
#Initial conditions for each of the variables.
if bgystart is None:
self.bgystart = np.array([18.0/np.sqrt(nfields),-0.1/np.sqrt(nfields)]*nfields
+ [0.0])
else:
self.bgystart = bgystart
#Lengthen bgystart to add perturbed fields.
self.ystart= np.append(self.bgystart, [0.0,0.0]*nfields**2)
if not tstartindex:
self.tstartindex = np.array([0])
else:
self.tstartindex = tstartindex
#Call superclass
newkwargs = dict(ystart=self.ystart,
tstart=tstart,
tstartindex=self.tstartindex,
tend=tend,
tstep_wanted=tstep_wanted,
solver=solver,
potential_func=potential_func,
pot_params=pot_params,
nfields=nfields,
**kwargs)
super(FOCanonicalTwoStage, self).__init__(**newkwargs)
#Set the field indices
self.setfieldindices()
if ainit is None:
#Don't know value of ainit yet so scale it to 1
self.ainit = 1
else:
self.ainit = ainit
#Let k roam if we don't know correct ks
if k is None:
self.k = 10**(np.arange(7.0)-62)
else:
self.k = k
self.simtstart = simtstart
if bgclass is None:
self.bgclass = CanonicalBackground
else:
self.bgclass = bgclass
if foclass is None:
self.foclass = CanonicalFirstOrder
else:
self.foclass = foclass
#Setup model variables
self.bgmodel = self.firstordermodel = None
[docs] def setfieldindices(self):
"""Set field indices. These can be used to select only certain parts of
the y variable, e.g. y[self.bg_ix] is the array of background values."""
self.H_ix = self.nfields * 2
self.bg_ix = slice(0, self.nfields * 2 + 1)
self.phis_ix = slice(0, self.nfields * 2, 2)
self.phidots_ix = slice(1, self.nfields * 2, 2)
self.pert_ix = slice(self.nfields * 2 + 1, None)
self.dps_ix = slice(self.nfields * 2 + 1, None, 2)
self.dpdots_ix = slice(self.nfields * 2 + 2, None, 2)
return
[docs] def setfoics(self):
"""After a bg run has completed, set the initial conditions for the
first order run."""
#debug
#set_trace()
#Find initial conditions for 1st order model
#Find a_end using instantaneous reheating
#Need to change to find using splines
Hend = self.bgmodel.yresult[self.fotendindex, self.H_ix]
self.a_end = self.finda_end(Hend)
self.ainit = self.a_end*np.exp(-self.bgmodel.tresult[self.fotendindex])
#Find epsilon from bg model
try:
self.bgepsilon
except AttributeError:
self.bgepsilon = self.bgmodel.getepsilon()
#Set etainit, initial eta at n=0
self.etainit = -1/(self.ainit*self.bgmodel.yresult[0,self.H_ix]*(1-self.bgepsilon[0]))
#find k crossing indices
kcrossings = self.findallkcrossings(self.bgmodel.tresult[:self.fotendindex],
self.bgmodel.yresult[:self.fotendindex, self.H_ix])
kcrossefolds = kcrossings[:,1]
#If mode crosses horizon before t=0 then we will not be able to propagate it
if any(kcrossefolds==0):
raise ModelError("Some k modes crossed horizon before simulation began and cannot be initialized!")
#Find new start time from earliest kcrossing
self.fotstart, self.fotstartindex = kcrossefolds, kcrossings[:,0].astype(np.int)
self.foystart = self.getfoystart()
return
[docs] def runbg(self):
"""Run bg model after setting initial conditions."""
#Check ystart is in right form (1-d array of three values)
if len(self.ystart.shape) == 1:
ys = self.ystart[self.bg_ix]
elif len(self.ystart.shape) == 2:
ys = self.ystart[self.bg_ix,0]
#Choose tstartindex to be simply the first timestep.
tstartindex = np.array([0])
kwargs = dict(ystart=ys,
tstart=self.tstart,
tstartindex=tstartindex,
tend=self.tend,
tstep_wanted=self.tstep_wanted,
solver=self.solver,
potential_func=self.potential_func,
pot_params=self.pot_params,
nfields=self.nfields)
self.bgmodel = self.bgclass(**kwargs)
#Start background run
self._log.info("Running background model...")
try:
self.bgmodel.run(saveresults=False)
except ModelError:
self._log.exception("Error in background run, aborting!")
#Find end of inflation
self.fotend, self.fotendindex = self.bgmodel.findinflend()
self._log.info("Background run complete, inflation ended " + str(self.fotend) + " efoldings after start.")
return
[docs] def runfo(self):
"""Run first order model after setting initial conditions."""
#Initialize first order model
kwargs = dict(ystart=self.foystart,
tstart=self.fotstart,
simtstart=self.simtstart,
tstartindex = self.fotstartindex,
tend=self.fotend,
tstep_wanted=self.tstep_wanted,
solver=self.solver,
k=self.k,
ainit=self.ainit,
potential_func=self.potential_func,
pot_params=self.pot_params,
fields=self.nfields)
self.firstordermodel = self.foclass(**kwargs)
#Start first order run
self._log.info("Beginning first order run...")
try:
self.firstordermodel.run(saveresults=False)
except ModelError, er:
raise ModelError("Error in first order run, aborting! Message: " + er.message)
#Set results to current object
self.tresult, self.yresult = self.firstordermodel.tresult, self.firstordermodel.yresult
return
[docs] def run(self, saveresults=True):
"""Run the full model.
The background model is first run to establish the end time of inflation and the start
times for the k modes. Then the initial conditions are set for the first order variables.
Finally the first order model is run and the results are saved if required.
Parameters
----------
saveresults : boolean, optional
Should results be saved at the end of the run. Default is False.
Returns
-------
filename : string
name of the results file if any
"""
#Run bg model
self.runbg()
#Set initial conditions for first order model
self.setfoics()
#Run first order model
self.runfo()
#Save results in file
#Aggregrate results and calling parameters into results list
self.lastparams = self.callingparams()
if saveresults:
try:
self._log.info("Results saved in " + self.saveallresults())
except IOError, er:
self._log.exception("Error trying to save results! Results NOT saved.")
return
[docs] def getfoystart(self, ts=None, tsix=None):
"""Model dependent setting of ystart"""
if _debug:
self._log.debug("Executing getfoystart to get initial conditions.")
#Set variables in standard case:
if ts is None or tsix is None:
ts, tsix = self.fotstart, self.fotstartindex
#Reset starting conditions at new time
foystart = np.zeros(((2*self.nfields**2 + self.nfields*2 +1), len(self.k)), dtype=np.complex128)
#set_trace()
#Get values of needed variables at crossing time.
astar = self.ainit*np.exp(ts)
#Truncate bgmodel yresult down if there is an extra dimension
if len(self.bgmodel.yresult.shape) > 2:
bgyresult = self.bgmodel.yresult[..., 0]
else:
bgyresult = self.bgmodel.yresult
Hstar = bgyresult[tsix,self.H_ix]
Hzero = bgyresult[0,self.H_ix]
epsstar = self.bgepsilon[tsix]
etastar = -1/(astar*Hstar*(1-epsstar))
try:
etadiff = etastar - self.etainit
except AttributeError:
etadiff = etastar + 1/(self.ainit*Hzero*(1-self.bgepsilon[0]))
keta = self.k*etadiff
#Set bg init conditions based on previous bg evolution
try:
foystart[self.bg_ix] = bgyresult[tsix,:].transpose()
except ValueError:
foystart[self.bg_ix] = bgyresult[tsix,:][:, np.newaxis]
#Find 1/asqrt(2k)
arootk = 1/(astar*(np.sqrt(2*self.k)))
#Only want to set the diagonal elements of the mode matrix
#Use a.flat[::a.shape[1]+1] to set diagonal elements only
#In our case already flat so foystart[slice,:][::nfields+1]
#Set \delta\phi_1 initial condition
foystart[self.dps_ix,:][::self.nfields+1] = arootk*np.exp(-keta*1j)
#set \dot\delta\phi_1 ic
foystart[self.dpdots_ix,:][::self.nfields+1] = -arootk*np.exp(-keta*1j)*(1 + (self.k/(astar*Hstar))*1j)
return foystart
[docs] def getdeltaphi(self):
return self.deltaphi
[docs] def deltaphi(self, recompute=False):
"""Return the calculated values of $\delta\phi$ for all times, fields and modes.
For multifield systems this is the quantum matrix of solutions:
\hat{\delta\phi} = \Sum_{\alpha, I} xi_{\alpha I} \hat{a}_I
The result is stored as the instance variable m.deltaphi but will be recomputed
if `recompute` is True.
Parameters
----------
recompute : boolean, optional
Should the values be recomputed? Default is False.
Returns
-------
deltaphi : array_like, dtype: complex128
Array of $\delta\phi$ values for all timesteps, fields and k modes.
"""
if not hasattr(self, "_deltaphi") or recompute:
self._deltaphi = self.yresult[:,self.dps_ix,:]
return self._deltaphi
#Helper functions to access results variables
@property
[docs] def phis(self):
"""Background fields \phi_i"""
return self.yresult[:,self.phis_ix]
@property
[docs] def phidots(self):
"""Derivatives of background fields w.r.t N \phi_i^\dagger"""
return self.yresult[:,self.phidots_ix]
@property
[docs] def H(self):
"""Hubble parameter"""
return self.yresult[:,self.H_ix]
@property
[docs] def dpmodes(self):
"""Quantum modes of first order perturbations"""
return self.yresult[:,self.dps_ix]
@property
[docs] def dpdotmodes(self):
"""Quantum modes of derivatives of first order perturbations"""
return self.yresult[:,self.dpdots_ix]
@property
[docs] def a(self):
"""Scale factor of the universe"""
return self.ainit*np.exp(self.tresult)
[docs]def make_wrapper_model(modelfile, *args, **kwargs):
"""Return a wrapper class that provides the given model class from a file."""
#Check file exists
if not os.path.isfile(modelfile):
raise IOError("File does not exist!")
try:
rf = tables.openFile(modelfile, "r")
try:
try:
params = rf.root.results.parameters
modelclassname = params[0]["classname"]
except tables.NoSuchNodeError:
raise ModelError("File does not contain correct model data structure!")
finally:
rf.close()
except IOError:
raise
try:
modelclass = globals()[modelclassname]
except AttributeError:
raise ModelError("Model class does not exist!")
class ModelWrapper(modelclass):
"""Wraps first order model using HDF5 file of results."""
def __init__(self, filename, *args, **kwargs):
"""Get results from file and instantiate variables.
Opens file with handle saved as self._rf. File is closed in __del__"""
#Call super class __init__ method
super(ModelWrapper, self).__init__(*args, **kwargs)
#Check file exists
if not os.path.isfile(filename):
raise IOError("File does not exist!")
try:
if _debug:
self._log.debug("Opening file " + filename + " to read results.")
try:
self._rf = tables.openFile(filename, "r")
results = self._rf.root.results
self.yresult = results.yresult
self.tresult = results.tresult
if "bgystart" in results:
self.bgystart = results.bgystart
if "ystart" in results:
self.ystart = results.ystart
self.fotstart = results.fotstart
if "fotstartindex" in results:
#for backwards compatability only set if it exists
self.fotstartindex = results.fotstartindex
self.foystart = results.foystart
self.k = results.k[:]
params = results.parameters
except tables.NoSuchNodeError:
raise ModelError("File does not contain correct model data structure!")
try:
self.source = results.sourceterm
except tables.NoSuchNodeError:
if _debug:
self._log.debug("First order file does not have a source term.")
self.source = None
# Put potential parameters into right variable
try:
potparamstab = results.pot_params
for row in potparamstab:
key = row["name"]
val = row["value"]
self.pot_params[key] = val
except tables.NoSuchNodeError:
if _debug:
self._log.debug("No pot_params table in file.")
#Put params in right slots
for ix, val in enumerate(params[0]):
self.__setattr__(params.colnames[ix], val)
#set correct potential function (only works with cmpotentials currently)
self.potentials = cmpotentials.__getattribute__(self.potential_func)
except IOError:
raise
#Set indices correctly
self.H_ix = self.nfields*2
self.bg_ix = slice(0,self.nfields*2+1)
self.phis_ix = slice(0,self.nfields*2,2)
self.phidots_ix = slice(1,self.nfields*2,2)
self.pert_ix = slice(self.nfields*2+1, None)
self.dps_ix = slice(self.nfields*2+1, None, 2)
self.dpdots_ix = slice(self.nfields*2+2, None, 2)
#Fix bgmodel to actual instance
if self.ystart is not None:
#Check ystart is in right form (1-d array of three values)
if len(self.ystart.shape) == 1:
ys = self.ystart[self.bg_ix]
elif len(self.ystart.shape) == 2:
ys = self.ystart[self.bg_ix,0]
else:
ys = results.bgresults.yresult[0]
self.bgmodel = self.bgclass(ystart=ys, tstart=self.tstart, tend=self.tend,
tstep_wanted=self.tstep_wanted, solver=self.solver,
potential_func=self.potential_func,
nfields=self.nfields, pot_params=self.pot_params)
#Put in data
try:
if _debug:
self._log.debug("Trying to get background results...")
self.bgmodel.tresult = self._rf.root.bgresults.tresult[:]
self.bgmodel.yresult = self._rf.root.bgresults.yresult
except tables.NoSuchNodeError:
raise ModelError("File does not contain background results!")
#Get epsilon
if _debug:
self._log.debug("Calculating self.bgepsilon...")
self.bgepsilon = self.bgmodel.getepsilon()
#Success
self._log.info("Successfully imported data from file into model instance.")
def __del__(self):
"""Close file when object destroyed."""
try:
if _debug:
self._log.debug("Trying to close file...")
self._rf.close()
except IOError:
raise
return ModelWrapper(modelfile, *args, **kwargs)
[docs]class SOCanonicalThreeStage(MultiStageDriver):
"""Runs third stage calculation (typically second order perturbations) using
a two stage model instance which could be wrapped from a file."""
def __init__(self, second_stage, soclass=None, ystart=None, **soclassargs):
"""Initialize variables and check that tsmodel exists and is correct form."""
#Test whether tsmodel is of correct type
if not isinstance(second_stage, FOCanonicalTwoStage):
raise ModelError("Need to provide a FOCanonicalTwoStage instance to get first order results from!")
else:
self.second_stage = second_stage
#Set properties to be those of second stage model
self.k = np.copy(self.second_stage.k)
self.simtstart = self.second_stage.tresult[0]
self.fotstart = np.copy(self.second_stage.fotstart)
self.fotstartindex = np.copy(self.second_stage.fotstartindex)
self.ainit = self.second_stage.ainit
self.potentials = self.second_stage.potentials
self.potential_func = self.second_stage.potential_func
self.pot_params = self.second_stage.pot_params
self.nfields = self.second_stage.nfields
if ystart is None:
ystart = np.zeros((4, len(self.k)))
#Need to make sure that the tstartindex terms are changed over to new timestep.
fotstep = self.second_stage.tstep_wanted
sotstep = fotstep*2
sotstartindex = np.around(self.fotstartindex*(fotstep/sotstep) + sotstep/2).astype(np.int)
kwargs = dict(ystart=ystart,
tstart=self.second_stage.tresult[0],
tstartindex=sotstartindex,
simtstart=self.simtstart,
tend=self.second_stage.tresult[-1],
tstep_wanted=sotstep,
solver="rkdriver_tsix",
potential_func=self.second_stage.potential_func,
pot_params=self.second_stage.pot_params,
nfields=self.nfields
)
#Update sokwargs with any arguments from soclassargs
if soclassargs is not None:
kwargs.update(soclassargs)
#Call superclass
super(SOCanonicalThreeStage, self).__init__(**kwargs)
if soclass is None:
self.soclass = CanonicalSecondOrder
else:
self.soclass = soclass
self.somodel = None
#Set up source term
if _debug:
self._log.debug("Trying to set source term for second order model...")
self.source = self.second_stage.source[:]
if self.source is None:
raise ModelError("First order model does not have a source term!")
#Try to put yresult array in memory
self.second_stage.yresultarr = self.second_stage.yresult
self.second_stage.yresult = self.second_stage.yresultarr[:]
[docs] def setfieldindices(self):
"""Set field indices. These can be used to select only certain parts of
the y variable, e.g. y[self.bg_ix] is the array of background values."""
# Indices for use with self.second_stage.yresult
self.H_ix = self.nfields * 2
self.bg_ix = slice(0, self.nfields * 2 + 1)
self.phis_ix = slice(0, self.nfields * 2, 2)
self.phidots_ix = slice(1, self.nfields * 2, 2)
self.pert_ix = slice(self.nfields * 2 + 1, None)
self.dps_ix = slice(self.nfields * 2 + 1, None, 2)
self.dpdots_ix = slice(self.nfields * 2 + 2, None, 2)
#Indices of second order quantities, to use with self.yresult
self.dp2s_ix = slice(0, None, 2)
self.dp2dots_ix = slice(1, None, 2)
return
[docs] def setup_soclass(self):
"""Initialize the second order class that will be used to run simulation."""
sokwargs = {
"ystart": self.ystart,
"tstart": self.fotstart,
"tstartindex": self.tstartindex,
"simtstart": self.simtstart,
"tend": self.tend,
"tstep_wanted": self.tstep_wanted,
"solver": self.solver,
"k": self.k,
"ainit": self.ainit,
"potential_func": self.potential_func,
"pot_params": self.pot_params,
"cq": self.cq,
"nfields": self.nfields,
}
self.somodel = self.soclass(**sokwargs)
#Set second stage and source terms for somodel
self.somodel.source = self.source
self.somodel.second_stage = self.second_stage
return
[docs] def runso(self):
"""Run second order model."""
#Initialize second order class
self.setup_soclass()
#Start second order run
self._log.info("Beginning second order run...")
try:
self.somodel.run(saveresults=False)
pass
except ModelError:
self._log.exception("Error in second order run, aborting!")
raise
self.tresult, self.yresult = self.somodel.tresult, self.somodel.yresult
return
[docs] def run(self, saveresults=True):
"""Run simulation and save results."""
#Run second order model
self.runso()
#Save results in file
#Aggregrate results and calling parameters into results list
self.lastparams = self.callingparams()
if saveresults:
try:
self._log.info("Results saved in " + self.saveallresults())
except IOError, er:
self._log.exception("Error trying to save results! Results NOT saved.")
return
@property
[docs] def deltaphi(self, recompute=False):
"""Return the calculated values of $\delta\phi$ for all times and modes.
The result is stored as the instance variable self.deltaphi but will be recomputed
if `recompute` is True.
Parameters
----------
recompute : boolean, optional
Should the values be recomputed? Default is False.
Returns
-------
deltaphi : array_like
Array of $\delta\phi$ values for all timesteps and k modes.
"""
if not hasattr(self, "_deltaphi") or recompute:
dp1 = self.second_stage.yresult[:,3,:] + self.second_stage.yresult[:,5,:]*1j
dp2 = self.yresult[:,0,:] + self.yresult[:,2,:]*1j
self._deltaphi = dp1 + 0.5*dp2
return self._deltaphi
#Helper functions to access results variables
@property
[docs] def phis(self):
"""Background fields \phi_i"""
return self.second_stage.yresult[:,self.phis_ix]
@property
[docs] def phidots(self):
"""Derivatives of background fields w.r.t N \phi_i^\dagger"""
return self.second_stage.yresult[:,self.phidots_ix]
@property
[docs] def H(self):
"""Hubble parameter"""
return self.second_stage.yresult[:,self.H_ix]
@property
[docs] def dpmodes(self):
"""Quantum modes of first order perturbations"""
return self.second_stage.yresult[:,self.dps_ix]
@property
[docs] def dpdotmodes(self):
"""Quantum modes of derivatives of first order perturbations"""
return self.second_stage.yresult[:,self.dpdots_ix]
@property
[docs] def dp2modes(self):
"""Quantum modes of second order perturbations"""
return self.yresult[:,self.dp2s_ix]
@property
[docs] def dp2dotmodes(self):
"""Quantum modes of derivatives of second order perturbations"""
return self.yresult[:,self.dp2dots_ix]
@property
[docs] def a(self):
"""Scale factor of the universe"""
return self.ainit*np.exp(self.tresult)
[docs]class SOHorizonStart(SOCanonicalThreeStage):
"""Runs third stage calculation (typically second order perturbations) using
a two stage model instance which could be wrapped from a file.
Second order calculation starts at horizon crossing.
"""
def __init__(self, second_stage, soclass=None, ystart=None, **soclassargs):
"""Initialize variables and check that tsmodel exists and is correct form."""
#Test whether tsmodel is of correct type
if not isinstance(second_stage, FOCanonicalTwoStage):
raise ModelError("Need to provide a FOCanonicalTwoStage instance to get first order results from!")
else:
self.second_stage = second_stage
#Set properties to be those of second stage model
self.k = np.copy(self.second_stage.k)
self.simtstart = self.second_stage.tresult[0]
self.fotstart = np.copy(self.second_stage.fotstart)
self.fotstartindex = np.copy(self.second_stage.fotstartindex)
self.ainit = self.second_stage.ainit
self.potentials = self.second_stage.potentials
self.potential_func = self.second_stage.potential_func
self.pot_params = self.second_stage.pot_params
if ystart is None:
ystart = np.zeros((4, len(self.k)))
#Need to make sure that the tstartindex terms are changed over to new timestep.
fotstep = self.second_stage.tstep_wanted
sotstep = fotstep*2
fohorizons = np.array([second_stage.findkcrossing(second_stage.k[kix],
second_stage.bgmodel.tresult,
second_stage.bgmodel.yresult[:,2],
factor=1) for kix in np.arange(len(second_stage.k)) ])
fohorizonindex = fohorizons[:,0]
fohorizontimes = fohorizons[:,1]
sotstartindex = np.around(fohorizonindex*(fotstep/sotstep) + sotstep/2).astype(np.int)
kwargs = dict(ystart=ystart,
tstart=self.second_stage.tresult[0],
tstartindex=sotstartindex,
simtstart=self.simtstart,
tend=self.second_stage.tresult[-1],
tstep_wanted=sotstep,
solver="rkdriver_new",
potential_func=self.second_stage.potential_func,
pot_params=self.second_stage.pot_params
)
#Update sokwargs with any arguments from soclassargs
if soclassargs is not None:
kwargs.update(soclassargs)
#Call superclass
super(SOCanonicalThreeStage, self).__init__(**kwargs)
if soclass is None:
self.soclass = CanonicalSecondOrder
else:
self.soclass = soclass
self.somodel = None
#Set up source term
if _debug:
self._log.debug("Trying to set source term for second order model...")
self.source = self.second_stage.source[:]
if self.source is None:
raise ModelError("First order model does not have a source term!")
#Try to put yresult array in memory
self.second_stage.yresultarr = self.second_stage.yresult
self.second_stage.yresult = self.second_stage.yresultarr[:]
[docs]class CombinedCanonicalFromFile(MultiStageDriver):
"""Model class for combined first and second order data, assumed to be used with a file wrapper."""
def __init__(self, *args, **kwargs):
"""Initialize vars and call super class."""
super(CombinedCanonicalFromFile, self).__init__(*args, **kwargs)
if "bgclass" not in kwargs or kwargs["bgclass"] is None:
self.bgclass = CanonicalBackground
else:
self.bgclass = bgclass
if "foclass" not in kwargs or kwargs["foclass"] is None:
self.foclass = CanonicalFirstOrder
else:
self.foclass = foclass
@property
[docs] def deltaphi(self, recompute=False):
"""Return the calculated values of $\delta\phi$ for all times and modes.
The result is stored as the instance variable self.deltaphi but will be recomputed
if `recompute` is True.
Parameters
----------
recompute : boolean, optional
Should the values be recomputed? Default is False.
Returns
-------
deltaphi : array_like
Array of $\delta\phi$ values for all timesteps and k modes.
"""
if not hasattr(self, "deltaphi") or recompute:
dp1 = self.dp1
dp2 = self.dp2
self._dp = dp1 + 0.5*dp2
return self._dp
@property
[docs] def dp1(self, recompute=False):
"""Return (and save) the first order perturbation."""
if not hasattr(self, "_dp1") or recompute:
dp1 = self.yresult[:,3,:] + self.yresult[:,5,:]*1j
self._dp1 = dp1
return self._dp1
@property
[docs] def dp2(self, recompute=False):
"""Return (and save) the first order perturbation."""
if not hasattr(self, "_dp2") or recompute:
dp2 = self.yresult[:,7,:] + self.yresult[:,9,:]*1j
self._dp2 = dp2
return self._dp2
[docs]class FixedainitTwoStage(FOCanonicalTwoStage):
"""First order driver class with ainit fixed to a specified value.
This is useful for comparing models which have different H behaviour and
therefore different ainit values.
It should be remembered that these models with fixed ainit are equivalent
to changing the number of efoldings from the end of inflation until today.
"""
def __init__(self, *args, **kwargs):
"""Extra keyword argument ainit is used to set value of ainit no matter
what the value of H at the end of inflation.
Parameters
----------
ainit : float
value of ainit to fix no matter what the value of H at the
end of inflation.
"""
super(FixedainitTwoStage, self).__init__(*args, **kwargs)
if "ainit" in kwargs:
self.fixedainit = kwargs["ainit"]
else:
#Set with ainit from standard msqphisq potential run.
self.fixedainit = 7.837219134630212e-65
[docs] def setfoics(self):
"""After a bg run has completed, set the initial conditions for the
first order run."""
#Find initial conditions for 1st order model
#Use fixed ainit in this class instead of calculating from aend
self.ainit = self.fixedainit
#Find epsilon from bg model
try:
self.bgepsilon
except AttributeError:
self.bgepsilon = self.bgmodel.getepsilon()
#Set etainit, initial eta at n=0
self.etainit = -1/(self.ainit*self.bgmodel.yresult[0,self.H_ix]*(1-self.bgepsilon[0]))
#find k crossing indices
kcrossings = self.findallkcrossings(self.bgmodel.tresult[:self.fotendindex],
self.bgmodel.yresult[:self.fotendindex,self.H_ix])
kcrossefolds = kcrossings[:,1]
#If mode crosses horizon before t=0 then we will not be able to propagate it
if any(kcrossefolds==0):
raise ModelError("Some k modes crossed horizon before simulation began and cannot be initialized!")
#Find new start time from earliest kcrossing
self.fotstart, self.fotstartindex = kcrossefolds, kcrossings[:,0].astype(np.int)
self.foystart = self.getfoystart()
return
[docs]class FONoPhase(FOCanonicalTwoStage):
"""First order two stage class which does not include a phase in the initial
conditions for the first order field."""
def __init__(self, *args, **kwargs):
super(FONoPhase, self).__init__(*args, **kwargs)
[docs] def getfoystart(self, ts=None, tsix=None):
"""Model dependent setting of ystart"""
if _debug:
self._log.debug("Executing getfoystart to get initial conditions.")
#Set variables in standard case:
if ts is None or tsix is None:
ts, tsix = self.fotstart, self.fotstartindex
#Reset starting conditions at new time
foystart = np.zeros(((2*self.nfields**2 + self.nfields*2 +1), len(self.k)), dtype=np.complex128)
#set_trace()
#Get values of needed variables at crossing time.
astar = self.ainit*np.exp(ts)
#Truncate bgmodel yresult down if there is an extra dimension
if len(self.bgmodel.yresult.shape) > 2:
bgyresult = self.bgmodel.yresult[..., 0]
else:
bgyresult = self.bgmodel.yresult
Hstar = bgyresult[tsix,self.H_ix]
# Hzero = bgyresult[0,self.H_ix]
#
# epsstar = self.bgepsilon[tsix]
# etastar = -1/(astar*Hstar*(1-epsstar))
# try:
# etadiff = etastar - self.etainit
# except AttributeError:
# etadiff = etastar + 1/(self.ainit*Hzero*(1-self.bgepsilon[0]))
# keta = self.k*etadiff
#Set bg init conditions based on previous bg evolution
try:
foystart[self.bg_ix] = bgyresult[tsix,:].transpose()
except ValueError:
foystart[self.bg_ix] = bgyresult[tsix,:][:, np.newaxis]
#Find 1/asqrt(2k)
arootk = 1/(astar*(np.sqrt(2*self.k)))
#Only want to set the diagonal elements of the mode matrix
#Use a.flat[::a.shape[1]+1] to set diagonal elements only
#In our case already flat so foystart[slice,:][::nfields+1]
#Set \delta\phi_1 initial condition
foystart[self.dps_ix,:][::self.nfields+1] = arootk
#set \dot\delta\phi_1 ic
foystart[self.dpdots_ix,:][::self.nfields+1] = -arootk*(1 + (self.k/(astar*Hstar))*1j)
return foystart
[docs]class FOSuppressOneField(FOCanonicalTwoStage):
"""First order two stage class which does not include a phase in the initial
conditions for the first order field."""
def __init__(self, suppress_ix=0, *args, **kwargs):
self.suppress_ix = suppress_ix
super(FOSuppressOneField, self).__init__(*args, **kwargs)
[docs] def getfoystart(self, ts=None, tsix=None):
"""Model dependent setting of ystart"""
if _debug:
self._log.debug("Executing getfoystart to get initial conditions.")
#Set variables in standard case:
if ts is None or tsix is None:
ts, tsix = self.fotstart, self.fotstartindex
#Reset starting conditions at new time
foystart = np.zeros(((2*self.nfields**2 + self.nfields*2 +1), len(self.k)), dtype=np.complex128)
#set_trace()
#Get values of needed variables at crossing time.
astar = self.ainit*np.exp(ts)
#Truncate bgmodel yresult down if there is an extra dimension
if len(self.bgmodel.yresult.shape) > 2:
bgyresult = self.bgmodel.yresult[..., 0]
else:
bgyresult = self.bgmodel.yresult
Hstar = bgyresult[tsix,self.H_ix]
Hzero = bgyresult[0,self.H_ix]
epsstar = self.bgepsilon[tsix]
etastar = -1/(astar*Hstar*(1-epsstar))
try:
etadiff = etastar - self.etainit
except AttributeError:
etadiff = etastar + 1/(self.ainit*Hzero*(1-self.bgepsilon[0]))
keta = self.k*etadiff
#Set bg init conditions based on previous bg evolution
try:
foystart[self.bg_ix] = bgyresult[tsix,:].transpose()
except ValueError:
foystart[self.bg_ix] = bgyresult[tsix,:][:, np.newaxis]
#Find 1/asqrt(2k)
arootk = 1/(astar*(np.sqrt(2*self.k)))
#Only want to set the diagonal elements of the mode matrix
#Use a.flat[::a.shape[1]+1] to set diagonal elements only
#In our case already flat so foystart[slice,:][::nfields+1]
#Set \delta\phi_1 initial condition
foystart[self.dps_ix,:][::self.nfields+1] = arootk*np.exp(-keta*1j)
#set \dot\delta\phi_1 ic
foystart[self.dpdots_ix,:][::self.nfields+1] = -arootk*np.exp(-keta*1j)*(1 + (self.k/(astar*Hstar))*1j)
#Suppress one field using self.suppress_ix
foystart[self.dps_ix,:][::self.nfields+1][self.suppress_ix] = 0
foystart[self.dpdots_ix,:][::self.nfields+1][self.suppress_ix] = 0
return foystart