def add_random(self, random_probability=0.3): """ Add one random structure to the population """ structure = Structure() if self.composition is None: raise ValueError('No composition associated to this population') comp = self.composition.composition.copy() rnd = random.random() natom_limit = self.max_comp_mult * self.composition.natom / self.composition.gcd condition = { 'structure.nspecies': self.composition.nspecies, 'structure.natom': { '$lte': natom_limit } } if self.pcdb_source is None or self.pcdb_source.entries.find( condition).count() <= len(self.source_blacklist): rnd = 0 origin = None if self.pcdb_source is None or rnd < random_probability or self.composition.nspecies > 1: pcm_log.debug('Random Structure') factor = np.random.randint(self.min_comp_mult, self.max_comp_mult + 1) for i in comp: comp[i] *= factor structure = Structure.random_cell(comp, method='stretching', stabilization_number=5, nparal=5, periodic=True) else: pcm_log.debug('From source') while True: entry = None condition['properties.spacegroup'] = random.randint(1, 230) print('Trying', condition['properties.spacegroup']) for ientry in self.pcdb_source.entries.find(condition): if ientry['_id'] not in self.source_blacklist: entry = ientry break if entry is not None: origin = entry['_id'] structure = self.pcdb_source.get_structure(entry['_id']) factor = covalent_radius( self.composition.species[0]) / covalent_radius( structure.species[0]) print('From source: %s Spacegroup: %d Scaling: %7.3f' % (structure.formula, entry['properties']['spacegroup'], factor)) structure.set_cell( np.dot(factor * np.eye(3), structure.cell)) structure.symbols = structure.natom * self.composition.species self.source_blacklist.append(entry['_id']) break return self.new_entry(structure), origin
def scale(self, symbols, rpos, tolerance=1.0): lattice = self.copy() factor = 1.0 for i in range(len(rpos)): # This is to separate each atom from its own image covalent_dim = 2.0 * tolerance * covalent_radius(symbols[i]) this_factor = covalent_dim / lattice.a if this_factor > factor: factor = this_factor this_factor = covalent_dim / lattice.b if this_factor > factor: factor = this_factor this_factor = covalent_dim / lattice.c if this_factor > factor: factor = this_factor for j in range(i + 1, len(rpos)): distance = lattice.minimal_distance(rpos[i], rpos[j]) covalent_dim = tolerance * sum(covalent_radius([symbols[i], symbols[j]])) this_factor = covalent_dim / distance if this_factor > factor: factor = this_factor a = lattice.a b = lattice.b c = lattice.c alpha = lattice.alpha beta = lattice.beta gamma = lattice.gamma return Lattice().from_parameters_to_cell(factor * a, factor * b, factor * c, alpha, beta, gamma)
def get_surface_atoms_new(structure, use_covalent_radius=False): dln = scipy.spatial.Delaunay(structure.positions) if use_covalent_radius: simplices = [] for j in dln.simplices: discard = False for ifacet in list(itertools.combinations(j, 3)): for ipair in itertools.combinations(ifacet, 2): distance = np.linalg.norm(structure.positions[ipair[0]] - structure.positions[ipair[1]]) cov_distance = covalent_radius(structure.symbols[ipair[0]]) + covalent_radius( structure.symbols[ipair[1]]) if distance > 3.0*cov_distance: print('Distance: %f Cov-distance: %f' % (distance, cov_distance)) discard = True break if not discard: print(j) simplices.append(j) else: simplices = dln.simplices c = np.array([[sorted(list(y)) for y in (itertools.combinations(x, 3))] for x in simplices]) d = [list(x) for x in c.reshape((-1, 3))] ret = [] dups = [] for i in range(len(d) - 1): if d[i] in d[i+1:]: dups.append(d[i]) for i in d: if i not in dups: ret.append(i) return np.unique(np.array(ret).flatten())
def get_surface_atoms_new(structure, use_covalent_radius=False): dln = scipy.spatial.Delaunay(structure.positions) if use_covalent_radius: simplices = [] for j in dln.simplices: discard = False for ifacet in list(itertools.combinations(j, 3)): for ipair in itertools.combinations(ifacet, 2): distance = np.linalg.norm(structure.positions[ipair[0]] - structure.positions[ipair[1]]) cov_distance = covalent_radius(structure.symbols[ipair[0]]) + covalent_radius( structure.symbols[ipair[1]]) if distance > 3.0*cov_distance: print('Distance: %f Cov-distance: %f' % (distance, cov_distance)) discard = True break if not discard: print(j) simplices.append(j) else: simplices=dln.simplices c = np.array([[sorted(list(y)) for y in (itertools.combinations(x, 3))] for x in simplices]) d = [list(x) for x in c.reshape((-1, 3))] ret = [] dups = [] for i in range(len(d) - 1): if d[i] in d[i+1:]: dups.append(d[i]) for i in d: if i not in dups: ret.append(i) return np.unique(np.array(ret).flatten())
def add_random(self, random_probability=0.3): """ Add one random structure to the population """ entry_id = None structure = Structure() if self.composition is None: raise ValueError('No composition associated to this population') factor = np.random.randint(self.min_comp_mult, self.max_comp_mult + 1) comp = self.composition.composition.copy() #print("Initial composition: %s" % comp) #print(Composition(comp)) #print(Composition(comp).symbols) for i in comp: comp[i] *= factor new_comp = Composition(comp) while True: rnd = random.random() condition = { 'structure.nspecies': new_comp.nspecies, 'structure.natom': new_comp.natom } if self.pcdb_source is None: rnd = 0 elif len(self.sources[factor]) == 0: rnd = 0 if self.pcdb_source is None or rnd < random_probability: pcm_log.debug('Random Structure') structure = Structure.random_cell(new_comp, method='stretching', stabilization_number=5, nparal=5, periodic=True) break else: pcm_log.debug('From source') entry_id = self.sources[factor][np.random.randint( 0, len(self.sources[factor]))] structure = self.pcdb_source.get_structure(entry_id) print("chosen structure from database =", structure) sym = CrystalSymmetry(structure) scale_factor = float( np.max(covalent_radius(new_comp.species)) / np.max(covalent_radius(structure.species))) reduce_scale = scale_factor**(1. / 3) # WIH msg = 'Mult: %d natom: %d From source: %s Spacegroup: %d Scaling: %7.3f' print(msg % (factor, structure.natom, structure.formula, sym.number(), scale_factor)) # structure.set_cell(np.dot(scale_factor * np.eye(3), structure.cell)) # WIH structure.set_cell( np.dot(reduce_scale * np.eye(3), structure.cell)) # WIH print("symbols before change = ", structure.symbols) structure.symbols = new_comp.symbols print("symbols after change = ", structure.symbols) self.sources[factor].remove(entry_id) break return self.new_entry(structure), entry_id
def random_structure(method, composition, periodic=True, best_volume=1E10): comp = Composition(composition) natom = comp.natom symbols = comp.symbols np.random.seed(struct.unpack("<L", os.urandom(4))[0]) if periodic: new_structure = None assert (method in ['scaling', 'stretching']) if method == 'scaling': lattice = Lattice.random_cell(comp) # Random reduced positions rpos = np.random.rand(natom, 3) mins = [min(rpos[:, i]) for i in range(3)] rpos -= mins else: lattice = Lattice.random_cell(comp) # Random reduced positions rpos = np.random.rand(natom, 3) mins = [min(rpos[:, i]) for i in range(3)] rpos -= mins new_lattice = lattice.stretch(symbols, rpos, tolerance=1.0, extra=0.1) if new_lattice.volume < best_volume: test = True for i in range(natom): for j in range(i + 1, natom): distance = new_lattice.minimal_distance(rpos[i], rpos[j]) covalent_dim = sum(covalent_radius([symbols[i], symbols[j]])) if distance < covalent_dim: test = False if test: new_structure = Structure(symbols=symbols, reduced=rpos, cell=new_lattice.cell, periodicity=True) minimal_distance = np.min(new_structure.distance_matrix() + 10 * np.eye(new_structure.natom)) # print(minimal_distance) else: new_structure = None else: pos = np.random.rand(natom, 3) mindis = cluster_minimal_distance(pos) if mindis == 0: raise ValueError("Distance too small") max_cov = np.max(covalent_radius(symbols)) pos *= max_cov / mindis current_volume = (max(pos[:, 0]) - min(pos[:, 0])) * (max(pos[:, 1]) - min(pos[:, 1])) * ( max(pos[:, 2]) - min(pos[:, 2])) if current_volume < best_volume: new_structure = Structure(symbols=symbols, positions=pos, periodicity=False) else: new_structure = None return new_structure
def hardness_XX(self, initial_cutoff_radius=0.8, use_laplacian=True): bonds, coordination, cutoff_radius = self.bonds_coordination( initial_cutoff_radius=initial_cutoff_radius, use_laplacian=use_laplacian) sigma = 3.0 c_hard = 1300.0 xprod = 1. tot = 0.0 f_d = 0.0 f_n = 1.0 atomicnumbers = atomic_number(self.structure.species) pcm_log.debug('Atomic numbers in the structure : %s' % str(atomicnumbers)) for i in atomicnumbers: f_d += valence(i) / covalent_radius(i) f_n *= valence(i) / covalent_radius(i) if f_d == 0: pcm_log.debug('Returning zero as hardness. f_d= %10.3f' % f_d) return 0.0 f = 1.0 - (self.structure.nspecies * f_n**(1.0 / self.structure.nspecies) / f_d)**2 diff_bonds = [x for x in bonds if len(bonds[x]) > 0] for pair in diff_bonds: i1 = pair[0] i2 = pair[1] ei = valence(self.structure.symbols[i1]) / covalent_radius( self.structure.symbols[i1]) ej = valence(self.structure.symbols[i2]) / covalent_radius( self.structure.symbols[i2]) for dij in bonds[pair]: sij = math.sqrt( ei * ej) / (coordination[i1] * coordination[i2]) / dij xprod *= sij num_i_j_bonds = len(bonds[pair]) pcm_log.debug('Number of bonds for pair %s = %d' % (str(pair), num_i_j_bonds)) tot += num_i_j_bonds vol = self.structure.volume pcm_log.debug("Structure volume: %7.3f" % vol) pcm_log.debug("Total number of bonds: %d" % tot) pcm_log.debug("Bonds: %s" % str(bonds)) hardness_value = (c_hard / vol) * tot * (xprod**(1. / tot)) * math.exp( -sigma * f) return round(hardness_value, 3), cutoff_radius, coordination
def hardness_XX(self, initial_cutoff_radius=0.8, use_laplacian=True): """ Implementation of Hardness algorithm: First-principles structural design of superhard materials J. Chem. Phys. 138, 114101 (2013); https://doi.org/10.1063/1.4794424 Xinxin Zhang, et al. """ bonds, coordination, cutoff_radius = self.bonds_coordination(initial_cutoff_radius=initial_cutoff_radius, use_laplacian=use_laplacian) sigma = 3.0 c_hard = 1300.0 xprod = 1. tot = 0.0 f_d = 0.0 f_n = 1.0 atomicnumbers = atomic_number(self.structure.species) pcm_log.debug('Atomic numbers in the structure : %s' % str(atomicnumbers)) for i in atomicnumbers: f_d += valence(i) / covalent_radius(i) f_n *= valence(i) / covalent_radius(i) if f_d == 0: pcm_log.debug('Returning zero as hardness. f_d= %10.3f' % f_d) return 0.0 f = 1.0 - (self.structure.nspecies * f_n ** (1.0 / self.structure.nspecies) / f_d) ** 2 diff_bonds = [x for x in bonds if len(bonds[x]) > 0] for pair in diff_bonds: i1 = pair[0] i2 = pair[1] ei = valence(self.structure.symbols[i1]) / covalent_radius(self.structure.symbols[i1]) ej = valence(self.structure.symbols[i2]) / covalent_radius(self.structure.symbols[i2]) for dij in bonds[pair]: sij = math.sqrt(ei * ej) / (coordination[i1] * coordination[i2]) / dij xprod *= sij num_i_j_bonds = len(bonds[pair]) pcm_log.debug('Number of bonds for pair %s = %d' % (str(pair), num_i_j_bonds)) tot += num_i_j_bonds vol = self.structure.volume pcm_log.debug("Structure volume: %7.3f" % vol) pcm_log.debug("Total number of bonds: %d" % tot) pcm_log.debug("Bonds: %s" % str(bonds)) hardness_value = (c_hard / vol) * tot * (xprod ** (1. / tot)) * math.exp(-sigma * f) return round(hardness_value, 3), cutoff_radius, coordination
def add_random(self, random_probability=0.3): """ Add one random structure to the population """ entry_id = None structure = Structure() if self.composition is None: raise ValueError('No composition associated to this population') factor = np.random.randint(self.min_comp_mult, self.max_comp_mult + 1) comp = self.composition.composition.copy() # print("Initial composition: %s" % comp) # print(Composition(comp)) # print(Composition(comp).symbols) for i in comp: comp[i] *= factor new_comp = Composition(comp) while True: rnd = random.random() condition = {'structure.nspecies': new_comp.nspecies, 'structure.natom': new_comp.natom} if self.pcdb_source is None: rnd = 0 elif len(self.sources[factor]) == 0: rnd = 0 if self.pcdb_source is None or rnd < random_probability: pcm_log.debug('Random Structure') structure = Structure.random_cell(new_comp, method='stretching', stabilization_number=5, nparal=5, periodic=True) break else: pcm_log.debug('From source') entry_id = self.sources[factor][np.random.randint(0, len(self.sources[factor]))] structure = self.pcdb_source.get_structure(entry_id) print("chosen structure from database =", structure) sym = CrystalSymmetry(structure) scale_factor = float(np.max(covalent_radius(new_comp.species)) / np.max(covalent_radius(structure.species))) reduce_scale = scale_factor ** (1. / 3) # WIH msg = 'Mult: %d natom: %d From source: %s Spacegroup: %d Scaling: %7.3f' print(msg % (factor, structure.natom, structure.formula, sym.number(), scale_factor)) # structure.set_cell(np.dot(scale_factor * np.eye(3), structure.cell)) # WIH structure.set_cell(np.dot(reduce_scale * np.eye(3), structure.cell)) # WIH print("symbols before change = ", structure.symbols) structure.symbols = new_comp.symbols print("symbols after change = ", structure.symbols) self.sources[factor].remove(entry_id) break return self.new_entry(structure), entry_id
def hardness_XX(self, initial_cutoff_radius=0.8, use_laplacian=True): bonds, coordination, cutoff_radius = self.bonds_coordination(initial_cutoff_radius=initial_cutoff_radius, use_laplacian=use_laplacian, verbose=True) sigma = 3.0 c_hard = 1300.0 xprod = 1. tot = 0.0 f_d = 0.0 f_n = 1.0 atomicnumbers = atomic_number(self.structure.species) pcm_log.debug('Atomic numbers in the structure : %s' % str(atomicnumbers)) for i in atomicnumbers: f_d += valence(i) / covalent_radius(i) f_n *= valence(i) / covalent_radius(i) if f_d == 0: pcm_log.debug('Returning zero as hardness. f_d= %10.3f' % f_d) return 0.0 f = 1.0 - (self.structure.nspecies * f_n ** (1.0 / self.structure.nspecies) / f_d) ** 2 diff_bonds = [x for x in bonds if len(bonds[x]) > 0] for pair in diff_bonds: i1 = pair[0] i2 = pair[1] ei = valence(self.structure.symbols[i1]) / covalent_radius(self.structure.symbols[i1]) ej = valence(self.structure.symbols[i2]) / covalent_radius(self.structure.symbols[i2]) for dij in bonds[pair]: sij = math.sqrt(ei * ej) / (coordination[i1] * coordination[i2]) / dij xprod *= sij num_i_j_bonds = len(bonds[pair]) pcm_log.debug('Number of bonds for pair %s = %d' % (str(pair), num_i_j_bonds)) tot += num_i_j_bonds vol = self.structure.volume pcm_log.debug("Structure volume: %7.3f" % vol) pcm_log.debug("Total number of bonds: %d" % tot) pcm_log.debug("Bonds: %s" % str(bonds)) hardness_value = (c_hard / vol) * tot * (xprod ** (1. / tot)) * math.exp(-sigma * f) return round(hardness_value, 3), cutoff_radius, coordination
def add_random(self, random_probability=0.3): """ Add one random structure to the population """ structure = Structure() if self.composition is None: raise ValueError('No composition associated to this population') comp = self.composition.composition.copy() rnd = random.random() natom_limit = self.max_comp_mult * self.composition.natom / self.composition.gcd condition = {'structure.nspecies': self.composition.nspecies, 'structure.natom': {'$lte': natom_limit}} if self.pcdb_source is None or self.pcdb_source.entries.find(condition).count() <= len(self.source_blacklist): rnd = 0 origin = None if self.pcdb_source is None or rnd < random_probability or self.composition.nspecies > 1: pcm_log.debug('Random Structure') factor = np.random.randint(self.min_comp_mult, self.max_comp_mult + 1) for i in comp: comp[i] *= factor structure = Structure.random_cell(comp, method='stretching', stabilization_number=5, nparal=5, periodic=True) else: pcm_log.debug('From source') while True: entry = None condition['properties.spacegroup'] = random.randint(1, 230) print('Trying', condition['properties.spacegroup']) for ientry in self.pcdb_source.entries.find(condition): if ientry['_id'] not in self.source_blacklist: entry = ientry break if entry is not None: origin = entry['_id'] structure = self.pcdb_source.get_structure(entry['_id']) factor = covalent_radius(self.composition.species[0]) / covalent_radius(structure.species[0]) print('From source: %s Spacegroup: %d Scaling: %7.3f' % (structure.formula, entry['properties']['spacegroup'], factor)) structure.set_cell(np.dot(factor * np.eye(3), structure.cell)) structure.symbols = structure.natom * self.composition.species self.source_blacklist.append(entry['_id']) break return self.new_entry(structure), origin
def covalent_volume(self, packing='cubes'): """ Returns the volume occupied by a given formula assuming a 'cubes' packing or 'spheres' packing :param packing: (str) The kind of packing could be 'cubes' or 'spheres' :rtype : (float) >>> import pychemia >>> comp=pychemia.Composition('C5H10') >>> comp.covalent_volume() 19.942320000000002 >>> comp.covalent_volume(packing='spheres') 10.441774334589468 """ if packing == 'cubes': factor = 8 elif packing == 'spheres': factor = 4 * pi / 3.0 else: raise ValueError('Non-valid packing value ', packing) # find volume of unit cell by adding cubes volume = 0.0 for specie in self: number_atoms_specie = self.composition[specie] # Pack each atom in a cube (2*r)^3 volume += factor * number_atoms_specie * covalent_radius(specie)**3 return volume
def covalent_volume(self, packing='cubes'): """ Returns the volume occupied by a given formula assuming a 'cubes' packing or 'spheres' packing :param packing: (str) The kind of packing could be 'cubes' or 'spheres' :rtype : (float) >>> import pychemia >>> comp=pychemia.Composition('C5H10') >>> comp.covalent_volume() 19.942320000000002 >>> comp.covalent_volume(packing='spheres') 10.441774334589468 """ if packing == 'cubes': factor = 8 elif packing == 'spheres': factor = 4 * pi / 3.0 else: raise ValueError('Non-valid packing value ', packing) # find volume of unit cell by adding cubes volume = 0.0 for specie in self: number_atoms_specie = self.composition[specie] # Pack each atom in a cube (2*r)^3 volume += factor * number_atoms_specie * covalent_radius(specie) ** 3 return volume
def get_bonds_coordination(self, tolerance=1, ensure_conectivity=False): bonds_dict, all_distances = self.get_all_distances() bonds = [] tolerances=[] for i in range(self.natom): tole = tolerance while True: tmp_bonds = [] min_proportion = sys.float_info.max for j in bonds_dict[str(i)]: atom1 = self.symbols[all_distances[j]['pair'][0]] atom2 = self.symbols[all_distances[j]['pair'][1]] sum_covalent_radius = sum(covalent_radius([atom1, atom2])) distance = all_distances[j]['distance'] if distance == 0.0: continue proportion = distance/sum_covalent_radius min_proportion = min(min_proportion, proportion) if proportion <= tole: #print all_distances[j] tmp_bonds.append(j) if len(tmp_bonds) == 0 and ensure_conectivity: #print 'Changing tolerance' tole = min_proportion else: bonds.append(tmp_bonds) tolerances.append(min_proportion) break coordination = [len(x) for x in bonds] return bonds, coordination, all_distances, tolerances
def compute_bonds(typat, xcart, znucl): """ Compute the bond lengths of all the atoms inside the unitary box (NEEDS EXTENSION TO OUTSIDE THE BOX) :param typat: (int, list) Type of atoms :param xcart: (numpy.ndarray) Cartesian positions :param znucl: (int, list) Atomic number for atoms in typat :return: """ npxcart = np.array(xcart).reshape((-1, 3)) if isinstance(typat, int): lsttypat = [typat] else: lsttypat = typat if isinstance(znucl, (int, float)): lstznucl = [znucl] else: lstznucl = znucl covrad = [covalent_radius(lstznucl[iatom - 1]) for iatom in lsttypat] bonds = [] for iatom in range(len(npxcart)): for jatom in range(iatom + 1, len(npxcart)): # Compute bond length between atoms i and j bl = math.sqrt(sum((npxcart[jatom] - npxcart[iatom])**2)) if 1.35 * (covrad[iatom] + covrad[jatom]) > bl: bonds.append( [iatom, jatom, bl, (npxcart[jatom] - npxcart[iatom]) / bl]) return bonds
def create_pov(self): ret = """ #version 3.7; #include "colors.inc" // The include files contain #include "stones.inc" // pre-defined scene elements #include "glass.inc" background{rgb 0} """ if self.structure.is_crystal: self.distance = max(self.structure.lattice.lengths) else: self.distance = 10 ret += "#declare r=%7.3f;\n #declare s=%7.3f;" % (self.distance, self.distance) ret += "camera {\n" ret += "\tlocation <%7.3f, %7.3f, %7.3f>\n" % (1.3 * self.distance, 1.3 * self.distance, -1.3 * self.distance) ret += "\tlook_at <%7.3f, %7.3f, %7.3f>\n" % tuple(0.5 * sum(self.structure.cell[:])) ret += "}\n\n" if self.structure.nsites > 0: d = self.distance ret += "light_source { <%7.3f, %7.3f, %7.3f> color White}\n" % (2 * d, 2 * d, 2 * d) for imagx in np.arange(-1, 2): for imagy in np.arange(-1, 2): for imagz in np.arange(-1, 2): for site in self.structure: for symbol in site.symbols: cell = self.structure.cell x = site.position[0] - imagx * cell[0, 0] - imagy * cell[1, 0] - imagz * cell[2, 0] y = site.position[1] - imagx * cell[0, 1] - imagy * cell[1, 1] - imagz * cell[2, 1] z = site.position[2] - imagx * cell[0, 2] - imagy * cell[1, 2] - imagz * cell[2, 2] if (x - self.distance) ** 2 + (y - self.distance) ** 2 + (z + self.distance) ** 2 < 2: continue cr = 0.5 * covalent_radius(symbol) rgb = cpk_colors[atomic_number(symbol)] color = 'rgb < %7.3f, %7.3f, %7.3f>' % (rgb[0], rgb[1], rgb[2]) ret += "sphere {\n" ret += "\t<%7.3f, %7.3f, %7.3f>, %7.3f\n\ttexture {\n" % (x, y, z, cr) ret += "\t\tpigment { color %s filter 0.4 transmit %7.3f}\n" % \ (color, 1 - 0.9 * np.exp(-0.1 * (abs(imagx) + abs(imagy) + abs(imagz)))) ret += "\t\tnormal { bumps 0.8 scale 0.1 }\n\t\tfinish { phong %7.3f }\n\t}\n}\n\n" % \ np.exp(-0.1 * (abs(imagx) + abs(imagy) + abs(imagz))) if self.structure.nsites <= 0: ret += "light_source { <%7.3f, %7.3f, %7.3f> color White}\n" % (x, y, z) ret += """union{ #include "cell.pov" scale 1 rotate <0, 0, 0> pigment{rgb <0.3,0.3,0.9>} finish{phong 0.9 ambient 0.42 reflection 0.1} } """ return ret
def plot(self, figname=None, size=(300, 325), view=(30, 30), color=(1.0, 1.0, 1.0)): fig = mlab.figure(size=size) figure = mlab.gcf() fig.scene.disable_render = True figure.scene.background = (0.0, 0.0, 0.0) mlab.view(0, 90, distance=0.2) assert (self.structure.natom > 0) x = self.structure.positions[:, 0] y = self.structure.positions[:, 1] z = self.structure.positions[:, 2] cr = covalent_radius(self.structure.symbols) s = np.apply_along_axis(np.linalg.norm, 1, self.structure.positions) mlab.points3d(x, y, z, s, scale_factor=1.0, resolution=8, opacity=1.0, color=color, scale_mode='none') if self.structure.is_crystal: frame, line1, line2, line3 = self.structure.get_cell().get_path() mlab.plot3d(frame[:, 0], frame[:, 1], frame[:, 2], tube_radius=.02, color=(1, 1, 1)) mlab.plot3d(line1[:, 0], line1[:, 1], line1[:, 2], tube_radius=.02, color=(1, 1, 1)) mlab.plot3d(line2[:, 0], line2[:, 1], line2[:, 2], tube_radius=.02, color=(1, 1, 1)) mlab.plot3d(line3[:, 0], line3[:, 1], line3[:, 2], tube_radius=.02, color=(1, 1, 1)) else: for i in range(self.structure.natom - 1): for j in range(i + 1, self.structure.natom): vector = self.structure.positions[i] - self.structure.positions[j] mvector = np.linalg.norm(vector) uvector = 1.0 / mvector * vector if 2 * mvector < covalent_radius(self.structure.symbols[i]) + \ covalent_radius(self.structure.symbols[j]): pair = np.concatenate( (self.structure.positions[i] - 0.1 * uvector, self.structure.positions[j] + 0.1 * uvector)).reshape((-1, 3)) mlab.plot3d(pair[:, 0], pair[:, 1], pair[:, 2], tube_radius=0.15, opacity=1.0, color=(1, 1, 1)) mlab.view(distance=12.0) fig.scene.disable_render = False if figname is not None: mlab.savefig(figname) return figure
def move(self, entry_id, entry_jd, factor=0.2, in_place=False): st_orig = self.get_structure(entry_id) st_dest = self.get_structure(entry_jd) cm = pychemia.analysis.ClusterMatch(st_orig, st_dest) cm.match() # pos_orig = np.array(entry_orig['structure']['positions']).reshape((-1, 3)) # pos_dest = np.array(entry_dest['structure']['positions']).reshape((-1, 3)) pos_orig = cm.structure1.positions pos_dest = cm.structure2.positions # Move to a position with negative energy reduc = 1 new_positions = np.array(pos_orig) while True: new_positions = rotation_move(pos_orig, pos_dest, fraction=reduc * factor) new_structure = pychemia.Structure(positions=new_positions, symbols=st_orig.symbols, periodicity=False) lj = pychemia.code.LennardJones(new_structure) if lj.get_energy() < 0.0: print('Effective factor reduced to %7.3f, original factor %7.3f' % (reduc * factor, factor)) break reduc -= 0.05 if reduc <= 0.0: # print 'No movement effective' break # Avoid condition with atoms too close distance_matrix = scipy.spatial.distance_matrix(new_positions, new_positions) tmp = np.max(distance_matrix.flatten()) # print 'Scaling by', tmp minimal_distance = np.min((distance_matrix + tmp * np.eye(len(new_positions))).flatten()) if minimal_distance < 1E-8: print("Null distance between different atoms, no moving") new_positions = pos_orig if tmp > 5: # print 'Big scaling, better not to move' new_positions = pos_orig else: max_cov = np.max(covalent_radius(st_orig.symbols)) new_positions *= max_cov / minimal_distance new_structure = pychemia.Structure(positions=new_positions, symbols=st_orig.symbols, periodicity=False) # print 'Density of cluster', new_structure.density if in_place: return self.pcdb.update(entry_id, structure=new_structure, properties={}) else: return self.new_entry(new_structure, active=False)
def view_projections(self): """ Show the 3 projections of the molecule in a single figure """ import matplotlib.patches as mpatches from matplotlib.collections import PatchCollection from matplotlib.pylab import subplots fig, ax = subplots(nrows=1, ncols=3) fig.set_size_inches(15, 4) color = ['r', 'g', 'b'] j = 0 structure = self.get_structure() for i in structure.cell: ax[0].plot([0, i[0]], [0, i[1]], color[j] + '-', lw=3) ax[1].plot([0, i[1]], [0, i[2]], color[j] + '-', lw=3) ax[2].plot([0, i[2]], [0, i[0]], color[j] + '-', lw=3) j += 1 proj = [[0, 1], [1, 2], [2, 0]] labels = [['x', 'y'], ['y', 'z'], ['z', 'x']] for j in range(3): patches = [] for i in range(structure.natom): radius = 0.5 * covalent_radius( atomic_number(structure.symbols[i])) pos = structure.positions[i] art = mpatches.Circle((pos[proj[j][0]], pos[proj[j][1]]), radius, fc='g', ec='g') patches.append(art) collection = PatchCollection(patches, color='k', alpha=0.5) col = ax[j].add_collection(collection) ax[j].set_xlim( min(structure.positions[:, proj[j][0]]) - 1, max(structure.positions[:, proj[j][0]]) + 1) ax[j].set_ylim( min(structure.positions[:, proj[j][1]]) - 1, max(structure.positions[:, proj[j][1]]) + 1) ax[j].set_aspect('equal', adjustable='datalim') ax[j].set_xlabel(labels[j][0]) ax[j].set_ylabel(labels[j][1]) return fig, ax
def bonds_coordination(self, initial_cutoff_radius=0.8, use_laplacian=True, jump=0.01, tol=1E-15): cutoff_radius = initial_cutoff_radius ad = self.all_distances() bonds = {} while True: laplacian = np.zeros((self.structure.natom, self.structure.natom), dtype=np.int8) for pair in ad: atom1 = self.structure.symbols[pair[0]] atom2 = self.structure.symbols[pair[1]] sum_covalent_radius = sum(covalent_radius([atom1, atom2])) condition = np.bitwise_and( ad[pair]['distance'] < cutoff_radius * sum_covalent_radius, ad[pair]['distance'] > 0) bonds[pair] = ad[pair]['distance'][condition] if len(bonds[pair]) > 0: laplacian[pair[0], pair[1]] = -1 laplacian[pair[1], pair[0]] = -1 for i in range(self.structure.natom): laplacian[i, i] = 0 laplacian[i, i] = -sum(laplacian[i]) if use_laplacian: if np.max(np.abs(laplacian)) == 0: cutoff_radius += jump ev = numpy.linalg.eigvalsh(laplacian) if sum(ev < tol) > 1: cutoff_radius += jump else: break else: break coordination = np.zeros(self.structure.natom, dtype=int) for pair in bonds: coordination[pair[0]] += len(bonds[pair]) coordination[pair[1]] += len(bonds[pair]) return bonds, coordination, round(cutoff_radius, 3)
def compute_bonds(typat, xcart, znucl): """ Compute the bond lengths of all the atoms inside the unitary box (NEEDS EXTENSION TO OUTSIDE THE BOX) """ covrad = [covalent_radius(znucl[iatom - 1]) for iatom in typat] bonds = [] for iatom in range(len(xcart)): for jatom in range(iatom + 1, len(xcart)): # Compute bond length between atoms i and j bl = math.sqrt(sum((xcart[jatom] - xcart[iatom]) ** 2)) if 1.35 * (covrad[iatom] + covrad[jatom]) > bl: bonds.append([iatom, jatom, bl, (xcart[jatom] - xcart[iatom]) / bl]) return bonds
def view_projections(self): """ Show the 3 projections of the molecule in a single figure """ import matplotlib.patches as mpatches from matplotlib.collections import PatchCollection from matplotlib.pylab import subplots fig, ax = subplots(nrows=1, ncols=3) fig.set_size_inches(15, 4) color = ['r', 'g', 'b'] j = 0 structure = self.get_structure() for i in structure.cell: ax[0].plot([0, i[0]], [0, i[1]], color[j] + '-', lw=3) ax[1].plot([0, i[1]], [0, i[2]], color[j] + '-', lw=3) ax[2].plot([0, i[2]], [0, i[0]], color[j] + '-', lw=3) j += 1 proj = [[0, 1], [1, 2], [2, 0]] labels = [['x', 'y'], ['y', 'z'], ['z', 'x']] for j in range(3): patches = [] for i in range(structure.natom): radius = 0.5 * covalent_radius(atomic_number(structure.symbols[i])) pos = structure.positions[i] art = mpatches.Circle((pos[proj[j][0]], pos[proj[j][1]]), radius, fc='g', ec='g') patches.append(art) collection = PatchCollection(patches, color='k', alpha=0.5) col = ax[j].add_collection(collection) ax[j].set_xlim(min(structure.positions[:, proj[j][0]]) - 1, max(structure.positions[:, proj[j][0]]) + 1) ax[j].set_ylim(min(structure.positions[:, proj[j][1]]) - 1, max(structure.positions[:, proj[j][1]]) + 1) ax[j].set_aspect('equal', adjustable='datalim') ax[j].set_xlabel(labels[j][0]) ax[j].set_ylabel(labels[j][1]) return fig, ax
def stretch(self, symbols, rpos, tolerance=1.0, extra=0.1): # Dummy array that always will be overwritten eigv = np.array([1, 0, 0]) lattice = self.copy() assert len(rpos) == len(symbols) natom = len(rpos) for i, j in combinations(range(natom), 2): ret = lattice.distance2(rpos[i], rpos[j]) mindist = sys.float_info.max for k in ret: if 0 < ret[k]['distance'] < mindist: mindist = ret[k]['distance'] eigv = ret[k]['image'] covalent_distance = sum(covalent_radius([symbols[i], symbols[j]])) if mindist < tolerance * covalent_distance: factor = (tolerance + extra) * covalent_distance / mindist v1, v2, v3 = vector_set_perpendicular(eigv) matrix_a = matrix_from_eig(v1, v2, v3, factor, 1, 1) lattice = Lattice(np.dot(matrix_a, lattice.cell)) return lattice
def plot(self): from mayavi import mlab assert(self.natom > 0) x = self.positions[:, 0] y = self.positions[:, 1] z = self.positions[:, 2] cr = covalent_radius(self.symbols) mlab.points3d(x, y, z, cr, scale_factor=1) if self.is_crystal: frame, line1, line2, line3 = self.get_cell().get_path() mlab.plot3d(frame[:, 0], frame[:, 1], frame[:, 2], tube_radius=.05, color=(1, 1, 1)) mlab.plot3d(line1[:, 0], line1[:, 1], line1[:, 2], tube_radius=.05, color=(1, 1, 1)) mlab.plot3d(line2[:, 0], line2[:, 1], line2[:, 2], tube_radius=.05, color=(1, 1, 1)) mlab.plot3d(line3[:, 0], line3[:, 1], line3[:, 2], tube_radius=.05, color=(1, 1, 1)) mlab.view() return mlab.gcf()
def bonds_coordination(self, initial_cutoff_radius=0.8, use_laplacian=True, jump=0.01, tol=1E-15): cutoff_radius = initial_cutoff_radius ad = self.all_distances() bonds = {} while True: laplacian = np.zeros((self.structure.natom, self.structure.natom), dtype=np.int8) for pair in ad: atom1 = self.structure.symbols[pair[0]] atom2 = self.structure.symbols[pair[1]] sum_covalent_radius = sum(covalent_radius([atom1, atom2])) condition = np.bitwise_and(ad[pair]['distance'] < cutoff_radius * sum_covalent_radius, ad[pair]['distance'] > 0) bonds[pair] = ad[pair]['distance'][condition] if len(bonds[pair]) > 0: laplacian[pair[0], pair[1]] = -1 laplacian[pair[1], pair[0]] = -1 for i in range(self.structure.natom): laplacian[i, i] = 0 laplacian[i, i] = -sum(laplacian[i]) if use_laplacian: if np.max(np.abs(laplacian)) == 0: cutoff_radius += jump ev = numpy.linalg.eigvalsh(laplacian) if sum(ev < tol) > 1: cutoff_radius += jump else: break else: break coordination = np.zeros(self.structure.natom, dtype=int) for pair in bonds: coordination[pair[0]] += len(bonds[pair]) coordination[pair[1]] += len(bonds[pair]) return bonds, coordination, round(cutoff_radius, 3)
def compute_bonds(typat, xcart, znucl): """ Compute the bond lengths of all the atoms inside the unitary box (NEEDS EXTENSION TO OUTSIDE THE BOX) :param typat: (int, list) Type of atoms :param xcart: (numpy.ndarray) Cartesian positions :param znucl: (int, list) Atomic number for atoms in typat :return: """ if isinstance(typat, int): lsttypat = [typat] else: lsttypat = typat if isinstance(znucl, (int, float)): lstznucl = [znucl] else: lstznucl = znucl covrad = [ angstrom_bohr * covalent_radius(lstznucl[i - 1]) for i in lsttypat ] bonds = [] for t in range(len(xcart)): bonds1 = [] for i in range(len(xcart[t])): for j in range(i + 1, len(xcart[t])): # print xcart[t][j] # print xcart[t][i] # Compute bond length between atoms i and j bl = math.sqrt(sum((xcart[t][j] - xcart[t][i])**2)) # print bl # print i,j # print covrad[i],covrad[j] if 1.35 * (covrad[i] + covrad[j]) > bl: bonds1.append([i, j, bl, (xcart[t][j] - xcart[t][i]) / bl]) bonds.append(bonds1) return bonds
def compute_bonds(typat, xcart, znucl): """ Compute the bond lengths of all the atoms inside the unitary box (NEEDS EXTENSION TO OUTSIDE THE BOX) :param typat: (int, list) Type of atoms :param xcart: (numpy.ndarray) Cartesian positions :param znucl: (int, list) Atomic number for atoms in typat :return: """ print(typat) print(xcart) print(znucl) npxcart = np.array(xcart).reshape((-1, 3)) if isinstance(typat, int): lsttypat = [typat] else: lsttypat = typat if isinstance(znucl, (int, float)): lstznucl = [znucl] else: lstznucl = znucl covrad = [covalent_radius(lstznucl[iatom - 1]) for iatom in lsttypat] bonds = [] for iatom in range(len(npxcart)): for jatom in range(iatom + 1, len(npxcart)): # Compute bond length between atoms i and j bl = math.sqrt(sum((npxcart[jatom] - npxcart[iatom]) ** 2)) if 1.35 * (covrad[iatom] + covrad[jatom]) > bl: bonds.append([iatom, jatom, bl, (npxcart[jatom] - npxcart[iatom]) / bl]) else: print("small distance: %s" % bl) return bonds
def compute_bonds(typat, xcart, znucl): """ Compute the bond lengths of all the atoms inside the unitary box (NEEDS EXTENSION TO OUTSIDE THE BOX) :param typat: (int, list) Type of atoms :param xcart: (numpy.ndarray) Cartesian positions :param znucl: (int, list) Atomic number for atoms in typat :return: """ if isinstance(typat, int): lsttypat = [typat] else: lsttypat = typat if isinstance(znucl, (int, float)): lstznucl = [znucl] else: lstznucl = znucl covrad = [angstrom_bohr * covalent_radius(lstznucl[i - 1]) for i in lsttypat] bonds = [] for t in range(len(xcart)): bonds1 = [] for i in range(len(xcart[t])): for j in range(i + 1, len(xcart[t])): # print xcart[t][j] # print xcart[t][i] # Compute bond length between atoms i and j bl = math.sqrt(sum((xcart[t][j] - xcart[t][i]) ** 2)) # print bl # print i,j # print covrad[i],covrad[j] if 1.35 * (covrad[i] + covrad[j]) > bl: bonds1.append([i, j, bl, (xcart[t][j] - xcart[t][i]) / bl]) bonds.append(bonds1) return bonds
def compute_bonds(typat, xcart, znucl): """ Compute the bond lengths of all the atoms inside the unitary box (NEEDS EXTENSION TO OUTSIDE THE BOX) """ covrad = [angstrom_bohr * covalent_radius(znucl[i - 1]) for i in typat] bonds = [] for t in range(len(xcart)): bonds1 = [] for i in range(len(xcart[t])): for j in range(i + 1, len(xcart[t])): #print xcart[t][j] #print xcart[t][i] # Compute bond length between atoms i and j bl = math.sqrt(sum((xcart[t][j] - xcart[t][i]) ** 2)) #print bl #print i,j #print covrad[i],covrad[j] if 1.35 * (covrad[i] + covrad[j]) > bl: bonds1.append([i, j, bl, (xcart[t][j] - xcart[t][i]) / bl]) bonds.append(bonds1) return bonds
def random_cell(composition, method='stretching', stabilization_number=20, nparal=5, periodic=True): """ Generate a random cell There are two algorithms implemented: scaling: Generate a random cell and random distribution of atoms and scale the lattice to separate the atoms. stretching: Generating a random cell and random distribution of atoms and stretching their bonds until the distance between any two atoms is always greater than the sum of covalent radius. :param composition: (pychemia.Composition) :param method: (str) :param stabilization_number: (int) :param nparal: (int) :param periodic: (bool) :return: Examples: >>> import pychemia >>> import os >>> st = pychemia.Structure.random_cell('LiAlCl4', stabilization_number=3) >>> st.natom 6 >>> st.save_json('test.json') >>> st2 = pychemia.Structure.load_json('test.json') >>> st == st2 True >>> os.remove('test.json') """ comp = Composition(composition) pcm_log.debug('Generating a random structure with composition: ' + str(comp.composition)) natom = comp.natom symbols = comp.symbols best_volume = sys.float_info.max best_volume = float('inf') best_structure = None optimal_volume = comp.covalent_volume('cubes') stabilization_history = 0 pool = Pool(processes=nparal) trial = 0 while stabilization_history < stabilization_number: args = list(best_volume * np.ones(10)) ret = pool.map(worker_star, zip(repeat(method), repeat(composition), repeat(periodic), args)) ngood = 0 for structure in ret: if structure is not None: ngood += 1 if best_structure is not None: if structure.volume < best_structure.volume: best_structure = structure else: best_structure = structure # log.debug('Good structures: %d/10 Best volume: %7.3f' % (ngood, best_structure.volume)) if best_structure is not None and best_volume > best_structure.volume: best_volume = best_structure.volume stabilization_history = 0 else: stabilization_history += 1 trial += 1 # log.debug('Trial: %4d Volume: %7.2f Optimal Volume: %7.2f Ratio: %5.2f' % # (trial, best_volume, optimal_volume, best_volume/optimal_volume)) pool.close() if best_structure is not None and periodic: # Analysis of the quality for the best structure rpos = best_structure.reduced for i, j in combinations(range(natom), 2): distance = best_structure.lattice.minimal_distance(rpos[i], rpos[j]) covalent_distance = sum(covalent_radius([symbols[i], symbols[j]])) if distance < covalent_distance: pcm_log.debug('Covalent distance: %7.4f Minimal distance: %7.4f Difference: %7.3e' % (covalent_distance, distance, covalent_distance - distance)) best_structure.canonical_form() return best_structure
def move(self, entry_id, entry_jd, factor=0.2, in_place=False): st_orig = self.get_structure(entry_id) st_dest = self.get_structure(entry_jd) cm = ClusterMatch(st_orig, st_dest) cm.match() # pos_orig = np.array(entry_orig['structure']['positions']).reshape((-1, 3)) # pos_dest = np.array(entry_dest['structure']['positions']).reshape((-1, 3)) pos_orig = cm.structure1.positions pos_dest = cm.structure2.positions # Move to a position with negative energy reduc = 1 new_positions = np.array(pos_orig) while True: new_positions = rotation_move(pos_orig, pos_dest, fraction=reduc * factor) new_structure = Structure(positions=new_positions, symbols=st_orig.symbols, periodicity=False) lj = LennardJones(new_structure) if lj.get_energy() < 0.0: break reduc -= 0.05 pcm_log.debug( 'Effective factor will be reduced to %7.3f, original factor %7.3f' % (reduc * factor, factor)) if reduc <= 0.0: # print 'No movement effective' break # Avoid condition with atoms too close distance_matrix = scipy.spatial.distance_matrix( new_positions, new_positions) tmp = np.max(distance_matrix.flatten()) # print 'Scaling by', tmp minimal_distance = np.min( (distance_matrix + tmp * np.eye(len(new_positions))).flatten()) if minimal_distance < 1E-8: pcm_log.debug("Null distance between different atoms, no moving") new_positions = pos_orig if tmp > 5: # print 'Big scaling, better not to move' new_positions = pos_orig else: max_cov = np.max(covalent_radius(st_orig.symbols)) new_positions *= max_cov / minimal_distance new_structure = Structure(positions=new_positions, symbols=st_orig.symbols, periodicity=False) # print 'Density of cluster', new_structure.density if in_place: self.unset_properties(entry_id) return self.set_structure(entry_id, new_structure) else: return self.new_entry(new_structure, active=False)
def hardness_old(self, noupdate=False, verbose=False, tolerance=0.05): """ Calculates the hardness of a structure based in the model of XX We use the covalent radii from pychemia.utils.periodic. If noupdate=False the Laplacian matrix method is not used and rcut is 2*max(cov_radii) :param noupdate: (bool) If True, the Laplacian method is used :param verbose: (bool) To print some debug info :param tolerance: (float) :rtype : (float) """ superc = self.structure.copy() superc.supercell(2, 2, 2) structure_analisys = StructureAnalysis(superc) natom = superc.natom volume = superc.volume max_covalent_radius = max(covalent_radius(superc.symbols)) if verbose: print('Number of atoms', natom) print('Volume ', volume) print('Covalent rad max', max_covalent_radius) rcut, coord, dis_dic = structure_analisys.get_bonds( 2.0 * max_covalent_radius, noupdate, verbose, tolerance) sigma = 3.0 c_hard = 1300.0 x = 1. tot = 0.0 f_d = 0.0 f_n = 1.0 dic_atms = {} for i in superc.symbols: dic_atms[i] = atomic_number(i) for i in dic_atms.keys(): f_d += valence(i) / covalent_radius(i) f_n *= valence(i) / covalent_radius(i) f = 1.0 - (len(dic_atms) * f_n**(1.0 / len(dic_atms)) / f_d)**2 if verbose: print('BONDS') print(dis_dic) print('COORDINATION') print(coord) for i in dis_dic.keys(): i1 = dis_dic[i][2][0] i2 = dis_dic[i][2][1] ei = valence(superc.symbols[i1]) / covalent_radius( superc.symbols[i1]) ej = valence(superc.symbols[i2]) / covalent_radius( superc.symbols[i2]) sij = math.sqrt(ei * ej) / (coord[i1] * coord[i2]) / dis_dic[i][0] tot += dis_dic[i][1] x *= sij * dis_dic[i][1] if verbose: print("V:", volume) print("f:", f) print("x:", x) hardness_value = c_hard / volume * ( len(dis_dic) * x**(1. / (len(dis_dic)))) * math.exp(-sigma * f) if verbose: print(hardness_value) return round(hardness_value, 3)
def hardness_OLD(self, noupdate=False, verbose=False, tolerance=0.05): """ Calculates the hardness of a structure based in the model of XX We use the covalent radii from pychemia.utils.periodic. If noupdate=False the Laplacian matrix method is not used and rcut is 2*max(cov_radii) :param noupdate: (bool) If True, the Laplacian method is used :param verbose: (bool) To print some debug info :param tolerance: (float) :rtype : (float) """ #from ase.data import covalent_radii #atms=atms.repeat([2,2,2]) spc = self.copy() spc.supercell(2, 2, 2) natom = spc.natom volume = spc.volume #max_covalent_radius = max([covalent_radii[i.number] for i in atms]) max_covalent_radius = max(covalent_radius(spc.symbols)) if verbose: print('Number of atoms', natom) print('Volume ', volume) print('Covalent rad max', max_covalent_radius) #rcut, coord, dis_dic = get_bonds(atms,2.0*max_covalent_radius, noupdate,verbose,tolerance) rcut, coord, dis_dic = spc.get_bonds(2.0 * max_covalent_radius, noupdate, verbose, tolerance) sigma = 3.0 c_hard = 1300.0 x = 1. tot = 0.0 f_d = 0.0 f_n = 1.0 dic_atms = {} #for i in atms: # dic_atms[i.symbol] = i.number for i in spc.symbols: dic_atms[i] = atomic_number(i) #for i in dic_atms.keys(): # f_d += Z[i] / covalent_radii[dic_atms[i]] # f_n *= Z[i] / covalent_radii[dic_atms[i]] #f = 1.0 - (len(dic_atms)*f_n**(1.0/len(dic_atms)) / f_d)**2 for i in dic_atms.keys(): f_d += valence(i) / covalent_radius(i) f_n *= valence(i) / covalent_radius(i) f = 1.0 - (len(dic_atms) * f_n ** (1.0 / len(dic_atms)) / f_d) ** 2 if verbose: print 'BONDS' print dis_dic print 'COORDINATION' print coord for i in dis_dic.keys(): i1 = dis_dic[i][2][0] i2 = dis_dic[i][2][1] #ei = Z[atms[i1].symbol] / covalent_radii[atms[i1].number] #ej = Z[atms[i2].symbol] / covalent_radii[atms[i2].number] ei = valence(spc.symbols[i1]) / covalent_radius(spc.symbols[i1]) ej = valence(spc.symbols[i2]) / covalent_radius(spc.symbols[i2]) #print 'bond ->', sqrt(ei * ej), (coord[i1] * coord[i2]), dis_dic[i][0] # print atms[i1].symbol, ei, atms[i2].symbol, ej sij = sqrt(ei * ej) / (coord[i1] * coord[i2]) / dis_dic[i][0] #print 'sij', sij #print 'num_i_j_bonds', dis_dic[i][1] tot += dis_dic[i][1] x *= sij * dis_dic[i][1] #print 'x', x if verbose: print("V:", volume) print("f:", f) print("x:", x) #print 'len_bonds', len(dis_dic) #print 'hardness_value =', c_hard, volume, (len(dis_dic)), ( x ** (1. / (len(dis_dic)))), exp(-sigma * f) hardness_value = c_hard / volume * (len(dis_dic) * x ** (1. / (len(dis_dic)))) * exp(-sigma * f) if verbose: print hardness_value return round(hardness_value, 3)
def hardness(self, verbose=False, initial_cutoff_radius=0.8, ensure_conectivity=False, use_laplacian=True, use_jump=True): """ Calculates the hardness of a structure based in the model of XX We use the covalent radii from pychemia.utils.periodic. If noupdate=False the Laplacian matrix method is not used and rcut is 2*max(cov_radii) :param use_jump: :param ensure_conectivity: :param verbose: (bool) To print some debug info :param initial_cutoff_radius: (float) :param use_laplacian: (bool) If True, the Laplacian method is used :rtype : (float) """ if self._supercell == (1, 1, 1) and verbose: print('''Only internal connectivity can be ensure, for complete connectivity in the crystal you must use a supercell at of (2,2,2)''') bonds, coordination, all_distances, tolerances, cutoff_radius = \ self.get_bonds_coordination(initial_cutoff_radius=initial_cutoff_radius, ensure_conectivity=ensure_conectivity, use_laplacian=use_laplacian, verbose=verbose, use_jump=use_jump) if verbose: print('Structure coordination : ', coordination) sigma = 3.0 c_hard = 1300.0 x = 1. tot = 0.0 f_d = 0.0 f_n = 1.0 atomicnumbers = atomic_number(self.structure.species) if verbose: print('Atomic numbers in the structure :', atomicnumbers) for i in atomicnumbers: f_d += valence(i) / covalent_radius(i) f_n *= valence(i) / covalent_radius(i) # if verbose: # print 'fd', f_d # print 'fn', f_n # print atomicnumbers if f_d == 0: return 0.0 f = 1.0 - (len(atomicnumbers) * f_n ** (1.0 / len(atomicnumbers)) / f_d) ** 2 # Selection of different bonds diff_bonds = np.unique(np.array(reduce(lambda xx, y: xx + y, bonds))) if verbose: print('Number of different bonds : ', len(diff_bonds)) for i in diff_bonds: i1 = all_distances[i]['pair'][0] i2 = all_distances[i]['pair'][1] ei = valence(self.structure.symbols[i1]) / covalent_radius(self.structure.symbols[i1]) ej = valence(self.structure.symbols[i2]) / covalent_radius(self.structure.symbols[i2]) # print 'bond ->', sqrt(ei * ej), (coordination[i1] * coordination[i2]), all_distances[i]['distance'] sij = math.sqrt(ei * ej) / (coordination[i1] * coordination[i2]) / all_distances[i]['distance'] num_i_j_bonds = len([j for j in diff_bonds if i1 in all_distances[j]['pair'] and i2 in all_distances[j]['pair']]) # print 'sij', sij # print 'num_i_j_bonds', num_i_j_bonds tot += num_i_j_bonds x *= sij # print 'x', x vol = self.structure.volume if verbose: print("Structure volume:", vol) # print("f:", f) # print("x:", x) # print 'len_bonds', len(diff_bonds hardness_value = c_hard / vol * (len(diff_bonds) * x ** (1. / (len(diff_bonds)))) * math.exp(-sigma * f) return round(hardness_value, 3), cutoff_radius, coordination
def random_structure(method, composition, periodic=True, max_volume=1E10): """ Random Structure created by random positioning of atoms followed by either scaling of the cell or adding a sheer stretching along the smaller distances. The purpose of the lattice change is to avoid any two atoms to be closer than the sum of their covalent radius. :param method: Can be 'stretching' or 'scaling'. :param composition: Can be a Composition object or formula. :param periodic: If True, the structure will be periodical in all directions, otherwise a finite system is created. :param max_volume: Threshold for creating the Structure, if the volume exceeds the target the method returns None :return: Structure if the volume is below than best_volume, None otherwise >>> st = random_structure(method='scaling', composition='H2O', periodic=False) >>> st.natom 3 >>> st = random_structure(method='stretching', composition='NaCl', periodic=True) >>> st.natom 2 """ comp = Composition(composition) natom = comp.natom symbols = comp.symbols np.random.seed(struct.unpack("<L", os.urandom(4))[0]) if periodic: new_structure = None assert (method in ['scaling', 'stretching']) while True: trial = 0 if method == 'scaling': lattice = Lattice.random_cell(comp) # Random reduced positions rpos = np.random.rand(natom, 3) mins = [min(rpos[:, i]) for i in range(3)] rpos -= mins new_lattice = lattice else: lattice = Lattice.random_cell(comp) # Random reduced positions rpos = np.random.rand(natom, 3) mins = [min(rpos[:, i]) for i in range(3)] rpos -= mins new_lattice = lattice.stretch(symbols, rpos, tolerance=1.0, extra=0.1) if new_lattice.volume < max_volume: test = True for i in range(natom): for j in range(i + 1, natom): distance = new_lattice.minimal_distance( rpos[i], rpos[j]) covalent_dim = sum( covalent_radius([symbols[i], symbols[j]])) if distance < covalent_dim: test = False if test: new_structure = Structure(symbols=symbols, reduced=rpos, cell=new_lattice.cell, periodicity=True) minimal_distance = np.min(new_structure.distance_matrix() + 10 * np.eye(new_structure.natom)) break else: print( "Trial failed, distance %f is less than covalent radious %f" % (distance, covalent_dim)) trial += 1 if trial > 100: print("Leaving after 100 attemps") break # else: # print('Volume of Structure %f is larger than max_volume=%f' % (new_lattice.volume, max_volume)) # new_structure = None else: pos = np.random.rand(natom, 3) mindis = cluster_minimal_distance(pos) if mindis == 0: raise ValueError("Distance too small") max_cov = np.max(covalent_radius(symbols)) pos *= max_cov / mindis current_volume = (max(pos[:, 0]) - min(pos[:, 0])) * (max( pos[:, 1]) - min(pos[:, 1])) * (max(pos[:, 2]) - min(pos[:, 2])) if current_volume < max_volume: new_structure = Structure(symbols=symbols, positions=pos, periodicity=False) else: print('Volume of Structure %f is larger than max_volume=%f' % (current_volume, max_volume)) new_structure = None return new_structure
def random_structure(method, composition, periodic=True, best_volume=1E10): comp = Composition(composition) natom = comp.natom symbols = comp.symbols np.random.seed(struct.unpack("<L", os.urandom(4))[0]) if periodic: new_structure = None assert (method in ['scaling', 'stretching']) if method == 'scaling': lattice = Lattice.random_cell(comp) # Random reduced positions rpos = np.random.rand(natom, 3) mins = [min(rpos[:, i]) for i in range(3)] rpos -= mins new_lattice = lattice.scale(symbols, rpos, tolerance=1.0) else: lattice = Lattice.random_cell(comp) # Random reduced positions rpos = np.random.rand(natom, 3) mins = [min(rpos[:, i]) for i in range(3)] rpos -= mins new_lattice = lattice.stretch(symbols, rpos, tolerance=1.0, extra=0.1) if new_lattice.volume < best_volume: test = True for i in range(natom): for j in range(i + 1, natom): distance = new_lattice.minimal_distance(rpos[i], rpos[j]) covalent_dim = sum( covalent_radius([symbols[i], symbols[j]])) if distance < covalent_dim: test = False if test: new_structure = Structure(symbols=symbols, reduced=rpos, cell=new_lattice.cell, periodicity=True) else: new_structure = None else: pos = np.random.rand(natom, 3) mindis = cluster_minimal_distance(pos) if mindis == 0: raise ValueError("Distance too small") max_cov = np.max(covalent_radius(symbols)) pos *= max_cov / mindis current_volume = (max(pos[:, 0]) - min(pos[:, 0])) * (max( pos[:, 1]) - min(pos[:, 1])) * (max(pos[:, 2]) - min(pos[:, 2])) if current_volume < best_volume: new_structure = Structure(symbols=symbols, positions=pos, periodicity=False) else: new_structure = None return new_structure
def random_cell(composition, method='stretching', stabilization_number=20, nparal=5, periodic=True): """ Generate a random cell There are two algorithms implemented: scaling: Generate a random cell and random distribution of atoms and scale the lattice to separate the atoms. stretching: Generating a random cell and random distribution of atoms and stretching their bonds until the distance between any two atoms is always greater than the sum of covalent radius. :param composition: (pychemia.Composition) :param method: (str) :param stabilization_number: (int) :param nparal: (int) :param periodic: (bool) :return: Examples: >>> import pychemia >>> import os >>> st = pychemia.Structure.random_cell('LiAlCl4', stabilization_number=3) >>> st.natom 6 >>> st.save_json('test.json') >>> st2 = pychemia.Structure.load_json('test.json') >>> st == st2 True >>> os.remove('test.json') """ comp = Composition(composition) pcm_log.debug('Generating a random structure with composition: ' + str(comp.composition)) natom = comp.natom symbols = comp.symbols best_volume = sys.float_info.max best_volume = float('inf') best_structure = None optimal_volume = comp.covalent_volume('cubes') stabilization_history = 0 pool = Pool(processes=nparal) trial = 0 while stabilization_history < stabilization_number: args = list(best_volume * np.ones(10)) ret = pool.map( worker_star, zip(repeat(method), repeat(composition), repeat(periodic), args)) ngood = 0 for structure in ret: if structure is not None: ngood += 1 if best_structure is not None: if structure.volume < best_structure.volume: best_structure = structure else: best_structure = structure # log.debug('Good structures: %d/10 Best volume: %7.3f' % (ngood, best_structure.volume)) if best_structure is not None and best_volume > best_structure.volume: best_volume = best_structure.volume stabilization_history = 0 else: stabilization_history += 1 trial += 1 # log.debug('Trial: %4d Volume: %7.2f Optimal Volume: %7.2f Ratio: %5.2f' % # (trial, best_volume, optimal_volume, best_volume/optimal_volume)) pool.close() if best_structure is not None and periodic: # Analysis of the quality for the best structure rpos = best_structure.reduced for i, j in combinations(range(natom), 2): distance = best_structure.lattice.minimal_distance( rpos[i], rpos[j]) covalent_distance = sum( covalent_radius([symbols[i], symbols[j]])) if distance < covalent_distance: pcm_log.debug( 'Covalent distance: %7.4f Minimal distance: %7.4f Difference: %7.3e' % (covalent_distance, distance, covalent_distance - distance)) best_structure.canonical_form() return best_structure
def get_bonds_coordination(self, initial_cutoff_radius=0.8, ensure_conectivity=False, use_laplacian=True, verbose=False, tol=1E-15, jump=0.01, use_jump=True): """ Computes simultaneously the bonds for all atoms and the coordination number using a multiplicative tolerance for the sum of covalent radius :param use_jump: :param jump: :param tol: :param verbose: :param use_laplacian: :param initial_cutoff_radius: (float) Tolerance factor (default is 1.2) :param ensure_conectivity: (bool) If True the tolerance of each bond is adjusted to ensure that each atom is connected at least once :return: tuple """ if verbose: print('Computing all distances...') bonds_dict, distances_list = self.close_distances() if verbose: print('Number of distances computed: ', len(distances_list)) cutoff_radius = initial_cutoff_radius bonds = None coordination = None tolerances = None while True: if verbose: print('Current cutoff radius : ', cutoff_radius) bonds = [] tolerances = [] for i in range(self.structure.natom): tole = cutoff_radius while True: tmp_bonds = [] min_proportion = sys.float_info.max for j in bonds_dict[str(i)]: atom1 = self.structure.symbols[distances_list[j] ['pair'][0]] atom2 = self.structure.symbols[distances_list[j] ['pair'][1]] sum_covalent_radius = sum( covalent_radius([atom1, atom2])) distance = distances_list[j]['distance'] if distance == 0.0: continue proportion = distance / sum_covalent_radius min_proportion = min(min_proportion, proportion) if proportion <= tole: tmp_bonds.append(j) if len(tmp_bonds) == 0 and ensure_conectivity: tole = min_proportion cutoff_radius = tole else: bonds.append(tmp_bonds) tolerances.append(min_proportion) break if use_laplacian: size = (self.structure.natom, self.structure.natom) laplacian = np.zeros(size, dtype=np.int8) for listibonds in bonds: for ibond in listibonds: data = distances_list[ibond] i = data['pair'][0] j = data['pair'][1] laplacian[i, j] = -1 laplacian[j, i] = -1 # print '%d %d' % (i,j) for i in range(self.structure.natom): laplacian[i, i] = 0 laplacian[i, i] = -sum(laplacian[i]) if verbose: print(laplacian) if np.max(np.abs(laplacian)) == 0: cutoff_radius += jump if verbose: print('The laplacian is all zero') print('Increasing cutoff radius by ', jump, 'A\n') continue # if verbose: # print laplacian # evals, evecs = scipy.sparse.linalg.eigsh(laplacian) ev = numpy.linalg.eigvalsh(laplacian) if verbose: print('Number of Eigenvalues close to zero :', sum(ev < tol)) print('Lowest Eigenvalues :', ev) # print 'Lowest Eigenvalues :', evals if sum(ev < tol) > 1 and use_jump: cutoff_radius += jump if verbose: print('Increasing cutoff radius by ', jump, 'A\n') else: increase = False for i in bonds: if sum(i) == 0 and use_jump: increase = True if increase: cutoff_radius += jump if verbose: print('Increasing cutoff radius by', jump, 'A\n') else: break else: break if bonds is not None: coordination = [len(x) for x in bonds] return bonds, coordination, distances_list, tolerances, cutoff_radius
def get_bonds_coordination(self, initial_cutoff_radius=0.8, ensure_conectivity=False, use_laplacian=True, verbose=False, tol=1E-15, jump=0.01, use_jump=True): """ Computes simultaneously the bonds for all atoms and the coordination number using a multiplicative tolerance for the sum of covalent radius :param use_jump: :param jump: :param tol: :param verbose: :param use_laplacian: :param initial_cutoff_radius: (float) Tolerance factor (default is 1.2) :param ensure_conectivity: (bool) If True the tolerance of each bond is adjusted to ensure that each atom is connected at least once :return: tuple """ if verbose: print('Computing all distances...') bonds_dict, distances_list = self.close_distances() if verbose: print('Number of distances computed: ', len(distances_list)) cutoff_radius = initial_cutoff_radius bonds = None coordination = None tolerances = None while True: if verbose: print('Current cutoff radius : ', cutoff_radius) bonds = [] tolerances = [] for i in range(self.structure.natom): tole = cutoff_radius while True: tmp_bonds = [] min_proportion = sys.float_info.max for j in bonds_dict[str(i)]: atom1 = self.structure.symbols[distances_list[j]['pair'][0]] atom2 = self.structure.symbols[distances_list[j]['pair'][1]] sum_covalent_radius = sum(covalent_radius([atom1, atom2])) distance = distances_list[j]['distance'] if distance == 0.0: continue proportion = distance / sum_covalent_radius min_proportion = min(min_proportion, proportion) if proportion <= tole: tmp_bonds.append(j) if len(tmp_bonds) == 0 and ensure_conectivity: tole = min_proportion cutoff_radius = tole else: bonds.append(tmp_bonds) tolerances.append(min_proportion) break if use_laplacian: size = (self.structure.natom, self.structure.natom) laplacian = np.zeros(size, dtype=np.int8) for listibonds in bonds: for ibond in listibonds: data = distances_list[ibond] i = data['pair'][0] j = data['pair'][1] laplacian[i, j] = -1 laplacian[j, i] = -1 # print '%d %d' % (i,j) for i in range(self.structure.natom): laplacian[i, i] = 0 laplacian[i, i] = -sum(laplacian[i]) if verbose: print(laplacian) if np.max(np.abs(laplacian)) == 0: cutoff_radius += jump if verbose: print('The laplacian is all zero') print('Increasing cutoff radius by ', jump, 'A\n') continue # if verbose: # print laplacian # evals, evecs = scipy.sparse.linalg.eigsh(laplacian) ev = numpy.linalg.eigvalsh(laplacian) if verbose: print('Number of Eigenvalues close to zero :', sum(ev < tol)) print('Lowest Eigenvalues :', ev) # print 'Lowest Eigenvalues :', evals if sum(ev < tol) > 1 and use_jump: cutoff_radius += jump if verbose: print('Increasing cutoff radius by ', jump, 'A\n') else: increase = False for i in bonds: if sum(i) == 0 and use_jump: increase = True if increase: cutoff_radius += jump if verbose: print('Increasing cutoff radius by', jump, 'A\n') else: break else: break if bonds is not None: coordination = [len(x) for x in bonds] return bonds, coordination, distances_list, tolerances, cutoff_radius
def hardness(self, verbose=True, initial_cutoff_radius=0.8, ensure_conectivity=False, use_laplacian=True, use_jump=True, tol=1E-15): """ Calculates the hardness of a structure based in the model of XX We use the covalent radii from pychemia.utils.periodic. If noupdate=False the Laplacian matrix method is not used and rcut is 2*max(cov_radii) :param use_jump: :param ensure_conectivity: :param verbose: (bool) To print some debug info :param initial_cutoff_radius: (float) :param use_laplacian: (bool) If True, the Laplacian method is used :param tol: (float) Tolerance for considering two atoms bonded :rtype : (float) """ if self._supercell == (1, 1, 1) and verbose: print( '''Only internal connectivity can be ensure, for complete connectivity in the crystal you must use a supercell at of (2,2,2)''') bonds, coordination, all_distances, tolerances, cutoff_radius = \ self.get_bonds_coordination(initial_cutoff_radius=initial_cutoff_radius, ensure_conectivity=ensure_conectivity, use_laplacian=use_laplacian, verbose=verbose, use_jump=use_jump, tol=tol) if verbose: print('Structure coordination : ', coordination) sigma = 3.0 c_hard = 1300.0 x = 1. tot = 0.0 f_d = 0.0 f_n = 1.0 atomicnumbers = atomic_number(self.structure.species) if verbose: print('Atomic numbers in the structure :', atomicnumbers) for i in atomicnumbers: f_d += valence(i) / covalent_radius(i) f_n *= valence(i) / covalent_radius(i) # if verbose: # print 'fd', f_d # print 'fn', f_n # print atomicnumbers if f_d == 0: return 0.0 f = 1.0 - (len(atomicnumbers) * f_n**(1.0 / len(atomicnumbers)) / f_d)**2 # Selection of different bonds diff_bonds = np.unique( np.array(functools.reduce(lambda xx, y: xx + y, bonds))) if verbose: print('Number of different bonds : ', len(diff_bonds)) for i in diff_bonds: i1 = all_distances[i]['pair'][0] i2 = all_distances[i]['pair'][1] ei = valence(self.structure.symbols[i1]) / covalent_radius( self.structure.symbols[i1]) ej = valence(self.structure.symbols[i2]) / covalent_radius( self.structure.symbols[i2]) # print 'bond ->', sqrt(ei * ej), (coordination[i1] * coordination[i2]), all_distances[i]['distance'] sij = math.sqrt(ei * ej) / (coordination[i1] * coordination[i2] ) / all_distances[i]['distance'] num_i_j_bonds = len([ j for j in diff_bonds if i1 in all_distances[j]['pair'] and i2 in all_distances[j]['pair'] ]) # print 'sij', sij # print 'num_i_j_bonds', num_i_j_bonds tot += num_i_j_bonds x *= sij # print 'x', x vol = self.structure.volume if verbose: print("Structure volume:", vol) # print("f:", f) # print("x:", x) # print 'len_bonds', len(diff_bonds hardness_value = c_hard / vol * ( len(diff_bonds) * x**(1. / (len(diff_bonds)))) * math.exp(-sigma * f) return round(hardness_value, 3), cutoff_radius, coordination
def hardness_old(self, noupdate=False, verbose=False, tolerance=0.05): """ Calculates the hardness of a structure based in the model of XX We use the covalent radii from pychemia.utils.periodic. If noupdate=False the Laplacian matrix method is not used and rcut is 2*max(cov_radii) :param noupdate: (bool) If True, the Laplacian method is used :param verbose: (bool) To print some debug info :param tolerance: (float) :rtype : (float) """ superc = self.structure.copy() superc.supercell(2, 2, 2) structure_analisys = StructureAnalysis(superc) natom = superc.natom volume = superc.volume max_covalent_radius = max(covalent_radius(superc.symbols)) if verbose: print('Number of atoms', natom) print('Volume ', volume) print('Covalent rad max', max_covalent_radius) rcut, coord, dis_dic = structure_analisys.get_bonds(2.0 * max_covalent_radius, noupdate, verbose, tolerance) sigma = 3.0 c_hard = 1300.0 x = 1. tot = 0.0 f_d = 0.0 f_n = 1.0 dic_atms = {} for i in superc.symbols: dic_atms[i] = atomic_number(i) for i in dic_atms.keys(): f_d += valence(i) / covalent_radius(i) f_n *= valence(i) / covalent_radius(i) f = 1.0 - (len(dic_atms) * f_n ** (1.0 / len(dic_atms)) / f_d) ** 2 if verbose: print('BONDS') print(dis_dic) print('COORDINATION') print(coord) for i in dis_dic.keys(): i1 = dis_dic[i][2][0] i2 = dis_dic[i][2][1] ei = valence(superc.symbols[i1]) / covalent_radius(superc.symbols[i1]) ej = valence(superc.symbols[i2]) / covalent_radius(superc.symbols[i2]) sij = math.sqrt(ei * ej) / (coord[i1] * coord[i2]) / dis_dic[i][0] tot += dis_dic[i][1] x *= sij * dis_dic[i][1] if verbose: print("V:", volume) print("f:", f) print("x:", x) hardness_value = c_hard / volume * (len(dis_dic) * x ** (1. / (len(dis_dic)))) * math.exp(-sigma * f) if verbose: print(hardness_value) return round(hardness_value, 3)
def hardness(self, noupdate=False, verbose=False, tolerance=1): """ Calculates the hardness of a structure based in the model of XX We use the covalent radii from pychemia.utils.periodic. If noupdate=False the Laplacian matrix method is not used and rcut is 2*max(cov_radii) :param noupdate: (bool) If True, the Laplacian method is used :param verbose: (bool) To print some debug info :param tolerance: (float) :rtype : (float) """ bonds, coordination, all_distances, tolerances = self.get_bonds_coordination(tolerance=tolerance, ensure_conectivity=True) if verbose: print 'BONDS' print bonds print 'COORDINATION' print coordination sigma = 3.0 c_hard = 1300.0 x = 1. tot = 0.0 f_d = 0.0 f_n = 1.0 atomicnumbers = atomic_number(self.get_composition().species) if verbose: print atomicnumbers for i in atomicnumbers: f_d += valence(i) / covalent_radius(i) f_n *= valence(i) / covalent_radius(i) f = 1.0 - (len(atomicnumbers) * f_n ** (1.0 / len(atomicnumbers)) / f_d) ** 2 # Selection of different bonds diff_bonds = _np.unique(_np.array(reduce(lambda x, y: x+y, bonds))) for i in diff_bonds: i1 = all_distances[i]['pair'][0] i2 = all_distances[i]['pair'][1] ei = valence(self.symbols[i1]) / covalent_radius(self.symbols[i1]) ej = valence(self.symbols[i2]) / covalent_radius(self.symbols[i2]) #print 'bond ->', sqrt(ei * ej), (coordination[i1] * coordination[i2]), all_distances[i]['distance'] sij = sqrt(ei * ej) / (coordination[i1] * coordination[i2]) / all_distances[i]['distance'] num_i_j_bonds = len([j for j in diff_bonds if i1 in all_distances[j]['pair'] and i2 in all_distances[j]['pair']]) #print 'sij', sij #print 'num_i_j_bonds', num_i_j_bonds tot += num_i_j_bonds x *= sij #print 'x', x if verbose: print("V:", self.volume) print("f:", f) print("x:", x) #print 'len_bonds', len(diff_bonds) #print 'hardness_value =', c_hard, self.volume, (len(diff_bonds)), ( x ** (1. / (len(diff_bonds)))), exp(-sigma * f) hardness_value = c_hard / self.volume * (len(diff_bonds) * x ** (1. / (len(diff_bonds)))) * exp(-sigma * f) if verbose: print hardness_value return round(hardness_value, 3)