P3M force validated; starting timestep convergence analysis
This commit is contained in:
parent
ae8bacd6a6
commit
13e6c3b32d
12 changed files with 21407 additions and 285 deletions
327
src/wip3m/convergence_baseline_ts_parser.py
Normal file
327
src/wip3m/convergence_baseline_ts_parser.py
Normal file
|
@ -0,0 +1,327 @@
|
|||
#!/usr/bin/env python3
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2025 Tristan Hoellinger
|
||||
# Distributed under the GNU General Public License v3.0 (GPLv3).
|
||||
# See the LICENSE file in the root directory for details.
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
__author__ = "Tristan Hoellinger"
|
||||
__version__ = "0.1.0"
|
||||
__date__ = "2025"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
"""
|
||||
Perform time step convergence tests towards implementing P3M gravity
|
||||
"""
|
||||
|
||||
# pyright: reportWildcardImportFromLibrary=false
|
||||
from os.path import isfile
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
|
||||
from pysbmy.power import PowerSpectrum
|
||||
from pysbmy.fft import FourierGrid
|
||||
|
||||
from wip3m import *
|
||||
from wip3m.tools import get_k_max, generate_sim_params, generate_white_noise_Field, run_simulation
|
||||
from wip3m.params import params_planck_kmax_missing, cosmo_small_to_full_dict, z2a, BASELINE_SEEDPHASE
|
||||
from wip3m.plot_utils import *
|
||||
|
||||
from wip3m.logger import getCustomLogger, INDENT, UNINDENT
|
||||
|
||||
logger = getCustomLogger(__name__)
|
||||
|
||||
workdir = ROOT_PATH + "results/"
|
||||
output_path = OUTPUT_PATH
|
||||
|
||||
from argparse import ArgumentParser
|
||||
|
||||
parser = ArgumentParser(
|
||||
description="Run convergence tests towards implementing P3M gravity."
|
||||
)
|
||||
|
||||
parser.add_argument("--run_id", type=str, help="Simulation run identifier.")
|
||||
parser.add_argument("--L", type=float, default=32.0, help="Box size in Mpc/h.")
|
||||
parser.add_argument("--N", type=int, default=128, help="Density grid size per dimension.")
|
||||
parser.add_argument("--Np", type=int, default=32, help="Number of particles per dimension.")
|
||||
parser.add_argument("--Npm", type=int, default=64, help="PM mesh resolution per dimension.")
|
||||
parser.add_argument("--n_Tiles", type=int, default=8, help="Number of tiles per dimension for short-range PP.")
|
||||
|
||||
# Timestep settings per method
|
||||
for scheme in ["pmref", "pm1", "pm2", "cola", "spm", "p3m1", "p3m2", "p3m3"]:
|
||||
parser.add_argument(f"--nsteps_{scheme}", type=int, default={
|
||||
"pmref": 200, "pm1": 100, "pm2": 20,
|
||||
"cola": 10, "spm": 200,
|
||||
"p3m1": 200, "p3m2": 100, "p3m3": 20
|
||||
}[scheme], help=f"Number of timesteps for {scheme.upper()}.")
|
||||
parser.add_argument(f"--tsd_{scheme}", type=int, default=0,
|
||||
help=f"TimeStepDistribution ID for {scheme.upper()}.")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
go_beyond_Nyquist_ss = True # for the summary statistics
|
||||
force = True
|
||||
force_hard = True
|
||||
|
||||
run_id = args.run_id
|
||||
L = args.L
|
||||
N = args.N
|
||||
Np = args.Np
|
||||
Npm = args.Npm
|
||||
n_Tiles = args.n_Tiles
|
||||
nsteps_pmref = args.nsteps_pmref
|
||||
nsteps_pm1 = args.nsteps_pm1
|
||||
nsteps_pm2 = args.nsteps_pm2
|
||||
nsteps_cola = args.nsteps_cola
|
||||
nsteps_spm = args.nsteps_spm
|
||||
nsteps_p3m1 = args.nsteps_p3m1
|
||||
nsteps_p3m2 = args.nsteps_p3m2
|
||||
nsteps_p3m3 = args.nsteps_p3m3
|
||||
tsd_pmref = args.tsd_pmref
|
||||
tsd_pm1 = args.tsd_pm1
|
||||
tsd_pm2 = args.tsd_pm2
|
||||
tsd_cola = args.tsd_cola
|
||||
tsd_spm = args.tsd_spm
|
||||
tsd_p3m1 = args.tsd_p3m1
|
||||
tsd_p3m2 = args.tsd_p3m2
|
||||
tsd_p3m3 = args.tsd_p3m3
|
||||
logger.info("Running convergence tests...")
|
||||
logger.info(f"Run ID: {run_id}")
|
||||
logger.info(f"Box size: {L} Mpc/h")
|
||||
logger.info(f"Density grid size: {N}^3")
|
||||
logger.info(f"Particles per dimension: {Np}^3")
|
||||
logger.info(f"PM grid size: {Npm}^3")
|
||||
logger.info(f"Number of tiles: {n_Tiles}^3")
|
||||
logger.info(f"Number of timesteps for PMREF: {nsteps_pmref}")
|
||||
logger.info(f"Number of timesteps for PM1: {nsteps_pm1}")
|
||||
logger.info(f"Number of timesteps for PM2: {nsteps_pm2}")
|
||||
logger.info(f"Number of timesteps for COLA: {nsteps_cola}")
|
||||
logger.info(f"Number of timesteps for sPM: {nsteps_spm}")
|
||||
logger.info(f"Number of timesteps for P3M1: {nsteps_p3m1}")
|
||||
logger.info(f"Number of timesteps for P3M2: {nsteps_p3m2}")
|
||||
logger.info(f"Number of timesteps for P3M3: {nsteps_p3m3}")
|
||||
INDENT()
|
||||
|
||||
corner = -L / 2.0
|
||||
RedshiftLPT = 19.0
|
||||
RedshiftFCs = 0.0
|
||||
ai = z2a(RedshiftLPT)
|
||||
af = z2a(RedshiftFCs)
|
||||
k_max = get_k_max(L, N) # k_max in h/Mpc
|
||||
print(f"k_max = {k_max}")
|
||||
cosmo = params_planck_kmax_missing.copy()
|
||||
cosmo["k_max"] = k_max
|
||||
|
||||
wd = workdir + run_id + "/"
|
||||
simdir = output_path + run_id + "/"
|
||||
logdir = simdir + "logs/"
|
||||
if force_hard:
|
||||
import shutil
|
||||
if Path(simdir).exists():
|
||||
shutil.rmtree(simdir)
|
||||
if Path(wd).exists():
|
||||
shutil.rmtree(wd)
|
||||
Path(wd).mkdir(parents=True, exist_ok=True)
|
||||
Path(logdir).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
input_white_noise_file = simdir + "input_white_noise.h5"
|
||||
input_seed_phase_file = simdir + "seed"
|
||||
ICs_path = simdir + "initial_density.h5"
|
||||
simpath = simdir
|
||||
|
||||
# Path to the input matter power spectrum (generated later)
|
||||
input_power_file = simdir + "input_power.h5"
|
||||
|
||||
sim_params = {
|
||||
"L": L,
|
||||
"N": N,
|
||||
"Np": Np,
|
||||
"Npm": Npm,
|
||||
"n_Tiles": n_Tiles,
|
||||
"RedshiftLPT": RedshiftLPT,
|
||||
"RedshiftFCs": RedshiftFCs,
|
||||
}
|
||||
with open(wd + "sim_params.txt", "w") as f:
|
||||
f.write(f"{sim_params}\n")
|
||||
|
||||
logger.info("Setting up simulation parameters...")
|
||||
|
||||
common_params = {
|
||||
"Np": Np,
|
||||
"N": N,
|
||||
"L": L,
|
||||
"corner0": corner,
|
||||
"corner1": corner,
|
||||
"corner2": corner,
|
||||
"h": cosmo["h"],
|
||||
"Omega_m": cosmo["Omega_m"],
|
||||
"Omega_b": cosmo["Omega_b"],
|
||||
"n_s": cosmo["n_s"],
|
||||
"sigma8": cosmo["sigma8"],
|
||||
}
|
||||
|
||||
lpt_params = common_params.copy()
|
||||
lpt_params["method"] = "lpt"
|
||||
lpt_params["InputPowerSpectrum"] = input_power_file
|
||||
lpt_params["ICsMode"] = 1
|
||||
lpt_params["InputWhiteNoise"] = input_white_noise_file
|
||||
|
||||
common_params_num = common_params.copy()
|
||||
common_params_num["ai"] = ai
|
||||
common_params_num["af"] = af
|
||||
common_params_num["RedshiftLPT"] = RedshiftLPT
|
||||
common_params_num["RedshiftFCs"] = RedshiftFCs
|
||||
common_params_num["Npm"] = Npm
|
||||
common_params_num["RunForceDiagnostic"] = False
|
||||
common_params_num["nBinsForceDiagnostic"] = 20
|
||||
common_params_num["nPairsForceDiagnostic"] = 3
|
||||
common_params_num["maxTrialsForceDiagnostic"] = int(1e8)
|
||||
common_params_num["OutputForceDiagnostic"] = simdir + "force_diagnostic.txt"
|
||||
|
||||
pmref_params = common_params_num.copy()
|
||||
pmref_params["method"] = "pm"
|
||||
pmref_params["TimeStepDistribution"] = tsd_pmref
|
||||
pmref_params["nsteps"] = nsteps_pmref
|
||||
|
||||
pm1_params = common_params_num.copy()
|
||||
pm1_params["method"] = "pm"
|
||||
pm1_params["TimeStepDistribution"] = tsd_pm1
|
||||
pm1_params["nsteps"] = nsteps_pm1
|
||||
|
||||
pm2_params = common_params_num.copy()
|
||||
pm2_params["method"] = "pm"
|
||||
pm2_params["TimeStepDistribution"] = tsd_pm2
|
||||
pm2_params["nsteps"] = nsteps_pm2
|
||||
|
||||
cola_params = common_params_num.copy()
|
||||
cola_params["method"] = "cola"
|
||||
cola_params["TimeStepDistribution"] = tsd_cola
|
||||
cola_params["nsteps"] = nsteps_cola
|
||||
|
||||
spm_params = common_params_num.copy()
|
||||
spm_params["method"] = "spm"
|
||||
spm_params["EvolutionMode"] = 5
|
||||
spm_params["TimeStepDistribution"] = tsd_spm
|
||||
spm_params["nsteps"] = nsteps_spm
|
||||
spm_params["n_Tiles"] = n_Tiles
|
||||
|
||||
p3m1_params = common_params_num.copy()
|
||||
p3m1_params["method"] = "p3m"
|
||||
p3m1_params["EvolutionMode"] = 4
|
||||
p3m1_params["TimeStepDistribution"] = tsd_p3m1
|
||||
p3m1_params["nsteps"] = nsteps_p3m1
|
||||
p3m1_params["n_Tiles"] = n_Tiles
|
||||
|
||||
p3m2_params = common_params_num.copy()
|
||||
p3m2_params["method"] = "p3m"
|
||||
p3m2_params["EvolutionMode"] = 4
|
||||
p3m2_params["TimeStepDistribution"] = tsd_p3m2
|
||||
p3m2_params["nsteps"] = nsteps_p3m2
|
||||
p3m2_params["n_Tiles"] = n_Tiles
|
||||
|
||||
p3m3_params = common_params_num.copy()
|
||||
p3m3_params["method"] = "p3m"
|
||||
p3m3_params["EvolutionMode"] = 4
|
||||
p3m3_params["TimeStepDistribution"] = tsd_p3m3
|
||||
p3m3_params["nsteps"] = nsteps_p3m3
|
||||
p3m3_params["n_Tiles"] = n_Tiles
|
||||
|
||||
logger.info("Setting up simulation parameters done.")
|
||||
|
||||
logger.info("Generating simulation parameters...")
|
||||
INDENT()
|
||||
reset_plotting() # Default style for Simbelmynë
|
||||
generate_sim_params(lpt_params, ICs_path, wd, simdir, None, force)
|
||||
|
||||
all_sim_params = [
|
||||
("pmref", pmref_params),
|
||||
("pm1", pm1_params),
|
||||
("pm2", pm2_params),
|
||||
("cola", cola_params),
|
||||
("spm", spm_params),
|
||||
("p3m1", p3m1_params),
|
||||
("p3m2", p3m2_params),
|
||||
("p3m3", p3m3_params),
|
||||
]
|
||||
for name, params in all_sim_params:
|
||||
logger.info(f"Generating parameters for {name.upper()} with nsteps = {params['nsteps']}...")
|
||||
file_ext = f"{name}_nsteps{params['nsteps']}"
|
||||
generate_sim_params(params, ICs_path, wd, simdir, file_ext, force)
|
||||
logger.info(f"Generating parameters for {name.upper()} done.")
|
||||
|
||||
UNINDENT()
|
||||
logger.info("Generating simulation parameters done.")
|
||||
|
||||
setup_plotting() # Reset plotting style for this project
|
||||
|
||||
logger.info("Generating white noise field...")
|
||||
generate_white_noise_Field(
|
||||
L=L,
|
||||
size=N,
|
||||
corner=corner,
|
||||
seedphase=BASELINE_SEEDPHASE,
|
||||
fname_whitenoise=input_white_noise_file,
|
||||
seedname_whitenoise=input_seed_phase_file,
|
||||
force_phase=force,
|
||||
)
|
||||
logger.info("Generating white noise field done.")
|
||||
|
||||
# If cosmo["WhichSpectrum"] == "class", then classy is required.
|
||||
if not isfile(input_power_file) or force:
|
||||
logger.info("Generating input power spectrum...")
|
||||
Pk = PowerSpectrum(L, L, L, N, N, N, cosmo_small_to_full_dict(cosmo))
|
||||
Pk.write(input_power_file)
|
||||
logger.info("Generating input power spectrum done.")
|
||||
|
||||
logger.info("Generating Fourier grid...")
|
||||
# k grid used to compute the final overdensity power spectrum
|
||||
Pinit = 100
|
||||
trim_threshold = 100 # Merge bins until this minimum number of modes per bin is reached
|
||||
log_kmin = np.log10(2 * np.pi / (np.sqrt(3) * L)) # Minimum non-zero k in h/Mpc
|
||||
if go_beyond_Nyquist_ss:
|
||||
k_max_ss = get_k_max(L, N)
|
||||
else:
|
||||
k_max_ss = get_k_max(L, N) / np.sqrt(3) # 1D Nyquist frequency
|
||||
Pbins_left_bnds = np.logspace(log_kmin, np.log10(k_max_ss), Pinit + 1, dtype=np.float32)
|
||||
Pbins_left_bnds = Pbins_left_bnds[:-1]
|
||||
input_ss_file = simdir + "input_ss_k_grid.h5"
|
||||
Gk = FourierGrid(
|
||||
L,
|
||||
L,
|
||||
L,
|
||||
N,
|
||||
N,
|
||||
N,
|
||||
k_modes=Pbins_left_bnds,
|
||||
kmax=k_max_ss,
|
||||
trim_bins=True,
|
||||
trim_threshold=trim_threshold,
|
||||
)
|
||||
Gk.write(input_ss_file)
|
||||
logger.info("Generating Fourier grid done.")
|
||||
|
||||
logger.info("Running simulations...")
|
||||
INDENT()
|
||||
logger.info("Running LPT simulation...")
|
||||
run_simulation("lpt", lpt_params, wd, logdir)
|
||||
logger.info("Running LPT simulation done.")
|
||||
|
||||
for name, parameters in all_sim_params:
|
||||
logger.info(f"Running {name.upper()} simulation...")
|
||||
run_simulation(name, parameters, wd, logdir)
|
||||
logger.info(f"Running {name.upper()} simulation done.")
|
||||
UNINDENT()
|
||||
logger.info("All simulations done.")
|
||||
|
||||
except OSError as e:
|
||||
logger.error("Directory or file access error: %s", str(e))
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.critical("An unexpected error occurred: %s", str(e))
|
||||
raise RuntimeError("Failed.") from e
|
||||
finally:
|
||||
UNINDENT()
|
||||
logger.info("Running convergence tests done.")
|
Loading…
Add table
Add a link
Reference in a new issue