Created
October 7, 2025 18:08
-
-
Save joaoreboucas1/508dfbcfad4c1f6790b4af03c63624ec to your computer and use it in GitHub Desktop.
Auxiliary functions for reproducing Riess' 1998 paper results for dark energy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from random import uniform | |
| from time import perf_counter | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| from scipy.integrate import quad | |
| import getdist | |
| from getdist import plots | |
| c = 299_792.458 # km/s | |
| class SN: | |
| def __init__(self, mu, sigma_mu, z=None, logcz=None, name=""): | |
| self.name = name | |
| if logcz is not None: | |
| self.z = 10**logcz/c | |
| else: | |
| self.z = z | |
| self.mu = mu | |
| self.sigma_mu = sigma_mu | |
| self.DL = 10**((self.mu - 25)/5) # in Mpc | |
| self.sigma_DL = 10**((self.mu - 25)/5)*np.log(10)/5*sigma_mu # in Mpc | |
| mlcs = [ | |
| SN(logcz=3.734, mu=34.72, sigma_mu=0.16, name="1992bo"), | |
| SN(logcz=3.779, mu=34.87, sigma_mu=0.11, name="1992bc"), | |
| SN(logcz=4.481, mu=38.41, sigma_mu=0.15, name="1992aq"), | |
| SN(logcz=4.350, mu=37.80, sigma_mu=0.17, name="1992ae"), | |
| SN(logcz=3.896, mu=35.76, sigma_mu=0.13, name="1992P"), | |
| SN(logcz=4.178, mu=36.53, sigma_mu=0.15, name="1990af"), | |
| SN(logcz=3.859, mu=35.39, sigma_mu=0.18, name="1994M"), | |
| SN(logcz=3.685, mu=34.27, sigma_mu=0.12, name="1994S"), | |
| SN(logcz=4.030, mu=36.19, sigma_mu=0.21, name="1994T"), | |
| SN(logcz=3.398, mu=33.01, sigma_mu=0.13, name="1995D"), | |
| SN(logcz=3.547, mu=33.60, sigma_mu=0.17, name="1995E"), | |
| SN(logcz=4.166, mu=36.85, sigma_mu=0.13, name="1995ac"), | |
| SN(logcz=3.820, mu=35.15, sigma_mu=0.16, name="1995ak"), | |
| SN(logcz=3.679, mu=34.15, sigma_mu=0.19, name="1995bd"), | |
| SN(logcz=3.924, mu=35.98, sigma_mu=0.20, name="1996C"), | |
| SN(logcz=4.572, mu=39.01, sigma_mu=0.13, name="1996ab"), | |
| SN(logcz=3.891, mu=35.37, sigma_mu=0.23, name="1992ag"), | |
| SN(logcz=3.625, mu=33.92, sigma_mu=0.23, name="1992al"), | |
| SN(logcz=4.024, mu=36.26, sigma_mu=0.23, name="1992bg"), | |
| SN(logcz=4.130, mu=36.91, sigma_mu=0.23, name="1992bh"), | |
| SN(logcz=4.111, mu=36.26, sigma_mu=0.23, name="1992bl"), | |
| SN(logcz=4.379, mu=37.65, sigma_mu=0.23, name="1992bp"), | |
| SN(logcz=4.418, mu=38.21, sigma_mu=0.23, name="1992br"), | |
| SN(logcz=4.283, mu=37.61, sigma_mu=0.23, name="1992bs"), | |
| SN(logcz=3.871, mu=35.20, sigma_mu=0.23, name="1993H"), | |
| SN(logcz=4.189, mu=37.03, sigma_mu=0.23, name="1993O"), | |
| SN(logcz=4.177, mu=36.80, sigma_mu=0.23, name="1993ag"), | |
| ] | |
| highz = [ | |
| SN(z=0.43, mu=41.74, sigma_mu=0.28, name="1996E"), | |
| SN(z=0.62, mu=42.98, sigma_mu=0.17, name="1996H"), | |
| SN(z=0.57, mu=42.76, sigma_mu=0.19, name="1996I"), | |
| SN(z=0.30, mu=41.38, sigma_mu=0.24, name="1996J"), | |
| SN(z=0.38, mu=41.63, sigma_mu=0.20, name="1996K"), | |
| SN(z=0.43, mu=42.55, sigma_mu=0.25, name="1996U"), | |
| SN(z=0.44, mu=41.95, sigma_mu=0.17, name="1997ce"), | |
| SN(z=0.50, mu=42.40, sigma_mu=0.17, name="1997cj"), | |
| SN(z=0.97, mu=44.39, sigma_mu=0.30, name="1997ck"), | |
| SN(z=0.45, mu=42.45, sigma_mu=0.17, name="1995K"), | |
| ] | |
| all = mlcs + highz | |
| zs = np.array([sn.z for sn in all]) | |
| mus = np.array([sn.mu for sn in all]) | |
| sigmas = np.array([sn.sigma_mu for sn in all]) | |
| def S_k(x, omegak): | |
| sqrt_omegak = np.sqrt(np.abs(omegak)) | |
| if omegak == 0: return x | |
| elif omegak > 0: return np.sinh(sqrt_omegak*x)/sqrt_omegak | |
| elif omegak < 0: return np.sin(sqrt_omegak*x)/sqrt_omegak | |
| def lum_dist(z, omegam, omegal, H0): | |
| omegak = 1 - omegam - omegal | |
| integrand = lambda z: 1/np.sqrt((1+z)**2*(1+omegam*z) - z*(2+z)*omegal) | |
| integral = quad(integrand, 0, z)[0] | |
| return c*(1+z)/H0 * S_k(integral, omegak) | |
| sigma_vel_disp_lowz = 5/np.log(10) * 200 / 299_792.458 | |
| sigma_vel_disp_highz = 5/np.log(10) * np.sqrt((200**2 + 2500**2)) / 299_792.458 | |
| sigma_vel_disp = np.array([sigma_vel_disp_lowz/z if z < 0.2 else sigma_vel_disp_highz/z for z in zs]) | |
| def chi2(model): | |
| global mus, sigmas, sigma_vel_disp | |
| om, ol, h0 = model | |
| if np.any(om*(1+zs)**3 + (1 - om - ol)*(1+zs)**2 + ol < 0): return np.nan | |
| lum_dists = np.array([lum_dist(z, *model) for z in zs]) | |
| mus_th = 5*np.log10(lum_dists) + 25 | |
| return np.sum((mus_th - mus)**2/(sigmas**2 + sigma_vel_disp**2)) | |
| class MCMCWalker: | |
| def __init__(self): | |
| # Hard-coding an initial point for now | |
| initial_om = 0.3 | |
| initial_ol = 0.7 | |
| initial_h0 = 70 | |
| initial_params = [initial_om, initial_ol, initial_h0] | |
| initial_chi2 = chi2(initial_params) | |
| if initial_chi2 == np.nan: | |
| self.__init__() | |
| return | |
| initial_sample = { | |
| 'params': initial_params, | |
| 'chi2': initial_chi2, | |
| 'weight': 1, | |
| } | |
| self.samples = [initial_sample] | |
| def accept_sample(self, params, chi2): | |
| sample = { | |
| 'params': params, | |
| 'chi2': chi2, | |
| 'weight': 1 | |
| } | |
| self.samples.append(sample) | |
| def step(self): | |
| while True: | |
| current_chi2 = self.samples[-1]['chi2'] | |
| new_om = self.samples[-1]['params'][0] + uniform(-0.25, 0.25) | |
| new_ol = self.samples[-1]['params'][1] + uniform(-0.25, 0.25) | |
| new_h0 = self.samples[-1]['params'][2] + uniform(-2.0, 2.0) | |
| new_params = [new_om, new_ol, new_h0] | |
| if new_h0 < 60 or new_h0 > 80 or new_om < 0 or new_om > 1 or new_ol < -1 or new_ol > 3: | |
| # Reject point outside the prior | |
| self.samples[-1]['weight'] += 1 | |
| continue | |
| new_chi2 = chi2(new_params) | |
| if new_chi2 == np.nan: | |
| # Reject points that have problematic chi2 | |
| self.samples[-1]['weight'] += 1 | |
| continue | |
| if new_chi2 < current_chi2: | |
| self.accept_sample(new_params, new_chi2) | |
| break | |
| else: | |
| r = uniform(0, 1) | |
| if r < np.exp(-(new_chi2 - current_chi2)/2): | |
| self.accept_sample(new_params, new_chi2) | |
| break | |
| else: | |
| self.samples[-1]['weight'] += 1 # Increment weight | |
| continue | |
| def gelman_rubin(self, n_split): | |
| all_params = np.array( | |
| [sample['params'] for sample in self.samples] | |
| )[:-(len(self.samples)%n_split)] | |
| np.random.shuffle(all_params) | |
| split_params = np.split(all_params, n_split) | |
| avg = np.mean(split_params, axis=1) | |
| std = np.std(split_params, axis=1) | |
| avg_of_std = np.mean(std, axis=0) | |
| std_of_avg = np.std(avg, axis=0) | |
| R_minus_one = std_of_avg/avg_of_std | |
| return np.max(R_minus_one) | |
| def run_mcmc(): | |
| w = MCMCWalker() | |
| print("Starting MCMC") | |
| start = perf_counter() | |
| while True: | |
| for _ in range(1000): w.step() | |
| R_minus_one = w.gelman_rubin(4) | |
| print(f"At {len(w.samples)} samples, R-1 = {R_minus_one}") | |
| if R_minus_one < 0.01: break | |
| print(f"MCMC Converged! Took {perf_counter() - start:.2f} seconds") | |
| return w | |
| def getdist_chain(w): | |
| mcmc = getdist.MCSamples( | |
| samples=np.array([sample['params'] + [sample['chi2']] for sample in w.samples]), | |
| weights=np.array([sample['weight'] for sample in w.samples]), | |
| names=["Omega_m", "Omega_Lambda", "H0", "chi2"], | |
| labels=["\\Omega_m", "\\Omega_\\Lambda", "H_0", "\\chi^2"], | |
| ranges={"Omega_m": (0, None)} | |
| ) | |
| mcmc.removeBurn(0.3) | |
| mcmc.addDerived(mcmc["Omega_m"]/2 - mcmc["Omega_Lambda"], name="q0", label="q_0") | |
| mcmc.addDerived(1 - mcmc["Omega_Lambda"] - mcmc["Omega_m"], name="Omega_k", label="\\Omega_k") | |
| return mcmc | |
| def plot_chain(chain): | |
| g = plots.get_single_plotter(width_inch=5, ratio=30/25) | |
| g.settings.num_plot_contours = 3 | |
| ax = g.get_axes() | |
| chain.updateSettings({"contours": [0.68, 0.95, 0.997]}) | |
| g.plot_2d(chain, "Omega_m", "Omega_Lambda", ls='-', lws=1) | |
| ax.set_xlim([0, 2.5]) | |
| ax.set_ylim([-1, 3]) | |
| # Deceleration parameter lines | |
| om = np.linspace(0, 2.5, 50) | |
| ol = om/2 # q_0 = 0 | |
| ax.plot(om, ol, color="black", ls="--", lw=1, dashes=(4, 4)) | |
| ax.text(2.0, ol[-7], r"$q_0 = 0$", rotation=22) | |
| ol = om/2 - 0.5 # q_0 = 0.5 | |
| ax.plot(om, ol, color="black", ls="--", lw=1, dashes=(4, 4)) | |
| ax.text(2.0, ol[-7], r"$q_0 = 0.5$", rotation=22) | |
| ol = om/2 + 0.5 # q_0 = -0.5 | |
| ax.plot(om, ol, color="black", ls="--", lw=1, dashes=(4, 4)) | |
| ax.text(2.0, ol[-7], r"$q_0 = -0.5$", rotation=22) | |
| # Closed/Open line | |
| ol = 1 - om | |
| ax.plot(om, ol, color="black", ls="-", lw=1) | |
| ax.text(1.2, ol[-23], "Closed", rotation=-35) | |
| ax.text(1.15, ol[-21], "Open", rotation=-35) | |
| # Omega_Lambda = 0 | |
| ol = 0*om | |
| ax.plot(om, ol, color="black", ls=":", lw=1) | |
| ax.text(2.1, -0.15, r"$\Omega_\Lambda = 0$") | |
| ax.set_xticks(np.arange(0, 2.51, 0.1), minor=True) | |
| ax.set_yticks(np.arange(-1, 3, 0.1), minor=True) | |
| ax.tick_params(direction="in", length=10, top=True, right=True, pad=10) | |
| ax.tick_params(which="minor", direction="in", length=5, top=True, right=True) | |
| g.export("riess_reproduction.pdf") | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment