hw2d.run2
1import fire 2import numpy as np 3import matplotlib.pyplot as plt 4from tqdm import tqdm 5import os 6import time 7from typing import Iterable 8 9# Local imports 10from hw2d.model import HW 11from hw2d.initializations.fourier_noise import get_fft_noise 12from hw2d.initializations.sine import get_2d_sine 13from hw2d.utils.io import ( 14 get_save_params, 15 create_appendable_h5, 16 save_to_buffered_h5, 17 append_h5, 18 continue_h5_file, 19) 20from hw2d.utils.namespaces import Namespace 21from hw2d.utils.plot.movie import create_movie 22from hw2d.utils.run_properties import calculate_properties 23from hw2d.utils.plot.timetrace import plot_timetraces, plot_timetrace_comparison 24from hw2d.utils.downsample import fourier_downsample 25from hw2d.utils.helpers import format_print_dict 26from hw2d.physical_properties.numpy_properties import ( 27 get_gamma_n, 28 get_gamma_n_spectrally, 29 get_gamma_c, 30 get_energy, 31 get_energy_N_spectrally, 32 get_energy_V_spectrally, 33 get_enstrophy, 34 get_enstrophy_phi, 35 get_DE, 36 get_DU, 37 get_dE_dt, 38 get_dU_dt, 39) 40 41 42property_fncs = { 43 "gamma_n": lambda n, p, dx, **kwargs: get_gamma_n(n=n, p=p, dx=dx), 44 "gamma_n_spectral": lambda n, p, dx, **kwargs: get_gamma_n_spectrally( 45 n=n, p=p, dx=dx 46 ), 47 "gamma_c": lambda n, p, dx, c1, **kwargs: get_gamma_c(n=n, p=p, c1=c1, dx=dx), 48 "energy": lambda n, p, dx, **kwargs: get_energy(n=n, phi=p, dx=dx), 49 "thermal_energy": lambda n, **kwargs: get_energy_N_spectrally(n=n), 50 "kinetic_energy": lambda p, dx, **kwargs: get_energy_V_spectrally(p=p, dx=dx), 51 "enstrophy": lambda n, o, dx, **kwargs: get_enstrophy(n=n, omega=o, dx=dx), 52 "enstrophy_phi": lambda n, p, dx, **kwargs: get_enstrophy_phi(n=n, phi=p, dx=dx), 53 "DE": lambda n, p, Dn, Dp, **kwargs: get_DE(n=n, p=p, Dn=Dn, Dp=Dp), 54 "DU": lambda n, o, Dn, Dp, **kwargs: get_DU(n=n, o=o, Dn=Dn, Dp=Dp), 55 "dE_dt": lambda gamma_n, gamma_c, DE, **kwargs: get_dE_dt(gamma_n=gamma_n, gamma_c=gamma_c, DE=DE), 56 "dU_dt": lambda gamma_n, DU, **kwargs: get_dU_dt(gamma_n=gamma_n, DU=DU), 57} 58 59 60def run( 61 # Physics & Numerics 62 step_size: float = 0.025, 63 adaptive_step_size: bool = False, 64 end_time: float = 1_000, 65 grid_pts: int = 512, 66 k0: float = 0.15, 67 N: int = 3, 68 nu: float = 5.0e-09, 69 c1: float = 1, 70 kappa_coeff: float = 1.0, 71 poisson_bracket_coeff: float = 1.0, 72 zonal: bool = False, 73 # Running 74 show_property: str = "gamma_n", 75 # Initialization 76 seed: int or None = None, 77 init_type: str = "normal", 78 init_scale: float = 1 / 100, 79 # Saving 80 output_path: str = "c1=1.0_nu=5e-09_dt=0.025_v=1.h5", 81 recording_start_time: float = 0, 82 continue_file: bool = False, 83 buffer_length: int = 100, 84 snaps: int = 1, 85 chunk_size: int = 100, 86 downsample_factor: float = 16, 87 add_last_state: bool = True, 88 # Movie 89 movie: bool = False, 90 min_fps: int = 10, 91 dpi: int = 75, 92 speed: int = 10, 93 # Properties 94 properties: Iterable[str] = [ 95 "gamma_n", 96 "gamma_n_spectral", 97 "gamma_c", 98 "energy", 99 "thermal_energy", 100 "kinetic_energy", 101 "enstrophy", 102 "enstrophy_phi", 103 ], 104 # Plotting 105 t0_std=500, 106 plot_properties: Iterable[str] = ( 107 "gamma_c", 108 "gamma_n", 109 "gamma_n_spectral", 110 "energy", 111 "kinetic_energy", 112 "thermal_energy", 113 "enstrophy", 114 "enstrophy_phi" 115 ), 116 # Other 117 debug: bool = False, 118 force_recompute: bool = False, 119): 120 """ 121 Run the simulation with the given parameters. 122 123 Args: 124 step_size (float, optional): Incremental step for simulation progression. Defaults to 0.025. 125 end_time (float, optional): Duration till the simulation should run. Defaults to 1_000. 126 grid_pts (int, optional): Grid points. Suggested: 128 for coarse, 1024 for fine. Defaults to 512. 127 k0 (float, optional): Determines k-focus. Suggested: 0.15 for high-k, 0.0375 for low-k. Defaults to 0.15. 128 N (int, optional): Dissipation exponent's half value. Defaults to 3. 129 nu (float, optional): Viscosity. Suggested: 5e-10 for coarse-large, 1e-4 for fine-small. Defaults to 5.0e-08. 130 c1 (float, optional): Transition scale between hydrodynamic and adiabatic. Suggested values: 0.1, 1, 5. Defaults to 1.0. 131 kappa_coeff (float, optional): Coefficient of d/dy phi. Defaults to 1.0. 132 poisson_bracket_coeff (float, optional): Coefficient of Poisson bracket [A,B] implemented with Arakawa Scheme. Defaults to 1.0. 133 zonal (bool, ooptional): If True, uses make_zonal_func to simulate mHW2D. Defaults to False. 134 seed (int or None, optional): Seed for random number generation. Defaults to None. 135 init_type (str, optional): Initialization method. Choices: 'fourier', 'sine', 'random', 'normal'. Defaults to 'normal'. 136 init_scale (float, optional): Scaling factor for initialization. Defaults to 0.01. 137 output_path (str, optional): Where to save the simulation data. Defaults to ''. 138 recording_start_time (float, optional): Time (t) from which onwards the data is recorded. Defaults to 0. 139 continue_file (bool, optional): If True, continue with existing file. Defaults to False. 140 buffer_length (int, optional): Size of buffer for storage. Defaults to 100. 141 chunks (int, optional): Chunks of the h5 file. Defaults to 100. 142 downsample_factor (float, optional): Factor along each axis that it is downsampled with for saving. Defaults to 1. 143 add_last_state (bool, optional): Whether the last high-resolution frame is saved. Turns True if downsampling on. Defaults to False. 144 snaps (int, optional): Snapshot intervals for saving. Defaults to 1. 145 movie (bool, optional): If True, generate a movie out of simulation. Defaults to True. 146 min_fps (int, optional): Parameter for movie generation. Defaults to 10. 147 dpi (int, optional): Parameter for movie generation. Defaults to 75. 148 speed (int, optional): Parameter for movie generation. Defaults to 5. 149 properties (Iterable[str], optional): List of properties to calculate for the saved file. 150 plot_properties (Iterable[str], optional): List of properties to plot a timetrace for. 151 debug (bool, optional): Enable or disable debug mode. Defaults to False. 152 153 Returns: 154 None: The function saves simulation data or generates a movie as specified. 155 """ 156 step_size = float(step_size) 157 nu = float(nu) 158 159 # Unpacking 160 y = grid_pts 161 x = grid_pts 162 L = 2 * np.pi / k0 # Physical box size 163 dx = L / x # Grid resolution 164 steps = int(end_time / step_size) # Number of Steps until end_time 165 snap_count = steps // snaps + 1 # number of snapshots 166 field_list = ("density", "omega", "phi") 167 initial_time = np.float64(0) 168 iteration_count = 0 169 np.random.seed(seed) 170 if downsample_factor != 1: 171 add_last_state = True 172 if show_property not in properties: 173 properties += [show_property] 174 175 # Define Initializations 176 noise = { 177 "fourier": lambda y, x: get_fft_noise( 178 resolution=[y, x], 179 size=L, 180 scale=1, 181 min_wavelength=dx * 12, 182 max_wavelength=dx * grid_pts * 100, 183 factor=2, 184 ), 185 "sine": lambda y, x: get_2d_sine((y, x), L), 186 "random": lambda y, x: np.random.rand(y, x).astype(np.float64), 187 "normal": lambda y, x: np.random.normal(size=(y, x)).astype(np.float64), 188 } 189 downsample_fnc = fourier_downsample 190 191 # Physics 192 # TODO: Move all to Tensors along batch dimension for simulations! 193 physics_params = dict( 194 dx=dx, 195 N=N, 196 c1=c1, 197 nu=nu, 198 k0=k0, 199 poisson_bracket_coeff=poisson_bracket_coeff, 200 kappa_coeff=kappa_coeff, 201 zonal=zonal 202 ) 203 # Initialize Plasma 204 plasma = Namespace( 205 density=noise[init_type](y, x) * init_scale, 206 omega=noise[init_type](y, x) * init_scale, 207 phi=noise[init_type](y, x) * init_scale, 208 age=0, 209 ) 210 211 # File Handling 212 if output_path: 213 y_save = y 214 x_save = x 215 if downsample_factor != 1: 216 y_save = int(round(y / downsample_factor)) 217 x_save = int(round(x / downsample_factor)) 218 print(f"Downsample by {downsample_factor}x: {y}x{x}->{y_save}x{x_save}") 219 # Output data from this run 220 buffer = { 221 field: np.zeros((buffer_length, y_save, x_save), dtype=np.float32) 222 for field in field_list 223 } 224 # Property buffer 225 buffer[f"time"] = np.zeros((buffer_length, 1), dtype=np.float32) 226 for prop_name in properties: 227 buffer[f"fullres_{prop_name}"] = np.zeros( 228 (buffer_length, 1), dtype=np.float32 229 ) 230 # Other output parameters 231 output_params = { 232 "buffer": buffer, 233 "buffer_index": 0, 234 "output_path": output_path, 235 } 236 # Load Data 237 if os.path.isfile(output_path) and not force_recompute: 238 if continue_file: 239 plasma, physics_params = continue_h5_file(output_path, field_list) 240 save_params = get_save_params( 241 physics_params, 242 step_size, 243 snaps, 244 x, 245 y, 246 x_save=x_save, 247 y_save=y_save, 248 recording_start_time=recording_start_time, 249 adaptive_step_size=adaptive_step_size, 250 ) 251 initial_time = np.float64(plasma.age) 252 print( 253 f"Loaded: {output_path} (age={plasma.age}, time={initial_time})\n{physics_params}" 254 ) 255 # Check if broken 256 for field in plasma.keys(): 257 # Not NaNs 258 if np.isnan(np.sum(plasma[field])): 259 print(f"Input file is broken: NaNs in {field}") 260 raise BaseException(f"Input file is broken: NaNs in {field}") 261 # Not zeros 262 if field in ("density", "omega"): 263 if np.all(plasma[field] == 0): 264 print(f"FAILED {field} is zero") 265 raise BaseException(f"Input File is zero: {field}") 266 else: 267 print(f"File already exists.") 268 return 269 # Create Data 270 else: 271 save_params = get_save_params( 272 physics_params, 273 step_size, 274 snaps, 275 x, 276 y, 277 x_save=x_save, 278 y_save=y_save, 279 recording_start_time=recording_start_time, 280 adaptive_step_size=adaptive_step_size, 281 ) 282 # Create file 283 create_appendable_h5( 284 output_path, 285 save_params, 286 properties=["time"] 287 + [f"fullres_{prop_name}" for prop_name in properties], 288 dtype=np.float32, 289 chunk_size=chunk_size, 290 add_last_state=add_last_state, 291 ) 292 # Save initial values 293 if not recording_start_time: 294 new_val = Namespace( 295 **{ 296 k: v 297 for k, v in plasma.items() 298 if k in ("phi", "omega", "density") 299 } 300 ) 301 last_state = {} 302 for k, v in plasma.items(): 303 if k in ("phi", "omega", "density"): 304 if downsample_factor != 1: 305 new_val[k] = downsample_fnc(v, downsample_factor) 306 if add_last_state: 307 last_state[f"state_{k}"] = v 308 for prop_name in properties: 309 new_val[f"fullres_{prop_name}"] = property_fncs[prop_name]( 310 n=plasma["density"], 311 p=plasma["phi"], 312 o=plasma["omega"], 313 dx=dx, 314 c1=c1, 315 ) 316 new_val["time"] = initial_time 317 output_params["buffer_index"] = save_to_buffered_h5( 318 new_val=new_val, 319 last_state=last_state, 320 buffer_length=buffer_length, 321 **output_params, 322 ) 323 324 # Display runtime parameters 325 # save_params["output_path"] = output_path # No support for strings 326 format_print_dict(save_params) 327 328 # Setup Simulation 329 hw = HW(**physics_params, debug=debug) 330 plasma["phi"] = hw.get_phi(plasma.omega, physics_params["dx"]) 331 332 # Setup Progress Bar 333 print("Running simulation...") 334 iteration_count = 0 335 bar_format = "{rate_fmt} | {desc} {percentage:>6.2f}%|{bar}| {n:.2f}/{total:.2f} [{elapsed}<{remaining}]" 336 # bar_format = "{percentage:>6.2f}%|{bar}| {n:.4f}/{total_fmt} [{elapsed}<{remaining}] ({rate_fmt})" 337 used_step_size = step_size 338 used_time_steps = [] 339 current_time = initial_time 340 new_val = {} 341 342 try: 343 with tqdm( 344 total=end_time - initial_time, 345 unit="t", 346 bar_format=bar_format, 347 ) as pbar: 348 # Run Simulation 349 while round(current_time, 4) < end_time: 350 # Progress one step, alternatively: hw.euler_step() 351 successful = False 352 while not successful: 353 try: 354 plasma = hw.rk4_step(plasma, dt=step_size, dx=dx) 355 used_step_size = step_size 356 successful = True 357 except Exception as e: 358 print(e) 359 time.sleep(10) 360 iteration_count += 1 361 362 # Improved accuracy of time tracking 363 if adaptive_step_size: 364 used_time_steps.append(np.float64(used_step_size)) 365 current_time = initial_time + np.sum(used_time_steps) 366 else: 367 current_time = initial_time + iteration_count * step_size 368 369 # Batched Saving 370 # TODO: Figure out whether it should be iteration_count % (snaps - 1) == 0 371 if (current_time >= recording_start_time) and ( 372 iteration_count % snaps == 0 373 ): 374 new_val = Namespace( 375 **{ 376 k: v.copy() 377 for k, v in plasma.items() 378 if k in ("phi", "omega", "density") 379 } 380 ) 381 # Add properties if they were selected 382 # TODO: Faster to do before saving through trivial parallelization 383 new_val["time"] = current_time 384 for prop_name in properties: 385 new_val[f"fullres_{prop_name}"] = property_fncs[prop_name]( 386 n=plasma["density"], 387 p=plasma["phi"], 388 o=plasma["omega"], 389 dx=dx, 390 c1=c1, 391 ) 392 393 # Save to records 394 if output_path: 395 last_state = {} 396 # Save downsampled & state 397 for k, v in plasma.items(): 398 if k in ("phi", "omega", "density"): 399 # Downsample for saving 400 if downsample_factor != 1: 401 new_val[k] = downsample_fnc(v.copy(), downsample_factor) 402 # Add last state as high-res for continuing simulation 403 if add_last_state: 404 last_state[f"state_{k}"] = v 405 # Save to buffer/file 406 output_params["buffer_index"] = save_to_buffered_h5( 407 new_val=new_val, 408 last_state=last_state, 409 buffer_length=buffer_length, 410 **output_params, 411 ) 412 413 # Check for breaking 414 if np.isnan(np.sum(plasma.density)): 415 print(f"FAILED @ {iteration_count:,} steps ({plasma.age:,})") 416 break 417 418 # Calculate iterations per second, handling division by zero 419 try: 420 iter_per_sec = iteration_count / pbar.format_dict["elapsed"] 421 except ZeroDivisionError: 422 iter_per_sec = float("inf") # or set to 0 or any default value 423 424 # Update progress 425 pbar.update(used_step_size) 426 new_description = f"{iter_per_sec:.2f}it/s" 427 if show_property: 428 new_description += f" | Γn = {new_val.get(show_property, 0):.2g}" 429 pbar.set_description(new_description) 430 431 except Exception as e: 432 print(e) 433 raise e 434 # os.remove(output_path) 435 # print(f"removed {output_path}") 436 return 437 438 # If output_path is defined, flush any remaining data in the buffer 439 if ( 440 output_path 441 and (current_time > recording_start_time) 442 and (output_params["buffer_index"] > 0) 443 ): 444 append_h5(**output_params) 445 446 # Get Performance stats 447 hw.print_log() 448 449 # Generate Movie from saved file 450 if movie and output_path: 451 print(f"Generating movie...") 452 create_movie( 453 input_filename=output_path, 454 output_filename=output_path.replace(".h5", ""), 455 t0=0, 456 t1=None, 457 plot_order=field_list, 458 min_fps=min_fps, 459 dpi=dpi, 460 speed=speed, 461 ) 462 463 if properties and output_path: 464 print(f"Calculating properties...") 465 calculate_properties( 466 file_path=output_path, 467 batch_size=buffer_length, 468 property_list=properties, 469 force_recompute=True, 470 is_debug=False, 471 ) 472 473 if plot_properties and output_path: 474 print(f"Plotting properties...") 475 plot_timetrace_comparison( 476 file_path=output_path, 477 out_path=None, 478 properties=plot_properties, 479 t0=0, 480 t0_std=t0_std, 481 ) 482 483 484if __name__ == "__main__": 485 fire.Fire(run)
property_fncs =
{'gamma_n': <function <lambda>>, 'gamma_n_spectral': <function <lambda>>, 'gamma_c': <function <lambda>>, 'energy': <function <lambda>>, 'thermal_energy': <function <lambda>>, 'kinetic_energy': <function <lambda>>, 'enstrophy': <function <lambda>>, 'enstrophy_phi': <function <lambda>>, 'DE': <function <lambda>>, 'DU': <function <lambda>>, 'dE_dt': <function <lambda>>, 'dU_dt': <function <lambda>>}
def
run( step_size: float = 0.025, adaptive_step_size: bool = False, end_time: float = 1000, grid_pts: int = 512, k0: float = 0.15, N: int = 3, nu: float = 5e-09, c1: float = 1, kappa_coeff: float = 1.0, poisson_bracket_coeff: float = 1.0, zonal: bool = False, show_property: str = 'gamma_n', seed: int = None, init_type: str = 'normal', init_scale: float = 0.01, output_path: str = 'c1=1.0_nu=5e-09_dt=0.025_v=1.h5', recording_start_time: float = 0, continue_file: bool = False, buffer_length: int = 100, snaps: int = 1, chunk_size: int = 100, downsample_factor: float = 16, add_last_state: bool = True, movie: bool = False, min_fps: int = 10, dpi: int = 75, speed: int = 10, properties: Iterable[str] = ['gamma_n', 'gamma_n_spectral', 'gamma_c', 'energy', 'thermal_energy', 'kinetic_energy', 'enstrophy', 'enstrophy_phi'], t0_std=500, plot_properties: Iterable[str] = ('gamma_c', 'gamma_n', 'gamma_n_spectral', 'energy', 'kinetic_energy', 'thermal_energy', 'enstrophy', 'enstrophy_phi'), debug: bool = False, force_recompute: bool = False):
61def run( 62 # Physics & Numerics 63 step_size: float = 0.025, 64 adaptive_step_size: bool = False, 65 end_time: float = 1_000, 66 grid_pts: int = 512, 67 k0: float = 0.15, 68 N: int = 3, 69 nu: float = 5.0e-09, 70 c1: float = 1, 71 kappa_coeff: float = 1.0, 72 poisson_bracket_coeff: float = 1.0, 73 zonal: bool = False, 74 # Running 75 show_property: str = "gamma_n", 76 # Initialization 77 seed: int or None = None, 78 init_type: str = "normal", 79 init_scale: float = 1 / 100, 80 # Saving 81 output_path: str = "c1=1.0_nu=5e-09_dt=0.025_v=1.h5", 82 recording_start_time: float = 0, 83 continue_file: bool = False, 84 buffer_length: int = 100, 85 snaps: int = 1, 86 chunk_size: int = 100, 87 downsample_factor: float = 16, 88 add_last_state: bool = True, 89 # Movie 90 movie: bool = False, 91 min_fps: int = 10, 92 dpi: int = 75, 93 speed: int = 10, 94 # Properties 95 properties: Iterable[str] = [ 96 "gamma_n", 97 "gamma_n_spectral", 98 "gamma_c", 99 "energy", 100 "thermal_energy", 101 "kinetic_energy", 102 "enstrophy", 103 "enstrophy_phi", 104 ], 105 # Plotting 106 t0_std=500, 107 plot_properties: Iterable[str] = ( 108 "gamma_c", 109 "gamma_n", 110 "gamma_n_spectral", 111 "energy", 112 "kinetic_energy", 113 "thermal_energy", 114 "enstrophy", 115 "enstrophy_phi" 116 ), 117 # Other 118 debug: bool = False, 119 force_recompute: bool = False, 120): 121 """ 122 Run the simulation with the given parameters. 123 124 Args: 125 step_size (float, optional): Incremental step for simulation progression. Defaults to 0.025. 126 end_time (float, optional): Duration till the simulation should run. Defaults to 1_000. 127 grid_pts (int, optional): Grid points. Suggested: 128 for coarse, 1024 for fine. Defaults to 512. 128 k0 (float, optional): Determines k-focus. Suggested: 0.15 for high-k, 0.0375 for low-k. Defaults to 0.15. 129 N (int, optional): Dissipation exponent's half value. Defaults to 3. 130 nu (float, optional): Viscosity. Suggested: 5e-10 for coarse-large, 1e-4 for fine-small. Defaults to 5.0e-08. 131 c1 (float, optional): Transition scale between hydrodynamic and adiabatic. Suggested values: 0.1, 1, 5. Defaults to 1.0. 132 kappa_coeff (float, optional): Coefficient of d/dy phi. Defaults to 1.0. 133 poisson_bracket_coeff (float, optional): Coefficient of Poisson bracket [A,B] implemented with Arakawa Scheme. Defaults to 1.0. 134 zonal (bool, ooptional): If True, uses make_zonal_func to simulate mHW2D. Defaults to False. 135 seed (int or None, optional): Seed for random number generation. Defaults to None. 136 init_type (str, optional): Initialization method. Choices: 'fourier', 'sine', 'random', 'normal'. Defaults to 'normal'. 137 init_scale (float, optional): Scaling factor for initialization. Defaults to 0.01. 138 output_path (str, optional): Where to save the simulation data. Defaults to ''. 139 recording_start_time (float, optional): Time (t) from which onwards the data is recorded. Defaults to 0. 140 continue_file (bool, optional): If True, continue with existing file. Defaults to False. 141 buffer_length (int, optional): Size of buffer for storage. Defaults to 100. 142 chunks (int, optional): Chunks of the h5 file. Defaults to 100. 143 downsample_factor (float, optional): Factor along each axis that it is downsampled with for saving. Defaults to 1. 144 add_last_state (bool, optional): Whether the last high-resolution frame is saved. Turns True if downsampling on. Defaults to False. 145 snaps (int, optional): Snapshot intervals for saving. Defaults to 1. 146 movie (bool, optional): If True, generate a movie out of simulation. Defaults to True. 147 min_fps (int, optional): Parameter for movie generation. Defaults to 10. 148 dpi (int, optional): Parameter for movie generation. Defaults to 75. 149 speed (int, optional): Parameter for movie generation. Defaults to 5. 150 properties (Iterable[str], optional): List of properties to calculate for the saved file. 151 plot_properties (Iterable[str], optional): List of properties to plot a timetrace for. 152 debug (bool, optional): Enable or disable debug mode. Defaults to False. 153 154 Returns: 155 None: The function saves simulation data or generates a movie as specified. 156 """ 157 step_size = float(step_size) 158 nu = float(nu) 159 160 # Unpacking 161 y = grid_pts 162 x = grid_pts 163 L = 2 * np.pi / k0 # Physical box size 164 dx = L / x # Grid resolution 165 steps = int(end_time / step_size) # Number of Steps until end_time 166 snap_count = steps // snaps + 1 # number of snapshots 167 field_list = ("density", "omega", "phi") 168 initial_time = np.float64(0) 169 iteration_count = 0 170 np.random.seed(seed) 171 if downsample_factor != 1: 172 add_last_state = True 173 if show_property not in properties: 174 properties += [show_property] 175 176 # Define Initializations 177 noise = { 178 "fourier": lambda y, x: get_fft_noise( 179 resolution=[y, x], 180 size=L, 181 scale=1, 182 min_wavelength=dx * 12, 183 max_wavelength=dx * grid_pts * 100, 184 factor=2, 185 ), 186 "sine": lambda y, x: get_2d_sine((y, x), L), 187 "random": lambda y, x: np.random.rand(y, x).astype(np.float64), 188 "normal": lambda y, x: np.random.normal(size=(y, x)).astype(np.float64), 189 } 190 downsample_fnc = fourier_downsample 191 192 # Physics 193 # TODO: Move all to Tensors along batch dimension for simulations! 194 physics_params = dict( 195 dx=dx, 196 N=N, 197 c1=c1, 198 nu=nu, 199 k0=k0, 200 poisson_bracket_coeff=poisson_bracket_coeff, 201 kappa_coeff=kappa_coeff, 202 zonal=zonal 203 ) 204 # Initialize Plasma 205 plasma = Namespace( 206 density=noise[init_type](y, x) * init_scale, 207 omega=noise[init_type](y, x) * init_scale, 208 phi=noise[init_type](y, x) * init_scale, 209 age=0, 210 ) 211 212 # File Handling 213 if output_path: 214 y_save = y 215 x_save = x 216 if downsample_factor != 1: 217 y_save = int(round(y / downsample_factor)) 218 x_save = int(round(x / downsample_factor)) 219 print(f"Downsample by {downsample_factor}x: {y}x{x}->{y_save}x{x_save}") 220 # Output data from this run 221 buffer = { 222 field: np.zeros((buffer_length, y_save, x_save), dtype=np.float32) 223 for field in field_list 224 } 225 # Property buffer 226 buffer[f"time"] = np.zeros((buffer_length, 1), dtype=np.float32) 227 for prop_name in properties: 228 buffer[f"fullres_{prop_name}"] = np.zeros( 229 (buffer_length, 1), dtype=np.float32 230 ) 231 # Other output parameters 232 output_params = { 233 "buffer": buffer, 234 "buffer_index": 0, 235 "output_path": output_path, 236 } 237 # Load Data 238 if os.path.isfile(output_path) and not force_recompute: 239 if continue_file: 240 plasma, physics_params = continue_h5_file(output_path, field_list) 241 save_params = get_save_params( 242 physics_params, 243 step_size, 244 snaps, 245 x, 246 y, 247 x_save=x_save, 248 y_save=y_save, 249 recording_start_time=recording_start_time, 250 adaptive_step_size=adaptive_step_size, 251 ) 252 initial_time = np.float64(plasma.age) 253 print( 254 f"Loaded: {output_path} (age={plasma.age}, time={initial_time})\n{physics_params}" 255 ) 256 # Check if broken 257 for field in plasma.keys(): 258 # Not NaNs 259 if np.isnan(np.sum(plasma[field])): 260 print(f"Input file is broken: NaNs in {field}") 261 raise BaseException(f"Input file is broken: NaNs in {field}") 262 # Not zeros 263 if field in ("density", "omega"): 264 if np.all(plasma[field] == 0): 265 print(f"FAILED {field} is zero") 266 raise BaseException(f"Input File is zero: {field}") 267 else: 268 print(f"File already exists.") 269 return 270 # Create Data 271 else: 272 save_params = get_save_params( 273 physics_params, 274 step_size, 275 snaps, 276 x, 277 y, 278 x_save=x_save, 279 y_save=y_save, 280 recording_start_time=recording_start_time, 281 adaptive_step_size=adaptive_step_size, 282 ) 283 # Create file 284 create_appendable_h5( 285 output_path, 286 save_params, 287 properties=["time"] 288 + [f"fullres_{prop_name}" for prop_name in properties], 289 dtype=np.float32, 290 chunk_size=chunk_size, 291 add_last_state=add_last_state, 292 ) 293 # Save initial values 294 if not recording_start_time: 295 new_val = Namespace( 296 **{ 297 k: v 298 for k, v in plasma.items() 299 if k in ("phi", "omega", "density") 300 } 301 ) 302 last_state = {} 303 for k, v in plasma.items(): 304 if k in ("phi", "omega", "density"): 305 if downsample_factor != 1: 306 new_val[k] = downsample_fnc(v, downsample_factor) 307 if add_last_state: 308 last_state[f"state_{k}"] = v 309 for prop_name in properties: 310 new_val[f"fullres_{prop_name}"] = property_fncs[prop_name]( 311 n=plasma["density"], 312 p=plasma["phi"], 313 o=plasma["omega"], 314 dx=dx, 315 c1=c1, 316 ) 317 new_val["time"] = initial_time 318 output_params["buffer_index"] = save_to_buffered_h5( 319 new_val=new_val, 320 last_state=last_state, 321 buffer_length=buffer_length, 322 **output_params, 323 ) 324 325 # Display runtime parameters 326 # save_params["output_path"] = output_path # No support for strings 327 format_print_dict(save_params) 328 329 # Setup Simulation 330 hw = HW(**physics_params, debug=debug) 331 plasma["phi"] = hw.get_phi(plasma.omega, physics_params["dx"]) 332 333 # Setup Progress Bar 334 print("Running simulation...") 335 iteration_count = 0 336 bar_format = "{rate_fmt} | {desc} {percentage:>6.2f}%|{bar}| {n:.2f}/{total:.2f} [{elapsed}<{remaining}]" 337 # bar_format = "{percentage:>6.2f}%|{bar}| {n:.4f}/{total_fmt} [{elapsed}<{remaining}] ({rate_fmt})" 338 used_step_size = step_size 339 used_time_steps = [] 340 current_time = initial_time 341 new_val = {} 342 343 try: 344 with tqdm( 345 total=end_time - initial_time, 346 unit="t", 347 bar_format=bar_format, 348 ) as pbar: 349 # Run Simulation 350 while round(current_time, 4) < end_time: 351 # Progress one step, alternatively: hw.euler_step() 352 successful = False 353 while not successful: 354 try: 355 plasma = hw.rk4_step(plasma, dt=step_size, dx=dx) 356 used_step_size = step_size 357 successful = True 358 except Exception as e: 359 print(e) 360 time.sleep(10) 361 iteration_count += 1 362 363 # Improved accuracy of time tracking 364 if adaptive_step_size: 365 used_time_steps.append(np.float64(used_step_size)) 366 current_time = initial_time + np.sum(used_time_steps) 367 else: 368 current_time = initial_time + iteration_count * step_size 369 370 # Batched Saving 371 # TODO: Figure out whether it should be iteration_count % (snaps - 1) == 0 372 if (current_time >= recording_start_time) and ( 373 iteration_count % snaps == 0 374 ): 375 new_val = Namespace( 376 **{ 377 k: v.copy() 378 for k, v in plasma.items() 379 if k in ("phi", "omega", "density") 380 } 381 ) 382 # Add properties if they were selected 383 # TODO: Faster to do before saving through trivial parallelization 384 new_val["time"] = current_time 385 for prop_name in properties: 386 new_val[f"fullres_{prop_name}"] = property_fncs[prop_name]( 387 n=plasma["density"], 388 p=plasma["phi"], 389 o=plasma["omega"], 390 dx=dx, 391 c1=c1, 392 ) 393 394 # Save to records 395 if output_path: 396 last_state = {} 397 # Save downsampled & state 398 for k, v in plasma.items(): 399 if k in ("phi", "omega", "density"): 400 # Downsample for saving 401 if downsample_factor != 1: 402 new_val[k] = downsample_fnc(v.copy(), downsample_factor) 403 # Add last state as high-res for continuing simulation 404 if add_last_state: 405 last_state[f"state_{k}"] = v 406 # Save to buffer/file 407 output_params["buffer_index"] = save_to_buffered_h5( 408 new_val=new_val, 409 last_state=last_state, 410 buffer_length=buffer_length, 411 **output_params, 412 ) 413 414 # Check for breaking 415 if np.isnan(np.sum(plasma.density)): 416 print(f"FAILED @ {iteration_count:,} steps ({plasma.age:,})") 417 break 418 419 # Calculate iterations per second, handling division by zero 420 try: 421 iter_per_sec = iteration_count / pbar.format_dict["elapsed"] 422 except ZeroDivisionError: 423 iter_per_sec = float("inf") # or set to 0 or any default value 424 425 # Update progress 426 pbar.update(used_step_size) 427 new_description = f"{iter_per_sec:.2f}it/s" 428 if show_property: 429 new_description += f" | Γn = {new_val.get(show_property, 0):.2g}" 430 pbar.set_description(new_description) 431 432 except Exception as e: 433 print(e) 434 raise e 435 # os.remove(output_path) 436 # print(f"removed {output_path}") 437 return 438 439 # If output_path is defined, flush any remaining data in the buffer 440 if ( 441 output_path 442 and (current_time > recording_start_time) 443 and (output_params["buffer_index"] > 0) 444 ): 445 append_h5(**output_params) 446 447 # Get Performance stats 448 hw.print_log() 449 450 # Generate Movie from saved file 451 if movie and output_path: 452 print(f"Generating movie...") 453 create_movie( 454 input_filename=output_path, 455 output_filename=output_path.replace(".h5", ""), 456 t0=0, 457 t1=None, 458 plot_order=field_list, 459 min_fps=min_fps, 460 dpi=dpi, 461 speed=speed, 462 ) 463 464 if properties and output_path: 465 print(f"Calculating properties...") 466 calculate_properties( 467 file_path=output_path, 468 batch_size=buffer_length, 469 property_list=properties, 470 force_recompute=True, 471 is_debug=False, 472 ) 473 474 if plot_properties and output_path: 475 print(f"Plotting properties...") 476 plot_timetrace_comparison( 477 file_path=output_path, 478 out_path=None, 479 properties=plot_properties, 480 t0=0, 481 t0_std=t0_std, 482 )
Run the simulation with the given parameters.
Arguments:
- step_size (float, optional): Incremental step for simulation progression. Defaults to 0.025.
- end_time (float, optional): Duration till the simulation should run. Defaults to 1_000.
- grid_pts (int, optional): Grid points. Suggested: 128 for coarse, 1024 for fine. Defaults to 512.
- k0 (float, optional): Determines k-focus. Suggested: 0.15 for high-k, 0.0375 for low-k. Defaults to 0.15.
- N (int, optional): Dissipation exponent's half value. Defaults to 3.
- nu (float, optional): Viscosity. Suggested: 5e-10 for coarse-large, 1e-4 for fine-small. Defaults to 5.0e-08.
- c1 (float, optional): Transition scale between hydrodynamic and adiabatic. Suggested values: 0.1, 1, 5. Defaults to 1.0.
- kappa_coeff (float, optional): Coefficient of d/dy phi. Defaults to 1.0.
- poisson_bracket_coeff (float, optional): Coefficient of Poisson bracket [A,B] implemented with Arakawa Scheme. Defaults to 1.0.
- zonal (bool, ooptional): If True, uses make_zonal_func to simulate mHW2D. Defaults to False.
- seed (int or None, optional): Seed for random number generation. Defaults to None.
- init_type (str, optional): Initialization method. Choices: 'fourier', 'sine', 'random', 'normal'. Defaults to 'normal'.
- init_scale (float, optional): Scaling factor for initialization. Defaults to 0.01.
- output_path (str, optional): Where to save the simulation data. Defaults to ''.
- recording_start_time (float, optional): Time (t) from which onwards the data is recorded. Defaults to 0.
- continue_file (bool, optional): If True, continue with existing file. Defaults to False.
- buffer_length (int, optional): Size of buffer for storage. Defaults to 100.
- chunks (int, optional): Chunks of the h5 file. Defaults to 100.
- downsample_factor (float, optional): Factor along each axis that it is downsampled with for saving. Defaults to 1.
- add_last_state (bool, optional): Whether the last high-resolution frame is saved. Turns True if downsampling on. Defaults to False.
- snaps (int, optional): Snapshot intervals for saving. Defaults to 1.
- movie (bool, optional): If True, generate a movie out of simulation. Defaults to True.
- min_fps (int, optional): Parameter for movie generation. Defaults to 10.
- dpi (int, optional): Parameter for movie generation. Defaults to 75.
- speed (int, optional): Parameter for movie generation. Defaults to 5.
- properties (Iterable[str], optional): List of properties to calculate for the saved file.
- plot_properties (Iterable[str], optional): List of properties to plot a timetrace for.
- debug (bool, optional): Enable or disable debug mode. Defaults to False.
Returns:
None: The function saves simulation data or generates a movie as specified.