def __init__(self, niggli_matrix, orientation=1, basis=None): """ Private constructor, as per httk coding guidelines. Use Cell.create instead. """ self.niggli_matrix = niggli_matrix self.orientation = orientation if basis is None: basis = FracVector.use( niggli_to_basis(niggli_matrix, orientation=orientation)) c = basis maxele = max(c[0, 0], c[0, 1], c[0, 2], c[1, 0], c[1, 1], c[1, 2], c[2, 0], c[2, 1], c[2, 2]) maxeleneg = max(-c[0, 0], -c[0, 1], -c[0, 2], -c[1, 0], -c[1, 1], -c[1, 2], -c[2, 0], -c[2, 1], -c[2, 2]) if maxeleneg > maxele: scale = (-maxeleneg).simplify() else: scale = (maxele).simplify() basis = (basis * scale.inv()).simplify() self._basis = basis self.det = basis.det() self.inv = basis.inv() self.volume = abs(self.det) self.metric = niggli_to_metric(self.niggli_matrix) self.lengths, self.angles = niggli_to_lengths_angles( self.niggli_matrix) self.lengths = [FracVector.use(x).simplify() for x in self.lengths] self.angles = [FracVector.use(x).simplify() for x in self.angles] self.a, self.b, self.c = self.lengths self.alpha, self.beta, self.gamma = self.angles
def add_phase(self, symbols, counts, id, energy): """ Handles energy=None, for a phase we don't know the energy of. """ counts = FracVector.use(counts) phase = {} # In Python 3 symbols in an iterator, so we should convert it to # a list. symbols = list(symbols) for i in range(len(symbols)): symbol = symbols[i] if symbol in phase: phase[symbol] += counts[i] else: phase[symbol] = counts[i] self.seen_symbols[symbol] = True if energy is not None: self.phases += [phase] self.energies += [energy] self.ids += [id] else: self.other_phases += [phase] self.other_ids += [id] # Clear out so things get reevaluated self._reset()
def niggli_vol_to_scale(niggli_matrix, vol): niggli_matrix = FracVector.use(niggli_matrix) metric = niggli_to_metric(niggli_matrix) volsqr = metric.det() if volsqr == 0: raise Exception("niggli_vol_to_scale: singular cell matrix.") det = sqrt(float(volsqr)) return (float(vol) / det)**(1.0 / 3.0)
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 niggli_scale_to_vol(niggli_matrix, scale): niggli_matrix = FracVector.use(niggli_matrix) metric = niggli_to_metric(niggli_matrix) volsqr = metric.det() if volsqr == 0: raise Exception("niggli_vol_to_scale: singular cell matrix.") det = sqrt(float(volsqr)) if abs(det) < 1e-12: raise Exception("niggli_scale_to_vol: singular cell matrix.") return (((scale)**3) * det)
def reduced_to_cartesian(cell, coordgroups): cell = FracVector.use(cell) newcoordgroups = [] for coordgroup in coordgroups: newcoordgroup = coordgroup * cell newcoordgroups.append(newcoordgroup) return newcoordgroups
def coordgroups_cartesian_to_reduced(coordgroups, basis): basis = FracVector.use(basis) cellinv = basis.inv() newcoordgroups = [] for coordgroup in coordgroups: newcoordgroup = coordgroup * cellinv newcoordgroups.append(newcoordgroup) return newcoordgroups
def cartesian_to_reduced(cell, coordgroups): cell = FracVector.use(cell) cellinv = cell.inv() newcoordgroups = [] for coordgroup in coordgroups: newcoordgroup = coordgroup * cellinv newcoordgroups.append(newcoordgroup) return newcoordgroups
def niggli_to_cell_old(niggli_matrix, orientation=1): cell = FracVector.use(niggli_matrix) niggli_matrix = niggli_matrix.to_floats() s11, s22, s33 = niggli_matrix[0][0], niggli_matrix[0][1], niggli_matrix[0][ 2] s23, s13, s12 = niggli_matrix[1][0] / 2.0, niggli_matrix[1][ 1] / 2.0, niggli_matrix[1][2] / 2.0 a, b, c = sqrt(s11), sqrt(s22), sqrt(s33) alpha_rad, beta_rad, gamma_rad = acos(s23 / (b * c)), acos( s13 / (c * a)), acos(s12 / (a * b)) iv = 1 - cos(alpha_rad)**2 - cos(beta_rad)**2 - cos( gamma_rad)**2 + 2 * cos(alpha_rad) * cos(beta_rad) * cos(gamma_rad) # Handle that iv may be very, very slightly < 0 by the floating point accuracy limit if iv > 0: v = sqrt(iv) else: v = 0.0 if c * v < 1e-14: raise Exception( "niggli_to_cell: Physically unreasonable cell, cell vectors degenerate or very close to degenerate." ) if orientation < 0: cell = [[-a, 0.0, 0.0], [-b * cos(gamma_rad), -b * sin(gamma_rad), 0.0], [ -c * cos(beta_rad), -c * (cos(alpha_rad) - cos(beta_rad) * cos(gamma_rad)) / sin(gamma_rad), -c * v / sin(gamma_rad) ]] else: cell = [[a, 0.0, 0.0], [b * cos(gamma_rad), b * sin(gamma_rad), 0.0], [ c * cos(beta_rad), c * (cos(alpha_rad) - cos(beta_rad) * cos(gamma_rad)) / sin(gamma_rad), c * v / sin(gamma_rad) ]] for i in range(3): for j in range(3): cell[i][j] = round(cell[i][j], 14) return cell
def occupations_and_coords_to_assignments_and_coordgroups( occupationscoords, occupations): if len(occupationscoords) == 0: return [], FracVector((), 1) occupationscoords = FracVector.use(occupationscoords) new_coordgroups = [] new_assignments = [] for i in range(len(occupations)): for j in range(len(new_assignments)): if occupations[i] == new_assignments[j]: new_coordgroups[j].append(occupationscoords[i]) break else: new_coordgroups.append([occupationscoords[i]]) new_assignments.append(occupations[i]) new_coordgroups = FracVector.create(new_coordgroups) return new_assignments, new_coordgroups
def normalized_formula_parts(assignments, ratios, counts): formula = {} alloccs = {} maxc = 0 for i in range(len(counts)): assignment = assignments[i] ratio = ratios[i] if is_sequence(assignment): if len(assignment) == 1: assignment = assignment[0] ratio = ratio[0] occ = ratio else: assignment = tuple([(x, FracVector.use(y)) for x, y in zip(assignment, ratio)]) occ = 1 else: occ = ratio if not assignment in formula: formula[assignment] = FracVector.create(0) alloccs[assignment] = FracVector.create(0) formula[assignment] += FracVector.create(occ * counts[i]) alloccs[assignment] += FracVector.create(counts[i]) if alloccs[assignment] > maxc: maxc = alloccs[assignment] alloccs = FracVector.create(alloccs.values()) alloccs = (alloccs / maxc).simplify() for symbol in formula.keys(): formula[symbol] = (formula[symbol] * alloccs.denom / maxc).simplify() # if abs(value-int(value))<1e-6: # formula[symbol] = int(value) # elif int(100*(value-(int(value)))) > 1: # formula[symbol] = float("%d.%.2e" % (value, 100*(value-(int(value))))) # else: # formula[symbol] = float("%d" % (value,)) return formula
def basis_to_niggli(basis): basis = FracVector.use(basis) A = basis.noms det = basis.det() if det == 0: raise Exception("basis_to_niggli: singular cell matrix.") if det > 0: orientation = 1 else: orientation = -1 s11 = A[0][0] * A[0][0] + A[0][1] * A[0][1] + A[0][2] * A[0][2] s22 = A[1][0] * A[1][0] + A[1][1] * A[1][1] + A[1][2] * A[1][2] s33 = A[2][0] * A[2][0] + A[2][1] * A[2][1] + A[2][2] * A[2][2] s23 = A[1][0] * A[2][0] + A[1][1] * A[2][1] + A[1][2] * A[2][2] s13 = A[0][0] * A[2][0] + A[0][1] * A[2][1] + A[0][2] * A[2][2] s12 = A[0][0] * A[1][0] + A[0][1] * A[1][1] + A[0][2] * A[1][2] new = FracVector.create(((s11, s22, s33), (2 * s23, 2 * s13, 2 * s12)), denom=basis.denom**2).simplify() return new, orientation
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 coords_reduced_to_cartesian(self, coords): coords = FracVector.use(coords) return coords * self.basis
def coords_cartesian_to_reduced(self, coords): coords = FracVector.use(coords) return coords * self.inv
def create(cls, cellshape=None, basis=None, metric=None, niggli_matrix=None, a=None, b=None, c=None, alpha=None, beta=None, gamma=None, lengths=None, angles=None, scale=None, scaling=None, volume=None, periodicity=None, nonperiodic_vecs=None, orientation=1): """ Create a new cell object, cell: any one of the following: - a 3x3 array with (in rows) the three basis vectors of the cell (a non-periodic system should conventionally use an identity matrix) - a dict with a single key 'niggli_matrix' with a 3x2 array with the Niggli Matrix representation of the cell - a dict with 6 keys, 'a', 'b', 'c', 'alpha', 'beta', 'gamma' giving the cell parameters as floats scaling: free form input parsed for a scale. positive value = multiply basis vectors by this value negative value = rescale basis vectors so that cell volume becomes abs(value). scale: set to non-None to multiply all cell vectors with this factor volume: set to non-None if the basis vectors only give directions, and the volume of the cell should be this value (overrides scale) periodicity: free form input parsed for periodicity sequence: True/False for each basis vector being periodic integer: number of non-periodic basis vectors """ if isinstance(cellshape, CellShape): basis = cellshape.basis elif cellshape is not None: basis = cell_to_basis(cellshape) if basis is not None: basis = FracVector.use(basis) if niggli_matrix is not None: niggli_matrix = FracVector.use(niggli_matrix) basis = FracVector.use( niggli_to_basis(niggli_matrix, orientation=orientation)) if niggli_matrix is None and basis is not None: niggli_matrix, orientation = basis_to_niggli(basis) if niggli_matrix is None and lengths is not None and angles is not None: niggli_matrix = lengths_angles_to_niggli(lengths, angles) niggli_matrix = FracVector.use(niggli_matrix) if basis is None: basis = FracVector.use( niggli_to_basis(niggli_matrix, orientation=1)) if niggli_matrix is None and not (a is None or b is None or c is None or alpha is None or beta is None or gamma is None): niggli_matrix = lengths_angles_to_niggli([a, b, c], [alpha, beta, gamma]) niggli_matrix = FracVector.use(niggli_matrix) if basis is None: basis = FracVector.use( niggli_to_basis(niggli_matrix, orientation=1)) if niggli_matrix is None: raise Exception( "CellShape.create: Not enough information to specify a cell given." ) if scaling is None and scale is not None: scaling = scale if scaling is not None and volume is not None: raise Exception( "CellShape.create: cannot specify both scaling and volume!") if volume is not None: scaling = vol_to_scale(basis, volume) if scaling is not None: scaling = FracVector.use(scaling) niggli_matrix = (basis * scaling * scaling).simplify() if basis is not None: basis = (basis * scaling).simplify() # For the basis we use a somewhat unusual normalization where the largest one element # in the cell = 1, this way we avoid floating point operations for prototypes created # from cell vector (prototypes created from lengths and angles is another matter) if basis is not None: c = basis maxele = max(c[0, 0], c[0, 1], c[0, 2], c[1, 0], c[1, 1], c[1, 2], c[2, 0], c[2, 1], c[2, 2]) maxeleneg = max(-c[0, 0], -c[0, 1], -c[0, 2], -c[1, 0], -c[1, 1], -c[1, 2], -c[2, 0], -c[2, 1], -c[2, 2]) if maxeleneg > maxele: scale = (-maxeleneg).simplify() else: scale = (maxele).simplify() basis = (basis * scale.inv()).simplify() c = niggli_matrix maxele = max(c[0, 0], c[0, 1], c[0, 2]) niggli_matrix = (niggli_matrix * maxele.inv()).simplify() return cls(niggli_matrix, orientation, basis)
def coords_reduced_to_cartesian(cell, coords): cell = FracVector.use(cell) newcoords = coords * cell return newcoords
def insert(self, table, types, keyvals, cursor=None, updatesid=None): #sid=self.sids[table] #types=self.types[table] #self.store[table][sid]={} if cursor is None: cursor = self.db.cursor() mycursor = True else: mycursor = False columns = [] columndata = [] subinserts = [] for column in tuple(types['keys']) + tuple(types['derived']): name = column[0] t = column[1] if name not in keyvals: val = None else: val = keyvals[name] if val is None: continue # Regular column, no strangeness if t in self.basics: #if issubclass(t,FracVector): # val = (val*1000000000).to_int() if issubclass(t, FracScalar): val = FracScalar.use(val) val = int(val.limit_denominator(50000000) * 1000000000) #if isinstance(t,FracVector): # print("DOES THIS HAPPEN?",t,val) # val = int(val.limit_denominator(50000000)) else: try: val = t(val) except UnicodeEncodeError: val = unicode_type(val) except TypeError: print("HUH", val, t) raise columns.append(name) columndata.append(val) # List type means we need to establish a second table and store key values elif isinstance(t, list): if not isinstance(t[0], tuple): t = [(name, t[0])] val = [(x, ) for x in val] subtablename = table + "_" + name for idx, entry in enumerate(val): subtypes = [(table + "_sid", int), (name + "_index", int)] data = {name + "_index": idx} for i in range(len(t)): if issubclass(t[i][1], HttkObject): subtypename = t[i][0] + "_" + t[i][1].types( )['name'] + "_sid" subtypes.append(( subtypename, int, )) if entry[i].db.sid is None: entryval = t[i][1].use(entry[i]) entryval.db.store(self) data[subtypename] = entry[i].db.sid else: subtypename = t[i][0] subtypes.append(( subtypename, t[i][1], )) if isinstance(entry, tuple): #print("TRYING",entry,i,subtypename) data[subtypename] = entry[i] elif isinstance(entry, dict): data[subtypename] = entry[t[i][0]] else: raise Exception( "Data for multifield of wrong type, got:" + str(entry)) subinserts.append((subtablename, subtypes, data, ())) #print("SUBINSETS",subinserts) # Tuple means numpy array elif isinstance(t, tuple): # Numpy array with fixed number of entries, just flatten and store as _1, _2, ... columns tupletype = t[0] #print("INSERT TUPLETYPE",tupletype,types,t) if t[1] >= 1: #flat=val.flatten() flat = tuple(flatten(val)) size = t[1] * t[2] for i in range(size): columname = name + "_" + str(i) columns.append(columname) if tupletype == FracVector: setval = (FracVector.use( flat[i]).limit_denominator(5000000) * 1000000000).to_ints() columndata.append(setval) elif tupletype == FracScalar: #print("FLATI",flat[i]) #columndata.append(map(lambda x:int(x*1000000000),flat[i])) if flat[i] is not None: setval = int( FracScalar.use( flat[i]).limit_denominator(5000000) * 1000000000) columndata.append(setval) else: columndata.append(None) else: columndata.append(flat[i]) # Variable length numpy array, needs subtable if t[1] == 0: subtablename = table + "_" + name subtablecolumnname = name subdimension = (tupletype, 1, t[2]) for idx, entry in enumerate( val): # loops over rows in 2d array #data = {table+"_sid":sid,name:entry} #self.insert(subtablename,data) data = {name: entry, name + "_index": idx} subtypes = [(subtablecolumnname, subdimension), (table + "_sid", int), (name + "_index", int)] subinserts.append((subtablename, subtypes, data, ())) elif issubclass(t, HttkObject): if val.db.sid is None: # This fixes an issue where sometimes a different class (e.g. a subclass) is sent in to be stored in a field. val = t.use(val) val.db.store(self) columnname = name + "_" + t.types()['name'] + "_sid" columns.append(columnname) columndata.append(val.db.sid) else: raise Exception( "Dictstore.insert: unexpected class; can only handle basic types and subclasses of Storable. Offending class:" + str(t)) # TODO: this logic is not finished, more elaborate updates need handling if (isinstance(updatesid, int) and updatesid >= 0) or updatesid is None: if updatesid is not None: sid = self.db.update_row(table, table + "_id", updatesid, columns, columndata, cursor) else: sid = self.db.insert_row(table, columns, columndata, cursor) for subinsert in subinserts: subinsert[2][table + "_sid"] = sid self.insert(subinsert[0], { 'keys': subinsert[1], 'derived': subinsert[3] }, subinsert[2], cursor) else: sid = -updatesid if mycursor: if not self._delay_commit: self.db.commit() cursor.close() return sid
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 show(self, showunstable=True, labelunstable=False, avoid_overlap=True): print( "Warning: graphical phase diagrams currently does not fill in *all* dividing lines." ) pd = self.phasediagram pp = PolygonPlot(labels=pd.coord_system, sides=len(pd.coord_system), label_offset=0.15) coords, ids = pd.hull_point_coords() coords = FracVector.use(coords).to_floats() coords2, ids2 = pd.interior_point_coords() coords2 = FracVector.use(coords2).to_floats() newdata2 = pp.translate_coords(coords2) allcoords, allids = pd.coords() allcoords = FracVector.use(allcoords).to_floats() alldata = pp.translate_coords(allcoords) if showunstable and len(newdata2) > 0: pp.ax.scatter(newdata2[:, 0], newdata2[:, 1], s=50, color='purple', marker='o', facecolor='none') newdata = pp.translate_coords(coords) pp.ax.scatter(newdata[:, 0], newdata[:, 1], s=100, marker='o') labelpos = [] for i, txt in enumerate(ids): labelpos += [[ newdata[i, 0], newdata[i, 1], newdata[i, 0] + 0.04, newdata[i, 1] + 0.01, txt, { 'color': 'blue' } ]] if showunstable and labelunstable: for i, txt in enumerate(ids2): labelpos += [[ newdata2[i, 0], newdata2[i, 1], newdata2[i, 0] + 0.04, newdata2[i, 1] + 0.01, txt, { 'color': 'purple' } ]] # TODO: Replace with true spring framework instead if avoid_overlap: xoffset = 0.00 yoffset = 0.02 overlap = True maxiters = 100 while overlap and maxiters > 0: overlap = False for i in range(len(labelpos)): pos1 = labelpos[i] if sqrt((pos1[0] - pos1[2])**2 + (pos1[1] - pos1[3])**2) < 0.02: labelpos[i] = [ pos1[0], pos1[1], pos1[2] + xoffset * (random.choice([+1, -1])), pos1[3] + yoffset * (random.choice([+1, -1])) ] + pos1[4:] overlap = True #print("OVERLAP1",i) for j in range(i + 1, len(labelpos)): if (i == j): continue pos1 = labelpos[i] pos2 = labelpos[j] if sqrt((pos1[0] - pos1[2])**2 + (pos1[1] - pos1[3])**2) < 0.02: labelpos[i] = [ pos1[0], pos1[1], pos1[2] + xoffset * (random.choice([+1, -1])), pos1[3] + yoffset * (random.choice([+1, -1])) ] + pos1[4:] #labelpos[j] = [pos2[0],pos2[1],pos2[2]+xoffset*(random.choice([+1, -1])),pos2[3]+yoffset*(random.choice([+1, -1]))]+pos2[4:] overlap = True #print("OVERLAP2",i,j,abs(sqrt(pos1[2]**2 + pos1[3]**2) - sqrt(pos2[2]**2 + pos2[3]**2))) print(pos1) print(pos2) #print(maxiters, labelpos) maxiters -= 1 for i, pos in enumerate(labelpos): if abs(sqrt(pos[0]**2 + pos[1]**2) - sqrt(pos[2]**2 + pos[3]**2)) > 0.04: pp.ax.annotate(pos[4], xy=(pos[0], pos[1]), xycoords='data', xytext=(pos[2], pos[3]), textcoords='data', arrowprops=dict(arrowstyle="->", connectionstyle="arc3"), **pos[5]) #pp.ax.annotate(pos[2], (pos[0],pos[1])) else: pp.ax.annotate(pos[4], (pos[2], pos[3]), **pos[5]) lines = pd.interior_competing_phase_lines() for line in lines: arrowplot(pp.ax, [alldata[line[0]][0], alldata[line[1]][0]], [alldata[line[0]][1], alldata[line[1]][1]], c='purple') #pp.ax.plot([alldata[line[0]][0],alldata[line[1]][0]],[alldata[line[0]][1],alldata[line[1]][1]],'-',color='purple') # lines = pd.hull_to_interior_competing_phase_lines() # for line in lines: # arrowplot(pp.ax,[alldata[line[0]][0],alldata[line[1]][0]],[alldata[line[0]][1],alldata[line[1]][1]],c='blue') # #pp.ax.plot([alldata[line[0]][0],alldata[line[1]][0]],[alldata[line[0]][1],alldata[line[1]][1]],'b-') # lines = pd.hull_competing_phase_lines() # for line in lines: # #print("LINE",line[0],"->",line[1]) # #arrowplot(pp.ax,[newdata[line[0]][0],newdata[line[1]][0]],[newdata[line[0]][1],newdata[line[1]][1]],c='black') # pp.ax.plot([newdata[line[0]][0],newdata[line[1]][0]],[newdata[line[0]][1],newdata[line[1]][1]],'k-') lines = pd.phase_lines for line in lines: #print("LINE",line[0],"->",line[1]) #arrowplot(pp.ax,[newdata[line[0]][0],newdata[line[1]][0]],[newdata[line[0]][1],newdata[line[1]][1]],c='black') pp.ax.plot([newdata[line[0]][0], newdata[line[1]][0]], [newdata[line[0]][1], newdata[line[1]][1]], 'k-') show()