Example #1
0
def solve_sextic(Cij, n=cry.ei(1), m=cry.ei(2)):
    '''Finds the eigenvalues and eigenvectors of the N tensor in the Stroh
    sextic eigenvalue formulation of anisotropic elasticity. Rearranges
    roots p and vectors A and L to have roots with Im(p) > 0 listed first.
    '''
    
    # Index list used to list eigenvalues with Im(p) > 0 first
    orderedIndices = np.array([0, 2, 4, 1, 3, 5])
    
    p, xsi = lin.eig(NStroh(Cij, n, m))
    
    # number of eigenvalues of N
    nEig = len(p)    
    # reorder p
    p = np.array([p[i] for i in orderedIndices])
    
    # Extract the (non-normalised) A and L vectors, noting that numpy.linalg
    # returns the i-th eigenvector as xsi[:, i]. Ensure that order is same as 
    # for pOrdered (tilde denotes non-normalised)
    ATilde = np.array([xsi[:3, i] for i in orderedIndices])
    LTilde = np.array([xsi[3:, i] for i in orderedIndices])
                                          
    # normalise A and L so that 2*(Ai.Li) = 1
    # begin by working out values of 2*(ATilde_i . LTilde_i)
    normalisation = np.array([1./cmath.sqrt(2*np.dot(ATilde[i], LTilde[i])) 
                                                 for i in range(nEig)])
                                               
    # now multiply each of the ATilde_i and LTilde_i by a normalization factor
    A = np.array([normalisation[i]*ATilde[i] for i in range(nEig)])
    L = np.array([normalisation[i]*LTilde[i] for i in range(nEig)])
    
    return p, A, L
Example #2
0
    def __init__(self,a=cry.ei(1),b=cry.ei(2),c=cry.ei(3)):
        '''Identical to <__init__> for <cry.Crystal>, but with the <__init__>
        call for the basis done using class <CastepBasis>.
        '''

        cry.Lattice.__init__(self,a,b,c)
        CastepBasis.__init__(self)
Example #3
0
def anisotropic_K_b(Cij, b, n=cry.ei(1), m=cry.ei(2), using_atomic=True):
    '''Caluculate the energy coefficient for a dislocation with a specific 
    Burgers vector.
    '''

    # solve sextic eigenvalue problem
    p, A, L = aniso.solve_sextic(Cij, n, m)
    energy_tensor = aniso.tensor_k(L)

    # calculate the scalar energy coefficient
    K = aniso.scalar_k(energy_tensor, b)

    return K
Example #4
0
def gamma_surface(slab,
                  resolution,
                  write_fn,
                  sys_info,
                  basename='gsf',
                  suffix='in',
                  limits=(1, 1),
                  vacuum=0.,
                  mkdir=False,
                  relax=None):
    '''Sets up gamma surface calculation with a sampling density of <N> along
    [100] and <M> along [010]. 
    
    Note: if N and M have been determined using <gs_sampling>, they will already
    be even but, for transferability, we nevertheless check that this condition
    is met.
    '''

    # using <gs_sampling>, calculate the number of increments along x and y
    # required to give *at least* the specified <resolution>.
    N, M = gs_sampling(slab.getLattice(), resolution, limits)

    # iterate over displacement vectors in the gamma surface (ie. (001))
    for n in range(0, N + 1):
        for m in range(0, M + 1):
            gsf_name = '{}.{}.{}'.format(basename, n, m)
            # insert vector into slab
            disp_vec = cry.ei(1) * n * limits[0] / float(N) + cry.ei(
                2) * m * limits[1] / float(M)
            insert_gsf(slab, disp_vec, vacuum=vacuum)

            # write to code appropriate output file
            if mkdir:  # make directory
                if not os.path.exists(gsf_name):
                    os.mkdir(gsf_name)
                outstream = open('{}/{}.{}'.format(gsf_name, gsf_name, suffix),
                                 'w')
            else:  # use current directory
                outstream = open('{}.{}'.format(gsf_name, suffix), 'w')

            write_fn(outstream,
                     slab,
                     sys_info,
                     to_cart=False,
                     defected=True,
                     do_relax=True,
                     add_constraints=True,
                     relax_type=relax,
                     prop=False)

    return
