def get_CG_monomer_dict(self): CG_monomer_dictionary = { "position": [], "image": [], "mass": [], "diameter": [], "type": [], "body": [], "bond": [], "angle": [], "dihedral": [], "improper": [], "charge": [], "lx": 0, "ly": 0, "lz": 0, "xy": 0, "xz": 0, "yz": 0, } # First, do just the positions and find the newsiteIDs for each CG site for site_ID in self.site_IDs: CG_monomer_dictionary["position"].append( self.CG_dictionary["position"][site_ID] ) # Now sort out the other one-per-atom properties for key in ["image", "mass", "diameter", "type", "body", "charge"]: if len(self.CG_dictionary[key]) != 0: for site_ID in self.site_IDs: CG_monomer_dictionary[key].append( self.CG_dictionary[key][site_ID] ) # Now rewrite the bonds based on the newsiteIDs for key in ["bond", "angle", "dihedral", "improper"]: for element in self.CG_dictionary[key]: for site_ID in self.site_IDs: if (site_ID in element) and ( element not in CG_monomer_dictionary[key] ): CG_monomer_dictionary[key].append(element) # Now update the box parameters for key in ["lx", "ly", "lz"]: CG_monomer_dictionary[key] = self.CG_dictionary[key] CG_monomer_dictionary = hf.add_unwrapped_positions( CG_monomer_dictionary ) CG_monomer_dictionary["natoms"] = len(CG_monomer_dictionary["position"]) return CG_monomer_dictionary
def main(): parser = argparse.ArgumentParser() parser.add_argument( "-m", "--molecule_source", required=False, help="""Specify the source of the add_hydrogens dictionary and sigma value. This can be in the form of a python script which returns a function of the dictionary and sigma or the name of the molecule.""", ) args, input_files = parser.parse_known_args() hydrogens_to_add, sigma_val = find_information(args) for input_file in input_files: # This dictionary has keys of the atom type, and values where the first # element is the number of bonds required for us to add a hydrogen to # the atom and the second element of the value defines how many # hydrogens to add to said atom. print( "THIS FUNCTION IS SET UP TO USE A DICTIONARY TO DEFINE HOW MANY HYDROGENS " " TO ADD TO BONDS OF A SPECIFIC TYPE WITH A CERTAIN NUMBER OF BONDS" ) print(hydrogens_to_add) print( "IF THE ABOVE DICTIONARY DOESN'T LOOK RIGHT, PLEASE TERMINATE NOW AND " " IGNORE ANY OUTPUTS UNTIL THE DICTIONARY HAS BEEN RECTIFIED" ) print("Additionally, we're using a sigma value of", sigma_val) morphology_dict = hf.load_morphology_xml(input_file, sigma=sigma_val) # We MUST fix the images first, otherwise the hydrogens will be put in # incorrectly. morphology_dict = fi.zero_out_images(morphology_dict) bond_dict = fi.get_bond_dict(morphology_dict) morphology_dict = fi.check_bonds(morphology_dict, bond_dict) # Now we can safely obtain the unwrapped positions morphology_dict = hf.add_unwrapped_positions(morphology_dict) hydrogen_positions = calculate_hydrogen_positions( morphology_dict, hydrogens_to_add ) morphology_dict = add_hydrogens_to_morph( morphology_dict, hydrogen_positions ) morphology_dict = hf.add_wrapped_positions(morphology_dict) hf.write_morphology_xml( morphology_dict, input_file.replace(".xml", "_AA.xml"), check_wrapped_posns=False, )
def __init__(self, morphology_xml, morphology_name, parameter_dict, chromophore_list): # Need to save the parameter_dict in full as well as its component # values because we're going to update the parameterDict with the new # type mappings by the end of this code module. self.parameter_dict = parameter_dict # Import parameters from the parXX.py for key, value in parameter_dict.items(): self.__dict__[key] = value self.xml_path = morphology_xml self.morphology_name = morphology_name # self.inputSigma is the `compression value' in Angstroms that has been # used to scale the morphology # E.G. the P3HT template uses sigma = 1, but the Marsh morphologies use # sigma = 3. self.CG_dictionary = hf.load_morphology_xml(self.xml_path, sigma=self.input_sigma) self.CG_dictionary = hf.add_unwrapped_positions(self.CG_dictionary) self.chromophore_list = chromophore_list
def __init__( self, molecule_index, site_IDs, CG_dictionary, molecule_lengths, rolling_AA_index, ghost_dictionary, parameter_dict, ): # This class sees individual molecules. self.no_atoms_in_morphology = rolling_AA_index self.molecule_index = molecule_index self.molecule_lengths = molecule_lengths self.site_IDs = site_IDs self.CG_dictionary = CG_dictionary # Get the dictionary of all the CG sites in this molecule # self.CGMonomerDictionary = self.getCGMonomerDict() # Import the parXX.py parameters for key, value in parameter_dict.items(): self.__dict__[key] = value self.AA_templates_dictionary = {} # Load the template file for each CG atom for CG_atom_type in list(self.CG_to_template_files.keys()): template_dictionary = hf.load_morphology_xml( os.path.join( self.CG_to_template_dirs[CG_atom_type], self.CG_to_template_files[CG_atom_type], ) ) template_dictionary = self.remap_atom_types( template_dictionary, parameter_dict["new_type_mappings"][CG_atom_type], ) template_dictionary = hf.add_unwrapped_positions( template_dictionary ) self.AA_templates_dictionary[CG_atom_type] = template_dictionary fine_grained = self.run_fine_grainer(ghost_dictionary) self.AA_dictionary = fine_grained[0] self.atom_ID_lookup_table = fine_grained[1] self.ghost_dictionary = fine_grained[2]
def main( AA_morphology_dict, CG_morphology_dict, CG_to_AAID_master, parameter_dict, chromophore_list, ): # Get the random seed now for all the child processes if parameter_dict["random_seed_override"] is not None: np.random.seed(parameter_dict["random_seed_override"]) # Main execution function for run_HOOMD that performs the required MD phases # First, scale the input morphology based on the pair potentials such that # the distances and energies are normalised to the strongest pair # interaction and the diameter of the largest atom (makes it easier on # HOOMDs calculations and ensures that T = 1.0 is an interesting temperature # threshold) current_files = os.listdir( os.path.join( parameter_dict["output_morph_dir"], os.path.splitext(parameter_dict["morphology"])[0], "morphology", ) ) # sScale, eScale = obtainScaleFactors(parameterDict) print("Under the hood eScaling and sScaling has been disabled.") s_scale = 1.0 e_scale = 1.0 # Only scale the morphology if it hasn't been already if (parameter_dict["overwrite_current_data"] is False) and ( "".join(["phase0_", parameter_dict["morphology"]]) in current_files ): pass else: scale_morphology(AA_morphology_dict, parameter_dict, s_scale, e_scale) # Reset logfile try: os.remove( os.path.join( parameter_dict["output_morph_dir"], os.path.splitext(parameter_dict["morphology"])[0], "morphology", "".join( [ "energies_", os.path.splitext(parameter_dict["morphology"])[0], ".log", ] ), ) ) except OSError: pass # Perform each molecular dynamics phase as specified in the parXX.py for phase_no in range(parameter_dict["number_of_phases"]): input_file = "phase{0:d}_{1:s}".format(phase_no, parameter_dict["morphology"]) output_file = "phase{0:d}_{1:s}".format( phase_no + 1, parameter_dict["morphology"] ) if output_file in current_files: if parameter_dict["overwrite_current_data"] is False: print(output_file, "already exists. Skipping...") continue md_phase( AA_morphology_dict, CG_morphology_dict, CG_to_AAID_master, parameter_dict, phase_no, os.path.join( parameter_dict["output_morph_dir"], os.path.splitext(parameter_dict["morphology"])[0], "morphology", input_file, ), os.path.join( parameter_dict["output_morph_dir"], os.path.splitext(parameter_dict["morphology"])[0], "morphology", output_file, ), s_scale, e_scale, ).optimise_structure() final_xml_name = os.path.join( parameter_dict["output_morph_dir"], os.path.splitext(parameter_dict["morphology"])[0], "morphology", "".join(["final_", parameter_dict["morphology"]]), ) if "".join(["final_", parameter_dict["morphology"]]) not in current_files: # Now all phases are complete, remove the ghost particles from the # system print("Removing ghost particles to create final output...") remove_ghost_particles( os.path.join( parameter_dict["output_morph_dir"], os.path.splitext(parameter_dict["morphology"])[0], "morphology", output_file, ), final_xml_name, sigma=s_scale, ) # Finally, update the pickle file with the most recent and realistic # AAMorphologyDict so that we can load it again further along the pipeline AA_morphology_dict = hf.load_morphology_xml(final_xml_name) # Now that we've obtained the final fine-grained morphology, we need to fix # the images to prevent issues with obtaining the chromophores and running # them through the ZINDO/S calculations later... AA_morphology_dict = hf.fix_images(AA_morphology_dict) # ...add in the unwrapped positions... AA_morphology_dict = hf.add_unwrapped_positions(AA_morphology_dict) # ...rewrite the final morphology xml... hf.write_morphology_xml(AA_morphology_dict, final_xml_name) # ...and write the pickle file. hf.write_pickle( ( AA_morphology_dict, CG_morphology_dict, CG_to_AAID_master, parameter_dict, chromophore_list, ), os.path.join( parameter_dict["output_morph_dir"], os.path.splitext(parameter_dict["morphology"])[0], "code", "".join([os.path.splitext(parameter_dict["morphology"])[0], ".pickle"]), ), ) return ( AA_morphology_dict, CG_morphology_dict, CG_to_AAID_master, parameter_dict, chromophore_list, )
def analyse_morphology(self): # Split the morphology into individual molecules print("Finding molecules...") molecule_IDs, molecule_lengths = self.split_molecules() rolling_AA_index = 0 CG_morphology_dict = {} AA_morphology_dict = {} # Set the AAMorphology and CGMorphology system sizes to the same as the # input file system size for box_dimension in ["lx", "ly", "lz", "xy", "xz", "yz"]: CG_morphology_dict[box_dimension] = self.CG_dictionary[ box_dimension ] AA_morphology_dict[box_dimension] = self.CG_dictionary[ box_dimension ] CG_to_AAID_master = [] # This is a list of dictionaries. Elements in # the list correspond to molecules (useful for splitting out individual # molecules for the xyz conversion) within the element, the dictionary # key is the CG site, the value is a list containing the CG type (e.g. # 'thio') as the first element and then another list of all the AAIDs # corresponding to that CG site as the second element. # If no CG_to_template info is present in the parameter dict, then we # can assume that the morphology is already fine-grained and so we can # just return the important information and skip this module if len(self.CG_to_template_dirs) == 0: print( "No CG to AA data found in parameter file - the morphology is already" " fine-grained! Skipping this module..." ) # Write the xml file and create the pickle print("Writing xml file...") AA_file_name = os.path.join( self.output_morph_dir, self.morphology_name, "morphology", "".join([self.morphology_name, ".xml"]), ) atomistic_morphology = hf.add_unwrapped_positions( self.CG_dictionary ) # Now write the morphology xml hf.write_morphology_xml(atomistic_morphology, AA_file_name) # And finally write the pickle with the CGDictionary as None (to # indicate to MorphCT that no fine-graining has taken place), but # the other parameters assigned as required. pickle_location = os.path.join( self.output_morph_dir, self.morphology_name, "code", "".join([self.morphology_name, ".pickle"]), ) hf.write_pickle( ( atomistic_morphology, None, None, self.parameter_dict, self.chromophore_list, ), pickle_location, ) return ( atomistic_morphology, None, None, self.parameter_dict, self.chromophore_list, ) # Create a ghost particle dictionary to be added at the end of the # morphology. This way, we don't mess up the order of atoms later on # when trying to split back up into individual molecules and monomers. # The ghost dictionary contains all of the type T and type X particles # that will anchor the thiophene rings to the CG COM positions. ghost_dictionary = { "position": [], "image": [], "unwrapped_position": [], "mass": [], "diameter": [], "type": [], "body": [], "bond": [], "angle": [], "dihedral": [], "improper": [], "charge": [], } # Need to check for atom-type conflicts and suitably increment the # type indices if more than one molecule type is being fine-grained new_type_mappings = self.get_new_type_mappings( self.CG_to_template_dirs, self.CG_to_template_force_fields ) # Need to update the self.parameterDict, which will be rewritten at the # end of this module self.parameter_dict["new_type_mappings"] = new_type_mappings molecule = [] unique_mappings = [] CG_sites, mappings = hf.parallel_sort( list(new_type_mappings.keys()), list(new_type_mappings.values()) ) for index, mapping in enumerate(mappings): if mapping not in unique_mappings: molecule.append([]) unique_mappings.append(mapping) molecule[-1].append(CG_sites[index]) printExplanation = True for index, CG_sites in enumerate(molecule): printMol = True initial_atoms, final_atoms = hf.parallel_sort( list(unique_mappings[index].keys()), list(unique_mappings[index].values()), ) for index, initial_atom in enumerate(initial_atoms): if initial_atom == final_atoms[index]: continue if printExplanation is True: print( "The following atom types have been remapped due to conflicting" " typenames in the atomistic templates:" ) printExplanation = False if printMol is True: print( "Atom types belonging the molecule described by", "".join([repr(CG_sites), ":"]), ) printMol = False print(initialAtom, "--->", finalAtoms[index]) print("Adding", len(molecule_IDs), "molecules to the system...") for molecule_number in range(len(molecule_IDs)): print("Adding molecule number", molecule_number, "\r", end=" ") if sys.stdout is not None: sys.stdout.flush() # Obtain the AA dictionary for each molecule using the # fine-graining procedure AA_molecule_dict, C_gto_AAIDs, ghost_dictionary = atomistic( molecule_number, molecule_IDs[molecule_number], self.CG_dictionary, molecule_lengths, rolling_AA_index, ghost_dictionary, self.parameter_dict, ).return_data() CG_to_AAID_master.append(C_gto_AAIDs) # Update the morphology dictionaries with this new molecule for key in list(self.CG_dictionary.keys()): if key not in [ "lx", "ly", "lz", "xy", "xz", "yz", "time_step", "dimensions", ]: if key not in list(AA_morphology_dict.keys()): AA_morphology_dict[key] = AA_molecule_dict[key] else: AA_morphology_dict[key] += AA_molecule_dict[key] rolling_AA_index += len(AA_molecule_dict["type"]) # Now add the ghost dictionary to the end of the morphology file # total_number_of_atoms should be == rolling_AA_index, but don't want # to take any chances. total_number_of_atoms = len(AA_morphology_dict["type"]) # Add in the wrapped positions of the ghosts. Need to know sim dims for # this for key in ["lx", "ly", "lz", "xy", "xz", "yz"]: ghost_dictionary[key] = AA_morphology_dict[key] ghost_dictionary = hf.add_wrapped_positions(ghost_dictionary) for key in ["lx", "ly", "lz", "xy", "xz", "yz"]: ghost_dictionary.pop(key) # The real atoms that the ghost particles are bonded to are already # correct and no longer need to be changed. # However, the ghost particles themselves will have the wrong indices # if we were to add them to the system directly. # Therefore, increment all of the ghost bond indices that begin with a # * (ghost particle) by the total number of atoms already in the # system. for bond_no, bond in enumerate(ghost_dictionary["bond"]): if str(bond[1])[0] == "*": ghost_dictionary["bond"][bond_no][1] = ( int(bond[1][1:]) + total_number_of_atoms ) if str(bond[2])[0] == "*": ghost_dictionary["bond"][bond_no][2] = ( int(bond[2][1:]) + total_number_of_atoms ) # Now append all ghosts to morphology for key in list(ghost_dictionary.keys()): AA_morphology_dict[key] += ghost_dictionary[key] # Finally, update the number of atoms AA_morphology_dict["natoms"] += len(ghost_dictionary["type"]) print("\n") # Now write the xml file and create the pickle print("Writing xml file...") AA_file_name = os.path.join( self.output_morph_dir, self.morphology_name, "morphology", "".join([self.morphology_name, ".xml"]), ) # Replace the `positions' with the `unwrapped_positions' ready for # writing AA_morphology_dict = hf.replace_wrapped_positions(AA_morphology_dict) # Update the additional_constraints that we put in by checking all of # the constraints have the correct names before writing AA_morphology_dict = hf.check_constraint_names(AA_morphology_dict) # Now write the morphology xml hf.write_morphology_xml(AA_morphology_dict, AA_file_name) # And finally write the pickle pickle_location = os.path.join( self.output_morph_dir, self.morphology_name, "code", "".join([self.morphology_name, ".pickle"]), ) hf.write_pickle( ( AA_morphology_dict, self.CG_dictionary, CG_to_AAID_master, self.parameter_dict, self.chromophore_list, ), pickle_location, ) return ( AA_morphology_dict, self.CG_dictionary, CG_to_AAID_master, self.parameter_dict, self.chromophore_list, )