├── .gitignore ├── pynstein_logo.png ├── cosmology ├── CurvedUniverse.py ├── S2E1Universe.py ├── BianchiRadiationUniverse.py └── AnisotropicUniverse.py ├── LICENSE.txt ├── README.md └── genrel ├── Bianchi.py ├── BianchiAnisotropicSE.py └── GR.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.pyc 3 | -------------------------------------------------------------------------------- /pynstein_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaiSmith/Pynstein/HEAD/pynstein_logo.png -------------------------------------------------------------------------------- /cosmology/CurvedUniverse.py: -------------------------------------------------------------------------------- 1 | class CurvedUniverse(): 2 | def __init__(self, shape_or_k = 0): 3 | self.shapes = {'o': -1, 'f': 0, 'c': 1} 4 | self.set_shape_or_k(shape_or_k) 5 | 6 | def set_shape_or_k(self, shape_or_k): 7 | if type(shape_or_k) == str: 8 | self.__dict__['shape'] = shape_or_k 9 | self.__dict__['k'] = self.shapes[shape_or_k] 10 | else: 11 | self.__dict__['k'] = shape_or_k 12 | self.__dict__['shape'] = [k for shape, k in self.shapes.items() if k == shape_or_k][0] 13 | 14 | def __setattr__(self, name, value): 15 | if name == 'shape' or name == 'k': 16 | self.set_shape_or_k(value) 17 | else: 18 | self.__dict__[name] = value -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 David Clark, Kai Smith 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cosmology/S2E1Universe.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | from AnisotropicUniverse import AnisotropicUniverse 3 | from CurvedUniverse import CurvedUniverse 4 | 5 | class S2E1Universe(AnisotropicUniverse, CurvedUniverse): 6 | def __init__(self, shape_or_k = 0): 7 | AnisotropicUniverse.__init__(self) 8 | CurvedUniverse.__init__(self, shape_or_k) 9 | 10 | def evolve(self, initial_conditions, times): 11 | a0, a_dot0, b0, b_dot0 = initial_conditions[:-2] 12 | Ha0, Hb0 = a_dot0/a0, b_dot0/b0 13 | self.const = -(self.k/a0**2.0 + 2.0*Ha0*Hb0 + Ha0**2.0) 14 | AnisotropicUniverse.evolve(self, initial_conditions, times) 15 | 16 | def dydt(self, y, t): 17 | a, a_dot, b, b_dot = y[:-2] 18 | k = self.k 19 | a0, a_dot0, b0, b_dot0 = self.initial_conditions[:-2] 20 | V = a**2.0*b 21 | V0 = a0**2.0*b0 22 | const = self.const 23 | 24 | a_dot_dot = -(a/2.0)*((a_dot**2.0)/(a**2.0) + k/a**2.0 + const*(V0/V)*(b0/b)) 25 | b_dot_dot = -(b/2.0)*(-(a_dot**2.0)/(a**2.0) - k/a**2.0 + 2.0*(a_dot*b_dot)/(a*b) + const*(V0/V)*(2*(a0/a) - (b0/b))) 26 | c_dot_dot = 0 27 | 28 | return [a_dot, a_dot_dot, b_dot, b_dot_dot, 0, 0] 29 | 30 | if __name__ == "__main__": 31 | universe = S2E1Universe('f') 32 | universe.evolve([1.0, 1.0, 1.0, 1.0, 0, 0], numpy.linspace(0, 10, 100)) 33 | universe.plot_variables(['sf']) 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![alt tag](https://raw.githubusercontent.com/KaiSmith/Pynstein/master/pynstein_logo.png) 2 | 3 | ##What is Pynstein? 4 | Pynstein is a python library that allows the user to easily do general relativity calculations. 5 | 6 | ##Features 7 | 8 | * Given a metric, Pynstein can calculate: 9 | * Inerse metrics 10 | * Christoffel Symbols 11 | * Reimann Curvature Tensor 12 | * Ricci Curvature Tensor 13 | * Conservation equation 14 | * Einstein Tensor 15 | * and given the stress energy tensor, the Einstein Equations 16 | * Can numerically evolve a universe given inital conditions 17 | 18 | ##Dependencies 19 | * numpy 20 | * scipy 21 | * sympy 22 | * matplotlib 23 | 24 | ##Example Usage 25 | ```python 26 | #Define variables 27 | t = sp.Symbol('t') 28 | r = sp.Symbol('r') 29 | theta = sp.Symbol('theta') 30 | phi = sp.Symbol('phi') 31 | 32 | #Define rho and p functions 33 | w = sp.Symbol('w') 34 | rho = sp.Function('rho')(t) 35 | p = w*rho 36 | 37 | #Create FRW metric 38 | frw_metric = np.diag([-1, a**2/(1-k*r**2), a**2*r**2,a**2*r**2*sp.sin(theta)**2]) 39 | frw_metric_key = [t, r, theta, phi] 40 | 41 | #Generate Einstein tensor 42 | einstein = einstein_tensor_from_scratch(frw_metric, frw_metric_key) 43 | einstein = raise_one_index(einstein, frw_metric) 44 | 45 | #Generate the 2 distinct Einstein field equations 46 | ein_eq = einstein_equations(einstein, np.diag([-rho, p, p, p])) 47 | ``` 48 | 49 | ##Authors 50 | Kai Smith (kai.smith@case.edu) 51 | 52 | David Clark (davidclark@berkeley.edu) 53 | -------------------------------------------------------------------------------- /cosmology/BianchiRadiationUniverse.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | from AnisotropicUniverse import AnisotropicUniverse 3 | 4 | """ 5 | Represents a radiation-filled Bianchi Type-1 universe with anisotripic stress-energy and optional curvature 6 | """ 7 | 8 | class BianchiRadiationUniverse(AnisotropicUniverse): 9 | def __init__(self, shape_or_k = 0): 10 | AnisotropicUniverse.__init__(self) 11 | self.shapes = {'o': -1, 'f': 0, 'c': 1} 12 | self.set_shape_or_k(shape_or_k) 13 | 14 | def set_shape_or_k(self, shape_or_k): 15 | if type(shape_or_k) == str: 16 | self.__dict__['shape'] = shape_or_k 17 | self.__dict__['k'] = self.shapes[shape_or_k] 18 | else: 19 | self.__dict__['k'] = shape_or_k 20 | self.__dict__['shape'] = [k for shape, k in self.shapes.items() if k == shape_or_k][0] 21 | 22 | def __setattr__(self, name, value): 23 | if name == 'shape' or name == 'k': 24 | self.set_shape_or_k(value) 25 | else: 26 | self.__dict__[name] = value 27 | 28 | 29 | def evolve(self, initial_conditions, times): 30 | a0, a_dot0, b0, b_dot0, c0, c_dot0 = initial_conditions 31 | V0 = a0*b0*c0 32 | Ha0, Hb0, Hc0 = a_dot0/a0, b_dot0/b0, c_dot0/c0 33 | Htot0 = Ha0 + Hb0 + Hc0 34 | I0 = Hb0*Hc0 + Ha0*Hc0 + Ha0*Hb0 35 | self.const = I0/3.0 - (2*self.k)/V0**(2.0/3.0) + (self.k/Htot0)*(Ha0/a0**2 + Hb0/b0**2 + Hc0/c0**2) 36 | AnisotropicUniverse.evolve(self, initial_conditions, times) 37 | 38 | def dydt(self, y, t): 39 | a, a_dot, b, b_dot, c, c_dot = y 40 | const = self.const 41 | k = self.k 42 | a0, a_dot0, b0, b_dot0, c0, c_dot0 = self.initial_conditions 43 | V0 = a0*b0*c0 44 | 45 | a_dot_dot = (a/2.0)*(-const*(V0/(a*b*c))*(-a0/a + b0/b + c0/c) + k*(-1/a**2 + 1/b**2 + 1/c**2) - (a_dot*b_dot)/(a*b) - (a_dot*c_dot)/(a*c) + (b_dot*c_dot)/(b*c)) 46 | b_dot_dot = (b/2.0)*(-const*(V0/(a*b*c))*(a0/a - b0/b + c0/c) + k*(1/a**2 - 1/b**2 + 1/c**2) - (a_dot*b_dot)/(a*b) + (a_dot*c_dot)/(a*c) - (b_dot*c_dot)/(b*c)) 47 | c_dot_dot = (c/2.0)*(-const*(V0/(a*b*c))*(a0/a + b0/b - c0/c) + k*(1/a**2 + 1/b**2 - 1/c**2) + (a_dot*b_dot)/(a*b) - (a_dot*c_dot)/(a*c) - (b_dot*c_dot)/(b*c)) 48 | 49 | return [a_dot, a_dot_dot, b_dot, b_dot_dot, c_dot, c_dot_dot] 50 | 51 | if __name__ == "__main__": 52 | universe = BianchiRadiationUniverse('f') 53 | universe.evolve([1.0, 1.0, 1.0, 1.0, 0, 0], numpy.linspace(0, 100, 100)) 54 | universe.plot_variables(['sf']) 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /cosmology/AnisotropicUniverse.py: -------------------------------------------------------------------------------- 1 | import scipy.integrate 2 | import numpy 3 | import matplotlib.pyplot 4 | 5 | """ 6 | Represents a universe described by three directional scale factors whose evolution is described by second order differential equations 7 | 8 | This is an abstract class -- it needs a dydt implimentation to work 9 | 10 | Subclasses must implement dydt, and should be sure to call the superclass constructor 11 | 12 | Subclasses might also want to implement more variable calculations, which can be done by overriding calculate_variable 13 | Just be sure to default to the superclass method if the subclass doesn't implement a variable 14 | """ 15 | class AnisotropicUniverse: 16 | 17 | def __init__(self): 18 | self.var_names = {'sf': 'Scale Factors', 'v': 'Volume', 'sfd': 'Scale Factor Derivatives', 'h': 'Hubble Parameters', 'hr': 'Hubble Parameter Ratios'} 19 | 20 | #Initial condiitons are taken to be specified at the time closest to 0 in times 21 | #dydt must return values in the form [a_dot, a_dot_dot, b_dot, b_dot_dot, c_dot, c_dot_dot] 22 | def evolve(self, initial_conditions, times): 23 | self.initial_conditions = initial_conditions 24 | self.a0, self.a_dot0, self.b0, self.b_dot0, self.c0, self.c_dot0 = initial_conditions 25 | self.V0 = self.a0*self.b0*self.c0 26 | self.times = times 27 | t0_index = min((abs(val), idx) for (idx, val) in enumerate(times))[1] 28 | if t0_index == 0: 29 | self.data = scipy.integrate.odeint(self.dydt, initial_conditions, times).transpose() 30 | else: 31 | first_half = scipy.integrate.odeint(self.dydt, initial_conditions, times[:t0_index+1][::-1])[::-1] 32 | second_half = scipy.integrate.odeint(self.dydt, initial_conditions, times[t0_index:])[1:] 33 | self.data = numpy.concatenate((first_half, second_half)).transpose() 34 | 35 | #var is an abbreviation for a varibale 36 | #Options are listed in the array in the constructor 37 | def calculate_variable(self, var): 38 | data = self.data 39 | if var == 'sf': 40 | return [data[0], data[2], data[4]] 41 | elif var == 'v': 42 | return [[data[0][i]*data[2][i]*data[4][i] for i in range(len(self.times))]] 43 | elif var == 'sfd': 44 | return [data[1], data[3], data[5]] 45 | elif var == 'h': 46 | return [[data[j][i]/data[j-1][i] for i in range(len(self.times))] for j in [1, 3, 5]] 47 | elif var == 'hr': 48 | Ha, Hb, Hc = self.calculate_variable('h') 49 | return [[H1[i]/H2[i] for i in range(len(self.times))] for [H1, H2] in [[Hb, Ha], [Ha, Hc], [Ha, Hb]]] 50 | 51 | #varaibles is a list of variable abbreviations 52 | #Options are listed in the array in the constructor 53 | def print_variables(self, variables): 54 | for var in variables: 55 | print(self.var_names[var]) 56 | print(self.calculate_variable(var)) 57 | print('\n') 58 | 59 | #varaibles is a list of variable abbreviations 60 | #Options are listed in the array in the constructor 61 | def plot_variables(self, variables): 62 | colors = ['b', 'g', 'r', 'c', 'm', 'y', 'k'] 63 | for var in variables: 64 | matplotlib.pyplot.title(self.var_names[var]) 65 | variable = self.calculate_variable(var) 66 | c = 0 67 | for l in variable: 68 | matplotlib.pyplot.scatter(self.times, l, c = colors[c]) 69 | c = c+1 70 | matplotlib.pyplot.show() 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /genrel/Bianchi.py: -------------------------------------------------------------------------------- 1 | """ 2 | Numerically evolves a Bianchi Class I universe given initial conditions. 3 | David Clark, Kai Smith 4 | Case Western Reserve University 5 | 2014 6 | """ 7 | from GR import * 8 | from math import * 9 | import numpy as np 10 | import sympy as sp 11 | import matplotlib.pyplot as pplot 12 | 13 | def dVdt(V, t): 14 | return sqrt(3*I0*(omega['m']*V0*V+omega['r']*V0**sp.Rational(4, 3)*V**sp.Rational(2, 3)+omega['v']*V**2)+c) 15 | 16 | def make_dHdt(V): 17 | def dHdt(H, t): 18 | V_t = get_value(V, t) 19 | return (I0/2*(omega['m']*V0+sp.Rational(2, 3)*omega['r']*V0**sp.Rational(4, 3)*V_t**sp.Rational(-1, 3)+2*omega['v']*V_t)-H*dVdt(V_t,t))/V_t 20 | return dHdt 21 | 22 | def make_dSdt(H): 23 | def dSdt(S, t): 24 | return get_value(H, t) * S 25 | return dSdt 26 | 27 | def hubble_parameters(): 28 | V = RK4(dVdt, V0, start, stop, step/4.0) 29 | dHdt = make_dHdt(V) 30 | return RK4(dHdt, A0, start, stop, step/2.0), RK4(dHdt, B0, start, stop, step/2.0), RK4(dHdt, C0, start, stop, step/2.0) 31 | 32 | def scale_factors(Ha, Hb, Hc): 33 | dadt = make_dSdt(Ha) 34 | dbdt = make_dSdt(Hb) 35 | dcdt = make_dSdt(Hc) 36 | return RK4(dadt, a0, start, stop, step), RK4(dbdt, b0, start, stop, step), RK4(dcdt, c0, start, stop, step) 37 | 38 | def plot_hubble_parameters(): 39 | Ha, Hb, Hc = hubble_parameters() 40 | pplot.scatter(t, np.float64(values_at_times(Ha, t)), c = 'r') 41 | pplot.scatter(t, np.float64(values_at_times(Hb, t)), c = 'g') 42 | pplot.scatter(t, np.float64(values_at_times(Hc, t)), c = 'b') 43 | pplot.title('Hubble Parameters') 44 | pplot.show() 45 | return Ha, Hb, Hc 46 | 47 | def plot_scale_factors(Ha, Hb, Hc): 48 | a, b, c = scale_factors(Ha, Hb, Hc) 49 | pplot.scatter(t, np.float64(values_at_times(a, t)), c = 'r') 50 | pplot.scatter(t, np.float64(values_at_times(b, t)), c = 'g') 51 | pplot.scatter(t, np.float64(values_at_times(c, t)), c = 'b') 52 | pplot.title('Scale Factors') 53 | pplot.show() 54 | 55 | def RK4(dfdt, f0, start, stop, step): 56 | f = {start: f0} 57 | t = start 58 | val = f0 59 | cond = iter_cond(start, stop) 60 | while (cond(t, stop)): 61 | k1 = dfdt(val, t) 62 | k2 = dfdt(val + step/2.0*k1, t + step/2.0) 63 | k3 = dfdt(val + step/2.0*k2, t + step/2.0) 64 | k4 = dfdt(val + step*k3, t + step) 65 | t = t + step 66 | val = val + step/6.0*(k1 + 2.0*k2 + 2.0*k3 + k4) 67 | set_value(f, t, val) 68 | return f 69 | 70 | def iter_cond(start, stop): 71 | if (start < stop): 72 | def cond(a, b): 73 | return round(a, 8) < round(b, 8) 74 | else: 75 | def cond(a, b): 76 | return round(a, 8) > round(b, 8) 77 | return cond 78 | 79 | def set_value(f, t, v): 80 | f[round(t, 8)] = v 81 | 82 | def get_value(f, t): 83 | return f[round(t, 8)] 84 | 85 | def values_at_times(v, t): 86 | values = [] 87 | for time in t: 88 | values.append(get_value(v, time)) 89 | return values 90 | 91 | if __name__ == '__main__': 92 | #Initial directional Hubble constants 93 | A0 = 1.0 94 | B0 = 2.0 95 | C0 = 3.0 96 | 97 | #Initial directional scale factors 98 | a0 = 0 99 | b0 = 0 100 | c0 = 0 101 | 102 | #Farctional energy-densities of the universe 103 | omega = {'m': 0, 'r': 1, 'v': 0} 104 | 105 | #Times at which to calculate functions 106 | start = 0 107 | stop = 1 108 | step = 0.05 109 | 110 | I0 = A0*B0+A0*C0+B0*C0 111 | H0 = A0+B0+C0 112 | V0 = a0*b0*c0 113 | c = V0**2*(H0**2-3*I0) 114 | t = np.linspace(start, stop, abs((stop-start)/step)+1) 115 | 116 | Ha, Hb, Hc = plot_hubble_parameters() 117 | plot_scale_factors(Ha, Hb, Hc) 118 | -------------------------------------------------------------------------------- /genrel/BianchiAnisotropicSE.py: -------------------------------------------------------------------------------- 1 | """ 2 | Numerically evolves a Bianchi Class I universe with anisotripic stress energy given initial conditions 3 | David Clark, Kai Smith 4 | Case Western Reserve University 5 | 2014 6 | """ 7 | from math import * 8 | import numpy as np 9 | import sympy as sp 10 | import scipy.integrate 11 | import matplotlib.pyplot as pplot 12 | from math import pi 13 | 14 | a0 = 10.0 15 | b0 = 10.0 16 | c0 = 10.0 17 | 18 | a_dot0 = 1.0 19 | b_dot0 = 1.0 20 | c_dot0 = 1.0 21 | 22 | A0 = a_dot0/a0 23 | B0 = b_dot0/b0 24 | C0 = c_dot0/c0 25 | 26 | omega0 = 1 27 | 28 | #Open -1 29 | #Flat 0 30 | #Closed 1 31 | k = 0 32 | 33 | t = np.linspace(0, 1, 100) 34 | 35 | I0 = A0*B0+B0*C0+A0*C0 36 | H0 = A0+B0+C0 37 | V0 = a0*b0*c0 38 | 39 | chi0 = (omega0*I0*H0)/(3*(a_dot0+b_dot0+c_dot0)) 40 | 41 | #const = 8*pi*G*p0 42 | const = 1 43 | 44 | def dydt(y, t): 45 | a, a_dot, b, b_dot, c, c_dot = y 46 | """ 47 | a_dot_dot = (a/2.0)*(chi0*(a0/a - b0/b - c0/c)*(V0/(a*b*c) + k) - (a_dot*b_dot)/(a*b) - (a_dot*c_dot)/(a*c) + (b_dot*c_dot)/(b*c)) 48 | b_dot_dot = (b/2.0)*(chi0*(-a0/a + b0/b - c0/c)*(V0/(a*b*c) + k) - (a_dot*b_dot)/(a*b) + (a_dot*c_dot)/(a*c) - (b_dot*c_dot)/(b*c)) 49 | c_dot_dot = (c/2.0)*(chi0*(-a0/a - b0/b + c0/c)*(V0/(a*b*c) + k) + (a_dot*b_dot)/(a*b) - (a_dot*c_dot)/(a*c) - (b_dot*c_dot)/(b*c)) 50 | """ 51 | a_dot_dot = (a/2.0)*(-const*(V0/(a*b*c))*(-a0/a + b0/b + c0/c) - k*(-1/a**2 + 1/b**2 + 1/c**2) - (a_dot*b_dot)/(a*b) - (a_dot*c_dot)/(a*c) + (b_dot*c_dot)/(b*c)) 52 | b_dot_dot = (b/2.0)*(-const*(V0/(a*b*c))*(a0/a - b0/b + c0/c) -k*(1/a**2 - 1/b**2 + 1/c**2) - (a_dot*b_dot)/(a*b) + (a_dot*c_dot)/(a*c) - (b_dot*c_dot)/(b*c)) 53 | c_dot_dot = (c/2.0)*(-const*(V0/(a*b*c))*(a0/a + b0/b - c0/c) -k*(1/a**2 + 1/b**2 - 1/c**2) + (a_dot*b_dot)/(a*b) - (a_dot*c_dot)/(a*c) - (b_dot*c_dot)/(b*c)) 54 | return [a_dot, a_dot_dot, b_dot, b_dot_dot, c_dot, c_dot_dot] 55 | 56 | def plot_evolution(): 57 | t = np.linspace(0, 1, 100) 58 | y0 = [a0, a_dot0, b0, b_dot0, c0, c_dot0] 59 | y = scipy.integrate.odeint(dydt, y0, t) 60 | 61 | a = [value[0] for value in y] 62 | a_dot = [value[1] for value in y] 63 | b = [value[2] for value in y] 64 | b_dot = [value[3] for value in y] 65 | c = [value[4] for value in y] 66 | c_dot = [value[5] for value in y] 67 | 68 | stop = len(t) - 1 69 | for values in [a, a_dot, b, b_dot, c, c_dot]: 70 | for i in range(1, len(t)): 71 | if abs(values[i]/values[i-1]) > 1000 and i < stop: 72 | stop = i 73 | break 74 | a, a_dot, b, b_dot, c, c_dot, t = a[:stop], a_dot[:stop], b[:stop], b_dot[:stop], c[:stop], c_dot[:stop], t[:stop] 75 | 76 | A = [a_dot[i]/a[i] for i in range(len(t))] 77 | B = [b_dot[i]/b[i] for i in range(len(t))] 78 | C = [c_dot[i]/c[i] for i in range(len(t))] 79 | 80 | V = [a[i]*b[i]*c[i] for i in range(len(t))] 81 | 82 | """ 83 | pplot.scatter(t, a_dot, c = 'r') 84 | pplot.scatter(t, b_dot, c = 'g') 85 | pplot.scatter(t, c_dot, c = 'b') 86 | pplot.title('First Derivatives') 87 | pplot.show() 88 | """ 89 | 90 | pplot.scatter(t, a, c = 'r') 91 | pplot.scatter(t, b, c = 'g') 92 | pplot.scatter(t, c, c = 'b') 93 | pplot.title('Scale Factors') 94 | pplot.show() 95 | 96 | pplot.scatter(t, A, c = 'r') 97 | pplot.scatter(t, B, c = 'g') 98 | pplot.scatter(t, C, c = 'b') 99 | pplot.title('Hubble Parameters') 100 | pplot.show() 101 | 102 | pplot.scatter(t, V, c = 'r') 103 | pplot.title('Volume') 104 | pplot.show() 105 | 106 | 107 | def print_long_term_ratios(): 108 | t = np.linspace(0, 1000000, 100000) 109 | y0 = [a0, a_dot0, b0, b_dot0, c0, c_dot0] 110 | y = scipy.integrate.odeint(dydt, y0, t) 111 | 112 | A = [value[1]/value[0] for value in y] 113 | B = [value[3]/value[2] for value in y] 114 | C = [value[5]/value[4] for value in y] 115 | 116 | B_over_C = [A[i]/B[i] for i in range(len(t))] 117 | C_over_A = [C[i]/A[i] for i in range(len(t))] 118 | B_over_A = [B[i]/A[i] for i in range(len(t))] 119 | 120 | print('B/C: ' + str(B_over_C[-1])) 121 | print('C/A: ' + str(C_over_A[-1])) 122 | print('B/A: ' + str(B_over_A[-1])) 123 | 124 | plot_evolution() -------------------------------------------------------------------------------- /genrel/GR.py: -------------------------------------------------------------------------------- 1 | """ 2 | genrel package for GR calculations 3 | David Clark, Kai Smith 4 | Case Western Reserve University 5 | 2014 6 | """ 7 | 8 | import numpy as np 9 | import sympy as sp 10 | 11 | #returns a rank 3 tensor that represents the symbols 12 | #first index corresponds to the upper index 13 | def christoffel_symbols(metric, metric_key): 14 | symbols = tensor(3) 15 | inverse = inverse_metric(metric) 16 | for alpha in range(4): 17 | for beta in range(4): 18 | for gamma in range(4): 19 | total = 0 20 | for delta in range(4): 21 | total += inverse[alpha][delta] * (sp.diff(metric[delta][beta], metric_key[gamma]) 22 | + sp.diff(metric[delta][gamma], metric_key[beta]) 23 | - sp.diff(metric[beta][gamma], metric_key[delta])) 24 | symbols[alpha][beta][gamma] = sp.simplify(total/2) 25 | return symbols 26 | 27 | #returns the rank 4 Reimann curvature tensor 28 | #the first index corresponds to an upper index -- the rest are lower 29 | def reimann_tensor(chris_sym, metric_key): 30 | reimann = tensor(4) 31 | for alpha in range(4): 32 | for beta in range(4): 33 | for gamma in range(4): 34 | for delta in range(4): 35 | total = 0 36 | total += sp.diff(chris_sym[alpha][beta][delta], metric_key[gamma]) 37 | total -= sp.diff(chris_sym[alpha][beta][gamma], metric_key[delta]) 38 | for epsilon in range(4): 39 | total += chris_sym[alpha][gamma][epsilon]*chris_sym[epsilon][beta][delta] 40 | total -= chris_sym[alpha][delta][epsilon]*chris_sym[epsilon][beta][gamma] 41 | reimann[alpha][beta][gamma][delta] = sp.cancel(total) 42 | return reimann 43 | 44 | #returns the rank 2 Ricci curvature tensor 45 | #both indicies are lower 46 | def ricci_tensor(reimann): 47 | ricci = tensor(2) 48 | for alpha in range(4): 49 | for beta in range(4): 50 | total = 0 51 | for gamma in range(4): 52 | total += reimann[gamma][alpha][gamma][beta] 53 | ricci[alpha][beta] = sp.cancel(total) 54 | return ricci 55 | 56 | #returns the Ricci scalar, a sympy symbol 57 | def ricci_scalar(ricci_t, metric): 58 | scalar = 0 59 | inverse = inverse_metric(metric) 60 | for alpha in range(4): 61 | for beta in range(4): 62 | scalar += inverse[alpha][beta] * ricci_t[alpha][beta] 63 | scalar = sp.cancel(scalar) 64 | return scalar 65 | 66 | #returns the rank 2 Einstein tensor 67 | #both indices are lower 68 | #think about whether you need to call raise_one_index before equating with a stress-energy tensor 69 | def einstein_tensor(ricci_t, ricci_s, metric): 70 | einstein = tensor(2) 71 | for alpha in range(4): 72 | for beta in range(4): 73 | einstein[alpha][beta] = sp.cancel(ricci_t[alpha][beta] - 0.5*metric[alpha][beta]*ricci_s) 74 | return einstein 75 | 76 | #runs through all parts of the program to find the Einstein tensor given only the metric and its key 77 | def einstein_tensor_from_scratch(metric, metric_key, showprogress = False): 78 | c_syms = christoffel_symbols(metric, metric_key) 79 | if showprogress: print("Christoffel Symbols calculated") 80 | reimann_t = reimann_tensor(c_syms, metric_key) 81 | if showprogress: print("Reimann Tensor calculated") 82 | ricci_t = ricci_tensor(reimann_t) 83 | if showprogress: print("Ricci Tensor calculated") 84 | ricci_s = ricci_scalar(ricci_t, metric) 85 | if showprogress: print("Ricci Scalar calculated") 86 | return einstein_tensor(ricci_t, ricci_s, metric) 87 | 88 | #returns expressions which, when set equal to zero, give the Einstein equations 89 | def einstein_equations(einstein_tensor, stress_energy_tensor): 90 | einstein_equations = [] 91 | for alpha in range(4): 92 | for beta in range(4): 93 | eq = sp.simplify(einstein_tensor[alpha][beta] - 8*sp.pi*sp.Symbol('G')*stress_energy_tensor[alpha][beta]) 94 | if eq != 0 and eq not in einstein_equations: 95 | einstein_equations.append(eq) 96 | return np.array(einstein_equations) 97 | 98 | def conservation_equations(metric, metric_key, stress_energy_tensor): 99 | equations = [] 100 | stress_energy_tensor = raise_one_index(stress_energy_tensor, metric) 101 | cs = christoffel_symbols(metric, metric_key) 102 | for u in range(4): 103 | eq = 0 104 | for v in range(4): 105 | eq += sp.diff(stress_energy_tensor[u][v], metric_key[v]) 106 | for s in range(4): 107 | eq += stress_energy_tensor[s][v]*cs[u][s][v] 108 | eq += stress_energy_tensor[u][s]*cs[v][s][v] 109 | eq = sp.simplify(eq) 110 | if eq != 0 and eq not in equations: 111 | equations.append(eq) 112 | return np.array(equations) 113 | 114 | #returns a 4 x 4 x ... x 4 array of sympy symbols which represent a tensor 115 | def tensor(rank): 116 | shape = [4 for i in range(rank)] 117 | return np.empty(shape, dtype = type(sp.Symbol(''))) 118 | 119 | #returns a 4 x 4 x ... x 4 array of sympy symbols filled with zeros which represent a tensor 120 | def zero_tensor(rank): 121 | shape = [4 for i in range(rank)] 122 | return np.zeros(shape, dtype = type(sp.Symbol(''))) 123 | 124 | #returns the rank of the tensor, passed in as a numpy array 125 | def rank(tensor): 126 | return len(tensor.shape) 127 | 128 | #returns the inverse of metric 129 | def inverse_metric(metric): 130 | return np.array(sp.Matrix(metric).inv()) 131 | 132 | #matrix-multiplies the inverse metric and the tensor 133 | #represents raising one index on a rank 2 tensor 134 | def raise_one_index(tensor, metric, index = 1): 135 | return np.tensordot(inverse_metric(metric), tensor, index) 136 | 137 | #matrix-multiplies the metric and the tensor 138 | #represents lowering one index on a rank 2 tensor 139 | def lower_one_index(tensor, metric, index = 1): 140 | return np.tensordot(metric, tensor, index) 141 | 142 | def kronecker_delta(a, b): 143 | if a == b: 144 | return 1 145 | return 0 146 | 147 | def inverse_perturbations(metric, pert): 148 | h_inv = tensor(2) 149 | g_inv = inverse_metric(metric) 150 | for u in range(4): 151 | for v in range(4): 152 | total = 0 153 | for rho in range(4): 154 | for sigma in range(4): 155 | total -= g_inv[u][rho]*g_inv[v][sigma]*pert[rho][sigma] 156 | h_inv[u][v] = sp.simplify(total) 157 | return h_inv 158 | 159 | #First index corresponds to upper index 160 | def perturbed_christoffel_symbols(metric, metric_key, perturbations, christoffel=None): 161 | perturbed_symbols = tensor(3) 162 | symbols = christoffel_symbols(metric, metric_key) if christoffel == None else christoffel 163 | inverse = inverse_metric(metric) 164 | for mu in range(4): 165 | for nu in range(4): 166 | for lamb in range(4): 167 | total = 0 168 | for rho in range(4): 169 | for sigma in range(4): 170 | total -= inverse[mu][rho]*perturbations[rho][sigma]*symbols[sigma][nu][lamb] 171 | total += sp.Rational(1,2)*inverse[mu][rho]*( 172 | sp.diff(perturbations[rho][nu], metric_key[lamb]) 173 | +sp.diff(perturbations[rho][lamb], metric_key[nu]) 174 | -sp.diff(perturbations[lamb][nu], metric_key[rho])) 175 | perturbed_symbols[mu][nu][lamb] = sp.simplify(total) 176 | return perturbed_symbols 177 | 178 | def perturbed_ricci_tensor(metric, metric_key, pert, chris = None, dchris = None): 179 | dRicci = tensor(2) 180 | if chris == None: 181 | chris = christoffel_symbols(metric, metric_key) 182 | if dchris == None: 183 | dchris = perturbed_christoffel_symbols(metric, metric_key, pert, chris) 184 | for u in range(4): 185 | for k in range(4): 186 | total = 0 187 | for l in range(4): 188 | total += sp.diff(dchris[l][u][l], metric_key[k]) 189 | total -= sp.diff(dchris[l][u][k], metric_key[l]) 190 | for v in range(4): 191 | for e in range(4): 192 | total += dchris[e][u][v]*chris[v][k][e] 193 | total += dchris[v][k][e]*chris[e][u][v] 194 | total -= dchris[e][u][k]*chris[v][v][e] 195 | total -= dchris[v][v][e]*chris[e][u][k] 196 | dRicci[u][k] = sp.simplify(total) 197 | return dRicci 198 | 199 | def perturbed_source_tensor(metric, stress_energy, perturbed_stress_energy, perturbations): 200 | perturbed_source = tensor(2) 201 | stress_energy_trace = sum(stress_energy[i][i] for i in range(4)) 202 | perturbed_stress_energy_trace = sum(perturbed_stress_energy[i][i] for i in range(4)) 203 | for mu in range(4): 204 | for nu in range(4): 205 | perturbed_source[mu][nu] = perturbed_stress_energy[mu][nu] 206 | -sp.Rational(1,2)*metric[mu][nu]*perturbed_stress_energy_trace 207 | -sp.Rational(1,2)*perturbations[mu][nu]*stress_energy_trace 208 | return sp.simplify(perturbed_source) 209 | 210 | def perturbed_einstein_equations(perturbed_ricci_tesor, perturbed_source_tesor): 211 | equations = [] 212 | for mu in range(4): 213 | for nu in range(4): 214 | eq = sp.simplify(perturbed_ricci_tesor[mu][nu] 215 | +8*sp.pi*sp.Symbol('G')*perturbed_source_tesor[mu][nu]) 216 | if eq != 0 and eq not in equations: 217 | equations.append(eq) 218 | return np.array(equations) 219 | 220 | #prints a tensor (or a sympy scalar) in a readable form 221 | def rprint(obj, position = []): 222 | if type(obj) != type(np.array([])): 223 | if obj != 0: 224 | sp.pprint(sp.simplify(obj)) 225 | else: 226 | for n, entry in enumerate(obj): 227 | if type(entry) != type(np.array([])) and entry != 0: 228 | print(str(position + [n]) + ": ") 229 | sp.pprint(sp.simplify(entry)) 230 | else: 231 | rprint(entry, position + [n]) 232 | 233 | #prints a tensor (or a sympy scalar) in LaTeX 234 | def lprint(obj, position = []): 235 | if type(obj) != type(np.array([])): 236 | if obj != 0: 237 | print(sp.latex(sp.simplify(entry))) 238 | else: 239 | for n, entry in enumerate(obj): 240 | if type(entry) != type(np.array([])) and entry != 0: 241 | print(str(position + [n]) + ": ") 242 | print(sp.latex(sp.simplify(entry))) 243 | else: 244 | lprint(entry, position + [n]) 245 | 246 | """#Prints a sympy expression or expressions in a Mathematica ready form 247 | def mprint(obj, position = []): 248 | if type(obj) != type(np.array([])): 249 | if obj != 0: 250 | print(mathematicize(obj)) 251 | else: 252 | for n, entry in enumerate(obj): 253 | if type(entry) != type(np.array([])) and entry != 0: 254 | print(str(position + [n]) + ": ") 255 | print(mathematicize(entry)) 256 | else: 257 | mprint(entry, position + [n])""" 258 | 259 | #Prints a sympy expression or expressions in a Mathematica (Matrix) ready form 260 | def mprint(obj, position = [], eol = True): 261 | if type(obj) != type(np.array([])): 262 | if obj != 0: 263 | print(mathematicize(obj)) 264 | else: 265 | print('{') 266 | for n, entry in enumerate(obj): 267 | if type(entry) != type(np.array([])): #and entry != 0: 268 | #print(str(position + [n]) + ": ") 269 | print(mathematicize(entry)) 270 | if n != len(obj)-1: 271 | print(',') 272 | 273 | else: 274 | mprint(entry, eol = len(obj)-1) #, position + [n]) 275 | print('}') 276 | if n != len(obj)-1: 277 | print(',') 278 | if eol == True: 279 | print('}') 280 | 281 | #Turns a single expression into Mathematica readable form 282 | def mathematicize(exp): 283 | #NOTE: Program currently assumes that all functions are functions of time and all derivatives are with respect to time 284 | exp = str(exp) 285 | #Deals with exponentiation 286 | exp = exp.replace('**', '^') 287 | #Deals with derivatives 288 | while exp.find('Derivative') != -1: 289 | plevel = 1 290 | clevel = 0 291 | fname = "" 292 | start = exp.find('Derivative') 293 | i = start + 11 294 | while plevel > 0: 295 | if exp[i] == '(': 296 | plevel += 1 297 | elif exp[i] == ')': 298 | plevel -= 1 299 | elif exp[i] == ',': 300 | clevel += 1 301 | elif plevel == 1 and clevel == 0: 302 | fname += exp[i] 303 | i += 1 304 | end = i 305 | exp = exp[:start] + fname + '\''*clevel +'[t]' + exp[end:] 306 | #Deals with giving function calls square brackets 307 | exp = exp.replace('(t)', '[t]') 308 | return exp 309 | 310 | if __name__ == "__main__": 311 | #Defines commonly used variable and functions 312 | t = sp.Symbol('t') 313 | r = sp.Symbol('r') 314 | theta = sp.Symbol('theta') 315 | phi = sp.Symbol('phi') 316 | x = sp.Symbol('x') 317 | y = sp.Symbol('y') 318 | z = sp.Symbol('z') 319 | k = sp.Symbol('k') 320 | pi = sp.pi 321 | 322 | a = sp.Function('a')(t) 323 | b = sp.Function('b')(t) 324 | c = sp.Function('c')(t) 325 | 326 | a0 = sp.Symbol('a0') 327 | b0 = sp.Symbol('b0') 328 | c0 = sp.Symbol('c0') 329 | 330 | #w = sp.Rational(1, 3)#sp.Symbol('w') 331 | #rho = sp.Function('rho')(t) 332 | #p = w*rho 333 | #G = sp.Symbol('G') 334 | 335 | I0 = sp.Symbol('I0') 336 | omega0 = sp.Symbol('Omega0') 337 | rho0 = sp.Symbol('rho0')#I0*omega0/(8 * sp.pi * G) 338 | p0 = sp.Symbol('p0')#w*rho0 339 | 340 | 341 | #FRW metric 342 | frw_metric, frw_metric_key = np.diag([-1, a**2/(1-k*r**2), a**2*r**2,a**2*r**2*sp.sin(theta)**2]), [t, r, theta, phi] 343 | #Bianchi metric (currently flat, does not assume isotropy) 344 | bc_metric, bc_metric_key = np.diag([-1, a**2, a**2, a**2]), [t, x, y, z] 345 | #Bianchi with cylindrical curvarute 346 | bcurve, bcurve_key = np.diag([-1, a**2/(1-k*r**2), a**2*r**2, b**2]), [t, r, theta, z] 347 | #Generalized Schwartzchild metric 348 | A, B = sp.Function('A')(r), sp.Function('B')(r) 349 | sc_metric, sc_metric_ky = np.diag([B, A, r**2, r**2*sp.sin(theta)**2]), [t, r, theta, phi] 350 | 351 | #FRW cartesian metric 352 | frw_c_metric_key = [t, x, y, z] 353 | frw_c_metric = zero_tensor(2) 354 | frw_c_metric[0][0] = -1 355 | for i in range(1, 4): 356 | for j in range(1, 4): 357 | frw_c_metric[i][j] = a**2*(kronecker_delta(i, j) + 358 | k*((frw_c_metric_key[i]*frw_c_metric_key[j])/(1-k*(x**2+y**2+z**2)))) 359 | #rprint(frw_c_metric) 360 | 361 | #FRW cartesian metric generalized to Bianchi 362 | b_c_metric_key = [t, x, y, z] 363 | b_c_scale_factors = [-1,a,b,c] 364 | b_c_metric = zero_tensor(2) 365 | b_c_metric[0][0] = -1 366 | for i in range(1, 4): 367 | for j in range(1, 4): 368 | b_c_metric[i][j] = b_c_scale_factors[i]*b_c_scale_factors[j]*(kronecker_delta(i, j) + 369 | k*((b_c_metric_key[i]*b_c_metric_key[j])/(1-k*(x**2+y**2+z**2)))) 370 | #mprint(b_c_metric) 371 | 372 | perturbations = tensor(2) 373 | for r in range(4): 374 | for c in range(4): 375 | perturbations[r][c] = sp.Function('h'+str(r)+str(c))(t, x, y, z) 376 | 377 | T = tensor(2) 378 | for r in range(4): 379 | for c in range(4): 380 | T[r][c] = sp.Function('T'+str(r)+str(c))(t, x, y, z) 381 | 382 | dT = tensor(2) 383 | for r in range(4): 384 | for c in range(4): 385 | dT[r][c] = sp.Function('dT'+str(r)+str(c))(t, x, y, z) 386 | 387 | dRicci = perturbed_ricci_tensor(bc_metric, bc_metric_key, perturbations) 388 | dSource = perturbed_source_tensor(bc_metric, T, dT, perturbations) 389 | dEin = perturbed_einstein_equations(dRicci, dSource) 390 | rprint(dEin) 391 | 392 | #T = np.diag([-rho0*(a0*b0*c0/(a*b*c))**sp.Rational(4, 3) - (3.0*k)/((a*b*c)**sp.Rational(2, 3)*8*pi*G) , p0*a0**2*b0*c0/(a**2*b*c) - k/(a**2*8*pi*G), p0*a0*b0**2*c0/(a*b**2*c) - k/(b**2*8*pi*G), p0*a0*b0*c0**2/(a*b*c**2) - k/(c**2*8*pi*G)]) 393 | #T = np.diag([-rho0*(a0/a)**4.0, (rho0*(a0/a)**4.0)/3.0, (rho0*(a0/a)**4.0)/3.0, (rho0*(a0/a)**4.0)/3.0]) 394 | #T = np.diag([0, 0, 0, 0]) 395 | #rho = sp.Symbol('rho') 396 | #p = sp.Symbol('p') 397 | #p1 = sp.Symbol('p1') 398 | #p2 = sp.Symbol('p2') 399 | #T = np.diag([-rho, p, p, p]) 400 | #einstein = raise_one_index(einstein_tensor_from_scratch(frw_metric, frw_metric_key, showprogress = True), frw_metric) 401 | #rprint(einstein) 402 | 403 | #print('Bianchi Spacetime Einstein Equations:') 404 | 405 | #ein_eq = einstein_equations(einstein, T) 406 | 407 | #rprint(einstein[1,1]*einstein[2,2]*einstein[3,3]/einstein[0,0]**3-(p0/rho0)**3) 408 | #rprint(einstein) 409 | #print(sp.simplify(-1*ein_eq[3] + sum(ein_eq[:3]))) 410 | #print('Conservation Equation for Bianchi Spacetime:') 411 | #rprint(conservation_equations(frw_metric, frw_metric_key, T)) 412 | 413 | #einstein = raise_one_index(einstein_tensor_from_scratch(frw_c_metric, bc_metric_key), frw_c_metric, showprogress = True) 414 | #print('FRW Spacetime Einstein Equations:') 415 | #rprint(einstein_equations(einstein, np.diag([-rho, p, p, p]))) 416 | #print('FRW Equation for Bianchi Spacetime:') 417 | #rprint(conservation_equations(frw_c_metric, frw_c_metric_key, np.diag([-rho, p, p, p]))) 418 | --------------------------------------------------------------------------------