def coordgroups_reduced_rc_to_unitcellsites(coordgroups, basis, hall_symbol, backends=[ 'cif2cell', 'internal', 'ase' ]): # TODO: Make own, or use cif2cell to generate reduced unitcell if hall_symbol == 'P 1': return UnitcellSites.create( reduced_coordgroups=coordgroups), Cell.create(basis) for backend in backends: if backend == 'internal': newcoordgroups, newcell = internal_coordgroups_reduced_rc_to_unitcellsites( coordgroups, basis, hall_symbol) return UnitcellSites.create( reduced_coordgroups=newcoordgroups), Cell.create(basis) if backend == 'cif2cell': try: from httk.external import cif2cell_ext return cif2cell_ext.coordgroups_reduced_rc_to_unitcellsites( coordgroups, basis, hall_symbol) except ImportError: pass if backend == 'ase': try: from httk.external import ase_glue return ase_glue.coordgroups_reduced_rc_to_unitcellsites( coordgroups, basis, hall_symbol) except ImportError: raise pass raise Exception( "structure_to_p1structure: None of the available backends available.")
def cubic_supercell_transformation(structure, tolerance=None, max_search_cells=1000): # Note: a better name for tolerance is max_extension or similar, it is not really a tolerance, it regulates the maximum number of repetitions of the primitive cell # in any directions to reach the soughts supercell if tolerance is None: prim_cell = structure.uc_cell.basis inv = prim_cell.inv().simplify() transformation = (inv * inv.denom).simplify() else: maxtol = max(int(FracVector.use(tolerance)), 2) bestlen = None bestortho = None besttrans = None #TODO: This loop may be possible to do with fewer iterations, since I suppose the only thing that #matter is the prime factors? for tol in range(1, maxtol): prim_cell = structure.uc_cell.basis prim_cell = structure.uc_cell.basis approxinv = prim_cell.inv().set_denominator(tol).simplify() if approxinv[0] == [0, 0, 0] or approxinv[1] == [ 0, 0, 0 ] or approxinv[2] == [0, 0, 0]: continue transformation = (approxinv * approxinv.denom).simplify() try: cell = Cell.create(basis=transformation * prim_cell) except Exception: continue ortho = (abs(cell.niggli_matrix[1][0]) + abs(cell.niggli_matrix[1][1]) + abs(cell.niggli_matrix[1][2])).simplify() equallen = abs(cell.niggli_matrix[0][0] - cell.niggli_matrix[0][1] ) + abs(cell.niggli_matrix[0][0] - cell.niggli_matrix[0][2]) if ortho == 0 and equallen == 0: # Already perfectly cubic, use this besttrans = transformation break elif bestlen is None or not (bestortho < ortho and bestlen < equallen): bestlen = equallen bestortho = ortho besttrans = transformation elif besttrans == None: bestlen = equallen bestortho = ortho besttrans = transformation transformation = besttrans if transformation == None: raise Exception( "Not possible to find a cubic supercell with this limitation of number of repeated cell (increase tolerance.)" ) return transformation
def transform(structure, transformation, max_search_cells=20, max_atoms=1000): transformation = FracVector.use(transformation).simplify() #if transformation.denom != 1: # raise Exception("Structure.transform requires integer transformation matrix") old_cell = structure.uc_cell new_cell = Cell.create(basis=transformation * old_cell.basis) conversion_matrix = (old_cell.basis * new_cell.inv).simplify() volume_ratio = abs( (new_cell.basis.det() / abs(old_cell.basis.det()))).simplify() seek_counts = [ int((volume_ratio * x).simplify()) for x in structure.uc_counts ] #print("HMM",(new_cell.basis.det()/old_cell.basis.det()).simplify()) #print("SEEK_COUNTS",seek_counts, volume_ratio, structure.uc_counts, transformation) total_seek_counts = sum(seek_counts) if total_seek_counts > max_atoms: raise Exception("Structure.transform: more than " + str(max_atoms) + " needed. Change limit with max_atoms parameter.") #if max_search_cells != None and maxvec[0]*maxvec[1]*maxvec[2] > max_search_cells: # raise Exception("Very obtuse angles in cell, to search over all possible lattice vectors will take a very long time. To force, set max_search_cells = None when calling find_prototypeid()") ### Collect coordinate list of all sites inside the new cell coordgroups = structure.uc_reduced_coordgroups extendedcoordgroups = [[] for x in range(len(coordgroups))] if max_search_cells is not None: max_search = [max_search_cells, max_search_cells, max_search_cells] else: max_search = None for offset in breath_first_idxs(dim=3, end=max_search, negative=True): #print("X",offset, seek_counts) for idx in range(len(coordgroups)): coordgroup = coordgroups[idx] newcoordgroup = coordgroup + FracVector([offset] * len(coordgroup)) new_reduced = newcoordgroup * conversion_matrix #print("NEW:",FracVector.use(new_reduced).to_floats(),) new_reduced = [ x for x in new_reduced if x[0] >= 0 and x[1] >= 0 and x[2] >= 0 and x[0] < 1 and x[1] < 1 and x[2] < 1 ] extendedcoordgroups[idx] += new_reduced c = len(new_reduced) seek_counts[idx] -= c total_seek_counts -= c #print("ADD",str(c)) if seek_counts[idx] < 0: #print("X",offset, seek_counts) raise Exception( "Structure.transform safety check error, internal error: too many atoms in supercell." ) if total_seek_counts == 0: break else: raise Exception( "Very obtuse angles in cell, to search over all possible lattice vectors will take a very long time. To force, set max_search_cells = None when calling find_prototypeid()" ) return structure.create(uc_reduced_coordgroups=extendedcoordgroups, uc_basis=new_cell.basis, assignments=structure.assignments)
def build_supercell_old(structure, transformation, max_search_cells=1000): ### New basis matrix, note: works in units of old_cell.scale to avoid floating point errors #print("BUILD SUPERCELL",structure.uc_sites.cell.basis.to_floats(), repetitions) transformation = FracVector.use(transformation).simplify() if transformation.denom != 1: raise Exception( "Structure.build_supercell requires integer transformation matrix") old_cell = structure.uc_sites.cell.get_normalized_longestvec() new_cell = Cell.create(basis=transformation * old_cell.basis) #conversion_matrix = (new_cell.inv*old_cell.basis).T().simplify() conversion_matrix = (old_cell.basis * new_cell.inv).T().simplify() volume_ratio = (new_cell.basis.det() / abs(old_cell.basis.det())).simplify() # Generate the reduced (old cell) coordinates of each corner in the new cell # This determines how far we must loop the old cell to cover all these corners nb = new_cell.basis corners = FracVector.create([(0, 0, 0), nb[0], nb[1], nb[2], nb[0] + nb[1], nb[0] + nb[2], nb[1] + nb[2], nb[0] + nb[1] + nb[2]]) reduced_corners = corners * (old_cell.basis.inv().T()) maxvec = [ int(reduced_corners[:, 0].max()) + 2, int(reduced_corners[:, 1].max()) + 2, int(reduced_corners[:, 2].max()) + 2 ] minvec = [ int(reduced_corners[:, 0].min()) - 2, int(reduced_corners[:, 1].min()) - 2, int(reduced_corners[:, 2].min()) - 2 ] if max_search_cells is not None and maxvec[0] * maxvec[1] * maxvec[ 2] > max_search_cells: raise Exception( "Very obtuse angles in cell, to search over all possible lattice vectors will take a very long time. To force, set max_search_cells = None when calling find_prototypeid()" ) ### Collect coordinate list of all sites inside the new cell coordgroups = structure.uc_reduced_coordgroups extendedcoordgroups = [[] for x in range(len(coordgroups))] for idx in range(len(coordgroups)): coordgroup = coordgroups[idx] for i in range(minvec[0], maxvec[0]): for j in range(minvec[1], maxvec[1]): for k in range(minvec[2], maxvec[2]): newcoordgroup = coordgroup + FracVector( ((i, j, k), ) * len(coordgroup)) new_reduced = newcoordgroup * conversion_matrix new_reduced = [ x for x in new_reduced if x[0] >= 0 and x[1] >= 0 and x[2] >= 0 and x[0] < 1 and x[1] < 1 and x[2] < 1 ] extendedcoordgroups[idx] += new_reduced # Safety check for avoiding bugs that change the ratio of atoms new_counts = [len(x) for x in extendedcoordgroups] for i in range(len(structure.uc_counts)): if volume_ratio * structure.uc_counts[i] != new_counts[i]: print("Volume ratio:", float(volume_ratio), volume_ratio) print("Extended coord groups:", FracVector.create(extendedcoordgroups).to_floats()) print("Old counts:", structure.uc_counts, structure.assignments.symbols) print("New counts:", new_counts, structure.assignments.symbols) #raise Exception("Structure.build_supercell safety check failure. Volume changed by factor "+str(float(volume_ratio))+", but atoms in group "+str(i)+" changed by "+str(float(new_counts[i])/float(structure.uc_counts[i]))) return structure.create(uc_reduced_coordgroups=extendedcoordgroups, basis=new_cell.basis, assignments=structure.assignments, cell=structure.uc_cell)
def orthogonal_supercell_transformation(structure, tolerance=None, ortho=[True, True, True]): # TODO: How to solve for exact orthogonal cell? if tolerance is None: prim_cell = structure.uc_cell.basis print("Starting cell:", prim_cell) inv = prim_cell.inv().simplify() if ortho[0]: row0 = (inv[0] / max(inv[0])).simplify() else: row0 = [1, 0, 0] if ortho[1]: row1 = (inv[1] / max(inv[1])).simplify() else: row1 = [0, 1, 0] if ortho[2]: row2 = (inv[2] / max(inv[2])).simplify() else: row2 = [0, 0, 1] transformation = FracVector.create( [row0 * row0.denom, row1 * row1.denom, row2 * row2.denom]) else: maxtol = max(int(FracVector.use(tolerance)), 2) bestval = None besttrans = None for tol in range(1, maxtol): prim_cell = structure.uc_cell.basis inv = prim_cell.inv().set_denominator(tol).simplify() if inv[0] == [0, 0, 0] or inv[1] == [0, 0, 0 ] or inv[2] == [0, 0, 0]: continue absinv = abs(inv) if ortho[0]: row0 = (inv[0] / max(absinv[0])).simplify() else: row0 = [1, 0, 0] if ortho[1]: row1 = (inv[1] / max(absinv[1])).simplify() else: row1 = [0, 1, 0] if ortho[2]: row2 = (inv[2] / max(absinv[2])).simplify() else: row2 = [0, 0, 1] transformation = FracVector.create( [row0 * row0.denom, row1 * row1.denom, row2 * row2.denom]) try: cell = Cell.create(basis=transformation * prim_cell) except Exception: continue maxval = (abs(cell.niggli_matrix[1][0]) + abs(cell.niggli_matrix[1][1]) + abs(cell.niggli_matrix[1][2])).simplify() if maxval == 0: besttrans = transformation break if bestval is None or maxval < bestval: bestval = maxval besttrans = transformation transformation = besttrans if transformation == None: raise Exception( "Not possible to find a othogonal supercell with this limitation of number of repeated cell (increase tolerance.)" ) return transformation
def create(cls, structure=None, rc_cell=None, rc_basis=None, rc_lengths=None, rc_angles=None, rc_niggli_matrix=None, rc_metric=None, rc_a=None, rc_b=None, rc_c=None, rc_alpha=None, rc_beta=None, rc_gamma=None, rc_sites=None, rc_reduced_coordgroups=None, rc_cartesian_coordgroups=None, rc_reduced_coords=None, rc_cartesian_coords=None, rc_reduced_occupationscoords=None, rc_cartesian_occupationscoords=None, rc_occupancies=None, rc_counts=None, wyckoff_symbols=None, multiplicities=None, spacegroup=None, hall_symbol=None, spacegroupnumber=None, setting=None, rc_scale=None, rc_scaling=None, rc_volume=None, vol_per_atom=None, assignments=None, periodicity=None, nonperiodic_vecs=None, refs=None, tags=None): """ A Structure represents N sites of, e.g., atoms or ions, in any periodic or non-periodic arrangement. This is a swiss-army-type constructor that allows a selection between a large number of optional arguments. To create a new structure, three primary components are: - cell: defines the basis vectors in which reduced coordinates are expressed, and the unit of repetition (*if* the structure has any periodicity - see the 'periodicity' parameter) - assignments: a list of 'things' (atoms, ions, etc.) that goes on the sites in the structure - sites: a sensible representation of location / coordinates of the sites. Note: `rc_`-prefixes are consistently enforced for any quantity that would be different in a UnitcellStructure. This is to allow for painless change between the various structure-type objects without worrying about accidently using the wrong type of sites object. Input parameters: - ONE OF: 'cell'; 'basis', 'length_and_angles'; 'niggli_matrix'; 'metric'; all of: a,b,c, alpha, beta, gamma. (cell requires a Cell object or a very specific format, so unless you know what you are doing, use one of the others.) - ONE OF: 'assignments', 'atomic_numbers', 'occupancies' (assignments requires an Assignments object or a sequence.), occupations repeats similar site assignments as needed - ONE OF: 'rc_sites', 'rc_coords' (IF rc_occupations OR rc_counts are also given), 'uc_coords' (IF uc_occupations OR uc_counts are also given) 'rc_B_C', where B=reduced or cartesian, C=coordgroups, coords, or occupationscoords Notes: - occupationscoords may differ from coords by *order*, since giving occupations as, e.g., ['H','O','H'] does not necessarily have the same order of the coordinates as the format of counts+coords as (2,1), ['H','O']. - rc_sites and uc_sites requires a Sites object or a very specific format, so unless you know what you are doing, use one of the others.) - ONE OF: scale or volume: scale = multiply the basis vectors with this scaling factor, volume = the representative (conventional) cell volume (overrides 'scale' if both are given) volume_per_atom = cell volume / number of atoms - ONE OF periodicity or nonperiodic_vecs See help(Structure) for more information on the data format of all these data representations. """ if structure is not None: return cls.use(structure) if isinstance(spacegroup, Spacegroup): hall_symbol = spacegroup.hall_symbol else: try: spacegroupobj = Spacegroup.create( spacegroup=spacegroup, hall_symbol=hall_symbol, spacegroupnumber=spacegroupnumber, setting=setting) hall_symbol = spacegroupobj.hall_symbol except Exception: spacegroupobj = None hall_symbol = None if rc_cell is not None: rc_cell = Cell.use(rc_cell) else: try: rc_cell = Cell.create(cell=rc_cell, basis=rc_basis, metric=rc_metric, niggli_matrix=rc_niggli_matrix, a=rc_a, b=rc_b, c=rc_c, alpha=rc_alpha, beta=rc_beta, gamma=rc_gamma, lengths=rc_lengths, angles=rc_angles, scale=rc_scale, scaling=rc_scaling, volume=rc_volume) except Exception: rc_cell = None if rc_sites is not None: rc_sites = RepresentativeSites.use(rc_sites) else: if rc_reduced_coordgroups is None and \ rc_reduced_coords is None and \ rc_occupancies is not None: # Structure created by occupationscoords and occupations, this is a slightly tricky representation if rc_reduced_occupationscoords is not None: assignments, rc_reduced_coordgroups = occupations_and_coords_to_assignments_and_coordgroups( rc_reduced_occupationscoords, rc_occupancies) if rc_reduced_coordgroups is not None or \ rc_reduced_coords is not None: try: rc_sites = RepresentativeSites.create( reduced_coordgroups=rc_reduced_coordgroups, reduced_coords=rc_reduced_coords, counts=rc_counts, hall_symbol=hall_symbol, periodicity=periodicity, wyckoff_symbols=wyckoff_symbols, multiplicities=multiplicities, occupancies=rc_occupancies) except Exception as e: raise #print("Ex",e) rc_sites = None else: rc_sites = None if rc_sites is None and rc_reduced_coordgroups is None and \ rc_reduced_coords is None and rc_reduced_occupationscoords is None: # Cartesian coordinate input must be handled here in structure since scalelessstructure knows nothing about cartesian coordinates... if rc_cartesian_coordgroups is None and rc_cartesian_coords is None and \ rc_occupancies is not None and rc_cartesian_occupationscoords is not None: assignments, rc_cartesian_coordgroups = occupations_and_coords_to_assignments_and_coordgroups( rc_cartesian_occupationscoords, rc_occupancies) if rc_cartesian_coords is not None and rc_cartesian_coordgroups is None: rc_cartesian_coordgroups = coords_and_counts_to_coordgroups( rc_cartesian_coords, rc_counts) if rc_cell is not None: rc_reduced_coordgroups = coordgroups_cartesian_to_reduced( rc_cartesian_coordgroups, rc_cell) if rc_sites is None: raise Exception( "Structure.create: representative sites specifications invalid." ) if assignments is not None: assignments = Assignments.use(assignments) if assignments is None or (rc_sites is None or rc_cell is None or hall_symbol is None): raise Exception( "Structure.create: not enough information given to create a structure object." ) new = cls(assignments=assignments, rc_sites=rc_sites, rc_cell=rc_cell) return new
def create(cls, structure=None, uc_cell=None, uc_basis=None, uc_lengths=None, uc_angles=None, uc_niggli_matrix=None, uc_metric=None, uc_a=None, uc_b=None, uc_c=None, uc_alpha=None, uc_beta=None, uc_gamma=None, uc_sites=None, uc_reduced_coordgroups=None, uc_cartesian_coordgroups=None, uc_reduced_coords=None, uc_cartesian_coords=None, uc_reduced_occupationscoords=None, uc_cartesian_occupationscoords=None, uc_occupancies=None, uc_counts=None, uc_scale=None, uc_scaling=None, uc_volume=None, volume_per_atom=None, assignments=None, periodicity=None, nonperiodic_vecs=None, other_reps=None, refs=None, tags=None): """ A FullStructure represents N sites of, e.g., atoms or ions, in any periodic or non-periodic arrangement, where the positions of all cites are given (as opposed to a set of unique sites + symmetry operations). This is a swiss-army-type constructor that allows several different ways to create a FullStructure object. To create a new structure, three primary components are: - cell: defines the basis vectors in which reduced coordinates are expressed, and the unit of repetition (*if* the structure has any periodicity - see the 'periodicity' parameter) - assignments: a list of 'things' (atoms, ions, etc.) that goes on the sites in the structure - sites: a sensible representation of location / coordinates of the sites. Note: `uc_`-prefixes are consistently enforced for any quantity that would be different in a UniqueSitesStructure. This is to allow for painless change between the various structure-type objects without worrying about accidently using the wrong type of sites object. Note: see help(Structure) for parameter naming conventions, i.e., what type of object is expected given a parameter name. Input parameters: - ONE OF: 'uc_cell'; 'uc_basis', 'uc_length_and_angles'; 'uc_niggli_matrix'; 'uc_metric'; all of: uc_a,uc_b,uc_c, uc_alpha, uc_beta, uc_gamma. (cell requires a Cell object or a very specific format, so unless you know what you are doing, use one of the others.) - ONE OF: 'uc_assignments', 'uc_atomic_numbers', 'uc_occupations' (uc_assignments requires an Assignments object or a sequence.), uc_occupations repeats similar site assignments as needed - ONE OF: 'uc_sites', 'uc_coords' (IF uc_occupations OR uc_counts are also given), or 'uc_B_C', where B=reduced or cartesian, C=coordgroups, coords, or occupationscoords Notes: - occupationscoords may differ from coords by *order*, since giving occupations as, e.g., ['H','O','H'] does not necessarily have the same order of the coordinates as the format of counts+coords as (2,1), ['H','O']. - uc_sites requires a Sites object or a python list on a very specific format, (so unless you know what you are doing, use one of the others.) - ONE OF: uc_scale, uc_volume, or volume_per_atom: scale = multiply the basis vectors with this scaling factor, volume = the unit cell volume (overrides 'scale' if both are given) volume_per_atom = cell volume / number of atoms - ONE OF periodicity or nonperiodic_vecs """ if structure is not None: UnitcellStructure.use(structure) #TODO: Handle that vollume_per_atom is given instead, move this block below sorting out sites and if uc_volume is not set, #calculate a new volume if uc_cell is not None: Cell.use(uc_cell) else: uc_cell = Cell.create(cell=uc_cell, basis=uc_basis, metric=uc_metric, niggli_matrix=uc_niggli_matrix, a=uc_a, b=uc_b, c=uc_c, alpha=uc_alpha, beta=uc_beta, gamma=uc_gamma, lengths=uc_lengths, angles=uc_angles, scale=uc_scale, scaling=uc_scaling, volume=uc_volume) if uc_sites is not None: uc_sites = UnitcellSites.use(uc_sites) else: if uc_reduced_coordgroups is None and \ uc_reduced_coords is None and \ uc_occupancies is not None: # Structure created by occupationscoords and occupations, this is a slightly tricky representation if uc_reduced_occupationscoords is not None: assignments, uc_reduced_coordgroups = occupations_and_coords_to_assignments_and_coordgroups( uc_reduced_occupationscoords, uc_occupancies) if uc_reduced_coordgroups is not None or \ uc_reduced_coords is not None: try: uc_sites = UnitcellSites.create( reduced_coordgroups=uc_reduced_coordgroups, reduced_coords=uc_reduced_coords, counts=uc_counts, periodicity=periodicity, occupancies=uc_occupancies) except Exception: uc_sites = None else: uc_sites = None if uc_sites is None and uc_reduced_coordgroups is None and \ uc_reduced_coords is None and uc_reduced_occupationscoords is None: # Cartesian coordinate input must be handled here in structure since scalelessstructure knows nothing about cartesian coordinates... if uc_cartesian_coordgroups is None and uc_cartesian_coords is None and \ uc_occupancies is not None and uc_cartesian_occupationscoords is not None: assignments, uc_cartesian_coordgroups = occupations_and_coords_to_assignments_and_coordgroups( uc_cartesian_occupationscoords, uc_occupancies) if uc_cartesian_coords is not None and uc_cartesian_coordgroups is None: uc_cartesian_coordgroups = coords_and_counts_to_coordgroups( uc_cartesian_coords, uc_counts) if uc_cell is not None: uc_reduced_coordgroups = coordgroups_cartesian_to_reduced( uc_cartesian_coordgroups, uc_cell) if assignments is not None: assignments = Assignments.use(assignments) if uc_sites is None: raise Exception( "Structure.create: not enough information for information about sites." ) new = cls(assignments=assignments, uc_sites=uc_sites, uc_cell=uc_cell) return new