Esempio n. 1
0
def parse_interpolate(args):
    """Parse command-line arguments for the interpolate subcommand."""
    # Check if the output file is writable at this point. Note that this
    # does not guarantee that it remains so. It is just a "courtesy" check
    # to avoid a lengthy calculation ending in failure to save.
    args.directory = os.path.abspath(args.directory)
    args.output = os.path.abspath(args.output)
    if not os.path.isdir(args.directory):
        lexit("the specified directory does not exist")
    if not is_writable(args.output):
        lexit("the output file is not writable")
    # Try and read the input.
    data = BoltzTraP2.dft.DFTData(args.directory, args.derivatives)
    # Perform some sanity checks on the energy window specification
    if args.emin >= args.emax:
        lexit("zero-width energy window")
    emin = args.emin + (0. if args.absolute else data.fermi)
    emax = args.emax + (0. if args.absolute else data.fermi)
    if emin >= data.fermi or emax <= data.fermi:
        lexit("the energy window must bracked the Fermi level")
    info("Nvalence (before BANDANA) =", data.nelect)
    # Drop bands outside the chosen energy range
    nemin = data.bandana(emin, emax)[0]
    info("Nvalence (after BANDANA) =", data.nelect)
    # Refuse to interpolate to fewer k points than there are in the input
    nkinput = data.kpoints.shape[0]
    logging.info(
        "there are {} irreducible k points in the input".format(nkinput))
    if args.multiplier is not None:
        nktarget = args.multiplier * nkinput
    else:
        nktarget = args.kpoints
    logging.info(
        "{} irreducible k points have been requested".format(nktarget))
    equivalences = BoltzTraP2.sphere.get_equivalences(data.atoms, nktarget)
    nkoutput = len(equivalences)
    logging.info(
        "{} irreducible k points have been generated".format(nkoutput))
    if nkoutput < nkinput:
        lexit("refusing to interpolate to a sparser grid")
    # Perform the interpolation
    with TimerContext() as timer:
        coeffs = BoltzTraP2.fite.fitde3D(data, equivalences, args.nworkers)
        deltat = timer.get_deltat()
        info("the interpolation took {:.3g} s".format(deltat))
    # Gather the metadata
    metadata = BoltzTraP2.serialization.gen_bt2_metadata(data,
                                                         args.derivatives)
    # Save the result
    info("about to save the results to", args.output)
    with TimerContext() as timer:
        BoltzTraP2.serialization.save_calculation(
            args.output, data, equivalences, coeffs, metadata)
        deltat = timer.get_deltat()
        info("saving the results took {:.3g} s".format(deltat))
Esempio n. 2
0
def compute_radius(atoms, nkpt, symprec=1e-4):
    """Estimate the right radius to be passed to Equivalence_builder to
    obtain the desired number of points.
    """
    lattvec = atoms.get_cell().T
    vol = abs(np.linalg.det(lattvec))
    spg = spglib.get_symmetry_dataset(atoms, symprec)
    rotations = spg["rotations"].astype(int)
    nrot = len(rotations)
    info("{} rotations".format(nrot))
    newrotations = set()
    # Include time-reversal symmetry in the estimate if total inversion of
    # the axes is not among the symmetries
    inv = 2
    for i in range(nrot):
        tmp = tuple(tuple(j) for j in rotations[i].tolist())
        newrotations.add(tmp)
        if tmp == ((-1, 0, 0), (0, -1, 0), (0, 0, -1)):
            inv = 1
    nrot = len(newrotations)
    info(nrot, "unique rotations",
         "including the inversion" if inv == 1 else "")
    npoints = nkpt * nrot * inv
    info(npoints, "total k points in the sphere")
    radius = (3. / (4. * np.pi) * npoints * vol)**(1. / 3.)
    return radius
Esempio n. 3
0
def parse_fermisurface(args):
    """Parse command-line arguments for the fermisurface subcommand."""
    global terminal_colors
    # Try and load the data from the interpolation step
    data, equivalences, coeffs, metadata = (
        BoltzTraP2.serialization.load_calculation(args.bt2_file))
    info("sucessfully loaded " + args.bt2_file)
    lattvec = data.get_lattvec()
    # Rebuild the bands
    with TimerContext() as timer:
        ebands = BoltzTraP2.fite.getBTPbands(equivalences, coeffs, lattvec,
                                             True, args.nworkers)[0]
        deltat = timer.get_deltat()
        info("rebuilding the bands took {:.3g} s".format(deltat))
    # Print out some useful information
    if terminal_colors:
        colors = dict(green=colorama.Fore.GREEN,
                      red=colorama.Fore.RED,
                      bold=colorama.Style.BRIGHT,
                      normal=colorama.Style.RESET_ALL)
    else:
        colors = dict(green="", red="", bold="", normal="")
    print(colors["bold"] + "─" * 72 + colors["normal"])
    print("""{bold}Keyboard and mouse interface:{normal}
{green}r{normal}: reset camera
{green}w{normal}: switch between surface and wireframe mode
{green}e{normal}: exit

{red}left mouse button{normal}: rotate around in-screen axes
{green}CTRL{normal} + {red}left mouse button{normal}: rotate around through-screen axis
{red}right mouse button{normal}: zoom
{red}middle mouse button{normal}: move / pick a point""".format(**colors))
    print(colors["bold"] + "─" * 72 + colors["normal"])
    # Call the function in charge of setting up the representation.
    # The function will only return after the user closes the interface.
    BoltzTraP2.fermisurface.plot_fermisurface(data,
                                              equivalences,
                                              ebands,
                                              args.mu,
                                              edge_thickness=args.thickness)
