Write inputs (optional), run BoltzTraP, and ensure
        convergence (optional)
        Args:
            path_dir (str): directory in which to run BoltzTraP
            convergence (bool): whether to check convergence and make
                corrections if needed
            write_input: (bool) whether to write input files before the run
                (required for convergence mode)
            clear_dir: (bool) whether to remove all files in the path_dir
                before starting
            max_lpfac: (float) maximum lpfac value to try before reducing egrid
                in convergence mode
            min_egrid: (float) minimum egrid value to try before giving up in
                convergence mode

        Returns:

def run(self, path_dir=None, convergence=True, write_input=True,
            clear_dir=False, max_lpfac=150, min_egrid=0.00005):
        """
        Write inputs (optional), run BoltzTraP, and ensure
        convergence (optional)
        Args:
            path_dir (str): directory in which to run BoltzTraP
            convergence (bool): whether to check convergence and make
                corrections if needed
            write_input: (bool) whether to write input files before the run
                (required for convergence mode)
            clear_dir: (bool) whether to remove all files in the path_dir
                before starting
            max_lpfac: (float) maximum lpfac value to try before reducing egrid
                in convergence mode
            min_egrid: (float) minimum egrid value to try before giving up in
                convergence mode

        Returns:

        """

        # TODO: consider making this a part of custodian rather than pymatgen
        # A lot of this functionality (scratch dirs, handlers, monitors)
        # is built into custodian framework

        if convergence and not write_input:
            raise ValueError("Convergence mode requires write_input to be "
                             "true")

        if self.run_type in ("BANDS", "DOS", "FERMI"):
            convergence = False
            if self.lpfac > max_lpfac:
                max_lpfac = self.lpfac

        if self.run_type == "BANDS" and self.bs.is_spin_polarized:
            print("Reminder: for run_type " + str(
                self.run_type) + ", spin component are not separated! "
                                 "(you have a spin polarized band structure)")

        if self.run_type in ("FERMI", "DOS") and self.spin is None:
            if self.bs.is_spin_polarized:
                raise BoltztrapError(
                    "Spin parameter must be specified for spin polarized "
                    "band structures!")
            else:
                self.spin = 1

        dir_bz_name = "boltztrap"
        if path_dir is None:
            temp_dir = tempfile.mkdtemp()
            path_dir = os.path.join(temp_dir, dir_bz_name)
        else:
            path_dir = os.path.abspath(
                os.path.join(path_dir, dir_bz_name))

        if not os.path.exists(path_dir):
            os.mkdir(path_dir)
        elif clear_dir:
            for c in os.listdir(path_dir):
                os.remove(os.path.join(path_dir, c))

        FORMAT = "%(message)s"
        logging.basicConfig(level=logging.INFO, format=FORMAT,
                            filename=os.path.join(path_dir, "../boltztrap.out"))

        with cd(path_dir):
            lpfac_start = self.lpfac
            converged = False

            while self.energy_grid >= min_egrid and not converged:
                self.lpfac = lpfac_start
                if time.time() - self.start_time > self.timeout:
                    raise BoltztrapError("no doping convergence after timeout "
                                         "of {} s".format(self.timeout))

                logging.info("lpfac, energy_grid: {} {}".format(self.lpfac, self.energy_grid))

                while self.lpfac <= max_lpfac and not converged:
                    if time.time() - self.start_time > self.timeout:
                        raise BoltztrapError("no doping convergence after "
                                        "timeout of {} s".format(self.timeout))

                    if write_input:
                        self.write_input(path_dir)

                    bt_exe = ["x_trans", "BoltzTraP"]
                    if self._bs.is_spin_polarized or self.soc:
                        bt_exe.append("-so")

                    p = subprocess.Popen(bt_exe, stdout=subprocess.PIPE,
                                         stdin=subprocess.PIPE,
                                         stderr=subprocess.PIPE)
                    p.wait()

                    for c in p.communicate():
                        logging.info(c.decode())
                        if "error in factorization" in c.decode():
                            raise BoltztrapError("error in factorization")

                    warning = ""

                    with open(os.path.join(path_dir,
                                           dir_bz_name + ".outputtrans")) as f:
                        for l in f:
                            if "Option unknown" in l:
                                raise BoltztrapError(
                                    "DOS mode needs a custom version of "
                                    "BoltzTraP code is needed")
                            if "WARNING" in l:
                                warning = l
                                break
                            if "Error - Fermi level was not found" in l:
                                warning = l
                                break

                    if not warning and convergence:
                        # check convergence for warning
                        analyzer = BoltztrapAnalyzer.from_files(path_dir)
                        for doping in ['n', 'p']:
                            for c in analyzer.mu_doping[doping]:
                                if len(analyzer.mu_doping[doping][c]) != len(
                                        analyzer.doping[doping]):
                                    warning = "length of mu_doping array is " \
                                              "incorrect"
                                    break

                                if doping == 'p' and \
                                                sorted(
                                                    analyzer.mu_doping[doping][
                                                        c], reverse=True) != \
                                                analyzer.mu_doping[doping][c]:
                                    warning = "sorting of mu_doping array " \
                                              "incorrect for p-type"
                                    break

                                # ensure n-type doping sorted correctly
                                if doping == 'n' and sorted(
                                        analyzer.mu_doping[doping][c]) != \
                                        analyzer.mu_doping[doping][c]:
                                    warning = "sorting of mu_doping array " \
                                              "incorrect for n-type"
                                    break

                    if warning:
                        self.lpfac += 10
                        logging.warn("Warning detected: {}! Increase lpfac to "
                                     "{}".format(warning, self.lpfac))

                    else:
                        converged = True

                if not converged:
                    self.energy_grid /= 10
                    logging.info("Could not converge with max lpfac; "
                                 "Decrease egrid to {}".format(self.energy_grid))

            if not converged:
                raise BoltztrapError(
                    "Doping convergence not reached with lpfac=" + str(
                        self.lpfac) + ", energy_grid=" + str(self.energy_grid))

            return path_dir