def force_dw(Plist): """ Method to return the force on a particle in a Gravitational potential given by: -(G*m1m2)/R**3)*vector_R where R is separation between two bodies and vector_R is a vector from pi to pj :param particlei: Particle3D instance :param particlej: Particle3D instance :param G: constant parameter :param vector_R: parameter vector_R from potential :param R: parameter R from potential :return: force acting on body as a Numpy array """ no_parts = len(Plist) #pos_list = [o.position for o in Plist] force_dw = np.zeros((no_parts, no_parts, 3)) for i,pi in enumerate(Plist): for j,pj in enumerate(Plist): vector_R = Particle3D.Vector_Separation(pi, pj) #vector_R = pos_list[i] - pos_list[j] R = np.linalg.norm(vector_R) m1m2 = Particle3D.mass(pi)*Particle3D.mass(pj) #m1m2 = Plist[pi].mass*Plist[pj].mass #if pi != pj: if R != 0: force_dw[i, j, :] = (((-1.48818E-34)*m1m2)/R**3)*vector_R #G is defined as the Gravitational constant and = -1.48818E-34 au^3/day^2/kg else: force_dw[i, j, :] = np.zeros(3) return force_dw
def pot_energy_dw(Plist): """ Method to return potential energy of a body in a Gravitational potential which is given by: -(G*m1m2)/R) :param particlei: Particle3D instance :param particlej: Particle3D instance :param G: constant parameter :param vector_R: parameter vector_R from potential :param R: parameter R from potential :return: potential energy of body as a float """ no_parts = len(Plist) #pos_list = [o.position for o in Plist] pot_energy_dw = np.zeros((no_parts, no_parts)) for i,pi in enumerate(Plist): for j,pj in enumerate(Plist): vector_R = Particle3D.Vector_Separation(pi, pj) #vector_R = pos_list[i] - pos_list[j] R = np.linalg.norm(vector_R) m1m2 = Particle3D.mass(pi)*Particle3D.mass(pj) #m1m2 = Plist[pi].mass*Plist[pj].mass #if pi != pj: if R != 0: pot_energy_dw[i, j] = ((-(1.48818E-34)*m1m2)/R) else: pot_energy_dw[i, j] = 0 return pot_energy_dw
def force_dw(Plist): """ Method to return the force on a particle in a Morse potential given by: -2aD_e(1-exp[-a*(R - r_e)])exp[-a*(R - re)]vector_R/R where R is separation between two atoms and vector_R is a vector from p1 to p2 :param particle1: Particle3D instance :param particle2: Particle3D instance :param D_e: parameter D_e from potential :param r_e: parameter r_e from potential :param alpha: parameter alpha from potential :return: force acting on particle as a Numpy array """ no_parts = len(Plist) #pos_list = [o.position for o in Plist] force_dw = np.zeros((no_parts, no_parts, 3)) for i,pi in enumerate(Plist): for j,pj in enumerate(Plist): vector_R = Particle3D.Vector_Separation(pi, pj) #vector_R = pos_list[i] - pos_list[j] R = np.linalg.norm(vector_R) m1m2 = Particle3D.mass(pi)*Particle3D.mass(pj) #m1m2 = Plist[pi].mass*Plist[pj].mass #if pi != pj: if R != 0: force_dw[i, j, :] = (((-1.48818E-34)*m1m2)/R**3)*vector_R else: force_dw[i, j, :] = np.zeros(3) return force_dw
def pot_energy_dw(Plist): """ Method to return potential energy of a particle in a Morse potential which is given by: V(r1,r2) = D_e * ((1-exp[-a*(R12 - re)])**2 - 1) :param particle1: Particle3D instance :param particle2: Particle3D instance :param D_e: parameter D_e from potential :param r_e: parameter r_e from potential :param alpha: parameter alpha from potential :return: potential energy of particle as float """ no_parts = len(Plist) #pos_list = [o.position for o in Plist] pot_energy_dw = np.zeros((no_parts, no_parts)) for i,pi in enumerate(Plist): for j,pj in enumerate(Plist): vector_R = Particle3D.Vector_Separation(pi, pj) #vector_R = pos_list[i] - pos_list[j] R = np.linalg.norm(vector_R) m1m2 = Particle3D.mass(pi)*Particle3D.mass(pj) #m1m2 = Plist[pi].mass*Plist[pj].mass #if pi != pj: if R != 0: pot_energy_dw[i, j] = ((-(1.48818E-34)*m1m2)/R) else: pot_energy_dw[i, j] = 0 return pot_energy_dw
def add_to_pos_lists(lst,d,p): """ Method to update lists in d and p at different points of time in the simulation, where d stores within it the list of distances between each body in lst(except the sun) and its parent body, and p stores within it the list of positions as numpy arrays of each body in lst. Note that these lists being updated are the second element of pairs in the lists of pairs, d and p, where the first is the label of the body that the information is relevant to. Ie d and p are lists of 2-tuples of a string and a list of floating point numbers. :param lst: list of the bodies as Particle3D instances :param d: list of pairs, in which the first pair element is the body label as a string, and the second is a list of all the scalar distances of the body from the body it is orbitting :param p: list of pairs, in which the first pair element is the body label as a string and the second is a list of positions of the body as a numpy array """ # find the two bodies that are being orbitted and assign them to Sun and Earth # done in seperate loop so that the order of particles in the input file doesn't matter for l in lst: if l.label == 'Sun': Sun = l if l.label == 'Earth': Earth = l for l in lst: p[lst.index(l)][1].append(l.position) # appends position of particle l to corresponding list of its positions in p if l.label == 'Sun': # sun is not orbitting anything so can ignore distances from anything else in this case pass elif l.label == 'Moon': # ensure distance from moon to earth is appended to the correct list in d d[lst.index(l)][1].append(np.linalg.norm(Particle3D.particle_separation(l,Earth))) else: # everything else is orbitting sun so the distance between sun and that body is appended do the correct list in d d[lst.index(l)][1].append(np.linalg.norm(Particle3D.particle_separation(l,Sun)))
def main(): if len(sys.argv) != 4: print("Wrong number of arguments.") print("Usage: " + sys.argv[0] + "<input file>" + " <output file>" + "method of time integration:either 'euler' or 'verlet'") quit() else: outfile_name = sys.argv[2] infile_name = sys.argv[1] # Open output file outfile = open(outfile_name, "w") #read in input file infile = open(infile_name, "r") #Get the specific constants for the particle from the input file #read in first line from input file constants = infile.readline() #split first line up tokens = constants.split(",") #set up initial parameters for particles D = tokens[0] alpha = tokens[2] r_e = tokens[1] dt = 0.01 numstep = 20 time = 0.0 #set up the initial state of the particles p1 = Particle3D.file_read(infile) p2 = Particle3D.file_read(infile) #create a list of particles p1,p2 particles = [p1, p2] sep_vector = p1.separation(p2) #calculate the magnitude of the separation between p1 and p2 distance = np.linalg.norm(sep_vector) #get the total initial energy of the system energy = Particle3D.kinetic_energy(p1) + Particle3D.kinetic_energy( p2) + pot_energy_morse(distance, D, alpha, r_e) #write the initial time, initial separation and initial total energy to the output file outfile.write("{0:f} {1:f} {2:12.8f}\n".format(time, distance, energy)) # Initialise data lists for plotting later time_list = [] distance_list = [] energy_list = []
def totalEnergy(G): totalEnergy=0.0 # Cycle through every Celestial object that has been called into existence in the program so far for obj in Celestial.objReg: # Add the kinetic energy of all the bodies to the total energy totalEnergy = totalEnergy + P3D.kineticEnergy(obj.P3D) # Now go through all the pair potentials and add their contribution to the total energy # Note the sum below is one of the type SIGMA_{j<i} ie. there is no double counting for obj2 in Celestial.objReg[:Celestial.objReg.index(obj)]: totalEnergy = totalEnergy + P3D.potential_energy(obj.P3D, obj2.P3D, G) return totalEnergy
def apsis(self): """ Appends the distances of each astronomical body from the Sun except the Moon (appends its distance from the Earth) to the corresponding list in the distances class variable, using the Particle3D v_sub_mag method. """ for i in range(1, self.N - 1): self.distances[i - 1].append( Particle3D.v_sub_mag(self.particlelist[0], self.particlelist[i])) self.distances[self.N - 2].append( Particle3D.v_sub_mag(self.particlelist[3], self.particlelist[self.N - 1]))
def list_creator(self): """ Method that fills the particlelist with the Particle3D objects created. Fills the labels list with the corresponding names. Creates the acceleration_list using the acceleration method. Corrects for centre of mass velocity using the com_velocity method. """ for i in range(self.N): self.particlelist.append(Particle3D(self.file_handle)) self.labels.append(Particle3D.label(self.particlelist[i])) self.acceleration_list = self.acceleration() self.com_velocity()
def force_morse(particle1, particle2, re, de, a): """ Gives the Morse force between particles interacting via the Morse potential :param re: equilibrium bond distance :param de: parameter controlling depth of the potential minimum :param a: parameter controlling curvature of the potential minimum (small a -> wide well) """ r12 = np.linalg.norm(Particle3D.separation(particle1, particle2)) exp = math.exp(-a * (r12 - re)) unit_r = np.true_divide(Particle3D.separation(particle1, particle2), r12) force = 2 * a * de * (1 - exp) * exp * unit_r return force
def apoperi(particle, apo, peri): """ Method to update the apo and periapsis of each particle with respect to the sun for all particles except the moon which is with respect to Earth :param particle: Particle list :param apo: Existing apoapses list :param peri: Existing periapses list :return: Lists of all particles' updated apo- and periapses """ #Loop for all planets except sun for f in range(1, len(particle) - 2): #All separations excluding the moon, since moon is placed innthe last positionin the poskms file if f != 4: #Calculate separation sep = np.linalg.norm( Particle3D.seperation(particle[0].position, particle[f].position)) #Determine if new apoapsis if apo[f] == None: apo[f] = sep elif (sep > apo[f]): apo[f] = sep #Determine if new periapsis #If no periapsis exists if peri[f] == None: peri[f] = sep elif (sep < peri[f]): peri[f] = sep #For the moon, relative to the Earth else: #Calculate separation, assuming Earth is particle 3 sep = np.linalg.norm( Particle3D.seperation(particle[3].position, particle[f].position)) #Determine if new apoapsis if apo[f] == None: apo[f] = sep elif (sep > apo[f]): apo[f] = sep #Determine if new periapsis if peri[f] == None: peri[f] = sep elif (sep < peri[f]): peri[f] = sep return apo, peri
def cm_velocity(Plist): cm_velocity = np.zeros((len(Plist), 3)) for i in range(len(Plist)): for j in range(i+1, len(Plist)): v1 = Particle3D.velocity(Plist[i]) v2 = Particle3D.velcoity(Plist[j]) m1 = Particle3D.mass(Plist[i]) m2 = Particle3D.mass(Plist[j]) cm_velocity[i] += (v1*m1 + v2*m2)/(m1 + m2) cm_velocity[j] -= (v1 * m1 + v2 * m2) / (m1 + m2) return cm_velocity
def check_observables(particle_list, max_arr, min_arr, wrt_arr, time, orbit_complete_flags, period_arr): """ Computes the observables for the planets Inputs: list particle_list: list of particles array max_arr: array of current maximum orbit disances (float) array min_arr: array of current maximum orbit disances (float) wrt_arr: array of integers corresponding to the index of the particle that the particle of the index of the element is orbiting time: current time in simulation (float) orbit_complete_flags: boolean array that is true when the particle with that index has traversed 2pi radians period_arr: array containing the period of the particle with that index Return: 4 tuple containing adjusted max_arr, min_arr, orbit_complete_flags, period_arr """ #apoapsis/periapsis for i in range(len(particle_list)): sep = Particle3D.separation(particle_list[i], particle_list[wrt_arr[i]]) if norm(sep) > max_arr[i]: max_arr[i] = norm(sep) elif norm(sep) < min_arr[i]: min_arr[i] = norm(sep) #period for i in range(len(particle_list)): if not orbit_complete_flags[i]: if orbit_complete(particle_list[i], particle_list[wrt_arr[i]]): orbit_complete_flags[i] = True period_arr[i] = time return (max_arr, min_arr, orbit_complete_flags, period_arr)
def pairwiseforces(particle1, particle2, re, De, alpha): """ Method to return pairwise forces of 2 particles interacting via the morse potential. :param particle1: 1st Particle3D instance :param particle2: 2nd Particle3D instance :param re: paramater re :param De: parameter De :param alpha: parameter alpha :return: potential energy of pairwise particles """ #find separation using Particle3D separation sep = Particle3D.separation(particle1, particle2) #magnitude of separation vector r12 = np.linalg.norm(sep) #normalized separation vector rnorm = sep / r12 #find constant out front L = -alpha * (r12 - re) const = 2 * alpha * De * (1 - np.exp(L)) * np.exp(L) #const = 2*alpha*De*((1-(np.exp(-alpha*(r12-re))))*(np.exp(-alpha*(r12-re)))) #find force on particle 1 f1 = np.multiply(const, rnorm) #unsure if return both f1 or just one and inverse in main code return f1
def get_forces(self): """Returns [N,3]-dim narray of forces on all particles.""" N = len(self.particles) particle_forces = np.zeros((N, 3)) # Initialises force output array. # Use C++ version if cppenabled if (self.cppenabled): accelerate_lib.c_getforces(self.get_positions(), particle_forces, self.boxdim, self.LJ_cutoff) return particle_forces # Python calculation if cppenabled = False: # Iterate over all i<j, then calculate # force for each i, j combination for i in range(N): for j in range(i): # Get force of particle i on j, respecting pbc and mic. sep = Particle3D.pbc_sep(self.particles[i], self.particles[j], self.boxdim) force = LJ_Force(sep, self.LJ_cutoff) particle_forces[j] += force particle_forces[i] += -force # Using Newtons 3rd law return particle_forces
def rdf(particles, rho, bins, box_size): bins_list = np.zeros(bins) dist_list = [] N = len(particles) for i in range(N): for j in range(i + 1, N): sep = Particle3D.sep(particles[i], particles[j]) distances = LA.norm(mic(sep, box_size)) / (N * rho) dist_list.append(distances) #compute the size of each bin bin_size = (max(dist_list) - min(dist_list)) / bins # Compare the value of dist_list against the range of each bin dist_N = len(dist_list) for i in range(dist_N): for j in range(bins): if dist_list[i] >= j * bin_size and dist_list[i] <= (j + 1) * bin_size: bins_list[j] = bins_list[j] + 1 # normalise each of the bins for n in range(bins): r = (n * bin_size + (n + 1) * bin_size) / 2 bins_list[n] = bins_list[n] / (4 * bin_size * math.pi * rho * (r**2)) dist_list2 = [] for i in range(bins): r = (i * bin_size + (i + 1) * bin_size) / 2 dist_list2.append(r) return bins_list, dist_list2
def get_input_vars(param_file): """ Reads in an arbitrary number of particles and the simulation parameters from a file input: str param_file: file containing appropriately formatted parameters and particles return: 3-tuple containing a list of sim params, particle params and the particles themselves """ in_file = str(param_file) read_file = open(in_file, 'r') in_file_contents = read_file.read() file_list = in_file_contents.split('\n') non_comments = [] for i in file_list: if i.startswith('#') == False and i != '': #filters out comment lines in file non_comments.append(i) sim_params_list = non_comments[0:3] #first 3 non comments are sim params (numstep, dt, init_time) part_params_list = non_comments[3:6] #second 3 non comments are particle params (D_e, r_e, alpha) particles_list = [] for i in range((len(non_comments) - 3) // 4): #4 because each particle is specified by four lines start = 6 + 4*(i) end = 6 + 4*(i+1) temp_l = non_comments[start:end] p_lab = str(temp_l[0]) p_mass = float(temp_l[1]) p_pos = temp_l[2].split(', ') for i in range(len(p_pos)): p_pos[i] = float(p_pos[i]) p_pos = np.array(p_pos) p_vel = temp_l[3].split(', ') for i in range(len(p_vel)): p_vel[i] = float(p_vel[i]) p_vel = np.array(p_vel) particle = Particle3D(p_lab, p_mass, p_pos, p_vel) particles_list.append(particle) read_file.close() return (sim_params_list, part_params_list, particles_list)
def totalKineticEnergy(): totalKE = 0.0 # Cycle through every Celestial object that has been called into existence in the program so far for obj in Celestial.objReg: # Add the kinetic energy of all the bodies to the total energy totalKE = totalKE + P3D.kineticEnergy(obj.P3D) return totalKE
def rij(particleList, boxDim): pDistances = [] for i, el in enumerate(particleList): for j, al in enumerate(particleList): if i < j: pDistances.append(Particle3D.scalarSeparation(el, al, boxDim)) return pDistances
def MeanSD(particleList, initialPosPart, boxDim): y = 0. for i in range(len(particleList)): x = Particle3D.scalarSeparation(particleList[i], initialPosPart[i], boxDim)**2 y += x MSD = (1. / len(particleList)) * y return MSD
def totalPotentialEnergy(G): totalPE = 0.0 for obj in Celestial.objReg: # Now go through all the pair potentials and add their contribution to the total energy # Note the sum below is one of the type SIGMA_{j<i} ie. there is no double counting for obj2 in Celestial.objReg[:Celestial.objReg.index(obj)]: totalPE = totalPE + P3D.potential_energy(obj.P3D, obj2.P3D, G) return totalPE
def pe_lj(particle1,particle2,sigma): """ LJ params for Argon epsilon = 119.8 k_B sigma = 3.405e-10 m = 0.03994 kg/mol """ r = np.linalg.norm(Particle3D.separation(particle1,particle2))/sigma pe = 4*((r)*10^(-12) - (r)*10^(-6)) return pe
def pNamer(PNUMBER): particles = [] for i in range(PNUMBER): position = 0 velocity = 0 mass = 1 name = i + 1 p = Particle3D(position, velocity, mass, name) particles.append(p) return particles
def mic(p1, p2, BOXSIZE): #checks if the distance between two particles is greater than half the boxsize for each dimension #and applies the minimum image convention if they arent for i in range(3): if abs(p1.position[i] - p2.position[i]) > 0.5 * BOXSIZE: if (p1.position[i] - p2.position[i]) > 0.5 * BOXSIZE: p2.position[i] = p2.position[i] + BOXSIZE elif (p1.position[i] - p2.position[i]) < 0.5 * BOXSIZE: p2.position[i] = p2.position[i] - BOXSIZE return p.Seperation(p1, p2)
def globalForceUpdate(G): for obj in Celestial.objReg: force = np.array([0.0,0.0,0.0]) for obj2 in Celestial.objReg: if obj!=obj2: force = force + P3D.force_vector(obj.P3D, obj2.P3D, G) # updated force vector is stacked at the bottom row of the force_History array. obj.force_History = np.vstack((obj.force_History, force))
def kinetic_energy_dw(Plist): no_parts = len(Plist) kinetic_energy = np.zeros(no_parts) for i in range(no_parts): kinetic_energy[i] = Particle3D.kinetic_energy(Plist[i]) return kinetic_energy
def force_dw(particle1, particle2): """ Method to return the force on a particle in a Morse potential given by: -2aD_e(1-exp[-a*(R - r_e)])exp[-a*(R - re)]vector_R/R where R is separation between two atoms and vector_R is a vector from p1 to p2 :param particle1: Particle3D instance :param particle2: Particle3D instance :param D_e: parameter D_e from potential :param r_e: parameter r_e from potential :param alpha: parameter alpha from potential :return: force acting on particle as a Numpy array """ force_dw = [] while i, j <= len(Plist) and i != j: vector_R = Particle3D.Vector_Separation(Plist[i],Plist[j]) R = np.linalg.norm(vector_R) m1m2 = Particle3D.mass(Plist[i])*Particle3D.mass(Plist[j]) force_dw[i] = sum((((1.48818E-34)*m1m2)/R**3)*vector_R) return force_dw
def pe_morse(particle1, particle2, re, de, a): """ Gives the Morse potential energy between particles interacting via the Morse potential :param re: equilibrium bond distance :param de: parameter controlling depth of the potential minimum :param a: parameter controlling curvature of the potential minimum (small a -> wide well) """ r12 = np.linalg.norm(Particle3D.separation(particle1, particle2)) exp = math.exp(-a * (r12 - re)) pe = de * (((1 - exp)**2) - 1) return pe
def trajectory(self, i): """ Method that writes the required output for VMD for every n'th (argument) timestep. n is taken from the parameters text file. Argument i is a dummy variable. """ if i % self.ne == 0: self.outfile.write(str(self.N) + "\n") self.outfile.write("Point = " + str(i / self.ne + 1) + "\n") for j in range(self.N): self.outfile.write( str(Particle3D.__str__(self.particlelist[j])) + "\n")
def globalForceUpdate_Initialization(G): # Each body will be treated in turn for obj in Celestial.objReg: # Initialize a force array force = np.array([0.0,0.0,0.0]) # Consider all other bodies that are different from the body of interest for obj2 in Celestial.objReg: if obj!=obj2: # Do a vector sum of the individual forces acting on the body of interest due to all other bodies force = force + P3D.force_vector(obj.P3D, obj2.P3D, G) # Update the net force acting on the body of interest obj.force_History = force