Esempio n. 4
0
def parse_plot(args):
    """Parse command-line arguments for the plotbands subcommand."""
    # Try and load the data from the integration step
    (data, fermi, Tr, mu0, mur, N, sdos, cv, cond, seebeck, kappa, hall,
     metadata) = BoltzTraP2.serialization.load_results(args.btj_file)
    info("sucessfully loaded " + args.btj_file)
    nformulas = data.get_formula_count()
    # Perform some sanity checks
    tensors = ("sigma", "S", "kappae", "L", "PF", "RH")
    components = args.components
    if args.quantity in tensors:
        if args.components is None:
            lexit("{} is a tensor but no components have been specified"
                  .format(args.quantity))
    if args.quantity not in tensors:
        if components:
            warning(("{} is not a tensor. "
                     "The --components options will have no effect"
                     ).format(args.quantity))
        components = [()]
    elif args.quantity == "RH":
        if not all(i is None or len(i) == 3 for i in args.components):
            lexit("component specifications for the Hall tensor"
                  " need three indices")
    else:
        if not all(i is None or len(i) == 2 for i in args.components):
            lexit(("component specifications for {}"
                   " need two indices").format(args.quantity))
    # Prepare the abscissas, the third variable and the axis labels
    if args.abscissa == "T":
        x = Tr
        xlabel = r"$T\;\left[\mathrm{K}\right]$"
        z = mur[::args.subsample]
        zlabel0 = r"$\mu - \varepsilon_F = {:5g}\;\mathrm{{Ry}}$"
    else:
        x = mur - fermi
        xlabel = r"$\mu - \varepsilon_F\;\left[\mathrm{Ha}\right]$"
        z = Tr[::args.subsample]
        zlabel0 = r"$T = {:5g}\;\mathrm{{K}}$"
    ylabel = dict(
        cv=r"$c_v\;\left[\mathrm{J\,mol^{-1}\,K^{-1}}\right]$",
        n=r"$n\;\left[\mathrm{\left|e\right|\,uc^{-1}}\right]$",
        DOS=r"$\mathrm{DOS}\;\left[\mathrm{uc^{-1}}\right]$",
        sigma=r"$\sigma^{{\left({}\right)}}/\tau_0\;"
        r"\left[\mathrm{{ohm^{{-1}}\,m^{{-1}}\,"
        r"s^{{-1}}}}\right]$",
        S=r"$S^{{\left({}\right)}}\;" +
        r"\left[\mathrm{{V\,K^{{-1}}}}\right]$",
        kappae=r"$\kappa_e^{{\left({}\right)}}/\tau_0\;"
        r"\left[\mathrm{{W\,m^{{-1}}\,K^{{-1}}\,s^{{-1}}}}\right]$",
        L=r"$L^{{\left({}\right)}}\;"
        r"\left[\mathrm{{W\,\Omega\,K^{{-2}}}}\right]$",
        PF=r"$\left(S^2\sigma\right)^{{\left({}\right)}}/ \tau_0"
        r"\;\left[\mathrm{{W\,m^{{-1}}\,K^{{-2}}\,s^{{-1}}}}\right]$",
        RH=r"$R_H^{{\left({}\right)}}\;" +
        r"\left[\mathrm{{m^3\,C^{{-1}}}}\right]$")
    ylabel0 = ylabel[args.quantity]
    # Prepare the ordinate
    if args.quantity == "cv":
        y = cv * AVOGADRO / nformulas
    elif args.quantity == "n":
        y = N + data.nelect
    elif args.quantity == "DOS":
        y = sdos
    elif args.quantity == "sigma":
        y = cond
    elif args.quantity == "S":
        y = seebeck
    elif args.quantity == "kappae":
        y = kappa
    elif args.quantity == "L":
        L0 = 2.44e-8
        y = np.empty_like(cond)
        for iT in range(y.shape[0]):
            for imu in range(y.shape[1]):
                y[iT, imu] = la.solve(cond[iT, imu].T,
                                      kappa[iT, imu].T).T / Tr[iT]
    elif args.quantity == "PF":
        y = np.empty_like(cond)
        for iT in range(y.shape[0]):
            for imu in range(y.shape[1]):
                y[iT, imu] = (cond[iT, imu] @ seebeck[iT, imu]
                              @ seebeck[iT, imu])
    elif args.quantity == "RH":
        y = hall
    if args.abscissa == "T":
        y = y[:, ::args.subsample]
    else:
        y = y[::args.subsample, :]
    # Create the plots
    for c in components:
        if args.quantity in tensors:
            desc = "scalar" if c is None else "".join("xyz" [i] for i in c)
            ylabel = ylabel0.format(desc)
        else:
            ylabel = ylabel0
        plt.figure()
        for iz, zv in enumerate(z):
            zlabel = zlabel0.format(zv)
            if args.abscissa == "T":
                indices = [slice(None, None, None), iz]
            else:
                indices = [iz, slice(None, None, None)]
            if c is None:
                thisy = np.zeros_like(x)
                if args.quantity == "RH":
                    complements = ((0, 1, 2), (1, 2, 0), (2, 0, 1))
                else:
                    complements = [[i, i] for i in range(3)]
                for i in complements:
                    tindices = tuple(indices + list(i))
                    thisy += y[tindices]
                thisy /= float(len(complements))
            else:
                indices += c
                indices = tuple(indices)
                thisy = y[indices]
            plt.plot(x, thisy, label=zlabel)
        if args.abscissa == "mu":
            plt.axvline(x=0., color=PSEUDO_BLACK, lw=2)
        if args.quantity == "L":
            plt.axhline(y=L0, color=PSEUDO_BLACK, lw=2)
        plt.xlabel(xlabel)
        plt.ylabel(ylabel)
        plt.legend(loc="best").draggable(True)
        plt.tight_layout()
    plt.show()
