def check_bonds(morphology, bond_dict): for bond in morphology["bond"]: posn1 = np.array(morphology["position"][bond[1]]) +( np.array(morphology["image"][bond[1]]) * np.array([morphology["lx"], morphology["ly"], morphology["lz"]]) ) posn2 = np.array(morphology["position"][bond[2]]) +( np.array(morphology["image"][bond[2]]) * np.array([morphology["lx"], morphology["ly"], morphology["lz"]]) ) separation = hf.calculate_separation(posn1, posn2) if separation >= morphology["lx"] / 2.0: print( "Periodic bond found:", bond, "because separation =", separation, ">=", morphology["lx"] / 2.0, ) morphology = move_bonded_atoms(bond[1], morphology, bond_dict) return morphology
def calculate_hop(self, chromophore_list): # Terminate if the next hop would be more than the termination limit if self.hop_limit is not None: if self.no_hops + 1 > self.hop_limit: return 1 # Determine the hop times to all possible neighbours hop_times = [] if self.use_average_hop_rates is True: # Use the average hop values given in the parameter dict to pick a # hop for neighbour_details in self.current_chromophore.neighbours: neighbour = chromophore_list[neighbour_details[0]] assert neighbour.ID == neighbour_details[0] if (self.mol_ID_dict[self.current_chromophore.ID] == self.mol_ID_dict[neighbour.ID]): hop_rate = self.average_intra_hop_rate else: hop_rate = self.average_inter_hop_rate hop_time = hf.determine_event_tau(hop_rate) # Keep track of the chromophoreID and the corresponding tau hop_times.append([neighbour.ID, hop_time]) else: # Obtain the reorganisation energy in J (from eV in the parameter # file) for neighbour_index, transfer_integral in enumerate( self.current_chromophore.neighbours_TI): # Ignore any hops with a NoneType transfer integral (usually # due to an orca error) if transfer_integral is None: continue delta_E_ij = self.current_chromophore.neighbours_delta_E[ neighbour_index] # Load the specified hopping prefactor prefactor = self.hopping_prefactor # Get the relative image so we can update the carrier image # after the hop relative_image = self.current_chromophore.neighbours[ neighbour_index][1] # All of the energies are in eV currently, so convert them to J if self.use_VRH is True: neighbour_chromo = chromophore_list[ self.current_chromophore.neighbours[neighbour_index] [0]] neighbour_chromo_posn = neighbour_chromo.posn + ( np.array(relative_image) * np.array([axis[1] - axis[0] for axis in self.sim_dims])) # Chromophore separation needs converting to m chromophore_separation = (hf.calculate_separation( self.current_chromophore.posn, neighbour_chromo_posn) * 1E-10) hop_rate = hf.calculate_carrier_hop_rate( self.lambda_ij * elementary_charge, transfer_integral * elementary_charge, delta_E_ij * elementary_charge, prefactor, self.T, use_VRH=True, rij=chromophore_separation, VRH_delocalisation=self.VRH_delocalisation, boltz_pen=self.use_simple_energetic_penalty, ) else: hop_rate = hf.calculate_carrier_hop_rate( self.lambda_ij * elementary_charge, transfer_integral * elementary_charge, delta_E_ij * elementary_charge, prefactor, self.T, boltz_pen=self.use_simple_energetic_penalty, ) hop_time = hf.determine_event_tau(hop_rate) # Keep track of the chromophoreID and the corresponding tau hop_times.append([ self.current_chromophore.neighbours[neighbour_index][0], hop_time, relative_image, ]) # Sort by ascending hop time hop_times.sort(key=lambda x: x[1]) if len(hop_times) == 0: # We are trapped here, so create a dummy hop with time 1E99 hop_times = [[self.current_chromophore.ID, 1E99, [0, 0, 0]]] # As long as we're not limiting by the number of hops: if self.hop_limit is None: # Ensure that the next hop does not put the carrier over its # lifetime if (self.current_time + hop_times[0][1]) > self.lifetime: # Send the termination signal to singleCoreRunKMC.py return 1 # Move the carrier and send the contiuation signal to # singleCoreRunKMC.py # Take the quickest hop self.perform_hop(chromophore_list[hop_times[0][0]], hop_times[0][1], hop_times[0][2]) return 0
def run_fine_grainer(self, ghost_dictionary): AA_dictionary = { "position": [], "image": [], "unwrapped_position": [], "mass": [], "diameter": [], "type": [], "body": [], "bond": [], "angle": [], "dihedral": [], "improper": [], "charge": [], "lx": 0, "ly": 0, "lz": 0, "xy": 0, "xz": 0, "yz": 0, } # Find the COMs of each CG site in the system, so that we know where to # move the template to CG_co_ms, self.CG_to_template_AAIDs = self.get_AA_template_position( self.CG_to_template_AAIDs ) # Need to keep track of the atom ID numbers globally - run_fine_grainer # sees individual monomers, atomistic sees molecules and the xml needs # to contain the entire morphology. no_atoms_in_molecule = 0 CG_type_list = {} for site_ID in self.site_IDs: CG_type_list[site_ID] = self.CG_dictionary["type"][site_ID] # Sort the CG sites into monomers so we can iterate over each monomer # in order to perform the fine-graining monomer_list = self.sort_into_monomers(CG_type_list) current_monomer_index = sum( self.molecule_lengths[: self.molecule_index] ) atom_ID_lookup_table = {} # Calculate the total number of permitted atoms total_permitted_atoms = self.get_total_permitted_atoms(monomer_list) # Set the initial and final atom indices to None initially, so that we # don't add terminating units for small molecules start_atom_index = None end_atom_index = None for monomer_no, monomer in enumerate(monomer_list): # This monomer should have the same template file for all CG sites # in the monomer, if not we've made a mistake in splitting the # monomers. So check this: template_files = [] monomer_CG_types = [] for CG_site in monomer: template_files.append( self.CG_to_template_files[ self.CG_dictionary["type"][CG_site] ] ) monomer_CG_types.append(self.CG_dictionary["type"][CG_site]) if len(set(template_files)) != 1: print(monomer) print(monomer_CG_types) print(template_files) raise SystemError("Not all monomer sites are the same template") # Copy the template dictionary for editing for this monomer this_monomer_dictionary = copy.deepcopy( self.AA_templates_dictionary[ self.CG_dictionary["type"][monomer[0]] ] ) for key in ["lx", "ly", "lz"]: # TODO: Tilts this_monomer_dictionary[key] = self.CG_dictionary[key] # Include the image tag in case it's not present in the template if len(this_monomer_dictionary["image"]) == 0: this_monomer_dictionary["image"] = [[0, 0, 0]] * len( this_monomer_dictionary["position"] ) for site_ID in monomer: site_posn = np.array( self.CG_dictionary["unwrapped_position"][site_ID] ) site_translation = site_posn - CG_co_ms[CG_type_list[site_ID]] atom_ID_lookup_table[site_ID] = [ CG_type_list[site_ID], [ x + no_atoms_in_molecule + self.no_atoms_in_morphology for x in self.CG_to_template_AAIDs[ CG_type_list[site_ID] ] ], ] # Add the atoms in based on the CG site position for AAID in self.CG_to_template_AAIDs[CG_type_list[site_ID]]: this_monomer_dictionary["unwrapped_position"][AAID] = list( np.array( this_monomer_dictionary["unwrapped_position"][AAID] ) + site_translation ) # Next sort out the rigid bodies if CG_type_list[site_ID] in self.rigid_body_sites: # Every rigid body needs a ghost particle that describes # its CoM AAID_positions = [] AAID_atom_types = [] # If the key is specified with no values, assume that all # the AAIDs in the template constitute the rigid body if len(self.rigid_body_sites[CG_type_list[site_ID]]) == 0: self.rigid_body_sites[CG_type_list[site_ID]] = list( np.arange( len( self.CG_to_template_AAIDs[ CG_type_list[site_ID] ] ) ) ) for AAID in self.rigid_body_sites[CG_type_list[site_ID]]: this_monomer_dictionary["body"][ AAID ] = current_monomer_index AAID_positions.append( this_monomer_dictionary["unwrapped_position"][AAID] ) AAID_atom_types.append( this_monomer_dictionary["type"][AAID] ) # Now create the ghost particle describing the rigid body ghost_COM = hf.calc_COM( AAID_positions, list_of_atom_types=AAID_atom_types ) ghost_dictionary["unwrapped_position"].append(ghost_COM) ghost_dictionary["mass"].append(1.0) ghost_dictionary["diameter"].append(1.0) ghost_dictionary["type"].append( "R{:s}".format(CG_type_list[site_ID]) ) ghost_dictionary["body"].append(current_monomer_index) ghost_dictionary["charge"].append(0.0) # Then create the corresponding CG anchorpoint ghost_dictionary["unwrapped_position"].append(ghost_COM) ghost_dictionary["mass"].append(1.0) ghost_dictionary["diameter"].append(1.0) ghost_dictionary["type"].append( "X{:s}".format(CG_type_list[site_ID]) ) ghost_dictionary["body"].append(-1) ghost_dictionary["charge"].append(0.0) # Now create a bond between them # We want to bond together the previous two ghost particles, # so this should work as it requires no knowledge of the # number of ghost particles already in the system. ghost_dictionary["bond"].append( [ "{0:s}-{1:s}".format( ghost_dictionary["type"][-2], ghost_dictionary["type"][-1], ), "".join( ["*" + str(len(ghost_dictionary["type"]) - 2)] ), "".join( ["*" + str(len(ghost_dictionary["type"]) - 1)] ), ] ) else: # Create a ghost particle that describe the CG anchorpoint # for the non-rigid body ghost_dictionary["unwrapped_position"].append( self.CG_dictionary["unwrapped_position"][site_ID] ) ghost_dictionary["mass"].append(1.0) ghost_dictionary["diameter"].append(1.0) ghost_dictionary["type"].append( "X{:s}".format(CG_type_list[site_ID]) ) ghost_dictionary["body"].append(-1) ghost_dictionary["charge"].append(0.0) # Add in bonds between the CG anchorpoints and the atom # closest to the CG site. # Find the atom closest to the CG site closest_atom_ID = None closest_atom_posn = 1e99 for AAID, AA_position in enumerate( this_monomer_dictionary["unwrapped_position"] ): separation = hf.calculate_separation( self.CG_dictionary["unwrapped_position"][site_ID], AA_position, ) if separation < closest_atom_posn: closest_atom_posn = separation closest_atom_ID = AAID # Add in the bond: # Note that, in order to distinguish between the # ghost_atom_IDs and the real_atomIDs, I've put an # underscore in front of the closest_atom_ID, and a * in # front of the ghost_atom_ID. When incrementing the # atom_IDs for this monomer or this molecule, the # real_atom_IDs will be incremented correctly. # Later, when the ghost atoms are added to the main system, # the ghost_atom_IDs will be incremented according to the # number of atoms in the whole system (i.e. the ghosts # appear at the end of the real atoms). # At this time, the real_atom_IDs will be left unchanged # because they are already correct for the whole system. ghost_dictionary["bond"].append( [ "{0:s}-{1:s}".format( ghost_dictionary["type"][-1], this_monomer_dictionary["type"][ closest_atom_ID ], ), "".join( ["*" + str(len(ghost_dictionary["type"]) - 1)] ), "".join( [ "_" + str( closest_atom_ID + no_atoms_in_molecule ) ] ), ] ) # Now add in the bonds between CG_sites in this monomer for bond in self.CG_dictionary["bond"]: if (bond[1] in monomer) and (bond[2] in monomer): CG_bond_type = bond[0] this_monomer_dictionary["bond"].append( self.CG_to_template_bonds[CG_bond_type] ) # Now need to add in the additional_constraints for this monomer # (which include the bond, angle and dihedral for the inter-monomer # connections). However, we need a check to make sure that we don't # add stuff for the final monomer (because those atoms +25 don't # exist in this molecule!) for constraint in self.additional_constraints: # Firstly, skip this constraint if the current monomer doesn't # have the relevant atom types if ( all( [ atom_type in set(this_monomer_dictionary["type"]) for atom_type in constraint[0].split("-") ] ) is False ): continue # Also check that we're not at the final monomer at_final_monomer = False for atom_ID in constraint[2:]: if ( no_atoms_in_molecule + atom_ID + 1 ) > total_permitted_atoms: at_final_monomer = True break if at_final_monomer is True: break # Work out which key to write the constraint to based on its # length: # 3 = Bond, 4 = Angle, 5 = Dihedral, 6 = Improper constraint_type = ["bond", "angle", "dihedral", "improper"] this_monomer_dictionary[ constraint_type[len(constraint) - 3] ].append(constraint) # Finally, increment the atom IDs to take into account previous # monomers in this molecule and then update the AADictionary. # Note that the ghost dictionary bond was already updated to have # the correct realAtom AAID for this molecule when the bond was # created. Therefore, leave the ghost dictionary unchanged. this_monomer_dictionary, ghost_dictionary = hf.increment_atom_IDs( this_monomer_dictionary, ghost_dictionary, no_atoms_in_molecule, modify_ghost_dictionary=False, ) no_atoms_in_molecule += len(this_monomer_dictionary["type"]) current_monomer_index += 1 # Update the current AA dictionary with this monomer AA_dictionary = self.update_molecule_dictionary( this_monomer_dictionary, AA_dictionary ) # All Monomers sorted, now for the final bits AA_dictionary["natoms"] = no_atoms_in_molecule for key in ["lx", "ly", "lz"]: AA_dictionary[key] = this_monomer_dictionary[key] # Now we terminate the molecules using the technique in the # add_hydrogens_to_UA analysis script new_hydrogen_data = [] for atom_index, atom_type in enumerate(AA_dictionary["type"]): if atom_type not in self.molecule_terminating_connections.keys(): continue bonded_AAIDs = [] # Iterate over all termination connections defined for this # atom_type (in case we are trying to do something mega # complicated) for connection_info in self.molecule_terminating_connections[ atom_type ]: for [bond_name, AAID1, AAID2] in AA_dictionary["bond"]: if AAID1 == atom_index: if AAID2 not in bonded_AAIDs: bonded_AAIDs.append(AAID2) elif AAID2 == atom_index: if AAID1 not in bonded_AAIDs: bonded_AAIDs.append(AAID1) if len(bonded_AAIDs) != connection_info[0]: continue new_hydrogen_positions = hf.get_terminating_positions( AA_dictionary["unwrapped_position"][atom_index], [ AA_dictionary["unwrapped_position"][bonded_AAID] for bonded_AAID in bonded_AAIDs ], 1, ) for hydrogen_position in new_hydrogen_positions: new_hydrogen_data.append( [atom_index, list(hydrogen_position)] ) AA_dictionary = self.add_terminating_to_molecule( AA_dictionary, new_hydrogen_data ) AA_dictionary = hf.add_wrapped_positions(AA_dictionary) AA_dictionary = hf.add_masses(AA_dictionary) AA_dictionary = hf.add_diameters(AA_dictionary) # Now the molecule is done, we need to add on the correct identifying # numbers for all the bonds, angles and dihedrals (just as we did # between monomers) for the other molecules in the system, so that they # all connect to the right atoms. # Note that here we need to increment the '_'+ATOMIDs in the ghost # dictionary to take into account the number of molecules. AA_dictionary, ghost_dictionary = hf.increment_atom_IDs( AA_dictionary, ghost_dictionary, self.no_atoms_in_morphology, modify_ghost_dictionary=True, ) return AA_dictionary, atom_ID_lookup_table, ghost_dictionary