def lengths_and_cosangles_to_conventional_basis(lengths, cosangles, lattice_system=None, orientation=1, eps=0): """ Returns the conventional cell basis given a list of lengths and cosine of angles Note: if your basis vector order does not follow the conventions for hexagonal and monoclinic cells, you get the triclinic conventional cell. Conventions: in hexagonal cell gamma=120 degrees, i.e, cosangles[2]=-1/2, in monoclinic cells beta =/= 90 degrees. """ if lattice_system is None: lattice_system = lattice_system_from_lengths_and_cosangles( lengths, cosangles) a, b, c = lengths cosalpha, cosbeta, cosgamma = cosangles #sinalpha = vectormath.sqrt(1-cosalpha**2) sinbeta = vectormath.sqrt(1 - cosbeta**2) singamma = vectormath.sqrt(1 - cosgamma**2) basis = None if lattice_system == 'cubic' or lattice_system == 'tetragonal' or lattice_system == 'orthorhombic': basis = FracVector.create([[a, 0, 0], [0, b, 0], [0, 0, c] ]) * orientation elif lattice_system == 'hexagonal': #basis = FracVector.create([[singamma*a, a*cosgamma, 0], # [0, b, 0], # [0, 0, c]])*orientation basis = FracVector.create([[a, 0, 0], [cosgamma * b, b * singamma, 0], [0, 0, c]]) * orientation elif lattice_system == 'monoclinic': basis = FracVector.create([[a, 0, 0], [0, b, 0], [c * cosbeta, 0, c * sinbeta] ]) * orientation elif lattice_system == 'rhombohedral': vc = cosalpha tx = vectormath.sqrt((1 - vc) / 2.0) ty = vectormath.sqrt((1 - vc) / 6.0) tz = vectormath.sqrt((1 + 2 * vc) / 3.0) basis = FracVector.create([[2 * a * ty, 0, a * tz], [-b * ty, b * tx, b * tz], [-c * ty, -c * tx, c * tz]]) * orientation else: angfac1 = (cosalpha - cosbeta * cosgamma) / singamma angfac2 = vectormath.sqrt(singamma**2 - cosbeta**2 - cosalpha**2 + 2 * cosalpha * cosbeta * cosgamma) / singamma basis = FracVector.create([[a, 0, 0], [b * cosgamma, b * singamma, 0], [c * cosbeta, c * angfac1, c * angfac2] ]) * orientation return basis
def simplex_le_solver(a, b, c): """ Minimizie func = a[0]*x + a[1]*y + a[2]*z + ... With constraints:: b[0,0]x + b[0,1]y + b[0,2]z + ... <= c[0] b[1,0]x + b[1,1]y + b[1,2]z + ... <= c[1] ... x,y,z, ... >= 0 Algorithm adapted from 'taw9', http://taw9.hubpages.com/hub/Simplex-Algorithm-in-Python """ Na = len(a) Nc = len(c) obj = a.get_insert(0, 1) M = [] for i in range(Nc): obj = obj.get_append(0) ident = [0] * Nc ident[i] = 1 M += [b[i].get_insert(0, 0).get_extend(ident).get_append(c[i])] M = MutableFracVector.create(M) obj = obj.get_append(0) base = list(range(Na, Na + Nc)) while not min(obj[1:-1]) >= 0: pivot_col = obj[1:-1].argmin() + 1 rhs = M[:, -1] lhs = M[:, pivot_col] nonzero = [i for i in range(len(rhs)) if lhs[i] != 0] pivot_row = min(nonzero, key=lambda i: rhs[i] / lhs[i]) M[pivot_row] = (M[pivot_row] / (M[pivot_row][pivot_col])).simplify() for i in range(Nc): if i == pivot_row: continue M[i] = (M[i] - M[i][pivot_col] * M[pivot_row]).simplify() obj = (obj - obj[pivot_col] * M[pivot_row]).simplify() base[pivot_row] = pivot_col - 1 solution = [0] * Na for j in range(Nc): if obj[base[j] + 1] == 0 and base[j] < Na: solution[base[j]] = M[j][-1].simplify() return FracVector.create(-obj[-1]), FracVector.create(solution)
def trivial_symmetry_reduce(coordgroups): """ Looks for 'trivial' ways to reduce the coordinates in the given coordgroups by a standard set of symmetry operations. This is not a symmetry finder (and it is not intended to be), but for a standard primitive cell taken from a standard conventional cell, it reverses the primitive unit cell coordgroups into the symmetry reduced coordgroups. """ # TODO: Actually implement, instead of this placeholder that just gives up and returns P 1 symops = [] symopvs = [] for symop in all_symops: symopv = FracVector.create(symop) if check_symop(coordgroups, symopv): symops += [all_symops[symop]] symopvs += [symopv] shash = symopshash(symops) if shash in symops_hash_index: hall_symbol = symops_hash_index[shash] rc_reduced_coordgroups, wyckoff_symbols, multiplicities = reduce_by_symops(coordgroups, symopvs, hall_symbol) return rc_reduced_coordgroups, hall_symbol, wyckoff_symbols, multiplicities rc_reduced_coordgroups = coordgroups hall_symbol = 'P 1' wyckoff_symbols = ['a']*sum([len(x) for x in coordgroups]) multiplicities = [1]*sum([len(x) for x in coordgroups]) return rc_reduced_coordgroups, hall_symbol, wyckoff_symbols, multiplicities
def cell_to_basis(cell): if cell is None: raise Exception("cell_to_basis: bad cell specification.") try: return cell.basis except AttributeError: pass if isinstance(cell, dict): if 'niggli_matrix' in cell: niggli_matrix = FracVector.use(cell['niggli_matrix']) if 'orientation' in cell: orientation = cell['orientation'] else: orientation = 1 basis = FracVector.use( niggli_to_basis(niggli_matrix, orientation=orientation)) elif 'a' in cell: lengths = FracVector.create((cell['a'], cell['b'], cell['c'])) cosangles = FracVector.create_cos( (cell['a'], cell['b'], cell['c']), degrees=True) basis = lengths_and_cosangles_to_conventional_basis( lengths, cosangles) #niggli_matrix = lengths_cosangles_to_niggli(lengths,cosangles) #basis = FracVector.use(niggli_to_basis(niggli_matrix, orientation=1)) else: basis = FracVector.use(cell) return basis
def get_primitive_to_conventional_basis_transform(basis, eps=1e-4): """ Figures out how the 'likley' transform of a primitive cell for getting to the conventional basis This may not be foolproof, and mostly works for re-inverting cells generated by lengths_and_cosangles_to_conventional_basis. (It should only be used when getting something that isn't really the conventional cell does not equal catastrophic failure, just, e.g., a non-optimal representation.) """ half = Fraction(1, 2) lattrans = None unit = FracVector.create([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) 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() rebase = basis / (2 * scale) #niggli, orientation = basis_to_niggli_and_orientation(basis) #lengths, angles = niggli_to_lengths_and_angles(niggli) #rebase = FracVector.create([basis[0]/lengths[0], basis[1]/lengths[1], basis[2]/lengths[2]]).simplify() invrebase = rebase.inv().simplify() #invrebase = FracVector.create([invrebase[0]*lengths[0], invrebase[1]*lengths[1], invrebase[2]*lengths[2]]).simplify() #print("BASIS",basis) #print("BASIS",basis.to_floats()) #print("INVREBASE:", invrebase.to_floats()) #print("TRANSFORMED", (basis*invrebase).to_floats()) for rows in invrebase: for val in rows: if val != 0 and val != 1 and val != -1: #if val != half and val != -half and val != 0 and val != 1 and val != -1: # This is not a primitive cell that can easily be turned into a cubic conventional cell #print("HUH",val) return unit #print("INVREBASE:", invrebase) return invrebase
def basis_to_niggli_and_orientation(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 hull_z(points, zs): """ points: a list of points=(x,y,..) with zs= a list of z values; a convex half-hull is constructed over negative z-values returns data on the following format.:: { 'hull_points': indices in points list for points that make up the convex hull, 'interior_points':indices for points in the interior, 'interior_zs':interior_zs 'zs_on_hull': hull z values for each point (for points on the hull, the value of the hull if this point is excluded) 'closest_points': list of best linear combination of other points for each point 'closest_weights': weights of best linear combination of other points for each point } where hull_points and interior_points are lists of the points on the hull and inside the hull. and hull_zs is a list of z-values that the hull *would have* at that point, had this point not been included. interior_zs is a list of z-values that the hull has at the interior points. """ hull_points = [] non_hull_points = [] hull_distances = [] closest_points = [] closest_weights = [] zvalues = len(zs) coeffs = len(points[0]) #print("zvalues",zvalues) #print("coeffs",coeffs) for i in range(zvalues): #print("SEND IN",[zs[j] for j in range(zvalues) if j != i]) a = FracVector.create([zs[j] for j in range(zvalues) if j != i]) b = [None] * coeffs c = [None] * coeffs includepoints = [j for j in range(len(points)) if i != j] for j in range(coeffs): b[j] = FracVector.create([points[x][j] for x in includepoints]) c[j] = points[i][j] # if i == 0: # print("A",a.to_floats()) # print("B",[x.to_floats() for x in b]) # print("C",c) solution = simplex_le_solver(a, b, c) val = solution[0] # if i == 0: # print("VAL",val) # print("SOL",solution[1]) # print("MAPPED SOL",[(solution[1][i],includepoints[i]) for i in range(len(solution[1]))]) # closest = solution[1] thisclosestpoints = [] thisclosestweights = [] for j in range(len(closest)): sol = closest[j] if sol != 0: thisclosestpoints += [includepoints[j]] thisclosestweights += [sol] #print("I AM",i,"I disolve into:",includepoints[j],"*",float(sol),"(",j,")") closest_points += [thisclosestpoints] closest_weights += [thisclosestweights] hull_distances += [zs[i] - val] if zs[i] <= val: hull_points += [i] else: non_hull_points += [i] return { 'hull_indices': hull_points, 'interior_indices': non_hull_points, 'hull_distances': hull_distances, 'competing_indices': closest_points, 'competing_weights': closest_weights, }
continue M[i] = (M[i] - M[i][pivot_col] * M[pivot_row]).simplify() obj = (obj - obj[pivot_col] * M[pivot_row]).simplify() base[pivot_row] = pivot_col - 1 solution = [0] * Na for j in range(Nc): if obj[base[j] + 1] == 0 and base[j] < Na: solution[base[j]] = M[j][-1].simplify() return FracVector.create(-obj[-1]), FracVector.create(solution) if __name__ == '__main__': a = FracVector.create([-3, -2]) b = FracVector.create([[2, 1], [2, 3], [3, 1]]) c = FracVector.create([18, 42, 24]) print(simplex_le_solver(a, b, c)) exit(0) from pylab import * xsample = FracVector.random((5, 1), 0, 100, 100) xsample = FracVector.create([[x[0], 1 - x[0]] for x in xsample] + [[1, 0]] + [[0, 1]]) ysample = (-1 * FracVector.random((5, ), 0, 100, 100)).get_extend([0, 0]) hull = hull_z(xsample, ysample)
def symopsmatrix(symop): transf, transl = symopstuple(symop, val_transform=lambda x: x) return FracVector.create(transf), FracVector.create(transl)
def niggli_to_conventional_basis(niggli_matrix, lattice_system=None, orientation=1, eps=1e-4): """ Returns the conventional cell given a niggli_matrix Note: if your basis vector order does not follow the conventions for hexagonal and monoclinic cells, you get the triclinic conventional cell. Conventions: in hexagonal cell gamma=120 degrees., in monoclinic cells beta =/= 90 degrees. """ if lattice_system is None: lattice_system = lattice_system_from_niggli(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 = vectormath.sqrt(s11), vectormath.sqrt(s22), vectormath.sqrt(s33) alphar, betar, gammar = vectormath.acos(s23 / (b * c)), vectormath.acos( s13 / (c * a)), vectormath.acos(s12 / (a * b)) basis = None if lattice_system == 'cubic' or lattice_system == 'tetragonal' or lattice_system == 'orthorhombic': basis = FracVector.create([[a, 0, 0], [0, b, 0], [0, 0, c] ]) * orientation elif lattice_system == 'hexagonal': basis = FracVector.create( [[a, 0, 0], [-0.5 * b, b * vectormath.sqrt(3.0) / 2.0, 0], [0, 0, c]]) * orientation elif lattice_system == 'monoclinic': basis = FracVector.create([ [a, 0, 0], [0, b, 0], [c * vectormath.cos(gammar), 0, c * vectormath.sin(gammar)] ]) * orientation elif lattice_system == 'rhombohedral': vc = vectormath.cos(alphar) tx = vectormath.sqrt((1 - vc) / 2.0) ty = vectormath.sqrt((1 - vc) / 6.0) tz = vectormath.sqrt((1 + 2 * vc) / 3.0) basis = FracVector.create([[2 * a * ty, 0, a * tz], [-b * ty, b * tx, b * tz], [-c * ty, -c * tx, c * tz]]) * orientation else: angfac1 = (vectormath.cos(alphar) - vectormath.cos(betar) * vectormath.cos(gammar)) / vectormath.sin(gammar) angfac2 = vectormath.sqrt( vectormath.sin(gammar)**2 - vectormath.cos(betar)**2 - vectormath.cos(alphar)**2 + 2 * vectormath.cos(alphar) * vectormath.cos(betar) * vectormath.cos(gammar)) / vectormath.sin(gammar) basis = FracVector.create([ [a, 0, 0], [b * vectormath.cos(gammar), b * vectormath.sin(gammar), 0], [c * vectormath.cos(betar), c * angfac1, c * angfac2] ]) * orientation return basis
def standard_order_axes_transform( niggli_matrix, lattice_system, eps=0, return_identity_if_no_transform_needed=False): """ Returns the transform that re-orders the axes to standard order for each possible lattice system. Note: returns None if no transform is needed, to make it easy to skip the transform in that case. If you want the identity matrix instead, set parameter return_identity_if_no_transform_needed = True, """ # Determine lattice system even if axes are mis-ordered qrtr = Fraction(1, 4) abeq = abs(niggli_matrix[0][0] - niggli_matrix[0][1]) <= eps aceq = abs(niggli_matrix[0][0] - niggli_matrix[0][2]) <= eps bceq = abs(niggli_matrix[0][1] - niggli_matrix[0][2]) <= eps alpha90 = abs(niggli_matrix[1][0]) <= eps beta90 = abs(niggli_matrix[1][1]) <= eps gamma90 = abs(niggli_matrix[1][2]) <= eps alpha120 = (abs(niggli_matrix[1][0]**2 / (niggli_matrix[0][1] * niggli_matrix[0][2]) - qrtr) <= eps) and niggli_matrix[1][0] < 0 beta120 = (abs(niggli_matrix[1][1]**2 / (niggli_matrix[0][0] * niggli_matrix[0][2]) - qrtr) <= eps) and niggli_matrix[1][1] < 0 gamma120 = (abs(niggli_matrix[1][2]**2 / (niggli_matrix[0][0] * niggli_matrix[0][1]) - qrtr) <= eps) and niggli_matrix[1][2] < 0 equals = sum([abeq, aceq, bceq]) if equals == 1: equals = 2 orthos = sum([alpha90, beta90, gamma90]) hexangles = sum([alpha120, beta120, gamma120]) if equals == 3 and orthos == 3: lattice_system = 'cubic' elif equals == 2 and orthos == 3: lattice_system = 'tetragonal' elif equals == 0 and orthos == 3: lattice_system = 'orthorombic' elif hexangles == 1 and orthos == 2 and equals == 2: lattice_system = 'hexagonal' elif orthos == 2: lattice_system = 'monoclinic' elif equals == 3: lattice_system = 'rhombohedral' else: lattice_system = 'triclinic' if lattice_system == 'hexagonal' and not gamma120: if alpha120: return FracVector.create([[0, 0, -1], [0, -1, 0], [-1, 0, 0]]) elif beta120: return FracVector.create([[-1, 0, 0], [0, 0, -1], [0, -1, 0]]) raise Exception( "axis_standard_order_transform: unexpected cell geometry.") elif lattice_system == 'monoclinic' and not (alpha90 and gamma90 and not beta90): if (alpha90 and beta90 and not gamma90): return FracVector.create([[-1, 0, 0], [0, 0, -1], [0, -1, 0]]) elif (beta90 and gamma90 and not alpha90): return FracVector.create([[0, 0, -1], [0, -1, 0], [-1, 0, 0]]) raise Exception( "axis_standard_order_transform: unexpected cell geometry.") return None