Example #5
0
    def write_constraints(self, write_fn):
        '''Write constraints to <write_fn> (usually a file I/O stream) in the 
        CASTEP format, listing the number of the atom in the list of all atoms
        contained in the basis (<n_atom>), as well as which number of atom of 
        this particular species we are writing to output.
        '''

        if type(write_fn) is file:
            write_fn = write_fn.write
            end = '\n'
        else:
            end = ''

        # test to see if any atom actually has constraints
        use_constraints = False
        for atom in self:
            if atom.has_constraints:
                use_constraints = True
                self._currentindex = 0
                break

        if not use_constraints:
            # no need to write an ionic constraints block
            return

        # otherwise, write the ionic constraints block to <write_fn>
        write_fn('%BLOCK IONIC_CONSTRAINTS' + end)
        n_constraints = 0
        
        # tracks number of species (+1) for which constraints have been written
        species_j = 1

        # track number of each type of atom for which species have been written
        species_k = 1

        for i, atom in enumerate(self):
            if i == 0:
                current_species = atom.getSpecies()
            elif atom.getSpecies() == current_species:
                species_k += 1
            else:
                # next species, reset counter for number of atoms of given type
                # and the identity of the current type of atom considered
                species_j += 1
                species_k = 1
                current_species = atom.getSpecies()
                
            # test for constraints
            for i, const in enumerate(atom.get_constraints()):
                if int(const) == 0: # fix atom in place
                    # note that, in castep, 1 implies that motion is constrained
                    fix = cry.ei(i+1)
                    write_fn('{:.0f} {} {:.0f} {:.2f} {:.2f} {:.2f}{}'.format(
                               species_j, atom.getSpecies(), species_k, fix[0], 
                                                          fix[1], fix[2], end))

        write_fn("%ENDBLOCK IONIC_CONSTRAINTS" + end + end)
   
        return
Example #6
0
def makeAnisoField(Cij, n=cry.ei(1), m=cry.ei(2)):
    '''Given an elastic constants matrix <Cij>, defines a function 
    <uAniso> that returns the displacement at <x> for a dislocation
    with Burgers vector <b>.
    '''
    
    # calculate eigenvalues <p>, and the <A> and <L> vectors defined on pg. 468
    # of Hirth and Lothe (1982).
    p, A, L = solve_sextic(Cij)
    
    def uAniso(x, b, x0, dummy1=0, dummy2=0):
        '''Dummy variables used to ensure that all displacement fields
        have the same form.
        '''
        
        u = 0j*np.zeros(3)
        dx = x[0] - x0[0]-1e-10
        dy = x[1] - x0[1]
        
        rho2 = dx**2 + dy**2
        
        # make sure that we are not at the line singularity of the dislocation
        coreradius2 = 1e-10
        if (rho2 < coreradius2):
            # inside the dislocation core
            u[0] = 0.
            u[1] = 0.
            u[2] = 0.
        else:
            # calculate displacement associated with each conjugate pair of 
            # eigenvalues/eigenvectors.
            for i in range(3):
                # original version
                posEig = A[i]*np.dot(L[i], b)*np.log(dy+p[i]*dx)
                negEig = A[i+3]*np.dot(L[i+3], b)*np.log(dy+p[i+3]*dx)
                u = u + (negEig-posEig).copy()
             
        # make real
        u *= 1/(2.*np.pi*1j)
        return u.real
       
    return uAniso   
Example #7
0
def gl_sampling(lattice, resolution=0.25, vector=cry.ei(1), limits=1.):
    '''Determine the number of samples along <vector> required to achieve
    specified <resolution>.
    '''

    # transform Burgers vector in cartesian coordinates
    burgers = np.zeros(3)
    for i in range(3):
        burgers += vector[i] * lattice[i]

    # get minimum number of samples required for desired resolution
    N = ceiling(abs(limits) * norm(burgers) / resolution)
    N = int(N)
    # Make sure that N is an even integer
    if N % 2 == 1:
        N = N + 1
        print("Incrementing N to make value even. New value is {}.".format(N))

    return N
