예제 #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))
예제 #2
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()
예제 #3
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()
예제 #4
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)
예제 #5
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)