Esempio n. 5
0
def parse_plotbands(args):
    """Parse command-line arguments for the plotbands subcommand."""
    # Try and load the data from the interpolation step
    data, equivalences, coeffs, metadata = (
        BoltzTraP2.serialization.load_calculation(args.bt2_file))
    lattvec = data.get_lattvec()
    info("sucessfully loaded " + args.bt2_file)
    # The second position alargument is first interpreted as a Python literal,
    # and after parsing it is cast to a NumPy array, which must have the right
    # dimensions. The special value None directs the parser to split the path
    # in several parts.
    try:
        kpaths = ast.literal_eval(args.kpath)
    except ValueError:
        lexit("'{}' cannot be parsed as a Python literal".format(kpaths))
    if not isinstance(kpaths, list):
        lexit("'{}' cannot be parsed as a Python list".format(kpaths))
    kpaths = [
        list(group)
        for k, group in itertools.groupby(
            kpaths, key=lambda x: x is not None) if k
    ]
    try:
        kpaths = [np.array(i, dtype=np.float64) for i in kpaths]
        for i in kpaths:
            if i.shape[0] < 2 or i.shape[1] != 3:
                raise ValueError
    except ValueError:
        lexit("the path cannot be interpreted as a set of N x 3"
              " arrays (with N >= 2")

    plt.figure()
    ax = plt.gca()
    ticks = []
    dividers = []
    offset = 0.
    for ikpath, kpath in enumerate(kpaths):
        ax.set_prop_cycle(
            color=matplotlib.rcParams["axes.prop_cycle"].by_key()["color"])
        info("k path #{}".format(i + 1))
        # Generate the explicit point list.
        kp, dkp, dcl = asekp.bandpath(kpath, data.atoms.cell, args.nkpoints)
        dkp += offset
        dcl += offset
        # Compute the band energies.
        with TimerContext() as timer:
            egrid = BoltzTraP2.fite.getBands(kp, equivalences,
                                             data.get_lattvec(), coeffs)[0]
            deltat = timer.get_deltat()
            info("rebuilding the bands took {:.3g} s".format(deltat))
        egrid -= data.fermi
        # Create the plot
        nbands = egrid.shape[0]
        for i in range(nbands):
            plt.plot(dkp, egrid[i, :], lw=2.)
        ticks += dcl.tolist()
        dividers += [dcl[0], dcl[-1]]
        offset = dkp[-1]
    ax.set_xticks(ticks)
    ax.set_xticklabels([])
    for d in ticks:
        plt.axvline(x=d, color=PSEUDO_BLACK, ls="--", lw=.5)
    for d in dividers:
        plt.axvline(x=d, color=PSEUDO_BLACK, ls="-", lw=2.)
    plt.axhline(y=0., color=PSEUDO_BLACK, lw=1.)
    plt.ylabel(r"$\varepsilon - \varepsilon_F\;\left[\mathrm{Ha}\right]$")
    plt.tight_layout()
    plt.show()