Example #8
0
def insert_gsf(slab, disp_vec, vacuum=0., eps=1e-6):
    '''Inserts generalised stacking fault with stacking fault vector <disp_vec>
    into the provided <slab>. If <vacuum> == 0., the stacking fault is inserted
    at z = 0.5, otherwise, we insert it at 0.5*(z-vacuum)/z (z := slab height). 
    '''

    height = norm(slab.getC())

    # disp_vec may be entered in 2D, but atomic coordinates are 3D
    if len(disp_vec) == 3:
        disp_vec = np.array(disp_vec)
    elif len(disp_vec) == 2:
        print("Converting displacement vector in R^{2} into a vector in R^{3}")
        temp = np.copy(disp_vec)
        disp_vec = np.array([temp[0], temp[1], 0.])
    else:
        raise ValueError("<disp_vec> has invalid (%d) # of dimensions" %
                         len(disp_vec))

    # find the middle of the slab of atoms
    middle = 0.5 * (height - vacuum) / norm(height)

    # record whether or not a small perturbation has been applied
    perturbed = False

    for atom in slab.getAtoms():
        if 0. <= atom.getCoordinates()[-1] < middle - eps:
            # atom in the lower half of the cell -> no slip
            continue
        else:
            # displace atom
            x0 = atom.getCoordinates()
            if not perturbed:
                new_x = (x0 + disp_vec + cry.ei(3) * 0.01) % 1
            else:
                new_x = (x0 + disp_vec) % 1
            atom.setDisplacedCoordinates((x0 + disp_vec) % 1)

    return
def command_line_options():
    '''Parse command line options to enable optional features and change
    default values of parameters.
    
    Example use:
    
    $: ./gsf_controller -u example_file.gin -sn example -n 10 -g $GULP/Src/./gulp
    
    -> defaults to a gamma surface calculation with a minimum resolution of 1 node
    per 0.25 x 0.25 square (in \AA, bohr, or whatever other distance units are 
    chosen.
    '''
    
    options = argparse.ArgumentParser()
    options.add_argument('-c', '--control-file', type=str, dest='control',
                    default='', help='File containing simulation parameters')
    options.add_argument('-u', '--unit-cell', type=str, dest='cell_name',
                         help='Name of GULP file containing the unit cell')
    options.add_argument('-sn', '--name', type=str, dest='sim_name', default='gsf',
                         help='Base name for GSF calculation input files')
    options.add_argument('-n', '--num-layers', type=int, dest='n', default=2,
                         help='Thickness of simulation slab in unit-cells.')
    options.add_argument('-v', '--vacuum', type=float, dest='vac', default=0.0,
                         help='Thickness of the vacuum layer.')
    options.add_argument('-p' '--prog', type=str, dest='prog', default=None,
                         help='Name of atomistic program used. Options:\n' +
                                                'GULP\n' +
                                                'Quantum Espresso (QE)\n' 
                                                'CASTEP')
    options.add_argument('-exe', '--executable', type=str, dest='progexec', default=None,
                         help='Path to the executable for the atomistic code.')
    options.add_argument('-t', '--type', type=str, choices=['gline', 'gsurface'],
                         dest='simulation_type', default='gsurface',
                         help='Choose whether to calculate the PES of a gamma' +
                              ' line or a gamma surface.\n\nDefault is gamma ' +
                              ' surface.')
    options.add_argument('-d', '--direction', nargs=3, type=float, dest='line_vec',
                       default=cry.ei(1), help='Direction of gamma line.')
    options.add_argument('-r', '--resolution', type=float, dest='res', default=0.25,
                         help='Sampling resolution of the gamma line/surface')
    options.add_argument('-s', '--shift', type=float, dest='shift', default=0.0,
                         help='Shifts origin of unit cell.')

    # limit the gamma surface/line calculation to one sector of the slip plane.
    # Only x and y limits are available through the command line and manual input.
    # For all other limits (in particular by angle), use the input file (not yet
    # implemented).

    options.add_argument('-x', '--xmax', type=float, dest='max_x', default=1.0,
                         help='Maximum displacement vector along x.')
    options.add_argument('-y', '--ymax', type=float, dest='max_y', default=1.0,
                         help='Maximum displacement vector along y.')
                         
                         
    # list of contraints    
    options.add_argument('-fx', '--dfix', type=float, dest='d_fix', default=5.0,
                         help='Thickness of static region in vacuum-buffered slab.')
    options.add_argument('-fr', '--free', type=str, nargs = '*', dest='free_atoms',  
                help='List of atomic species allowed to relax without constraint.',
                                                                    default=[])
                                    
    return options
