#!/usr/bin/env python ################################################################################ ### parse script parameters ### ################################################################################ from optparse import OptionParser usage = "usage: %prog [options] []" arg_parser = OptionParser(usage = usage) arg_parser.add_option("-i", "--input", action = "store", type = str, dest = "input", default = None, help = "input file path (required!)") arg_parser.add_option("-A", "--outputA", action = "store", type = str, dest = "outputA", default = "task04A.txt", help = "first output file path (default: 'task04A.txt')") arg_parser.add_option("-B", "--outputB", action = "store", type = str, dest = "outputB", default = "task04B.txt", help = "second output file path (default: 'task04B.txt')") arg_parser.add_option("-s", "--cdf_sample_count", action = "store", type = int, dest = "cdf_sample_count", default = 100, help = "Radius interval sample count for CDF estimate (default: 100)") arg_parser.add_option("-a", "--animate", action = "store_true", dest = "animate", default = False, help = "creates an trajectory animation of the states in the input file") # Parse command line arguments (as def. above) or store defaults to `config` config, args = arg_parser.parse_args() # overwrite options with positional arguments if supplied try: if len(args) > 0: config.input = args[0] except ValueError as expression: arg_parser.print_help() print(f"Error: {expression}") exit(-1) else: # quick and dirty validation if config.input is None: arg_parser.print_help() print("Error: missing or illegal argument") exit(-1) ################################################################################ ### task 4 / post processing ### ################################################################################ # note, load module _after_ processing script parameters (no need to load all # of the heavy numeric modules if only usage or alike is needed) import numpy as np from jax import jit, numpy as jnp from molecular_dynamics import iter_load, energy, kinetic # counte the number of states (system snapshots) in the input file nr_states = 0 # first output file and collect some info used in the next analysis! with open(config.outputA, "w") as output: # iterate over all state snapshots stored in the `config.input` file for (position, velocity), box_size in iter_load(config.input): nr_states += 1 # compute potneital and kinetic energy for current snapshot e_pot = energy(position, box_size) e_kin = kinetic(velocity) # energy results print(e_pot, e_kin, file = output) # report file path print(f"Saved energy change to '{config.outputA}'") # CDF radius sample points (`box_size` known from last loop) radii = np.linspace(0, box_size / 2.0, config.cdf_sample_count)[np.newaxis, :] # radius Commulative Distribution Function estimate CDF = np.zeros((config.cdf_sample_count, )) # extract only one of two distance combinations of non-equal particles lower_tri = np.tril_indices(position.shape[0], k = -1) # maximum distance two particles (in folded space) can be apart max_dist = box_size / 2.0 @jit def loop_body(position, box_size): # magic for all particle pair distances diff = position[:, jnp.newaxis, :] - position diff = diff[lower_tri[0], lower_tri[1], :] # fold space in all directions. The `max_dist` is the maximum distance # in the folded space untill the distance through the folded space # border is smaller than inside the box. 3-axis parallel fold diff = jnp.mod(diff + max_dist, box_size) - max_dist # count nr particles pairs with distance smaller than r dist = jnp.linalg.norm(diff, axis = 1) # accumulate per timepoint partial mean estimates of the CDF return jnp.mean(dist[:, jnp.newaxis] < radii, axis = 0) # second iteration to estimate pairwise distance CDF nr_time_points = 0 for i, ((position, velocity), box_size) in enumerate(iter_load(config.input)): # ignore the first 25% (just keep it simple) if 4 * i < nr_states: continue # Count number of actually considured states / time points nr_time_points += 1 # aggregate CDF sum CDF += loop_body(position, box_size) # final division by timesteps as mean over pairs and time. CDF /= nr_time_points # store the CDF estimate to the second file with open(config.outputB, "w") as output: for r, cdf in zip(radii.ravel(), CDF.ravel()): print(r, cdf, file = output) # report file path print(f"Saved radius CDF estimate to '{config.outputB}'") # import matplotlib.pyplot as plt # from mpl_toolkits import mplot3d # if config.animate: # # new plot with 3D axis # count = 0 # fig = plt.figure() # ax = fig.add_subplot(111, projection = "3d") # for (position, velocity), box_size in iter_load(config.input): # e_pot = energy(position, box_size) # e_kin = kinetic(velocity) # print(f"{e_pot:10.5e} {e_kin:10.5e} {(e_pot + e_kin):10.5e}") # if config.animate: # count += 1 # ax.cla() # plt.title(f"time step {count}") # plt.xlim([0, box_size]) # plt.ylim([0, box_size]) # ax.set_zlim([0, box_size]) # # create 3D position scatter plot # position = np.mod(position, box_size) # ax.scatter(position[:, 0], position[:, 1], position[:, 2]) # # # and save to file # # plt.savefig(config.plot) # plt.pause(0.01) # import numpy as np # import matplotlib.pyplot as plt # from mpl_toolkits import mplot3d # from molecular_dynamics import load, energy, kinetic # (position, velocity), box_size = load("task02.xyz"): # fig = plt.figure() # ax = fig.add_subplot(111, projection = "3d") # ax.cla() # plt.xlim([0, box_size]) # plt.ylim([0, box_size]) # ax.set_zlim([0, box_size]) # # create 3D position scatter plot # position = np.mod(position, box_size) # ax.scatter(position[:, 0], position[:, 1], position[:, 2])