Esempio n. 6
0
def parse_integrate(args):
    """Parse command-line arguments for the integrate subcommand."""
    # If the number of bins is not set by hand, let the code handle it
    # automatically.
    try:
        args.bins
    except AttributeError:
        args.bins = None
    # Try and load the data from the interpolation step
    data, equivalences, coeffs, metadata = (
        BoltzTraP2.serialization.load_calculation(args.bt2_file))
    lattvec = data.get_lattvec()
    info("sucessfully loaded " + args.bt2_file)
    # Rebuild the bands
    with TimerContext() as timer:
        eband, vvband, cband = BoltzTraP2.fite.getBTPbands(
            equivalences, coeffs, lattvec, True, args.nworkers)
        deltat = timer.get_deltat()
        info("rebuilding the bands took {:.3g} s".format(deltat))

    # Compute the DOS histogram
    with TimerContext() as timer:
        epsilon, dos, vvdos, cdos = BoltzTraP2.bandlib.BTPDOS(
            eband,
            vvband,
            cband,
            npts=args.bins,
            scattering_model=args.scattering_model)
        deltat = timer.get_deltat()
        info("computing the DOS took {:.3g} s".format(deltat))
    info("Number of DOS bins:", epsilon.size)

    # Refine the estimate of the intrinsic chemical potential at
    # each temperature.
    Tr = args.temperature
    mu0 = np.empty_like(Tr)
    for iT, T in enumerate(Tr):
        mu0[iT] = BoltzTraP2.bandlib.refine_mu0(epsilon, dos, data.nelect, T,
                                                data.dosweight)
    # And at 0 K
    fermi = BoltzTraP2.bandlib.refine_mu0(epsilon, dos, data.nelect, 0.,
                                          data.dosweight)

    # Compute the moments of the FD distribution at each point.
    # Determine the chemical potentials to explore by taking the values of the
    # energy bins and discarding those that are too close to the edge to give
    # meaningful results.
    # 9 * kB * T gives a reduction in fFD of about 1e-4, a relatively
    # safe margin (although still far from the hard cutoff in bandlib).
    margin = 9. * BOLTZMANN * Tr.max()
    mur_indices = np.logical_and(epsilon > epsilon.min() + margin,
                                 epsilon < epsilon.max() - margin)
    mur = epsilon[mur_indices]
    if mur.size == 0:
        lexit("the energy window is too narrow")

    # Get the smooth DOS at each temperature
    with TimerContext() as timer:
        sdos = np.empty((Tr.size, epsilon.size))
        for iT, T in enumerate(Tr):
            sdos[iT, :] = BoltzTraP2.bandlib.smoothen_DOS(epsilon, dos, T)
        sdos = sdos[:, mur_indices]
        deltat = timer.get_deltat()
        info("smoothing the DOS took {:.3g} s".format(deltat))

    info("Temperatures:", Tr)
    info("Fermi level from DFT:", data.fermi)
    info("Refined Fermi level:", fermi)
    info("Intrinsic chemical potential for each T:", mu0)
    info("Chemical potentials:", mur)
    with TimerContext() as timer:
        cv = BoltzTraP2.bandlib.calc_cv(
            epsilon, dos, mur, Tr, dosweight=data.dosweight)
        N, L0, L1, L2, Lm11 = BoltzTraP2.bandlib.fermiintegrals(
            epsilon,
            dos,
            vvdos,
            mur=mur,
            Tr=Tr,
            dosweight=data.dosweight,
            cdos=cdos)
        deltat = timer.get_deltat()
        info("computing the FD moments took {:.3g} s".format(deltat))

    # Rescale and combine the moments to get the Onsager transport coefficients
    vuc = data.atoms.get_volume() * Angstrom**3
    L11, seebeck, kappa, hall = BoltzTraP2.bandlib.calc_Onsager_coefficients(
        L0, L1, L2, mur, Tr, vuc, Lm11)

    # Save the results to BoltzTraP-style files
    basefn = os.path.splitext(args.bt2_file)[0]
    tracefn = basefn + ".trace"
    info("trace output file:", tracefn)
    BoltzTraP2.io.save_trace(tracefn, data, Tr, mur, N, sdos, cv, L11, seebeck,
                             kappa, hall)
    condtensfn = basefn + ".condtens"
    info("conductivity/seebeck output file:", condtensfn)
    BoltzTraP2.io.save_condtens(condtensfn, Tr, mur, N, L11, seebeck, kappa)
    halltensfn = basefn + ".halltens"
    info("Hall coefficient output file:", halltensfn)
    BoltzTraP2.io.save_halltens(halltensfn, Tr, mur, N, hall)

    # Save the results to a single compressed JSON file
    metadata2 = BoltzTraP2.serialization.gen_btj_metadata(
        metadata, args.scattering_model)
    jsonfn = basefn + ".btj"
    info("JSON output file:", jsonfn)
    BoltzTraP2.serialization.save_results(jsonfn, data, fermi, Tr, mu0, mur, N,
                                          sdos, cv, L11, seebeck, kappa, hall,
                                          metadata2)