Example #10
0
def bond_candidates_cluster(dis_cell,
                            atom_type,
                            max_bond_length,
                            R,
                            RI,
                            RII,
                            use_species=False,
                            bonded_type=None):
    '''Extracts candidate bonds for all sites of the specified type with radius
    R.
    '''

    if bonded_type is None:
        # calculate Nye tensor on the <atom_type> sublattice
        bonded_type = atom_type

    # read in file containing the relaxed dislocation structure, then extract
    # atoms in the relaxed region, as well as the cell thickness
    discluster, sinfo = gulp.cluster_from_grs(dis_cell, RI, RII)
    relaxed = discluster.getRegionIAtoms()
    H = discluster.getHeight()

    n = len(relaxed)

    # extract a list of potential bonds for atoms in the sublattice of interest
    Qpot = dict()
    for i in range(n):
        # check that atom i belongs to the sublattice of interest (usually not
        # oxygen)
        if use_species:
            atomspecies = relaxed[i].getSpecies()
            if atomspecies != atom_type:
                continue

        # ensure that atom i is within the specified region
        x = relaxed[i].getCoordinates()
        if (np.sqrt(x[0]**2 + x[1]**2)) > R:
            continue

        Qpoti = []
        for j in range(n):
            # check that atom j is one whose bonds with atom i we care about
            if use_species:
                if relaxed[j].getSpecies() != bonded_type:
                    continue

            # extract coordinates of atom j and its periodic images
            y0 = relaxed[j].getCoordinates()
            ydown = y0 - cry.ei(3) * H
            yup = y0 + cry.ei(3) * H

            # calculate distance to atom i and check to see if bond length is
            # below given maximum value
            if i != j and norm(x - y0) < max_bond_length:
                Qpoti.append([j, x - y0])
            if norm(x - yup) < max_bond_length:
                Qpoti.append([j, x - yup])
            if norm(x - ydown) < max_bond_length:
                Qpoti.append([j, x - ydown])

        Qpot[i] = [x, Qpoti]

    return Qpot
Example #11
0
def make_slab(unit_cell,
              num_layers,
              vacuum=0.0,
              d_fix=5.,
              free_atoms=[],
              axis=-1):
    '''Makes a slab for GSF calculation, with <num_layers> atomic layers, a 
    vacuum buffer of thickness <vacuum is added to the top, and <constraints>
    (a list of functions testing specific constraints, such as proximity to the
    buffer, atom type, etc.) are applied.
    
    ### NEEDS TO BE GENERALIZED ###
    '''

    # set up slab, without the vacuum layer
    new_dimensions = np.ones(3)
    new_dimensions[axis] = num_layers
    slab = cry.superConstructor(unit_cell, dims=new_dimensions)

    # total height of cell, including vacuum layer
    old_height = norm(slab.getVector(axis))
    new_height = old_height + vacuum

    # test to see if there is a vacuum layer, in which case a proximity
    # constraint will be applied
    if vacuum > 1e-10:
        print('Non-zero vacuum buffer. Proximity constraint to be used ' +
              'with d_fix = {:.1f}.'.format(d_fix))
        use_vacuum = True
    else:
        print('3D-periodic boundary conditions. Proximity constraints' +
              ' will not be applied.')
        use_vacuum = False

    # apply constraints
    for atom in slab.getAtoms():
        # fix atom if it is within d_fix of the slab-vacuum interface, provided
        # that vacuum thickness is non-zero -> notify user if this constraint is
        # set
        if use_vacuum:
            near_interface = proximity_constraint(atom, old_height, d_fix,
                                                  axis)
        else:
            near_interface = False

        if not near_interface:
            if atom.getSpecies() in free_atoms or free_atoms == 'all':
                # atom allowed to relax freely
                pass
            else:
                # relax normal to the slip plane
                atom.set_constraints(cry.ei(3, usetype=int))

    # add vacuum to the top of the slab
    # begin by computing coordinates of all atoms in the vacuum-buffered slab
    if use_vacuum:
        for atom in slab:
            coords = atom.getCoordinates()
            new_length = coords[axis] * old_height / new_height
            new_coords = np.copy(coords)
            new_coords[axis] = new_length
            atom.setCoordinates(new_coords)

            coords_disp = atom.getDisplacedCoordinates()
            length_disp = coords_disp[axis] * old_height / new_height
            new_disp = np.copy(coords_disp)
            new_disp[axis] = length_disp
            atom.setDisplacedCoordinates(new_disp)

        # increase the height of the slab
        slab.setVector(slab.getVector(axis) * new_height / old_height, axis)

    return slab