def description_text(self, P=None): """Create the text description of what this step will do. The dictionary of control values is passed in as P so that the code can test values, etc. Parameters ---------- P: dict An optional dictionary of the current values of the control parameters. Returns ------- str A description of the current step. """ if not P: P = self.parameters.values_to_dict() script = P['script'].splitlines() if len(script) > 10: text = '\n'.join(script[0:9]) text += '\n...' else: text = '\n'.join(script) return (self.header + '\n' + __(text, indent=4 * ' ', wrap=False).__str__())
def description_text(self, P=None): """Return a short description of this step. Return a nicely formatted string describing what this step will do. Keyword arguments: P: a dictionary of parameter values, which may be variables or final values. If None, then the parameters values will be used as is. """ if not P: P = self.parameters.values_to_dict() text = 'Solvate the system' if P['method'][0] == '$': text += ( '. Depending on {method} either make it a periodic box or ' 'spherical droplet of') elif P['method'] == 'making periodic': text += ' making it a periodic system filled with' elif P['method'] == 'within a sphere of solvent': text += ' within a spherical droplet of' else: raise RuntimeError("Don't recognize the method {}".format( P['method'])) if P['solvent'] == 'water': text += ' water, using the {water_model} model.' else: text += ' molecules of {solvent}.' if P['submethod'][0] == '$': text += ' It will be created according to {submethod}.' elif P['submethod'] == ( "fixing the volume and adding a given number of molecules"): text += (' The cell volume is fixed, with {number of molecules} ' 'solvent molecules added.') elif P['submethod'] == ("fixing the volume and filling to a density"): text += ( ' The cell volume is fixed and will be filled to a density ' 'of {density}.') elif P['submethod'] == ( "with the density and number of molecules of solvent"): text += (' The density is {density} with {number of molecules} ' 'solvent molecules.') elif P['submethod'] == ( "with the density and approximate number of atoms of solvent"): text += ( ' The density is {density} and solvent molecules with a total ' 'of approximately {approximate number of atoms} will be used.') else: raise RuntimeError("Don't recognize the submethod {}".format( P['submethod'])) return self.header + '\n' + __(text, **P, indent=4 * ' ').__str__()
def run(self): """Run a System step. Parameters ---------- None Returns ------- seamm.Node The next node object in the flowchart. """ next_node = super().run(printer) # Get the values of the parameters, dereferencing any variables P = self.parameters.current_values_to_dict( context=seamm.flowchart_variables._data) # Print what we are doing printer.important(__(self.description_text(P), indent=self.indent)) # Temporary code just to print the parameters. You will need to change # this! for key in P: print('{:>15s} = {}'.format(key, P[key])) printer.normal( __('{key:>15s} = {value}', key=key, value=P[key], indent=4 * ' ', wrap=False, dedent=False)) # Analyze the results self.analyze() # Add other citations here or in the appropriate place in the code. # Add the bibtex to data/references.bib, and add a self.reference.cite # similar to the above to actually add the citation to the references. return next_node
def analyze(self, indent='', **kwargs): """Do any analysis needed for this step, and print important results to the local step.out file using 'printer' """ printer.normal( __( 'This is a placeholder for the results form step ' 'Cassandra', indent=4 * ' ', wrap=True, dedent=False ) )
def description_text(self, P=None): """Create the text description of what this step will do. The dictionary of control values is passed in as P so that the code can test values, etc. Parameters ---------- P: dict An optional dictionary of the current values of the control parameters. Returns ------- str A description of the current step. """ if not P: P = self.parameters.values_to_dict() method = P['method'] if method == 'density': density = P['density'] if self.is_expr(density): text = ("The cell will be adjusted isotropically to a density " f"given by '{density}'.") else: text = ("The cell will be adjusted isotropically to a density " f"of {density}.") elif method == 'volume': volume = P['volume'] if self.is_expr(volume): text = ("The cell will be adjusted isotropically to a volume " f"given by '{volume}'.") else: text = ("The cell will be adjusted isotropically to a volume " f"of {volume}.") elif method == 'cell parameters': text = "The cell parameters will be set as follows:" for parameter in ('a', 'b', 'c', 'alpha', 'beta', 'gamma'): text += f" {parameter:>5}: {P[parameter]}" elif method == 'uniform contraction/expansion': text = ( f"The cell will be adjusted isotropically by {P['expansion']} " "in each direction.") else: raise RuntimeError(f"Don't recognize method '{method}'!") return self.header + '\n' + __(text, **P, indent=4 * ' ').__str__()
def analyze(self, indent='', **kwargs): """Do any analysis of the output from this step. Also print important results to the local step.out file using 'printer'. Parameters ---------- indent: str An extra indentation for the output """ printer.normal( __('This is a placeholder for the results from the ' 'System step', indent=4 * ' ', wrap=True, dedent=False))
def describe(self, indent='', json_dict=None): """Write out information about what this step will do If json_dict is passed in, add information to that dictionary so that it can be written out by the controller as appropriate. """ # Call superclasses which will print some information next_node = super().describe() # Local copies of variables in a dictionary P = self.parameters.values_to_dict() text = self.description_text(P) job.job(__(text, indent=self.indent + ' ', **P)) return next_node
def description_text(self, P=None): """Create the text description of what this step will do. The dictionary of control values is passed in as P so that the code can test values, etc. Parameters ---------- P: dict An optional dictionary of the current values of the control parameters. Returns ------- str A description of the current step. """ if not P: P = self.parameters.values_to_dict() text = ('Please replace this with a short summary of the ' 'System step, including key parameters.') return self.header + '\n' + __(text, **P, indent=4 * ' ').__str__()
def analyze(self, indent='', data={}): """Parse the output and generating the text output and store the data in variables for other stages to access """ printer.normal(self._long_header) # The results printer.normal( __(('\nThe geometry converged in {NUMBER_SCF_CYCLES} ' 'iterations to a heat of formation of {HEAT_OF_FORMATION} ' 'kcal/mol.'), **data, indent=7 * ' ')) # Put any requested results into variables or tables self.store_results( data=data, properties=mopac_step.properties, results=self.parameters['results'].value, create_tables=self.parameters['create tables'].get()) printer.normal('\n')
def run(self): """Run a Cassandra step. """ # Get the values of the parameters, dereferencing any variables P = self.parameters.current_values_to_dict( context=seamm.flowchart_variables._data ) # Temporary code just to print the parameters. You will need to change # this! for key in P: print('{:>15s} = {}'.format(key, P[key])) printer.normal( __( '{key:>15s} = {value}', key=key, value=P[key], indent=4 * ' ', wrap=False, dedent=False ) ) return super().run()
def run(self): """Run a Read Structure step.""" next_node = super().run(printer) # Get the values of the parameters, dereferencing any variables P = self.parameters.current_values_to_dict( context=seamm.flowchart_variables._data) # What type of file? if isinstance(P["file"], Path): filename = str(P["file"]) else: filename = ["file"].strip() file_type = P["file type"] if file_type != "from extension": extension = file_type.split()[0] else: path = PurePath(filename) extension = path.suffix if extension == ".gz": extension = path.stem.suffix if extension == "": extension = guess_extension(filename, use_file_name=False) P["file type"] = extension # Print what we are doing printer.important(self.description_text(P)) # Read the file into the system system_db = self.get_variable("_system_db") system, configuration = self.get_system_configuration( P, structure_handling=True) read( filename, configuration, extension=extension, add_hydrogens=P["add hydrogens"], system_db=system_db, system=system, indices=P["indices"], subsequent_as_configurations=(P["subsequent structure handling"] == "Create a new configuration"), system_name=P["system name"], configuration_name=P["configuration name"], printer=printer.important, references=self.references, bibliography=self._bibliography, ) # Finish the output if configuration.periodicity == 3: space_group = configuration.symmetry.group if space_group == "": symmetry_info = "" else: symmetry_info = f" The space group is {space_group}." printer.important( __( f"\n Created a periodic structure with {configuration.n_atoms} " f"atoms.{symmetry_info}" f"\n System name = {system.name}" f"\n Configuration name = {configuration.name}", indent=4 * " ", )) else: printer.important( __( f"\n Created a molecular structure with {configuration.n_atoms} " "atoms." f"\n System name = {system.name}" f"\n Configuration name = {configuration.name}", indent=4 * " ", )) printer.important("") return next_node
def run(self): """Run a Write Structure step.""" next_node = super().run(printer) # Get the values of the parameters, dereferencing any variables P = self.parameters.current_values_to_dict( context=seamm.flowchart_variables._data) # What type of file? filename = P["file"].strip() file_type = P["file type"] if file_type != "from extension": extension = file_type.split()[0] else: path = PurePath(filename) extension = path.suffix if extension == ".gz": extension = path.stem.suffix if extension == "": extension = guess_extension(filename, use_file_name=False) P["file type"] = extension # Print what we are doing printer.important(__(self.description_text(P), indent=4 * " ")) # Write the file into the system system_db = self.get_variable("_system_db") system, configuration = self.get_system_configuration(P) structures = P["structures"] if structures == "current configuration": configurations = [configuration] elif structures == "all configurations of current system": for configuration in system.configurations(): configurations.append(configuration) elif structures == "all systems": configurations = [] for system in system_db.systems(): for configuration in system.configurations(): configurations.append(configuration) write( filename, configurations, extension=extension, remove_hydrogens=P["remove hydrogens"], printer=printer.important, references=self.references, bibliography=self._bibliography, ) # Finish the output printer.important( __( f"\n Wrote the structure with {configuration.n_atoms} " "atoms." f"\n System name = {system.name}" f"\n Configuration name = {configuration.name}", indent=4 * " ", )) printer.important("") return next_node
def run(self): """Solvate the system usiing PACKMOL To run PACKMOL we need to specify either the size of the cell for periodic systems or the radius of the sphere for molecules, along with the number of solvent molecules to use. Since PACKMOL does not really handle periodic systems, we will pack the solvent in a smaller box that fits in the actual periodic cell to avoid overlaps at the cell boundaries. """ next_node = super().run(printer) # The options from command line, config file ... o = self.options packmol_exe = os.path.join(o.packmol_path, 'packmol') seamm_util.check_executable(packmol_exe, key='--packmol-path', parser=self.parser) P = self.parameters.current_values_to_dict( context=seamm.flowchart_variables._data) method = P['method'] submethod = P['submethod'] logger.debug(' method = {}'.format(method)) logger.debug('submethod = {}'.format(submethod)) # Print what we are doing printer.important(__(self.description_text(P), indent=self.indent)) # Check that there is a system and get its mass, etc. if data.structure is None: logger.error('Solvate: there is no system!') raise RuntimeError('Solvate: there is no system to solvate!') else: system = data.structure atoms = system['atoms'] # Get the molecular weight elements = atoms['elements'] mass = 0.0 for element in elements: mass += mendeleev.element(element).mass system_mass = mass * ureg.g / ureg.mol system_mass.ito('kg') # Mass per 'system', in kg # And center of box containing the molecule minx, miny, minz = atoms['coordinates'][0] maxx, maxy, maxz = atoms['coordinates'][0] for x, y, z in atoms['coordinates']: minx = x if x < minx else minx maxx = x if x > maxx else maxx miny = y if y < miny else miny maxy = y if y > maxy else maxy minz = z if z < minz else minz maxz = z if z > maxz else maxz cx = (minx + maxx) / 2 cy = (miny + maxy) / 2 cz = (minz + maxz) / 2 # And the solvent molecule(s) if P['solvent'] == 'water': model = Water.create_model(P['water_model']) solvent_mass = model.mass * ureg.g / ureg.mol solvent_mass.ito('kg') # Mass per solvent molecule, in kg n_solvent_atoms = 3 solvent_pdb = model.pdb() solvent_system = model.system() else: raise NotImplementedError( 'Solvents other than water not available yet.') if method == "within a sphere of solvent": # Nonperiodic case raise NotImplementedError( 'Solvating with a sphere of solvent not supported yet.') else: # Periodic case if submethod == ( "fixing the volume and adding a given number of molecules" ): if system['periodicity'] != 3: raise RuntimeError('Solvate: the system is not periodic.') a, b, c, alpha, beta, gamma = system['cell'] if alpha != 90 or beta != 90 or gamma != 90: raise NotImplementedError( 'Solvate cannot handle non-orthorhombic cells yet') lx = a ly = b lz = c n_molecules = P['number of molecules'] elif submethod == ("fixing the volume and filling to a density"): if system['periodicity'] != 3: raise RuntimeError('Solvate: the system is not periodic.') a, b, c, alpha, beta, gamma = system['cell'] if alpha != 90 or beta != 90 or gamma != 90: raise NotImplementedError( 'Solvate cannot handle non-orthorhombic cells yet') lx = a ly = b lz = c density = P['density'] volume = a * b * c * math.pow(ureg.m / 1.0e10, 3) target_mass = density * volume target_mass.ito('kg') n_molecules = int( round((target_mass - system_mass) / solvent_mass)) elif submethod == ( "with the density and number of molecules of solvent"): density = P['density'] n_molecules = P['number of molecules'] mass = system_mass + n_molecules * solvent_mass volume = mass / density lx = math.pow(volume.to('Å^3').magnitude, 1 / 3) ly = lx lz = lx system['periodicity'] = 3 system['cell'] = [lx, ly, lz, 90.0, 90.0, 90.0] elif submethod == ( "with the density and approximate number of atoms of solvent" ): density = P['density'] n_molecules = int( round(P['approximate number of atoms'] / n_solvent_atoms)) mass = system_mass + n_molecules * solvent_mass volume = mass / density lx = math.pow(volume.to('Å^3').magnitude, 1 / 3) ly = lx lz = lx system['periodicity'] = 3 system['cell'] = [lx, ly, lz, 90.0, 90.0, 90.0] else: raise RuntimeError( "Don't recognize the submethod {}".format(submethod)) # gap = P['gap'].to('Å').magnitude gap = 2.0 lxp = lx - gap / 2 lyp = ly - gap / 2 lzp = lz - gap / 2 dx = (lx / 2) - cx dy = (ly / 2) - cy dz = (lz / 2) - cz lines = [] lines.append('tolerance {}'.format(gap)) lines.append('output solvate.pdb') lines.append('filetype pdb') lines.append('structure system.pdb') lines.append(' number 1') lines.append(' fixed {} {} {} 0.0 0.0 0.0'.format(dx, dy, dz)) lines.append('end structure') lines.append('structure solvent.pdb') lines.append(' number {}'.format(n_molecules)) lines.append(' inside box {l0} {l0} {l0} {} {} {}'.format(lxp, lyp, lzp, l0=gap / 2)) lines.append('end structure') lines.append('') files = { 'system.pdb': pdbfile.from_molssi(data.structure), 'solvent.pdb': solvent_pdb, 'input.inp': '\n'.join(lines) } for key, value in files.items(): logger.debug(80 * '*') logger.debug('File: ' + key) logger.debug(80 * '*') logger.debug('\n' + value + '\n\n') local = seamm.ExecLocal() result = local.run(cmd=(packmol_exe + ' < input.inp'), shell=True, files=files, return_files=['solvate.pdb']) tmp_files = [] for key, value in result.items(): if key == 'files': tmp_files = value if key in tmp_files: logger.debug(80 * '*') logger.debug('File: ' + key) if value['exception'] is not None: logger.debug(' Exception = ' + value['exception']) logger.debug(80 * '*') logger.debug('\n' + value['data'] + '\n\n') elif len(str(value)) < 100: logger.debug(key + ': ' + str(value)) else: logger.debug(80 * '*') logger.debug('Result: ' + key) logger.debug(80 * '*') logger.debug('\n' + str(value) + '\n\n') # Parse the resulting PDB file new_structure = pdbfile.to_molssi(result['solvate.pdb']['data']) logger.debug('Coordinates for the solvated system:') for x, y, z in new_structure['atoms']['coordinates']: logger.debug('{:9.4f} {:9.4f} {:9.4f}'.format(x, y, z)) # Packmol just gives back atoms and coordinates, so we need to # build out the system with added solvent molecules and then # replace the coordinates with the new coordinates solvent_atoms = solvent_system['atoms'] for molecule_id in range(n_molecules): first_atom = len(atoms['elements']) for key in atoms: if key in solvent_atoms: if key == 'atom_types' or key == 'charges': types = atoms[key] solvent_types = solvent_atoms[key] for type_ in types: if type_ in solvent_types: types[type_].extend(solvent_types[type_]) elif '*' in solvent_types: types[type_].extend(solvent_types['*']) else: atoms[key].extend(solvent_atoms[key]) else: logger.warning(key + 'not in solvent_system.atoms') for i in range(n_solvent_atoms): atoms[key].append('') for i, j, order in solvent_system['bonds']: system['bonds'].append([first_atom + i, first_atom + j, order]) # And the new coordinates atoms['coordinates'] = new_structure['atoms']['coordinates'] string = 'Solvated the system with {nmol} solvent molecules.' if system['periodicity'] == 3: string += ' The periodic cell is {lx:.2f}x{ly:.2f}x{lz:.2f}' string += ' with a density of {density:.5~P}.' printer.important( __(string, indent=' ', lx=lx, ly=ly, lz=lz, nmol=n_molecules, density=density)) printer.important('') logger.log( 0, 'Structure created by Packmol:\n\n' + pprint.pformat(data.structure)) return next_node
def run(self): """Run a Set Cell step. Parameters ---------- None Returns ------- seamm.Node The next node object in the flowchart. """ next_node = super().run(printer) # Get the values of the parameters, dereferencing any variables P = self.parameters.current_values_to_dict( context=seamm.flowchart_variables._data) # Print what we are doing -- getting formatted values for printing PP = self.parameters.current_values_to_dict( context=seamm.flowchart_variables._data, formatted=True, units=False) self.logger.debug(f'Formatted values:\n{pprint.pformat(PP)}') printer.normal(__(self.description_text(PP), indent=self.indent)) method = P['method'] system_db = self.get_variable('_system_db') configuration = system_db.system.configuration cell = configuration.cell a, b, c, alpha, beta, gamma = cell.parameters if method == 'density': rho = P['density'].to('g/mL').magnitude rho0 = configuration.density delta = (rho0 / rho)**(1 / 3) a = a * delta b = b * delta c = c * delta elif method == 'volume': V = P['volume'].to('Å^3').magnitude V0 = configuration.volume delta = (V / V0)**(1 / 3) a = a * delta b = b * delta c = c * delta elif method == 'cell parameters': a = P['a'].to('Å').magnitude b = P['b'].to('Å').magnitude c = P['c'].to('Å').magnitude alpha = P['alpha'].to('degree').magnitude beta = P['beta'].to('degree').magnitude gamma = P['gamma'].to('degree').magnitude elif method == 'uniform contraction/expansion': delta = (1 + P['expansion'])**(1 / 3) a = a * delta b = b * delta c = c * delta else: raise RuntimeError(f"Don't recognize method '{method}'!") configuration.cell.parameters = (a, b, c, alpha, beta, gamma) text = '\nAdjusted the cell:\n' text += f' a: {a:8.3f}\n' text += f' b: {b:8.3f}\n' text += f' c: {c:8.3f}\n' text += f' alpha: {alpha:7.2f}\n' text += f' beta: {beta:7.2f}\n' text += f' gamma: {gamma:7.2f}\n' text += '\n' text += f' volume: {configuration.volume:10.1f} Å^3\n' text += f' density: {configuration.density:11.2f} g/mL\n' printer.normal(__(text, indent=self.indent + 4 * ' ')) printer.normal('') # Add other citations here or in the appropriate place in the code. # Add the bibtex to data/references.bib, and add a self.reference.cite # similar to the above to actually add the citation to the references. return next_node
def assign_parameters(self, configuration=None): if configuration is None: raise TypeError("A configuration must be provided when assigning forcefield parameters") printer.important( __( "Assigning the atom types and charges for forcefield " f"'{self.selected_forcefield}' to the system", ) ) logger.debug('Atom typing, getting the SMILES for the system') smiles = configuration.to_smiles(hydrogens=True) logger.debug('Atom typing -- smiles = ' + smiles) pat3 = re.compile(r'H3\]') pat2 = re.compile(r'H2\]') pat1 = re.compile(r'(?P<c1>[^[])H\]') smiles = pat3.sub(']([H])([H])([H])', smiles) smiles = pat2.sub(']([H])([H])', smiles) smiles = pat1.sub(r'\g<c1>]([H])', smiles) h_subst = None for el in ('Rb', 'Cs', 'Fr', 'At'): if el not in smiles: h_subst = el pat4 = re.compile(r'\[H\]') smiles = pat4.sub('[{}]'.format(el), smiles) logger.debug("Subst SMILES = '{}'".format(smiles)) break molecule = rdkit.Chem.MolFromSmiles(smiles) if molecule is None: print("There was problem with the SMILES '{}'".format(smiles)) return if h_subst is not None: for atom in molecule.GetAtoms(): if atom.GetSymbol() == h_subst: atom.SetAtomicNum(1) # if add_hydrogens: # molecule = rdkit.Chem.AddHs(molecule) # n_atoms = molecule.GetNumAtoms() # logger.debug( # "'{}' has {} atoms with hydrogens added".format( # smiles, n_atoms # ) # ) # else: # n_atoms = molecule.GetNumAtoms() # logger.debug("'{}' has {} atoms".format(smiles, n_atoms)) n_atoms = molecule.GetNumAtoms() atomtypes = ['?'] * n_atoms templates = self.forcefield.get_templates() for atom_type in templates: template = templates[atom_type] for smarts in template['smarts']: pattern = rdkit.Chem.MolFromSmarts(smarts) ind_map = {} for atom in pattern.GetAtoms(): map_num = atom.GetAtomMapNum() if map_num: ind_map[map_num - 1] = atom.GetIdx() map_list = [ind_map[x] for x in sorted(ind_map)] matches = molecule.GetSubstructMatches(pattern) logger.debug(atom_type + ': ') if len(matches) > 0: for match in matches: atom_ids = [match[x] for x in map_list] for x in atom_ids: atomtypes[x] = atom_type tmp = [str(x) for x in atom_ids] logger.debug('\t' + ', '.join(tmp)) i = 0 untyped = [] for atom, atom_type in zip(molecule.GetAtoms(), atomtypes): if atom_type == '?': untyped.append(i) logger.debug("{}: {}".format(atom.GetSymbol(), atom_type)) i += 1 if len(untyped) > 0: logger.warning( 'The forcefield does not have atom types for' ' the molecule!. See missing_atomtypes.png' ' for more detail.' ) #rdkit.Chem.AllChem.Compute2DCoords(molecule) #img = rdkit.Chem.Draw.MolToImage( # molecule, # size=(1000, 1000), # highlightAtoms=untyped, # highlightColor=(0, 1, 0) #) #img.save('missing_atomtypes.png') #if self.have_tk: # root = tk.Tk() # root.title('Atom types') # tkPI = ImageTk.PhotoImage(img) # tkLabel = tk.Label(root, image=tkPI) # tkLabel.place(x=0, y=0, width=img.size[0], height=img.size[1]) # root.geometry('%dx%d' % (img.size)) # root.mainloop() else: logger.info('The molecule was successfully atom-typed') logger.info('Atom types: ' + ', '.join(atomtypes)) key = f'atomtypes_{self.selected_forcefield}' if key not in configuration.atoms: configuration.atoms.add_attribute(key, coltype='str') configuration.atoms[key] = atomtypes # Now get the charges if forcefield has them. terms = self.forcefield.data['forcefield'][self.selected_forcefield]['parameters'] if 'bond_increments' in terms: logger.debug('Getting the charges for the system') neighbors = configuration.bonded_neighbors(as_indices=True) charges = [] total_q = 0.0 for i in range(configuration.n_atoms): itype = atomtypes[i] parameters = self.forcefield.charges(itype)[3] q = float(parameters['Q']) for j in neighbors[i]: jtype = atomtypes[j] parameters = self.forcefield.bond_increments(itype, jtype)[3] q += float(parameters['deltaij']) charges.append(q) total_q += q if abs(total_q) > 0.0001: logger.warning('Total charge is not zero: {}'.format(total_q)) logger.info( 'Charges from increments and charges:\n' + pprint.pformat(charges) ) else: logger.debug( 'Charges from increments:\n' + pprint.pformat(charges) ) key = f'charges_{self.selected_forcefield}' if key not in configuration.atoms: configuration.atoms.add_attribute(key, coltype='float') charge_column = configuration.atoms.get_column(key) charge_column[0:] = charges logger.debug(f"Set column '{key}' to the charges") printer.important( __( "Assigned atom types and charges to " f"{configuration.n_atoms} atoms.", ) ) else: printer.important( __( f"Assigned atom types to {configuration.n_atoms} " "atoms.", ) )