Esempio n. 7
0
def plot_fermisurface(data, equivalences, ebands, mu, nworkers=1):
    """Launch an interactive VTK representation of the Fermi surface.

    Make sure to check the module-level variable "available" before calling
    this function.

    Args:
        data: a DFTData object
        equivalences: list of k-point equivalence classes
        ebands: eband: (nbands, nkpoints) array with the band energies
        mu: initial value of the energy at which the surface will be plotted,
            with respect to data.fermi. This can later be changed by the user
            using an interactive slider.
        nworkers: number of worker processes to use for the band reconstruction

    Returns:
        None.
    """
    lattvec = data.get_lattvec()
    rlattvec = 2. * np.pi * la.inv(lattvec).T

    # Obtain the first Brillouin zone as the Voronoi polyhedron of Gamma
    points = []
    for ijk0 in itertools.product(range(5), repeat=3):
        ijk = [i if i <= 2 else i - 5 for i in ijk0]
        points.append(rlattvec @ np.array(ijk))
    voronoi = scipy.spatial.Voronoi(points)
    region_index = voronoi.point_region[0]
    vertex_indices = voronoi.regions[region_index]
    vertices = voronoi.vertices[vertex_indices, :]
    # Compute a center and an outward-pointing normal for each of the facets
    # of the BZ
    facets = []
    for ridge in voronoi.ridge_vertices:
        if all(i in vertex_indices for i in ridge):
            facets.append(ridge)
    centers = []
    normals = []
    for f in facets:
        corners = np.array([voronoi.vertices[i, :] for i in f])
        center = corners.mean(axis=0)
        v1 = corners[0, :]
        for i in range(1, corners.shape[0]):
            v2 = corners[i, :]
            prod = np.cross(v1 - center, v2 - center)
            if not np.allclose(prod, 0.):
                break
        if np.dot(center, prod) < 0.:
            prod = -prod
        centers.append(center)
        normals.append(prod)

    # Get the extent of the regular grid in reciprocal space
    hdims = np.max(np.abs(np.vstack(equivalences)), axis=0)
    dims = 2 * hdims + 1

    class PointPickerInteractorStyle(vtk.vtkInteractorStyleTrackballCamera):
        """Custom interaction style enabling the user to pick points on
        the screen.
        """

        def __init__(self, parent=None):
            """Simple constructor that adds an observer to the middle mouse
            button press.
            """
            self.AddObserver("MiddleButtonPressEvent", self.pick_point)

        def pick_point(self, obj, event):
            """Get the coordinates of the point selected with the middle mouse
            button, find the nearest data point, and print its direct
            coordinates.
            """
            interactor = self.GetInteractor()
            picker = interactor.GetPicker()
            pos = interactor.GetEventPosition()
            picker.Pick(
                pos[0], pos[1], 0,
                interactor.GetRenderWindow().GetRenderers().GetFirstRenderer())
            picked = np.array(picker.GetPickPosition())
            # Move the sphere to the new coordinates and make it visible
            sphere.SetCenter(*picked.tolist())
            sphere_actor.VisibilityOn()
            picked = la.solve(rlattvec, picked)
            print("Point picked:", picked)
            self.OnMiddleButtonDown()

    # Create the VTK representation of the grid
    sgrid = vtk.vtkStructuredGrid()
    sgrid.SetDimensions(*dims)
    spoints = vtk.vtkPoints()
    for ijk0 in itertools.product(*(range(0, d) for d in dims)):
        ijk = [
            ijk0[i] if ijk0[i] <= hdims[i] else ijk0[i] - dims[i]
            for i in range(len(dims))
        ]
        abc = np.array(ijk, dtype=np.float64) / np.array(dims)
        xyz = rlattvec @ abc
        spoints.InsertNextPoint(*xyz.tolist())
    sgrid.SetPoints(spoints)

    # Find the shortest distance between points to compute a good
    # radius for the selector sphere later.
    dmin = np.infty
    for i in range(3):
        abc = np.zeros(3)
        abc[i] = 1. / dims[i]
        xyz = rlattvec @ abc
        dmin = min(dmin, la.norm(xyz))

    ebands -= data.fermi
    emax = ebands.max(axis=1)
    emin = ebands.min(axis=1)
    # Remove all points outside the BZ
    for i, ijk0 in enumerate(itertools.product(*(range(0, d) for d in dims))):
        ijk = [
            ijk0[j] if ijk0[j] <= hdims[j] else ijk0[j] - dims[j]
            for j in range(len(dims))
        ]
        abc = np.array(ijk, dtype=np.float64) / np.array(dims)
        xyz = rlattvec @ abc
        for c, n in zip(centers, normals):
            if np.dot(xyz - c, n) > 0.:
                ebands[:, i] = np.nan
                break

    # Create a 2D chemical potential slider
    slider = vtk.vtkSliderRepresentation2D()
    slider.SetMinimumValue(emin.min())
    slider.SetMaximumValue(emax.max())
    slider.SetValue(mu)
    slider.SetTitleText("Chemical potential")
    slider.GetPoint1Coordinate().SetCoordinateSystemToNormalizedDisplay()
    slider.GetPoint1Coordinate().SetValue(0.1, 0.9)
    slider.GetPoint2Coordinate().SetCoordinateSystemToNormalizedDisplay()
    slider.GetPoint2Coordinate().SetValue(0.9, 0.9)
    slider.GetTubeProperty().SetColor(*colors.hex2color("#2e3436"))
    slider.GetSliderProperty().SetColor(*colors.hex2color("#a40000"))
    slider.GetCapProperty().SetColor(*colors.hex2color("#babdb6"))
    slider.GetSelectedProperty().SetColor(*colors.hex2color("#a40000"))
    slider.GetTitleProperty().SetColor(*colors.hex2color("#2e3436"))

    # Find all the isosurfaces with energy equal to the threshold
    allcontours = []
    with TimerContext() as timer:
        fermiactors = []
        for band in ebands:
            sgridp = vtk.vtkStructuredGrid()
            sgridp.DeepCopy(sgrid)
            # Feed the energies to VTK
            scalar = vtk.vtkFloatArray()
            for i in band:
                scalar.InsertNextValue(i)
            sgridp.GetPointData().SetScalars(scalar)
            # Estimate the isosurfaces
            contours = vtk.vtkMarchingContourFilter()
            contours.SetInputData(sgridp)
            contours.UseScalarTreeOn()
            contours.SetValue(0, mu)
            contours.ComputeNormalsOff()
            contours.ComputeGradientsOff()
            allcontours.append(contours)

            # Use vtkStrippers to plot the surfaces faster
            stripper = vtk.vtkStripper()
            stripper.SetInputConnection(contours.GetOutputPort())

            # Compute the normals to the surfaces to obtain better lighting
            normals = vtk.vtkPolyDataNormals()
            normals.SetInputConnection(stripper.GetOutputPort())
            normals.ComputeCellNormalsOn()
            normals.ComputePointNormalsOn()

            # Create a mapper and an actor for the surfaces
            mapper = vtk.vtkPolyDataMapper()
            mapper.SetInputConnection(normals.GetOutputPort())
            mapper.ScalarVisibilityOff()
            fermiactors.append(vtk.vtkActor())
            fermiactors[-1].SetMapper(mapper)
            fermiactors[-1].GetProperty().SetColor(*colors.hex2color(
                "#a40000"))
        deltat = timer.get_deltat()
        info("building the surfaces took {:.3g} s".format(deltat))

    # Represent the BZ as a polyhedron in VTK
    points = vtk.vtkPoints()
    for v in voronoi.vertices:
        points.InsertNextPoint(*v)
    fids = vtk.vtkIdList()
    fids.InsertNextId(len(facets))
    for f in facets:
        fids.InsertNextId(len(f))
        for i in f:
            fids.InsertNextId(i)
    fgrid = vtk.vtkUnstructuredGrid()
    fgrid.SetPoints(points)
    fgrid.InsertNextCell(vtk.VTK_POLYHEDRON, fids)

    # Create an actor and a mapper for the BZ
    mapper = vtk.vtkDataSetMapper()
    mapper.SetInputData(fgrid)
    bzactor = vtk.vtkActor()
    bzactor.SetMapper(mapper)
    bzactor.GetProperty().SetColor(*colors.hex2color("#204a87"))
    bzactor.GetProperty().SetOpacity(0.2)

    # Create a visual representation of the selected point, and hide
    # it for the time being.
    sphere = vtk.vtkSphereSource()
    sphere.SetRadius(dmin / 2.)
    sphere_mapper = vtk.vtkPolyDataMapper()
    sphere_mapper.SetInputConnection(sphere.GetOutputPort())
    sphere_mapper.ScalarVisibilityOff()
    sphere_actor = vtk.vtkActor()
    sphere_actor.SetMapper(sphere_mapper)
    sphere_actor.GetProperty().SetColor(*colors.hex2color("#f57900"))
    sphere_actor.VisibilityOff()

    # Create a VTK window and other elements of an interactive scene
    renderer = vtk.vtkRenderer()
    renderer.AddActor(bzactor)
    renderer.AddActor(sphere_actor)
    for f in fermiactors:
        renderer.AddActor(f)
    renderer.ResetCamera()
    renderer.GetActiveCamera().Zoom(5.)
    renderer.SetBackground(1., 1., 1.)

    window = vtk.vtkRenderWindow()
    window.AddRenderer(renderer)
    interactor = vtk.vtkRenderWindowInteractor()
    interactor.SetInteractorStyle(PointPickerInteractorStyle())
    interactor.SetRenderWindow(window)

    # Add a set of axes
    axes = vtk.vtkAxesActor()
    assembly = vtk.vtkPropAssembly()
    assembly.AddPart(axes)
    marker = vtk.vtkOrientationMarkerWidget()
    marker.SetOrientationMarker(assembly)
    marker.SetInteractor(interactor)
    marker.SetEnabled(1)
    marker.InteractiveOff()

    def callback(obj, ev):
        """Update the isosurface with a new value"""
        mu = obj.GetRepresentation().GetValue()
        for e, E, c, a in zip(emin, emax, allcontours, fermiactors):
            visible = e <= mu and E >= mu
            a.SetVisibility(visible)
            if visible:
                c.SetValue(0, mu)

    # Add the slider widget
    widget = vtk.vtkSliderWidget()
    widget.SetInteractor(interactor)
    widget.SetRepresentation(slider)
    widget.SetAnimationModeToJump()
    widget.EnabledOn()
    widget.AddObserver(vtk.vtkCommand.InteractionEvent, callback)

    # Launch the visualization
    interactor.Initialize()
    window.Render()
    interactor.Start()
