def set_surfaces_layers(self, surfaces, layers): if len(surfaces) != len(layers): raise ValueError("Improper size of surface and layer arrays: %i != %i" % (len(surfaces), len(layers))) sg = Spacegroup(self.spacegroup) surfaces = np.array(surfaces) layers = np.array(layers) for i, s in enumerate(surfaces): s = reduce_miller(s) surfaces[i] = s surfaces_full = surfaces.copy() layers_full = layers.copy() for s, l in zip(surfaces, layers): equivalent_surfaces = sg.equivalent_reflections(s.reshape(-1, 3)) for es in equivalent_surfaces: # If the equivalent surface (es) is not in the surface list, # then append it. if not np.equal(es, surfaces_full).all(axis=1).any(): surfaces_full = np.append(surfaces_full, es.reshape(1, 3), axis=0) layers_full = np.append(layers_full, l) self.surfaces = surfaces_full.copy() self.layers = layers_full.copy()
def fit_elastic(): from ase.db import connect atoms = bulk("Al") * (2, 2, 2) elastic = ElasticConstants(atoms, DB_NAME) db = connect(DB_NAME) stresses = [] strains = [] for row in db.select('id>=25'): stress = np.array(row["stress"]) stresses.append(stress) strain = np.array(db.get(id=row.init_struct).data["strain"]) strains.append(strain) from ase.spacegroup import Spacegroup spg = Spacegroup(123) print(spg.get_rotations()) C = elastic.get(stresses=stresses, strains=strains, spg=225, perm="zxy") print("Bulk:") print("Voigt: {}".format(elastic.bulk_modulus(mode="V") / GPa)) print("Reuss: {}".format(elastic.bulk_modulus(mode="R") / GPa)) print("VRH: {}".format(elastic.bulk_modulus(mode="VRH") / GPa)) print("Shear:") print("Voigt: {}".format(elastic.shear_modulus(mode="V") / GPa)) print("Reuss: {}".format(elastic.shear_modulus(mode="R") / GPa)) print("VRH: {}".format(elastic.shear_modulus(mode="VRH") / GPa)) print("Shear: {}".format(elastic.shear_modulus(mode="VRH"))) print("Poisson: {}".format(elastic.poisson_ratio)) #np.set_printoptions(precision=2) print(C) np.savetxt("data/C_MgSi100_225.csv", C, delimiter=",")
def _spacegroup_reciprocal_cell(no, setting): try: sg = Spacegroup(no, setting) except SpacegroupNotFoundError: return reciprocal_check = np.linalg.inv(sg.scaled_primitive_cell).T assert_allclose(sg.reciprocal_cell, reciprocal_check, atol=TOL)
def __init__(self, atoms, tolerance=1e-3): self.spglibdata = get_symmetry_dataset((atoms.get_cell(), atoms.get_scaled_positions(), atoms.get_atomic_numbers()), symprec=tolerance) self.tolerance = tolerance self.spacegroup = self.spglibdata['number'] self.Spacegroup = Spacegroup(self.spacegroup) self.atoms = self.get_conventional_atoms(atoms) super().__init__(spacegroup=self.spacegroup) self.set_wyckoff_species() WyckoffSymmetries.wyckoffs = self.wyckoffs WyckoffSymmetries.species = self.species
def writeFile(self, filename): ''' Write molecule to P1 cell to check things ''' from ase import Atoms from ase.spacegroup import Spacegroup dummyAtoms = Atoms(symbols=[x['symbol'] for x in self.atomList], positions=[x['position'] for x in self.atomList], cell=50. * np.eye(3), info={'spacegroup': Spacegroup(1)}) dummyAtoms.write(filename)
def writeFile(self, filename): ''' Write molecule to P1 cell to check things -note that ase cif writer might wrap atoms ''' from ase import Atoms from ase.spacegroup import Spacegroup dummyAtoms = Atoms(symbols=self.aseAtoms.get_chemical_symbols(), positions=self.aseAtoms.get_positions(), cell=50. * np.eye(3), info={'spacegroup': Spacegroup(1)}) dummyAtoms.write(filename)
def _symmetrize_elastic_tensor(self, spg=1, perm="xyz"): if spg == 1: return permut_lut = {"xyz": 0, "zxy": -1, "yzx": -2} from ase.spacegroup import Spacegroup spg = Spacegroup(spg) sym_op = spg.get_rotations() full = self._to_full_rank4(self.elastic_tensor) new_tensor = np.zeros((3, 3, 3, 3)) for op in sym_op: op = np.roll(op, permut_lut[perm], (0, 1)) avg_tensor = np.zeros((3, 3, 3, 3)) avg_tensor = np.einsum("pl,ijkl->ijkp", op, full) avg_tensor = np.einsum("ok,ijkp->ijop", op, avg_tensor) avg_tensor = np.einsum("nj,ijop->inop", op, avg_tensor) avg_tensor = np.einsum("mi,inop->mnop", op, avg_tensor) new_tensor += avg_tensor new_tensor /= len(sym_op) self.elastic_tensor = self._to_mandel_rank4(new_tensor)
def _get_spacegroup(atoms, symprec=1e-5, center=None): """ASE implementation of get_spacegroup. """ # we try all available spacegroups from 1 to 230, backwards # a Space group is the collection of all symmetry operations which lets the # unit cell invariant. found = None positions = atoms.get_scaled_positions(wrap=True) # in the lattice frame # make sure we are insensitive to translation. this choice is arbitrary and # could lead to a 'slightly' wrong guess for the Space group, e.g. do not # guess centro-symmetry. if center: try: positions -= positions[center] except IndexError: pass # search space groups from the highest symmetry to the lowest # retain the first match for nb in range(230, 0, -1): sg = Spacegroup(nb) # # now we scan all atoms in the cell and look for equivalent sites try: sites, kinds = sg.equivalent_sites(positions, onduplicates='keep', symprec=symprec) except TypeError: # ASE <= 3.9 sites, kinds = sg.equivalent_sites(positions, ondublicates='keep', symprec=symprec) # # the equivalent sites should match all other atom locations in the cell # as the spacegroup transforms the unit cell in itself if len(sites) == len(positions): # store the space group into the list found = sg break return found
def aseAtoms(self): ''' Take symbols and positions from atomList Function as atomList may be updated Cell is just large P1 box ''' from ase import Atoms from ase.spacegroup import Spacegroup return Atoms(symbols=[x['symbol'] for x in self.atomList], positions=[x['position'] for x in self.atomList], cell=50. * np.eye(3), info={'spacegroup': Spacegroup(1)})
def test_spacegroup_miscellaneous(): no = 225 sg = Spacegroup(no) assert int(sg) == no == sg.no assert sg.centrosymmetric assert sg.symbol == 'F m -3 m' assert sg.symbol in str(sg) assert sg.lattice == 'F' # face-centered assert sg.setting == 1 assert sg.scaled_primitive_cell == pytest.approx(FCC(1.0).tocell()[:]) assert sg.reciprocal_cell @ sg.scaled_primitive_cell == pytest.approx( np.identity(3))
def __init__(self, interface: "CppInterfaceClass" = None, weight=0.5) -> None: bottom = cpp_atoms_to_ase_atoms(interface.bottom) top = cpp_atoms_to_ase_atoms(interface.top) stack = cpp_atoms_to_ase_atoms(interface.stack) self.bottom = recenter(bottom) self.top = recenter(top) self.stack = recenter(stack) self.stack = stack self.M = [[j for j in k] for k in interface.M] self.N = [[j for j in k] for k in interface.N] self.spacegroup = Spacegroup(interface.spacegroup) self.angle = interface.angle self._weight = weight self._stress = None
def get_sg(lattice): """ Get the space-group of the system. Args: lattice: the ASE crystal class Returns: sg (int): integer number of the spacegroup """ spacegroup = spglib.get_spacegroup(lattice, symprec=1e-5) space_split=spacegroup.split() spg_num = space_split[1].replace('(','').replace(')','') sg = Spacegroup(int(spg_num)) return sg
def write_jsv(f, atoms): """Writes JSV file.""" f.write('asymmetric_unit_cell\n') f.write('[cell]') for v in cell_to_cellpar(atoms.cell): f.write(' %g' % v) f.write('\n') f.write('[natom] %d\n' % len(atoms)) f.write('[nbond] 0\n') # FIXME f.write('[npoly] 0\n') # FIXME if 'spacegroup' in atoms.info: sg = Spacegroup(atoms.info['spacegroup']) f.write('[space_group] %d %d\n' % (sg.no, sg.setting)) else: f.write('[space_group] 1 1\n') f.write('[title] %s\n' % atoms.info.get('title', 'untitled')) f.write('\n') f.write('[atoms]\n') if 'labels' in atoms.info: labels = atoms.info['labels'] else: labels = [ '%s%d' % (s, i + 1) for i, s in enumerate(atoms.get_chemical_symbols()) ] numbers = atoms.get_atomic_numbers() scaled = atoms.get_scaled_positions() for l, n, p in zip(labels, numbers, scaled): f.write('%-4s %2d %9.6f %9.6f %9.6f\n' % (l, n, p[0], p[1], p[2])) f.write('Label AtomicNumber x y z (repeat natom times)\n') f.write('\n') f.write('[bonds]\n') f.write('\n') f.write('[poly]\n') f.write('\n')
def get_spacegroup(atoms, symprec=1e-5, method='phonopy'): """Determine the spacegroup to which belongs the Atoms object. Parameters: atoms: an Atoms object symprec: Symmetry tolerance, i.e. distance tolerance in Cartesian coordinates to find crystal symmetry. method: 'phonopy' when available, or 'ase' The Spacegroup object is returned, and stored in atoms.info['spacegroup'] when this key does not exist (avoids overwrite). To force overwrite of the spacegroup first use: del atoms.info["spacegroup"] Examples: >>> from ase.lattice import bulk >>> atoms = bulk("Cu", "fcc", a=3.6, cubic=True) >>> sg = ifit.get_spacegroup(atoms) """ # use spglib when it is available (and return) if has_spglib and method in ('phonopy', 'spglib'): sg = spglib.get_spacegroup(atoms) sg_no = int(sg[sg.find("(") + 1:sg.find(")")]) atoms.info["spacegroup"] = Spacegroup(sg_no) return atoms.info["spacegroup"] # no spglib, we use our own spacegroup finder. Not as robust as spglib. # we center the Atoms positions on each atom in the cell, and find the # spacegroup of highest symmetry found = None for kind, pos in enumerate(atoms.get_scaled_positions()): sg = _get_spacegroup(atoms, symprec=1e-5, center=kind) if found is None or sg.no > found.no: found = sg # return None when no space group is found (would be surprising) if found is not None and 'spacegroup' not in atoms.info: atoms.info["spacegroup"] = found return found
def writeASEAtoms(self, filename, pbc=True, wrapAtoms=False): ''' Combine aseAtoms objects and then write to filename N.B. Atoms.write puts things into P1 for some reason ''' from ase import Atoms if filename.lower()[-4:] == '.xyz': from ase.spacegroup import Spacegroup dummyAtoms = Atoms(symbols=np.concatenate([ x.aseAtoms.get_chemical_symbols() for x in self.asymmetricMolecules ]), positions=np.concatenate([ x.aseAtoms.get_positions() for x in self.asymmetricMolecules ]), cell=50. * np.eye(3), info={'spacegroup': Spacegroup(1)}) dummyAtoms.write(filename) return True _writingAtoms = Atoms( symbols=[ x for m in self.asymmetricMolecules for x in m.aseAtoms.get_chemical_symbols() ], scaled_positions=np.array([ x for m in self.asymmetricMolecules for x in m.aseAtoms.get_scaled_positions(wrap=False) ]), cell=self.aseCell, pbc=pbc, info=self.aseInfo) # cell = self.asymmetricMolecules[0].aseAtoms.cell, # pbc = True, # info = self.asymmetricMolecules[0].aseAtoms.info) if wrapAtoms == False: from ioAndInterfaces import aseWriteCifNoWrap aseWriteCifNoWrap(filename, [_writingAtoms]) else: _writingAtoms.write(filename)
def interface_energy_poly_expansion(self, order=2, show=False, spg=1, penalty=0.0, average_cutoff=10.0): """Fit a multidimensional polynomial of a certain order.""" from itertools import combinations_with_replacement interf = self.interface_energy(average_cutoff=average_cutoff) self.spg = spg if spg > 1: from ase.spacegroup import Spacegroup self.spg_group = Spacegroup(spg) num_terms = int((3**(order + 1) - 1) / 2) print("Number of terms in polynomial expansion: {}".format(num_terms)) # A = np.zeros((len(interf), num_terms)) A = [] num_data_points = len(interf) A.append(np.ones(num_data_points)) col = 1 mult_order = [()] now = time.time() output_every = 10 self.spg_num = spg # Try to load already computed orders try: import json with open(self.symmetry_fname, 'r') as infile: precomputed_order = json.load(infile) print("Valid terms are read from {}. " "Delete the file if you want a " "new calculation from scratch." "".format(self.symmetry_fname)) except IOError: precomputed_order = {} pre_computed_sizes = [int(key) for key in precomputed_order.keys()] for p in range(1, order + 1): if p in pre_computed_sizes: # Use only the ones that already have been # calculated combs = precomputed_order[str(p)] else: # No precalculations was made # Check all combinations combs = combinations_with_replacement(range(3), p) for comb in combs: if not self.is_valid(comb): continue if time.time() - now > output_every: print("Calculating order {} permutation {}" "".format(p, comb)) now = time.time() vec = np.zeros(len(interf)) row = 0 for n, value in interf: x = self._get_x_value(n, comb) vec[row] = x row += 1 A.append(vec) mult_order.append(comb) col += 1 A = np.array(A).T print(A.shape) rhs = np.zeros(len(interf)) row = 0 for n, value in interf: rhs[row] = value row += 1 print("Filtering duplicates...") # unique_cols = self._unique_columns(A) # A = A[:, unique_cols] num_rows = A.shape[0] # A, unique_cols = np.unique(A, axis=1, return_index=True) unique_cols = self._unique_columns(A) A = A[:, unique_cols] assert A.shape[0] == num_rows mult_order = [mult_order[indx] for indx in unique_cols] # Filter constant columns constant_columns = [] for i in range(1, A.shape[1]): if np.allclose(A[:, i], A[0, i]): constant_columns.append(i) A = np.delete(A, constant_columns, axis=1) assert A.shape[0] == num_rows mult_order = [ mult_order[indx] for indx in range(len(mult_order)) if indx not in constant_columns ] self._save_valid_order(mult_order) print( "Number of terms after applying spacegroup symmetries: {}".format( A.shape[1])) print("Solving linear system...") if A.shape[1] == 1: # TODO: Trivial solution, calculate this directly coeff, residual, rank, s = np.linalg.lstsq(A, rhs) else: N = A.shape[1] matrix = np.linalg.inv(A.T.dot(A) + penalty * np.identity(N)) coeff = matrix.dot(A.T.dot(rhs)) # Perform one consistency check mean_val = np.mean(rhs) if coeff[0] > 2.0 * mean_val or coeff[0] < 0.5 * mean_val: print("Warning! This fit looks suspicious. Constant term {}" "Mean of dataset: {}." "Consider to increase the penalty!" "".format(coeff[0], mean_val)) self.linear_fit["coeff"] = coeff self.linear_fit["order"] = mult_order pred = A.dot(coeff) rmse = np.sqrt(np.mean((pred - rhs)**2)) print("RMSE of surface parametrization: {}".format(rmse)) if show: from matplotlib import pyplot as plt fig = plt.figure() ax = fig.add_subplot(1, 1, 1) ax.plot(pred, rhs, 'o', mfc="none") min_val = np.min(pred) - 1 max_val = np.max(pred) + 1 ax.plot([min_val, max_val], [min_val, max_val]) ax.plot([min_val, max_val], [min_val + rmse, max_val + rmse], "--") ax.plot([min_val, max_val], [min_val - rmse, max_val - rmse], "--") self.plot_fitting_coefficients() plt.show()
class PrototypeClassification(WyckoffSymmetries): """Prototype classification of atomic structure in ASE Atoms format""" def __init__(self, atoms, tolerance=1e-3): self.spglibdata = get_symmetry_dataset((atoms.get_cell(), atoms.get_scaled_positions(), atoms.get_atomic_numbers()), symprec=tolerance) self.tolerance = tolerance self.spacegroup = self.spglibdata['number'] self.Spacegroup = Spacegroup(self.spacegroup) self.atoms = self.get_conventional_atoms(atoms) super().__init__(spacegroup=self.spacegroup) self.set_wyckoff_species() WyckoffSymmetries.wyckoffs = self.wyckoffs WyckoffSymmetries.species = self.species def get_conventional_atoms(self, atoms): """Transform from primitive to conventional cell""" std_cell = self.spglibdata['std_lattice'] positions = self.spglibdata['std_positions'] numbers = self.spglibdata['std_types'] atoms = Atoms(numbers=numbers, cell=std_cell, pbc=True) atoms.set_scaled_positions(positions) atoms.wrap() return atoms def get_primitive_atoms(self, atoms): """Transform from primitive to conventional cell""" lattice, scaled_positions, numbers = standardize_cell(atoms, to_primitive=True, no_idealize=False, symprec=1e-5) atoms = Atoms(numbers=numbers, cell=lattice, pbc=True) atoms.set_scaled_positions(scaled_positions) atoms.wrap() return atoms def set_wyckoff_species(self): self.wyckoffs = [] self.species = [] relative_positions = np.dot(np.linalg.inv( self.atoms.cell.T), self.atoms.positions.T).T relative_positions = np.round(relative_positions, 10) normalized_sites = \ self.Spacegroup.symmetry_normalised_sites( np.array(relative_positions, ndmin=2)) equivalent_sites = [] for i, site in enumerate(normalized_sites): indices = np.all(np.isclose(site, normalized_sites[:i+1], rtol=self.tolerance), axis=1) index = np.min(np.where(indices)[0]) equivalent_sites += [index] unique_site_indices = list(set(equivalent_sites)) unique_sites = normalized_sites[unique_site_indices] count_sites = [list(equivalent_sites).count(i) for i in unique_site_indices] symbols = self.atoms[unique_site_indices].get_chemical_symbols() for i, position in enumerate(unique_sites): found = False for w in sorted(self.wyckoff_symmetries.keys()): m = self.wyckoff_multiplicities[w] if not count_sites[i] == m: continue for w_sym in self.wyckoff_symmetries[w]: if found: break if self.is_position_wyckoff(position, w_sym): found = True self.wyckoffs += [w] self.species += [symbols[i]] break if not found: print('Error: position: ', position, ' not identified') indices = np.argsort(self.species) w_sorted = [] # for s in set(self.species): # indices = [i for i, s0 in enumerate(self.species) if s0==s] # w_sorted += sorted([self.wyckoffs[i] for i in indices]) #self.wyckoffs = w_sorted free_wyckoffs = self.get_free_wyckoffs() self.atoms_wyckoffs = [] self.free_atoms = [] for site in equivalent_sites: index = unique_site_indices.index(site) w_position = self.wyckoffs[index] self.atoms_wyckoffs += [w_position + str(index)] self.free_atoms += [w_position in free_wyckoffs] def get_spacegroup(self): return self.spacegroup def get_wyckoff_species(self): return self.wyckoffs, self.species def get_classification(self, include_parameters=True): p_name = self.get_prototype_name(self.species) structure_name = str(self.spacegroup) for spec, wy_spec in zip(self.species, self.wyckoffs): structure_name += '_{}_{}'.format(spec, wy_spec) prototype = {'p_name': p_name, 'structure_name': structure_name, 'spacegroup': self.spacegroup, 'wyckoffs': self.wyckoffs, 'species': self.species} if include_parameters: cell_parameters = self.get_cell_parameters(self.atoms) return prototype, cell_parameters else: return prototype
def main(): args = parse_args() print("args", args) if args.implementation == TORCHANI: from torchani_calculator import torchani_calculator calculator = torchani_calculator(args.network_type) elif args.implementation == AES_ANI: from ani_ase import ani_ase_calculator calculator = ani_ase_calculator(args.network_type) elif args.implementation == KHAN: from khan_calculator import khan_calculator calculator = khan_calculator(args.network_type, args.khan_network, args.numb_networks) assert args.cif_file.endswith('.cif') print('debug? ', args.debug) atoms = ase_io.read(args.cif_file) numb_molecules = len(molecule_lists(atoms)) print('number of molecules: ', numb_molecules) MC_stpes = args.MC space_group = Spacegroup(args.space_group) # step size in MC f1, f2, f3, f4, f5, f6, f7, f8 = args.MC_f1_f2_f3_f4_f5_f6_f7_f8 perturbation_type = args.perturbation_type molecule1_in_cell = [] for i in range(1, atoms.get_number_of_atoms()): if atom_belong_to_mol1(i, atoms): molecule1_in_cell.append(i) print("initial unit cell") print(atoms.cell) print("To do Monte Carlo for %d step." % MC_stpes) print("Molecule index in ASU are: ", molecule1_in_cell) counter = 0 atoms.set_calculator(calculator) while (counter <= MC_stpes): if isinstance(perturbation_type, list): # random_number = random.choice([1,2,3,4,5,6,7,8]) random_choices = get_random_choices( [f1, f2, f3, f4, f5, f6, f7, f8]) random_number = random.choice(random_choices) else: random_number = perturbation_dict[perturbation_type] if random_number == 1: # translation Monte_Carlo(atoms, calculator, space_group, f1, 'translate', counter, numb_molecules, molecule1_in_cell, MC_stpes, args.debug) counter += 1 elif random_number == 2: # rotation Monte_Carlo(atoms, calculator, space_group, f2, 'rotation', counter, numb_molecules, molecule1_in_cell, MC_stpes, args.debug) counter += 1 elif random_number == 3: # cell_length a Monte_Carlo(atoms, calculator, space_group, f3, 'cell_length_a', counter, numb_molecules, molecule1_in_cell, MC_stpes, args.debug) counter += 1 elif random_number == 4: # cell_length b Monte_Carlo(atoms, calculator, space_group, f4, 'cell_length_b', counter, numb_molecules, molecule1_in_cell, MC_stpes, args.debug) counter += 1 elif random_number == 5: # cell_length c Monte_Carlo(atoms, calculator, space_group, f5, 'cell_length_c', counter, numb_molecules, molecule1_in_cell, MC_stpes, args.debug) counter += 1 elif random_number == 6: # cell_angle alpha Monte_Carlo(atoms, calculator, space_group, f6, 'cell_angle_alpha', counter, numb_molecules, molecule1_in_cell, MC_stpes, args.debug) counter += 1 elif random_number == 7: # cell_angle beta Monte_Carlo(atoms, calculator, space_group, f7, 'cell_angle_beta', counter, numb_molecules, molecule1_in_cell, MC_stpes, args.debug) counter += 1 elif random_number == 8: # cell_angle gamma Monte_Carlo(atoms, calculator, space_group, f8, 'cell_angle_gamma', counter, numb_molecules, molecule1_in_cell, MC_stpes, args.debug) counter += 1
def read_castep_cell(fd, index=None, calculator_args={}, find_spg=False, units=units_CODATA2002): """Read a .cell file and return an atoms object. Any value found that does not fit the atoms API will be stored in the atoms.calc attribute. By default, the Castep calculator will be tolerant and in the absence of a castep_keywords.json file it will just accept all keywords that aren't automatically parsed. """ from ase.calculators.castep import Castep cell_units = { # Units specifiers for CASTEP 'bohr': units_CODATA2002['a0'], 'ang': 1.0, 'm': 1e10, 'cm': 1e8, 'nm': 10, 'pm': 1e-2 } calc = Castep(**calculator_args) if calc.cell.castep_version == 0: # No valid castep_keywords.json was found print('read_cell: Warning - Was not able to validate CASTEP input.') print(' This may be due to a non-existing ' '"castep_keywords.json"') print(' file or a non-existing CASTEP installation.') print(' Parsing will go on but keywords will not be ' 'validated and may cause problems if incorrect during a CASTEP ' 'run.') celldict = read_freeform(fd) def parse_blockunit(line_tokens, blockname): u = 1.0 if len(line_tokens[0]) == 1: usymb = line_tokens[0][0].lower() u = cell_units.get(usymb, 1) if usymb not in cell_units: warnings.warn( ('read_cell: Warning - ignoring invalid ' 'unit specifier in %BLOCK {0} ' '(assuming Angstrom instead)').format(blockname)) line_tokens = line_tokens[1:] return u, line_tokens # Arguments to pass to the Atoms object at the end aargs = {'pbc': True} # Start by looking for the lattice lat_keywords = [w in celldict for w in ('lattice_cart', 'lattice_abc')] if all(lat_keywords): warnings.warn('read_cell: Warning - two lattice blocks present in the' ' same file. LATTICE_ABC will be ignored') elif not any(lat_keywords): raise ValueError('Cell file must contain at least one between ' 'LATTICE_ABC and LATTICE_CART') if 'lattice_abc' in celldict: lines = celldict.pop('lattice_abc').split('\n') line_tokens = [l.split() for l in lines] u, line_tokens = parse_blockunit(line_tokens, 'lattice_abc') if len(line_tokens) != 2: warnings.warn('read_cell: Warning - ignoring additional ' 'lines in invalid %BLOCK LATTICE_ABC') abc = [float(p) * u for p in line_tokens[0][:3]] angles = [float(phi) for phi in line_tokens[1][:3]] aargs['cell'] = cellpar_to_cell(abc + angles) if 'lattice_cart' in celldict: lines = celldict.pop('lattice_cart').split('\n') line_tokens = [l.split() for l in lines] u, line_tokens = parse_blockunit(line_tokens, 'lattice_cart') if len(line_tokens) != 3: warnings.warn('read_cell: Warning - ignoring more than ' 'three lattice vectors in invalid %BLOCK ' 'LATTICE_CART') aargs['cell'] = [[float(x) * u for x in lt[:3]] for lt in line_tokens] # Now move on to the positions pos_keywords = [w in celldict for w in ('positions_abs', 'positions_frac')] if all(pos_keywords): warnings.warn('read_cell: Warning - two lattice blocks present in the' ' same file. POSITIONS_FRAC will be ignored') del celldict['positions_frac'] elif not any(pos_keywords): raise ValueError('Cell file must contain at least one between ' 'POSITIONS_FRAC and POSITIONS_ABS') aargs['symbols'] = [] pos_type = 'positions' pos_block = celldict.pop('positions_abs', None) if pos_block is None: pos_type = 'scaled_positions' pos_block = celldict.pop('positions_frac', None) aargs[pos_type] = [] lines = pos_block.split('\n') line_tokens = [l.split() for l in lines] if not 'scaled' in pos_type: u, line_tokens = parse_blockunit(line_tokens, 'positions_abs') else: u = 1.0 # Here we extract all the possible additional info # These are marked by their type add_info = { 'SPIN': (float, 0.0), # (type, default) 'MAGMOM': (float, 0.0), 'LABEL': (str, 'NULL') } add_info_arrays = dict((k, []) for k in add_info) def parse_info(raw_info): re_keys = (r'({0})\s*[=:\s]{{1}}\s' r'*([^\s]*)').format('|'.join(add_info.keys())) # Capture all info groups info = re.findall(re_keys, raw_info) info = {g[0]: add_info[g[0]][0](g[1]) for g in info} return info # Array for custom species (a CASTEP special thing) # Usually left unused custom_species = None for tokens in line_tokens: # Now, process the whole 'species' thing spec_custom = tokens[0].split(':', 1) elem = spec_custom[0] if len(spec_custom) > 1 and custom_species is None: # Add it to the custom info! custom_species = list(aargs['symbols']) if custom_species is not None: custom_species.append(tokens[0]) aargs['symbols'].append(elem) aargs[pos_type].append([float(p) * u for p in tokens[1:4]]) # Now for the additional information info = ' '.join(tokens[4:]) info = parse_info(info) for k in add_info: add_info_arrays[k] += [info.get(k, add_info[k][1])] # Now on to the species potentials... if 'species_pot' in celldict: lines = celldict.pop('species_pot').split('\n') line_tokens = [l.split() for l in lines] for tokens in line_tokens: if len(tokens) == 1: # It's a library all_spec = (set(custom_species) if custom_species is not None else set(aargs['symbols'])) for s in all_spec: calc.cell.species_pot = (s, tokens[0]) else: calc.cell.species_pot = tuple(tokens[:2]) # Ionic constraints raw_constraints = {} if 'ionic_constraints' in celldict: lines = celldict.pop('ionic_constraints').split('\n') line_tokens = [l.split() for l in lines] for tokens in line_tokens: if not len(tokens) == 6: continue _, species, nic, x, y, z = tokens # convert xyz to floats x = float(x) y = float(y) z = float(z) nic = int(nic) if (species, nic) not in raw_constraints: raw_constraints[(species, nic)] = [] raw_constraints[(species, nic)].append(np.array([x, y, z])) # Symmetry operations if 'symmetry_ops' in celldict: lines = celldict.pop('symmetry_ops').split('\n') line_tokens = [l.split() for l in lines] # Read them in blocks of four blocks = np.array(line_tokens).astype(float) if (len(blocks.shape) != 2 or blocks.shape[1] != 3 or blocks.shape[0] % 4 != 0): warnings.warn('Warning: could not parse SYMMETRY_OPS' ' block properly, skipping') else: blocks = blocks.reshape((-1, 4, 3)) rotations = blocks[:, :3] translations = blocks[:, 3] # Regardless of whether we recognize them, store these calc.cell.symmetry_ops = (rotations, translations) # Anything else that remains, just add it to the cell object: for k, val in celldict.items(): try: calc.cell.__setattr__(k, val) except Exception: raise RuntimeError('Problem setting calc.cell.%s = %s' % (k, val)) # Get the relevant additional info aargs['magmoms'] = np.array(add_info_arrays['SPIN']) # SPIN or MAGMOM are alternative keywords aargs['magmoms'] = np.where(aargs['magmoms'] != 0, aargs['magmoms'], add_info_arrays['MAGMOM']) labels = np.array(add_info_arrays['LABEL']) aargs['calculator'] = calc atoms = ase.Atoms(**aargs) # Spacegroup... if find_spg: # Try importing spglib try: import spglib except ImportError: try: from pyspglib import spglib except ImportError: # spglib is not present warnings.warn('spglib not found installed on this system - ' 'automatic spacegroup detection is not possible') spglib = None if spglib is not None: symmd = spglib.get_symmetry_dataset(atoms) atoms_spg = Spacegroup(int(symmd['number'])) atoms.info['spacegroup'] = atoms_spg atoms.new_array('castep_labels', labels) if custom_species is not None: atoms.new_array('castep_custom_species', np.array(custom_species)) fixed_atoms = [] constraints = [] for (species, nic), value in raw_constraints.items(): absolute_nr = atoms.calc._get_absolute_number(species, nic) if len(value) == 3: # Check if they are linearly independent if np.linalg.det(value) == 0: print('Error: Found linearly dependent constraints attached ' 'to atoms %s' % (absolute_nr)) continue fixed_atoms.append(absolute_nr) elif len(value) == 2: direction = np.cross(value[0], value[1]) # Check if they are linearly independent if np.linalg.norm(direction) == 0: print('Error: Found linearly dependent constraints attached ' 'to atoms %s' % (absolute_nr)) continue constraint = ase.constraints.FixedLine(a=absolute_nr, direction=direction) constraints.append(constraint) elif len(value) == 1: constraint = ase.constraints.FixedPlane(a=absolute_nr, direction=np.array( value[0], dtype=np.float32)) constraints.append(constraint) else: print('Error: Found %s statements attached to atoms %s' % (len(value), absolute_nr)) # we need to sort the fixed atoms list in order not to raise an assertion # error in FixAtoms if fixed_atoms: constraints.append( ase.constraints.FixAtoms(indices=sorted(fixed_atoms))) if constraints: atoms.set_constraint(constraints) atoms.calc.atoms = atoms atoms.calc.push_oldstate() return atoms
def _analyze(self): """Analyze the topology to cut the fragments out.""" # separate the dummies from the rest logger.debug("Analyzing fragments of topology {0}.".format(self.name)) numbers = numpy.asarray(self.atoms.get_atomic_numbers()) Xis = numpy.where(numbers == 0)[0] Ais = numpy.where(numbers > 0)[0] # setup the tags tags = numpy.zeros(len(self.atoms)) tags[Xis] = Xis + 1 self.atoms.set_tags(tags) tags = self.atoms.get_tags() # analyze # first build the neighborlist cutoffs = self._get_cutoffs(Xis=Xis, Ais=Ais) neighborlist = NeighborList(cutoffs=cutoffs, bothways=True, self_interaction=False, skin=0.0) neighborlist.update(self.atoms) # iterate over non-dummies to find dummy neighbors for ai in Ais: # get indices and offsets of dummies only! ni, no = neighborlist.get_neighbors(ai) ni, no = zip(*[(idx, off) for idx, off in list(zip(ni, no)) if idx in Xis]) ni = numpy.asarray(ni) no = numpy.asarray(no) # get absolute positions, no offsets positions = self.atoms.positions[ni] + no.dot(self.atoms.cell) # create the Atoms object fragment = Atoms("X" * len(ni), positions, tags=tags[ni]) # calculate the point group properties max_order = min(8, len(ni)) shape = symmetry.get_symmetry_elements(mol=fragment.copy(), max_order=max_order) pg = symmetry.PointGroup(mol=fragment.copy(), tol=0.1) # save that info self.fragments[ai] = fragment self.shapes[ai] = shape self.pointgroups[ai] = pg.schoenflies # now getting the equivalent sites using the Spacegroup object sg = self.atoms.info["spacegroup"] if not isinstance(sg, Spacegroup): sg = Spacegroup(sg) scaled_positions = self.atoms.get_scaled_positions() seen_indices = [] symbols = numpy.array(self.atoms.get_chemical_symbols()) for ai in Ais: if ai in seen_indices: continue sites, _ = sg.equivalent_sites(scaled_positions[ai]) these_indices = [] for site in sites: norms = numpy.linalg.norm(scaled_positions - site, axis=1) if norms.min() < 1e-6: these_indices.append(norms.argmin()) # take pbc into account norms = numpy.abs(norms - 1.0) if norms.min() < 1e-6: these_indices.append(norms.argmin()) these_indices = [idx for idx in these_indices if idx in Ais] seen_indices += these_indices self.equivalent_sites.append(these_indices) logger.info("{es} equivalent sites kinds.".format( es=len(self.equivalent_sites))) return None
def _dlite_set_info(value): self.info.update(value) self.info['spacegroup'] = Spacegroup(self.info['spacegroup'])
def read_magres(fd, include_unrecognised=False): """ Reader function for magres files. """ blocks_re = re.compile(r'[\[<](?P<block_name>.*?)[>\]](.*?)[<\[]/' + r'(?P=block_name)[\]>]', re.M | re.S) """ Here are defined the various functions required to parse different blocks. """ def tensor33(x): return np.squeeze(np.reshape(x, (3, 3))).tolist() def tensor31(x): return np.squeeze(np.reshape(x, (3, 1))).tolist() def get_version(file_contents): """ Look for and parse the magres file format version line """ lines = file_contents.split('\n') match = re.match(r'\#\$magres-abinitio-v([0-9]+).([0-9]+)', lines[0]) if match: version = match.groups() version = tuple(vnum for vnum in version) else: version = None return version def parse_blocks(file_contents): """ Parse series of XML-like deliminated blocks into a list of (block_name, contents) tuples """ blocks = blocks_re.findall(file_contents) return blocks def parse_block(block): """ Parse block contents into a series of (tag, data) records """ def clean_line(line): # Remove comments and whitespace at start and ends of line line = re.sub('#(.*?)\n', '', line) line = line.strip() return line name, data = block lines = [clean_line(line) for line in data.split('\n')] records = [] for line in lines: xs = line.split() if len(xs) > 0: tag = xs[0] data = xs[1:] records.append((tag, data)) return (name, records) def check_units(d): """ Verify that given units for a particular tag are correct. """ allowed_units = {'lattice': 'Angstrom', 'atom': 'Angstrom', 'ms': 'ppm', 'efg': 'au', 'efg_local': 'au', 'efg_nonlocal': 'au', 'isc': '10^19.T^2.J^-1', 'isc_fc': '10^19.T^2.J^-1', 'isc_orbital_p': '10^19.T^2.J^-1', 'isc_orbital_d': '10^19.T^2.J^-1', 'isc_spin': '10^19.T^2.J^-1', 'isc': '10^19.T^2.J^-1', 'sus': '10^-6.cm^3.mol^-1', 'calc_cutoffenergy': 'Hartree', } if d[0] in d and d[1] == allowed_units[d[0]]: pass else: raise RuntimeError('Unrecognized units: %s %s' % (d[0], d[1])) return d def parse_magres_block(block): """ Parse magres block into data dictionary given list of record tuples. """ name, records = block # 3x3 tensor def ntensor33(name): return lambda d: {name: tensor33([float(x) for x in data])} # Atom label, atom index and 3x3 tensor def sitensor33(name): return lambda d: {'atom': {'label': data[0], 'index': int(data[1])}, name: tensor33([float(x) for x in data[2:]])} # 2x(Atom label, atom index) and 3x3 tensor def sisitensor33(name): return lambda d: {'atom1': {'label': data[0], 'index': int(data[1])}, 'atom2': {'label': data[2], 'index': int(data[3])}, name: tensor33([float(x) for x in data[4:]])} tags = {'ms': sitensor33('sigma'), 'sus': ntensor33('S'), 'efg': sitensor33('V'), 'efg_local': sitensor33('V'), 'efg_nonlocal': sitensor33('V'), 'isc': sisitensor33('K'), 'isc_fc': sisitensor33('K'), 'isc_spin': sisitensor33('K'), 'isc_orbital_p': sisitensor33('K'), 'isc_orbital_d': sisitensor33('K'), 'units': check_units} data_dict = {} for record in records: tag, data = record if tag not in data_dict: data_dict[tag] = [] data_dict[tag].append(tags[tag](data)) return data_dict def parse_atoms_block(block): """ Parse atoms block into data dictionary given list of record tuples. """ name, records = block # Lattice record: a1, a2 a3, b1, b2, b3, c1, c2 c3 def lattice(d): return tensor33([float(x) for x in data]) # Atom record: label, index, x, y, z def atom(d): return {'species': data[0], 'label': data[1], 'index': int(data[2]), 'position': tensor31([float(x) for x in data[3:]])} def symmetry(d): return ' '.join(data) tags = {'lattice': lattice, 'atom': atom, 'units': check_units, 'symmetry': symmetry} data_dict = {} for record in records: tag, data = record if tag not in data_dict: data_dict[tag] = [] data_dict[tag].append(tags[tag](data)) return data_dict def parse_generic_block(block): """ Parse any other block into data dictionary given list of record tuples. """ name, records = block data_dict = {} for record in records: tag, data = record if tag not in data_dict: data_dict[tag] = [] data_dict[tag].append(data) return data_dict """ Actual parser code. """ block_parsers = {'magres': parse_magres_block, 'atoms': parse_atoms_block, 'calculation': parse_generic_block, } file_contents = fd.read() # This works as a validity check version = get_version(file_contents) if version is None: # This isn't even a .magres file! raise RuntimeError('File is not in standard Magres format') blocks = parse_blocks(file_contents) data_dict = {} for block_data in blocks: block = parse_block(block_data) if block[0] in block_parsers: block_dict = block_parsers[block[0]](block) data_dict[block[0]] = block_dict else: # Throw in the text content of blocks we don't recognise if include_unrecognised: data_dict[block[0]] = block_data[1] # Now the loaded data must be turned into an ASE Atoms object # First check if the file is even viable if 'atoms' not in data_dict: raise RuntimeError('Magres file does not contain structure data') # Allowed units handling. This is redundant for now but # could turn out useful in the future magres_units = {'Angstrom': ase.units.Ang} # Lattice parameters? if 'lattice' in data_dict['atoms']: try: u = dict(data_dict['atoms']['units'])['lattice'] except KeyError: raise RuntimeError('No units detected in file for lattice') u = magres_units[u] cell = np.array(data_dict['atoms']['lattice'][0]) * u pbc = True else: cell = None pbc = False # Now the atoms symbols = [] positions = [] indices = [] labels = [] if 'atom' in data_dict['atoms']: try: u = dict(data_dict['atoms']['units'])['atom'] except KeyError: raise RuntimeError('No units detected in file for atom positions') u = magres_units[u] # Now we have to account for the possibility of there being CASTEP # 'custom' species amongst the symbols custom_species = None for a in data_dict['atoms']['atom']: spec_custom = a['species'].split(':', 1) if len(spec_custom) > 1 and custom_species is None: # Add it to the custom info! custom_species = list(symbols) symbols.append(spec_custom[0]) positions.append(a['position']) indices.append(a['index']) labels.append(a['label']) if custom_species is not None: custom_species.append(a['species']) atoms = Atoms(cell=cell, pbc=pbc, symbols=symbols, positions=positions) # Add custom species if present if custom_species is not None: atoms.new_array('castep_custom_species', np.array(custom_species)) # Add the spacegroup, if present and recognizable if 'symmetry' in data_dict['atoms']: try: spg = Spacegroup(data_dict['atoms']['symmetry'][0]) except: # Not found spg = Spacegroup(1) # Most generic one atoms.info['spacegroup'] = spg # Set up the rest of the properties as arrays atoms.new_array('indices', np.array(indices)) atoms.new_array('labels', np.array(labels)) # Now for the magres specific stuff li_list = list(zip(labels, indices)) def create_magres_array(name, order, block): if order == 1: u_arr = [None] * len(li_list) elif order == 2: u_arr = [[None] * (i + 1) for i in range(len(li_list))] else: raise ValueError( 'Invalid order value passed to create_magres_array') for s in block: # Find the atom index/indices if order == 1: # First find out which atom this is at = (s['atom']['label'], s['atom']['index']) try: ai = li_list.index(at) except ValueError: raise RuntimeError('Invalid data in magres block') # Then add the relevant quantity u_arr[ai] = s[mn] else: at1 = (s['atom1']['label'], s['atom1']['index']) at2 = (s['atom2']['label'], s['atom2']['index']) ai1 = li_list.index(at1) ai2 = li_list.index(at2) # Sort them ai1, ai2 = sorted((ai1, ai2), reverse=True) u_arr[ai1][ai2] = s[mn] return np.array(u_arr) if 'magres' in data_dict: if 'units' in data_dict['magres']: atoms.info['magres_units'] = dict(data_dict['magres']['units']) for u in atoms.info['magres_units']: # This bit to keep track of tags u0 = u.split('_')[0] if u0 not in _mprops: raise RuntimeError('Invalid data in magres block') mn, order = _mprops[u0] if order > 0: u_arr = create_magres_array(mn, order, data_dict['magres'][u]) atoms.new_array(u, u_arr) else: # atoms.info['magres_data'] = atoms.info.get('magres_data', # {}) # # We only take element 0 because for this sort of data # # there should be only that # atoms.info['magres_data'][u] = data_dict['magres'][u][0][mn] if atoms.calc is None: calc = SinglePointDFTCalculator(atoms) atoms.set_calculator(calc) atoms.calc.results[u] = data_dict['magres'][u][0][mn] if 'calculation' in data_dict: atoms.info['magresblock_calculation'] = data_dict['calculation'] if include_unrecognised: for b in data_dict: if b not in block_parsers: atoms.info['magresblock_' + b] = data_dict[b] return atoms
def main(): args = parse_args() print("args", args) if args.implementation == TORCHANI: from torchani_calculator import torchani_calculator calculator = torchani_calculator(args.network_type) elif args.implementation == AES_ANI: from ani_ase import ani_ase_calculator calculator = ani_ase_calculator(args.network_type) elif args.implementation == KHAN: from khan_calculator import khan_calculator calculator = khan_calculator(args.network_type, args.khan_network, args.numb_networks) assert args.cif_file.endswith('.cif') print('debug? ', args.debug) atoms = ase_io.read(args.cif_file) numb_molecules = len(molecule_lists(atoms)) print('number of molecules: ', numb_molecules) MC_stpes = args.MC space_group = Spacegroup(args.space_group) # step size in MC f1, f2, f3, f4, f5, f6, f7, f8 = args.MC_f1_f2_f3_f4_f5_f6_f7_f8 perturbation_type = args.perturbation_type molecule1_in_cell = [] for i in range(1, atoms.get_number_of_atoms()): if atom_belong_to_mol1(i, atoms): molecule1_in_cell.append(i) print("initial unit cell") print(atoms.cell) print("To do Monte Carlo for %d step." % MC_stpes) print("Molecule index in ASU are: ", molecule1_in_cell) translation_stats = [] rotation_stats = [] cell_length_a_stats = [] cell_length_b_stats = [] cell_length_c_stats = [] cell_angle_alpha_stats = [] cell_angle_beta_stats = [] cell_angle_gamma_stats = [] counter = 0 atoms_input = atoms.copy() while (counter <= MC_stpes): if isinstance(perturbation_type, list): # random_number = random.choice([1,2,3,4,5,6,7,8]) random_choices = get_random_choices( [f1, f2, f3, f4, f5, f6, f7, f8]) random_number = random.choice(random_choices) else: random_number = perturbation_dict[perturbation_type] if random_number == 1: # translation atoms_input.set_calculator(calculator) ret_atoms, message = Monte_Carlo(atoms_input, calculator, space_group, f1, 'translate', counter, molecule1_in_cell, MC_stpes, args.debug) translation_stats.append(message) counter += 1 atoms_input = ret_atoms.copy() elif random_number == 2: # rotation atoms_input.set_calculator(calculator) ret_atoms, message = Monte_Carlo(atoms_input, calculator, space_group, f2, 'rotation', counter, molecule1_in_cell, MC_stpes, args.debug) rotation_stats.append(message) counter += 1 atoms_input = ret_atoms.copy() elif random_number == 3: # cell_length a atoms_input.set_calculator(calculator) ret_atoms, message = Monte_Carlo(atoms_input, calculator, space_group, f3, 'cell_length_a', counter, molecule1_in_cell, MC_stpes, args.debug) cell_length_a_stats.append(message) counter += 1 atoms_input = ret_atoms.copy() elif random_number == 4: # cell_length b atoms_input.set_calculator(calculator) ret_atoms, message = Monte_Carlo(atoms_input, calculator, space_group, f4, 'cell_length_b', counter, molecule1_in_cell, MC_stpes, args.debug) cell_length_b_stats.append(message) counter += 1 atoms_input = ret_atoms.copy() elif random_number == 5: # cell_length c atoms_input.set_calculator(calculator) ret_atoms, message = Monte_Carlo(atoms_input, calculator, space_group, f5, 'cell_length_c', counter, molecule1_in_cell, MC_stpes, args.debug) cell_length_c_stats.append(message) counter += 1 atoms_input = ret_atoms.copy() elif random_number == 6: # cell_angle alpha atoms_input.set_calculator(calculator) ret_atoms, message = Monte_Carlo(atoms_input, calculator, space_group, f6, 'cell_angle_alpha', counter, molecule1_in_cell, MC_stpes, args.debug) cell_angle_alpha_stats.append(message) counter += 1 atoms_input = ret_atoms.copy() elif random_number == 7: # cell_angle beta atoms_input.set_calculator(calculator) ret_atoms, message = Monte_Carlo(atoms_input, calculator, space_group, f7, 'cell_angle_beta', counter, molecule1_in_cell, MC_stpes, args.debug) cell_angle_beta_stats.append(message) counter += 1 atoms_input = ret_atoms.copy() elif random_number == 8: # cell_angle gamma atoms_input.set_calculator(calculator) ret_atoms, message = Monte_Carlo(atoms_input, calculator, space_group, f8, 'cell_angle_gamma', counter, molecule1_in_cell, MC_stpes, args.debug) cell_angle_gamma_stats.append(message) counter += 1 atoms_input = ret_atoms.copy() #if counter % 1000 == 0: # with open("MC_trace_%d.json"%counter, "w") as fh: # json.dump(MC_trace, fh, indent=4) #with open("MC_trace.json", "w") as fh: # json.dump(MC_trace, fh, indent=4) #print("MC trace are recorded in: MC_trace.json") if len(translation_stats): print("Acceptance ratio of translation move is %f" % (countX("Accepted", translation_stats) / len(translation_stats))) if len(rotation_stats): print("Acceptance ratio of rotation move is %f" % (countX("Accepted", rotation_stats) / len(rotation_stats))) if len(cell_length_a_stats): print("Acceptance ratio of cell length a move is %f" % (countX("Accepted", cell_length_a_stats) / len(cell_length_a_stats))) if len(cell_length_b_stats): print("Acceptance ratio of cell length b move is %f" % (countX("Accepted", cell_length_b_stats) / len(cell_length_b_stats))) if len(cell_length_c_stats): print("Acceptance ratio of cell length c move is %f" % (countX("Accepted", cell_length_c_stats) / len(cell_length_c_stats))) if len(cell_angle_alpha_stats): print("Acceptance ratio of cell angle alpha move is %f" % (countX("Accepted", cell_angle_alpha_stats) / len(cell_angle_alpha_stats))) if len(cell_angle_beta_stats): print("Acceptance ratio of cell angle beta move is %f" % (countX("Accepted", cell_angle_beta_stats) / len(cell_angle_beta_stats))) if len(cell_angle_gamma_stats): print("Acceptance ratio of cell angle gamma move is %f" % (countX("Accepted", cell_angle_gamma_stats) / len(cell_angle_gamma_stats)))
def update(self, *args): """ all changes of physical constants are handled here, atoms are set up""" if self.clearing_in_process: return True self.update_element() a_equals = self.lattice_lequals[0].get_active() b_equals = self.lattice_lequals[1].get_active() c_equals = self.lattice_lequals[2].get_active() alpha_equals = self.lattice_aequals[0].get_active() beta_equals = self.lattice_aequals[1].get_active() gamma_equals = self.lattice_aequals[2].get_active() sym = self.spacegroup.get_text() valid = True try: no = int(sym) spg = Spacegroup(no).symbol self.spacegroupinfo.set_label(_('Symbol: %s') % str(spg)) spg = no except: try: no = Spacegroup(sym).no self.spacegroupinfo.set_label(_('Number: %s') % str(no)) spg = no except: self.spacegroupinfo.set_label(_('Invalid Spacegroup!')) valid = False if a_equals == 0: self.lattice_lbuts[0].set_sensitive(True) elif a_equals == 1: self.lattice_lbuts[0].set_sensitive(False) self.lattice_lbuts[0].set_value(self.lattice_lbuts[1].get_value()) elif a_equals == 2: self.lattice_lbuts[0].set_sensitive(False) self.lattice_lbuts[0].set_value(self.lattice_lbuts[2].get_value()) else: self.lattice_lbuts[0].set_sensitive(False) if b_equals == 0: self.lattice_lbuts[1].set_sensitive(True) elif b_equals == 1: self.lattice_lbuts[1].set_sensitive(False) self.lattice_lbuts[1].set_value(self.lattice_lbuts[0].get_value()) elif b_equals == 2: self.lattice_lbuts[1].set_sensitive(False) self.lattice_lbuts[1].set_value(self.lattice_lbuts[2].get_value()) else: self.lattice_lbuts[1].set_sensitive(False) if c_equals == 0: self.lattice_lbuts[2].set_sensitive(True) elif c_equals == 1: self.lattice_lbuts[2].set_sensitive(False) self.lattice_lbuts[2].set_value(self.lattice_lbuts[0].get_value()) elif c_equals == 2: self.lattice_lbuts[2].set_sensitive(False) self.lattice_lbuts[2].set_value(self.lattice_lbuts[1].get_value()) else: self.lattice_lbuts[2].set_sensitive(False) if alpha_equals == 0: self.lattice_abuts[0].set_sensitive(True) elif alpha_equals == 1: self.lattice_abuts[0].set_sensitive(False) self.lattice_abuts[0].set_value(self.lattice_abuts[1].get_value()) elif alpha_equals == 2: self.lattice_abuts[0].set_sensitive(False) self.lattice_abuts[0].set_value(self.lattice_abuts[2].get_value()) else: self.lattice_abuts[0].set_sensitive(False) if beta_equals == 0: self.lattice_abuts[1].set_sensitive(True) elif beta_equals == 1: self.lattice_abuts[1].set_sensitive(False) self.lattice_abuts[1].set_value(self.lattice_abuts[0].get_value()) elif beta_equals == 2: self.lattice_abuts[1].set_sensitive(False) self.lattice_abuts[1].set_value(self.lattice_abuts[2].get_value()) else: self.lattice_abuts[1].set_sensitive(False) if gamma_equals == 0: self.lattice_abuts[2].set_sensitive(True) elif gamma_equals == 1: self.lattice_abuts[2].set_sensitive(False) self.lattice_abuts[2].set_value(self.lattice_abuts[0].get_value()) elif gamma_equals == 2: self.lattice_abuts[2].set_sensitive(False) self.lattice_abuts[2].set_value(self.lattice_abuts[1].get_value()) else: self.lattice_abuts[2].set_sensitive(False) valid = len(self.elements[0][0].get_text()) and valid self.get_data.set_sensitive(valid and self.get_n_elements() == 1 and self.update_element()) self.atoms = None if valid: basis_count = -1 for el in self.elements: if el[-1]: basis_count += 1 if basis_count: symbol_str = '[' basis_str = "[" symbol = [] basis = [] else: symbol_str = '' basis_str = '' basis = None for el in self.elements: if el[-1]: symbol_str += "'" + el[0].get_text() + "'" if basis_count: symbol_str += ',' symbol += [el[0].get_text()] exec('basis += [[float(' + el[1].get_text() + '),float(' + el[2].get_text() + '),float(' + el[3].get_text() + ')]]') else: symbol = el[0].get_text() exec('basis = [[float(' + el[1].get_text() + '),float(' + el[2].get_text() + '),float(' + el[3].get_text() + ')]]') basis_str += '[' + el[1].get_text() + ',' + el[2].get_text( ) + ',' + el[3].get_text() + '],' basis_str = basis_str[:-1] if basis_count: symbol_str = symbol_str[:-1] + ']' basis_str += ']' size_str = '(' + str(int(self.size[0].get_value())) + ',' + str( int(self.size[1].get_value())) + ',' + str( int(self.size[2].get_value())) + ')' size = (int(self.size[0].get_value()), int(self.size[1].get_value()), int(self.size[2].get_value())) cellpar_str = '' cellpar = [] for i in self.lattice_lbuts: cellpar_str += str(i.get_value()) + ',' cellpar += [i.get_value()] for i in self.lattice_abuts: cellpar_str += str(i.get_value()) + ',' cellpar += [i.get_value()] cellpar_str = '[' + cellpar_str[:-1] + ']' args = { 'symbols': symbol, 'basis': basis, 'size': size, 'spacegroup': spg, 'cellpar': cellpar } args_str = { 'symbols': symbol_str, 'basis': basis_str, 'size': size_str, 'spacegroup': spg, 'cellpar': cellpar_str } self.pybut.python = py_template % args_str try: self.atoms = crystal(**args) label = label_template % { 'natoms': len(self.atoms), 'symbols': formula(self.atoms.get_atomic_numbers()), 'volume': self.atoms.get_volume() } self.status.set_label(label) except: self.atoms = None self.status.set_markup( _("Please specify a consistent set of atoms.")) else: self.atoms = None self.status.set_markup( _("Please specify a consistent set of atoms."))
def read(self): """ Read data from the CIF file """ with open(self.filename, 'r') as f: ciffile = ReadCif(f) for block in ciffile: spacegroup = Spacegroup(int(block['_space_group_IT_number'])) cellpar = np.array([ block['_cell_length_a'], block['_cell_length_b'], block['_cell_length_c'], block['_cell_angle_alpha'], block['_cell_angle_beta'], block['_cell_angle_gamma'], ], dtype=float) try: site_labels = block['_atom_site_label'] except KeyError: print('Could not get site labels from cif file') try: occupancies = np.array(block['_atom_site_occupancy'], dtype=float) except KeyError: print('Could not get occupancies from cif file') try: fract_x = np.array(block['_atom_site_fract_x'], dtype=float) fract_y = np.array(block['_atom_site_fract_y'], dtype=float) fract_z = np.array(block['_atom_site_fract_z'], dtype=float) except KeyError: warn( 'Could not get fractional coordinates from cif file, getting absolute coordinates instead.' ) try: x = np.array(block['_atom_site_cartn_x'], dtype=float) y = np.array(block['_atom_site_cartn_y'], dtype=float) z = np.array(block['_atom_site_cartn_z'], dtype=float) except KeyError: warn( 'Could not get absolute coordinates from cif file') x = [np.nan] * len(block) y = [np.nan] * len(block) z = [np.nan] * len(block) finally: fract_x = x / cellpar[0] fract_y = y / cellpar[1] fract_z = z / cellpar[2] else: x = fract_x * cellpar[0] y = fract_y * cellpar[1] z = fract_y * cellpar[2] finally: x = x.T y = y.T z = z.T fract_x = fract_x.T fract_y = fract_y.T fract_z = fract_z.T positions = np.array([x, y, z]) fractional_positions = np.array( [fract_x, fract_y, fract_z]) try: symbols = block['_atom_site_type_symbol'] except KeyError: print( 'Could not get atom site chemical symbols from cif file' ) try: dwf = block['_atom_site_B_iso_or_equiv'] except KeyError: print('Could not get Debye-Waller factors from cif file') basis_atoms = [] for label, occ, fx, fy, fz, symbol, B in zip( site_labels, occupancies, fractional_positions[0], fractional_positions[1], fractional_positions[2], symbols, dwf): atom = CIFAtom(cellpar, symbol=symbol, occupancy=occ, fractional_position=(fx, fy, fz), dwf=B, site_label=label) basis_atoms.append(atom) atoms = [] for atom in basis_atoms: equivalent_sites, kinds = spacegroup.equivalent_sites( atom.fractional_position, onduplicates='warn', occupancies=atom.occupancy) for site in equivalent_sites: position = site * cellpar[:3] equivalent_atom = CIFAtom(cellpar, fractional_position=site, site_label=atom.site_label, symbol=atom.symbol, dwf=atom.dwf, occupancy=atom.occupancy) atoms.append(equivalent_atom) self.atoms = atoms self.crystal = Crystal(atoms, cellpar)
def tag_sites(atoms): name = spglib.get_spacegroup(get_cell(atoms)) number = int(re.search(r'\((.*?)\)', name).group(1)) spg = Spacegroup(number) sites = spg.tag_sites(atoms.get_scaled_positions()) return sites
def crystal(symbols=None, basis=None, occupancies=None, spacegroup=1, setting=1, cell=None, cellpar=None, ab_normal=(0, 0, 1), a_direction=None, size=(1, 1, 1), onduplicates='warn', symprec=0.001, pbc=True, primitive_cell=False, **kwargs): """Create an Atoms instance for a conventional unit cell of a space group. Parameters: symbols : str | sequence of str | sequence of Atom | Atoms Element symbols of the unique sites. Can either be a string formula or a sequence of element symbols. E.g. ('Na', 'Cl') and 'NaCl' are equivalent. Can also be given as a sequence of Atom objects or an Atoms object. basis : list of scaled coordinates Positions of the unique sites corresponding to symbols given either as scaled positions or through an atoms instance. Not needed if *symbols* is a sequence of Atom objects or an Atoms object. occupancies : list of site occupancies Occupancies of the unique sites. Defaults to 1.0 and thus no mixed occupancies are considered if not explicitly asked for. If occupancies are given, the most dominant species will yield the atomic number. spacegroup : int | string | Spacegroup instance Space group given either as its number in International Tables or as its Hermann-Mauguin symbol. setting : 1 | 2 Space group setting. cell : 3x3 matrix Unit cell vectors. cellpar : [a, b, c, alpha, beta, gamma] Cell parameters with angles in degree. Is not used when `cell` is given. ab_normal : vector Is used to define the orientation of the unit cell relative to the Cartesian system when `cell` is not given. It is the normal vector of the plane spanned by a and b. a_direction : vector Defines the orientation of the unit cell a vector. a will be parallel to the projection of `a_direction` onto the a-b plane. size : 3 positive integers How many times the conventional unit cell should be repeated in each direction. onduplicates : 'keep' | 'replace' | 'warn' | 'error' Action if `basis` contain symmetry-equivalent positions: 'keep' - ignore additional symmetry-equivalent positions 'replace' - replace 'warn' - like 'keep', but issue an UserWarning 'error' - raises a SpacegroupValueError symprec : float Minimum "distance" betweed two sites in scaled coordinates before they are counted as the same site. pbc : one or three bools Periodic boundary conditions flags. Examples: True, False, 0, 1, (1, 1, 0), (True, False, False). Default is True. primitive_cell : bool Wheter to return the primitive instead of the conventional unit cell. Keyword arguments: All additional keyword arguments are passed on to the Atoms constructor. Currently, probably the most useful additional keyword arguments are `info`, `constraint` and `calculator`. Examples: Two diamond unit cells (space group number 227) >>> diamond = crystal('C', [(0,0,0)], spacegroup=227, ... cellpar=[3.57, 3.57, 3.57, 90, 90, 90], size=(2,1,1)) >>> ase.view(diamond) # doctest: +SKIP A CoSb3 skutterudite unit cell containing 32 atoms >>> skutterudite = crystal(('Co', 'Sb'), ... basis=[(0.25,0.25,0.25), (0.0, 0.335, 0.158)], ... spacegroup=204, cellpar=[9.04, 9.04, 9.04, 90, 90, 90]) >>> len(skutterudite) 32 """ sg = Spacegroup(spacegroup, setting) if (not isinstance(symbols, basestring) and hasattr(symbols, '__getitem__') and len(symbols) > 0 and isinstance(symbols[0], ase.Atom)): symbols = ase.Atoms(symbols) if isinstance(symbols, ase.Atoms): basis = symbols symbols = basis.get_chemical_symbols() if isinstance(basis, ase.Atoms): basis_coords = basis.get_scaled_positions() if cell is None and cellpar is None: cell = basis.cell if symbols is None: symbols = basis.get_chemical_symbols() else: basis_coords = np.array(basis, dtype=float, copy=False, ndmin=2) if occupancies is not None: occupancies_dict = {} for index, coord in enumerate(basis_coords): # Compute all distances and get indices of nearest atoms dist = spatial.distance.cdist(coord.reshape(1, 3), basis_coords) indices_dist = np.flatnonzero(dist < symprec) occ = {symbols[index]: occupancies[index]} # Check nearest and update occupancy for index_dist in indices_dist: if index == index_dist: continue else: occ.update({symbols[index_dist]: occupancies[index_dist]}) occupancies_dict[index] = occ.copy() sites, kinds = sg.equivalent_sites(basis_coords, onduplicates=onduplicates, symprec=symprec) # this is needed to handle deuterium masses masses = None if 'masses' in kwargs: masses = kwargs['masses'][kinds] del kwargs['masses'] symbols = parse_symbols(symbols) if occupancies is None: symbols = [symbols[i] for i in kinds] else: # make sure that we put the dominant species there symbols = [ sorted(occupancies_dict[i].items(), key=lambda x: x[1])[-1][0] for i in kinds ] if cell is None: cell = cellpar_to_cell(cellpar, ab_normal, a_direction) info = dict(spacegroup=sg) if primitive_cell: info['unit_cell'] = 'primitive' else: info['unit_cell'] = 'conventional' if 'info' in kwargs: info.update(kwargs['info']) if occupancies is not None: info['occupancy'] = occupancies_dict kwargs['info'] = info atoms = ase.Atoms(symbols, scaled_positions=sites, cell=cell, pbc=pbc, masses=masses, **kwargs) # if all occupancies are 1, no partial occupancy present if occupancies: if not all([occ == 1 for occ in occupancies]): # use tags to identify sites, and in particular the occupancy atoms.set_tags(kinds) if isinstance(basis, ase.Atoms): for name in basis.arrays: if not atoms.has(name): array = basis.get_array(name) atoms.new_array(name, [array[i] for i in kinds], dtype=array.dtype, shape=array.shape[1:]) if primitive_cell: from ase.build import cut prim_cell = sg.scaled_primitive_cell # Preserve calculator if present: calc = atoms.calc atoms = cut(atoms, a=prim_cell[0], b=prim_cell[1], c=prim_cell[2]) atoms.calc = calc if size != (1, 1, 1): atoms = atoms.repeat(size) return atoms
print("after translation ", molecule_lists(ret_atoms_translate)) ret_atoms = ret_atoms_translate.copy() return ret_atoms if __name__ == "__main__": print("runing main function for debugging...") atoms = ase_io.read(sys.argv[1]) spacegroup_number = sys.argv[2] molecule1_atoms_index = molecule_lists(atoms)[0] print(molecule_lists(atoms)) print("molecule1_atoms_index: ", molecule1_atoms_index) if spacegroup_number: space_group = Spacegroup(int(spacegroup_number)) else: space_group = spacegroup.get_spacegroup(atoms) print("before perturbation: ", space_group) # first enlarge cell and then change the anlge. #ret_atoms = ret_atoms_translate.copy() # atoms_input = atoms.copy() # ret_atoms = generate_perturb(atoms_input) # dummy input ret_atoms = Atoms('Au', positions=[[0, 10 / 2, 10 / 2]], cell=[10, 10, 10], pbc=[1, 0, 0]) counter = 1 while (len(molecule_lists(atoms)) != len(molecule_lists(ret_atoms))):
def crystal(symbols=None, basis=None, spacegroup=1, setting=1, cell=None, cellpar=None, ab_normal=(0, 0, 1), a_direction=None, size=(1, 1, 1), onduplicates='warn', symprec=0.001, pbc=True, primitive_cell=False, **kwargs): """Create an Atoms instance for a conventional unit cell of a space group. Parameters: symbols : str | sequence of str | sequence of Atom | Atoms Element symbols of the unique sites. Can either be a string formula or a sequence of element symbols. E.g. ('Na', 'Cl') and 'NaCl' are equivalent. Can also be given as a sequence of Atom objects or an Atoms object. basis : list of scaled coordinates Positions of the unique sites corresponding to symbols given either as scaled positions or through an atoms instance. Not needed if *symbols* is a sequence of Atom objects or an Atoms object. spacegroup : int | string | Spacegroup instance Space group given either as its number in International Tables or as its Hermann-Mauguin symbol. setting : 1 | 2 Space group setting. cell : 3x3 matrix Unit cell vectors. cellpar : [a, b, c, alpha, beta, gamma] Cell parameters with angles in degree. Is not used when `cell` is given. ab_normal : vector Is used to define the orientation of the unit cell relative to the Cartesian system when `cell` is not given. It is the normal vector of the plane spanned by a and b. a_direction : vector Defines the orientation of the unit cell a vector. a will be parallel to the projection of `a_direction` onto the a-b plane. size : 3 positive integers How many times the conventional unit cell should be repeated in each direction. onduplicates : 'keep' | 'replace' | 'warn' | 'error' Action if `basis` contain symmetry-equivalent positions: 'keep' - ignore additional symmetry-equivalent positions 'replace' - replace 'warn' - like 'keep', but issue an UserWarning 'error' - raises a SpacegroupValueError symprec : float Minimum "distance" betweed two sites in scaled coordinates before they are counted as the same site. pbc : one or three bools Periodic boundary conditions flags. Examples: True, False, 0, 1, (1, 1, 0), (True, False, False). Default is True. primitive_cell : bool Wheter to return the primitive instead of the conventional unit cell. Keyword arguments: All additional keyword arguments are passed on to the Atoms constructor. Currently, probably the most useful additional keyword arguments are `info`, `constraint` and `calculator`. Examples: Two diamond unit cells (space group number 227) >>> diamond = crystal('C', [(0,0,0)], spacegroup=227, ... cellpar=[3.57, 3.57, 3.57, 90, 90, 90], size=(2,1,1)) >>> ase.view(diamond) # doctest: +SKIP A CoSb3 skutterudite unit cell containing 32 atoms >>> skutterudite = crystal(('Co', 'Sb'), ... basis=[(0.25,0.25,0.25), (0.0, 0.335, 0.158)], ... spacegroup=204, cellpar=[9.04, 9.04, 9.04, 90, 90, 90]) >>> len(skutterudite) 32 """ sg = Spacegroup(spacegroup, setting) if (not isinstance(symbols, str) and hasattr(symbols, '__getitem__') and len(symbols) > 0 and isinstance(symbols[0], ase.Atom)): symbols = ase.Atoms(symbols) if isinstance(symbols, ase.Atoms): basis = symbols symbols = basis.get_chemical_symbols() if isinstance(basis, ase.Atoms): basis_coords = basis.get_scaled_positions() if cell is None and cellpar is None: cell = basis.cell if symbols is None: symbols = basis.get_chemical_symbols() else: basis_coords = np.array(basis, dtype=float, copy=False, ndmin=2) sites, kinds = sg.equivalent_sites(basis_coords, onduplicates=onduplicates, symprec=symprec) symbols = parse_symbols(symbols) symbols = [symbols[i] for i in kinds] if cell is None: cell = cellpar_to_cell(cellpar, ab_normal, a_direction) info = dict(spacegroup=sg) if primitive_cell: info['unit_cell'] = 'primitive' else: info['unit_cell'] = 'conventional' if 'info' in kwargs: info.update(kwargs['info']) kwargs['info'] = info atoms = ase.Atoms(symbols, scaled_positions=sites, cell=cell, pbc=pbc, **kwargs) if isinstance(basis, ase.Atoms): for name in basis.arrays: if not atoms.has(name): array = basis.get_array(name) atoms.new_array(name, [array[i] for i in kinds], dtype=array.dtype, shape=array.shape[1:]) if primitive_cell: from ase.build import cut prim_cell = sg.scaled_primitive_cell atoms = cut(atoms, a=prim_cell[0], b=prim_cell[1], c=prim_cell[2]) if size != (1, 1, 1): atoms = atoms.repeat(size) return atoms
import numpy as np import ase from ase.visualize import view from ase.spacegroup import crystal, Spacegroup a = b = 4.9134 c = 5.4052 alpha = beta = 90 gamma = 120 Si = ase.Atom("Si", [0.4699, 0, 2 / 3]) O = ase.Atom("O", [0.4141, 0.2681, 0.1188 + 2 / 3]) spacegroup = Spacegroup(154) cell = crystal([Si, O], spacegroup=spacegroup, cellpar=[a, b, c, alpha, beta, gamma]) ase.io.write("alpha-quartz.eps", cell, rotation="10x,-10y", show_unit_cell=2) cell = cell.repeat([2, 2, 1]) cell.set_cell([[a, 0, 0], [0, b * np.sqrt(3), 0], [0, 0, c]]) cell.wrap() positions = cell.get_positions() atomic_numbers = cell.get_atomic_numbers() basis_vectors = cell.get_cell() ase.geometry.get_duplicate_atoms(cell, delete=True) with open("orthogonal_alpha_quartz.data", "w") as outfile: outfile.write("# Orthogonal alpha quartz supercell\n\n") outfile.write(f"{len(positions)} atoms\n" "2 atom types\n"
def _dlite_get_info(self): d = self.info.copy() sg = Spacegroup(d.get('spacegroup', 'P 1')) d['spacegroup'] = sg.symbol return [(k, str(v)) for k, v in d.items()]