Esempio n. 8
0
def parse_dope(args):
    """Parse command-line arguments for the dope subcommand.

    This is based on parse_integrate, but it selects the values of the
    chemical potential to explore based on the requested doping levels.
    """
    try:
        args.bins
    except AttributeError:
        args.bins = None
    Tr = args.temperature
    if Tr.size == 0:
        lexit("empty temperature specification")
    elif Tr.min() <= 0.:
        lexit("all temperatures must be positive")
    data, equivalences, coeffs, metadata = (
        BoltzTraP2.serialization.load_calculation(args.bt2_file))
    lattvec = data.get_lattvec()
    info("sucessfully loaded " + args.bt2_file)
    with TimerContext() as timer:
        eband, vvband, cband = BoltzTraP2.fite.getBTPbands(
            equivalences, coeffs, lattvec, True, args.nworkers)
        deltat = timer.get_deltat()
        info("rebuilding the bands took {:.3g} s".format(deltat))

    # Calculate the DOS.
    with TimerContext() as timer:
        epsilon, dos, vvdos, cdos = BoltzTraP2.bandlib.BTPDOS(
            eband,
            vvband,
            cband,
            npts=args.bins,
            scattering_model=args.scattering_model,
            Tmin=Tr.min())
        deltat = timer.get_deltat()
        info("computing the DOS took {:.3g} s".format(deltat))

    # Change the band gap if required.
    if args.scissor is not None:
        args.scissor *= eV
        info("Band gap value of {:.3g} Ha specified.".format(args.scissor) +
             " Trying to shift the gap to that value.")
        eband = BoltzTraP2.bandlib.apply_scissor(epsilon, dos, data.nelect,
                                                 eband, args.scissor,
                                                 data.dosweight)
        with TimerContext() as timer:
            epsilon, dos, vvdos, cdos = BoltzTraP2.bandlib.BTPDOS(
                eband,
                vvband,
                cband,
                npts=args.bins,
                scattering_model=args.scattering_model,
                Tmin=Tr.min())
        deltat = timer.get_deltat()
        info("recomputing the DOS took {:.3g} s".format(deltat))

    info("Number of DOS bins:", epsilon.size)
    nT = len(Tr)
    mu0 = np.empty_like(Tr)
    for iT, T in enumerate(Tr):
        mu0[iT] = BoltzTraP2.bandlib.solve_for_mu(epsilon,
                                                  dos,
                                                  data.nelect,
                                                  T,
                                                  data.dosweight,
                                                  refine=True,
                                                  try_center=True)
    fermi = BoltzTraP2.bandlib.solve_for_mu(epsilon,
                                            dos,
                                            data.nelect,
                                            0.,
                                            data.dosweight,
                                            refine=True,
                                            try_center=True)
    margin = 9. * BOLTZMANN * Tr.max()
    # The function diverges from parse_integrate from this point on.
    info("Temperatures:", Tr)
    info("Fermi level from DFT:", data.fermi)
    info("Refined Fermi level:", fermi)
    info("Intrinsic chemical potential for each T:", mu0)
    # Minimum and maximum reasonable values of mu
    mumin = epsilon.min() + margin
    mumax = epsilon.max() - margin
    if mumin >= mumax:
        lexit("the energy window is too narrow")
    # Convert the doping levels to values of mu at each temperature
    vuc = data.atoms.get_volume() * Angstrom**3
    vuccm3 = data.atoms.get_volume() * 1e-24  # in cm^3
    dopingr = args.doping_level
    ndoping = len(dopingr)
    mur = np.empty((nT, ndoping))
    for iT, T in enumerate(Tr):
        dopingmin = BoltzTraP2.bandlib.calc_N(epsilon, dos, mumax, T,
                                              data.dosweight) + data.nelect
        dopingmin /= vuccm3
        dopingmax = BoltzTraP2.bandlib.calc_N(epsilon, dos, mumin, T,
                                              data.dosweight) + data.nelect
        dopingmax /= vuccm3
        # Check that the requested doping levels are in the acceptable range
        if dopingr.min() <= dopingmin or dopingr.max() >= dopingmax:
            lexit("minimum and maximum possible concentrations"
                  " at T = {:.2f} K: {:.2g}, {:.2g}".format(
                      T, dopingmin, dopingmax))
        for idoping, doping in enumerate(dopingr):
            # Invert N(mu) to obtain an estimate of mu for each case.
            # Refine the estimate by not constraining it to one of the energies
            # in the histogram.
            N = data.nelect - doping * vuccm3
            mur[iT, idoping] = BoltzTraP2.bandlib.solve_for_mu(
                epsilon,
                dos,
                N,
                T,
                data.dosweight,
                refine=True,
                try_center=False)
        info("Chemical potentials for T = {:.2f} K:".format(T), mur[iT, :])
    # The smooth DOS is obtained using a direct KDE in energy space, instead
    # of a convolution
    with TimerContext() as timer:
        sdos = np.empty((nT, ndoping))
        for iT, T in enumerate(Tr):
            sdos[iT, :] = BoltzTraP2.bandlib.smoothen_DOS_direct(
                epsilon, dos, T, mur[iT, :])
        deltat = timer.get_deltat()
        info("smoothing the DOS took {:.3g} s".format(deltat))
    # The flow from this point is again similar to that in parse_integrate,
    # with the exception that the chemical potentials are different for
    # each temperature.
    cv = np.empty((nT, ndoping))
    N = np.empty((nT, ndoping))
    L0 = np.empty((nT, ndoping, 3, 3))
    L1 = np.empty((nT, ndoping, 3, 3))
    L2 = np.empty((nT, ndoping, 3, 3))
    Lm11 = np.empty((nT, ndoping, 3, 3, 3))
    with TimerContext() as timer:
        for iT, T in enumerate(Tr):
            cv[iT] = BoltzTraP2.bandlib.calc_cv(epsilon,
                                                dos,
                                                mur[iT, :],
                                                np.array([T]),
                                                dosweight=data.dosweight)
            (N[iT], L0[iT], L1[iT], L2[iT],
             Lm11[iT]) = BoltzTraP2.bandlib.fermiintegrals(
                 epsilon,
                 dos,
                 vvdos,
                 mur=mur[iT],
                 Tr=np.array([T]),
                 dosweight=data.dosweight,
                 cdos=cdos)
        deltat = timer.get_deltat()
        info("computing the FD moments took {:.3g} s".format(deltat))
    L11 = np.empty((nT, ndoping, 3, 3))
    seebeck = np.empty((nT, ndoping, 3, 3))
    kappa = np.empty((nT, ndoping, 3, 3))
    hall = np.empty((nT, ndoping, 3, 3, 3))
    for iT, T in enumerate(Tr):
        (L11[iT], seebeck[iT], kappa[iT],
         hall[iT]) = BoltzTraP2.bandlib.calc_Onsager_coefficients(
             L0[[iT]], L1[[iT]], L2[[iT]], mur[iT], np.array([T]), vuc,
             Lm11[[iT]])
    basefn = os.path.splitext(args.bt2_file)[0]
    tracefn = basefn + ".dope.trace"
    info("trace output file:", tracefn)
    BoltzTraP2.io.save_trace(tracefn,
                             data,
                             Tr,
                             mur,
                             N,
                             sdos,
                             cv,
                             L11,
                             seebeck,
                             kappa,
                             hall,
                             scattering_model=args.scattering_model)
    condtensfn = basefn + ".dope.condtens"
    info("conductivity/seebeck output file:", condtensfn)
    BoltzTraP2.io.save_condtens(condtensfn,
                                data,
                                Tr,
                                mur,
                                N,
                                L11,
                                seebeck,
                                kappa,
                                scattering_model=args.scattering_model)
    halltensfn = basefn + ".dope.halltens"
    info("Hall coefficient output file:", halltensfn)
    BoltzTraP2.io.save_halltens(halltensfn, data, Tr, mur, N, hall)