Instruction:
Write a Python 3 function for
returning the data about the VBM.

Response:
def get_vbm(self):
        """
        Returns data about the VBM.

        Returns:
            dict as {"band_index","kpoint_index","kpoint","energy"}
            - "band_index": A dict with spin keys pointing to a list of the
            indices of the band containing the VBM (please note that you
            can have several bands sharing the VBM) {Spin.up:[],
            Spin.down:[]}
            - "kpoint_index": The list of indices in self.kpoints for the
            kpoint vbm. Please note that there can be several
            kpoint_indices relating to the same kpoint (e.g., Gamma can
            occur at different spots in the band structure line plot)
            - "kpoint": The kpoint (as a kpoint object)
            - "energy": The energy of the VBM
            - "projections": The projections along sites and orbitals of the
            VBM if any projection data is available (else it is an empty
            dictionnary). The format is similar to the projections field in
            BandStructure: {spin:{'Orbital': [proj]}} where the array
            [proj] is ordered according to the sites in structure
    """
        if self.is_metal():
            return {"band_index": [], "kpoint_index": [],
                    "kpoint": [], "energy": None, "projections": {}}
        max_tmp = -float("inf")
        index = None
        kpointvbm = None
        for spin, v in self.bands.items():
            for i, j in zip(*np.where(v < self.efermi)):
                if v[i, j] > max_tmp:
                    max_tmp = float(v[i, j])
                    index = j
                    kpointvbm = self.kpoints[j]

        list_ind_kpts = []
        if kpointvbm.label is not None:
            for i in range(len(self.kpoints)):
                if self.kpoints[i].label == kpointvbm.label:
                    list_ind_kpts.append(i)
        else:
            list_ind_kpts.append(index)
        # get all other bands sharing the vbm
        list_ind_band = collections.defaultdict(list)
        for spin in self.bands:
            for i in range(self.nb_bands):
                if math.fabs(self.bands[spin][i][index] - max_tmp) < 0.001:
                    list_ind_band[spin].append(i)
        proj = {}
        for spin, v in self.projections.items():
            if len(list_ind_band[spin]) == 0:
                continue
            proj[spin] = v[list_ind_band[spin][0]][list_ind_kpts[0]]
        return {'band_index': list_ind_band,
                'kpoint_index': list_ind_kpts,
                'kpoint': kpointvbm, 'energy': max_tmp,
                'projections': proj}


Instruction:
Given the following Python 3 function, write the documentation
def get_cbm(self):
        if self.is_metal():
            return {"band_index": [], "kpoint_index": [],
                    "kpoint": [], "energy": None, "projections": {}}
        max_tmp = float("inf")

        index = None
        kpointcbm = None
        for spin, v in self.bands.items():
            for i, j in zip(*np.where(v >= self.efermi)):
                if v[i, j] < max_tmp:
                    max_tmp = float(v[i, j])
                    index = j
                    kpointcbm = self.kpoints[j]

        list_index_kpoints = []
        if kpointcbm.label is not None:
            for i in range(len(self.kpoints)):
                if self.kpoints[i].label == kpointcbm.label:
                    list_index_kpoints.append(i)
        else:
            list_index_kpoints.append(index)

        # get all other bands sharing the cbm
        list_index_band = collections.defaultdict(list)
        for spin in self.bands:
            for i in range(self.nb_bands):
                if math.fabs(self.bands[spin][i][index] - max_tmp) < 0.001:
                    list_index_band[spin].append(i)
        proj = {}
        for spin, v in self.projections.items():
            if len(list_index_band[spin]) == 0:
                continue
            proj[spin] = v[list_index_band[spin][0]][list_index_kpoints[0]]

        return {'band_index': list_index_band,
                'kpoint_index': list_index_kpoints,
                'kpoint': kpointcbm, 'energy': max_tmp,
                'projections': proj}

Response: Returns the data about the CBM.


Instruction:
Write a Python 3 script for
returning the band gap data.

Response:
def get_band_gap(self):
        """
        Returns band gap data.

        Returns:
            A dict {"energy","direct","transition"}:
            "energy": band gap energy
            "direct": A boolean telling if the gap is direct or not
            "transition": kpoint labels of the transition (e.g., "\\Gamma-X")
        """
        if self.is_metal():
            return {"energy": 0.0, "direct": False, "transition": None}
        cbm = self.get_cbm()
        vbm = self.get_vbm()
        result = dict(direct=False, energy=0.0, transition=None)

        result["energy"] = cbm["energy"] - vbm["energy"]

        if (cbm["kpoint"].label is not None and cbm["kpoint"].label == vbm[
            "kpoint"].label) \
                or np.linalg.norm(cbm["kpoint"].cart_coords
                                  - vbm["kpoint"].cart_coords) < 0.01:
            result["direct"] = True

        result["transition"] = "-".join(
            [str(c.label) if c.label is not None else
             str("(") + ",".join(["{0:.3f}".format(c.frac_coords[i])
                                  for i in range(3)])
             + str(")") for c in [vbm["kpoint"], cbm["kpoint"]]])

        return result


Instruction:
How would you explain what the following Python 3 function does
def get_direct_band_gap_dict(self):
        if self.is_metal():
            raise ValueError("get_direct_band_gap_dict should"
                             "only be used with non-metals")
        direct_gap_dict = {}
        for spin, v in self.bands.items():
            above = v[np.all(v > self.efermi, axis=1)]
            min_above = np.min(above, axis=0)
            below = v[np.all(v < self.efermi, axis=1)]
            max_below = np.max(below, axis=0)
            diff = min_above - max_below
            kpoint_index = np.argmin(diff)
            band_indices = [np.argmax(below[:, kpoint_index]),
                            np.argmin(above[:, kpoint_index]) + len(below)]
            direct_gap_dict[spin] = {"value": diff[kpoint_index],
                                     "kpoint_index": kpoint_index,
                                     "band_indices": band_indices}
        return direct_gap_dict

Response: Returns a dictionary of information about the direct band gap containing the k - point indices and the k - point index.


Instruction:
Can you write a function in Python 3 where it
returns the value of the direct band gap.

Response:
def get_direct_band_gap(self):
        """
        Returns the direct band gap.

        Returns:
             the value of the direct band gap
        """
        if self.is_metal():
            return 0.0
        dg = self.get_direct_band_gap_dict()
        return min(v['value'] for v in dg.values())


Instruction:
How would you implement a function in Python 3 that
returns a list of unique symmetrically equivalent k - points.

Response:
def get_sym_eq_kpoints(self, kpoint, cartesian=False, tol=1e-2):
        """
        Returns a list of unique symmetrically equivalent k-points.

        Args:
            kpoint (1x3 array): coordinate of the k-point
            cartesian (bool): kpoint is in cartesian or fractional coordinates
            tol (float): tolerance below which coordinates are considered equal

        Returns:
            ([1x3 array] or None): if structure is not available returns None
        """
        if not self.structure:
            return None
        sg = SpacegroupAnalyzer(self.structure)
        symmops = sg.get_point_group_operations(cartesian=cartesian)
        points = np.dot(kpoint, [m.rotation_matrix for m in symmops])
        rm_list = []
        # identify and remove duplicates from the list of equivalent k-points:
        for i in range(len(points) - 1):
            for j in range(i + 1, len(points)):
                if np.allclose(pbc_diff(points[i], points[j]), [0, 0, 0], tol):
                    rm_list.append(i)
                    break
        return np.delete(points, rm_list, axis=0)


Instruction:
Can you write a function in Python 3 where it
returns the number of degeneracy of a given k - point in the structure

Response:
def get_kpoint_degeneracy(self, kpoint, cartesian=False, tol=1e-2):
        """
        Returns degeneracy of a given k-point based on structure symmetry
        Args:
            kpoint (1x3 array): coordinate of the k-point
            cartesian (bool): kpoint is in cartesian or fractional coordinates
            tol (float): tolerance below which coordinates are considered equal

        Returns:
            (int or None): degeneracy or None if structure is not available
        """
        all_kpts = self.get_sym_eq_kpoints(kpoint, cartesian, tol=tol)
        if all_kpts is not None:
            return len(all_kpts)


Instruction:
Can you write a function in Python 3 where it
creates a new object from a dictionary.

Response:
def from_dict(cls, d):
        """
        Create from dict.

        Args:
            A dict with all data for a band structure object.

        Returns:
            A BandStructure object
        """
        labels_dict = d['labels_dict']
        projections = {}
        structure = None
        if isinstance(list(d['bands'].values())[0], dict):
            eigenvals = {Spin(int(k)): np.array(d['bands'][k]['data'])
                         for k in d['bands']}
        else:
            eigenvals = {Spin(int(k)): d['bands'][k] for k in d['bands']}
        if 'structure' in d:
            structure = Structure.from_dict(d['structure'])
        if d.get('projections'):
            projections = {Spin(int(spin)): np.array(v)
                           for spin, v in d["projections"].items()}

        return BandStructure(
            d['kpoints'], eigenvals,
            Lattice(d['lattice_rec']['matrix']), d['efermi'],
            labels_dict, structure=structure, projections=projections)


Instruction:
Can you create a Python 3 function that
returns the list of kpoint indices equivalent to the given kpoint index.

Response:
def get_equivalent_kpoints(self, index):
        """
        Returns the list of kpoint indices equivalent (meaning they are the
        same frac coords) to the given one.

        Args:
            index: the kpoint index

        Returns:
            a list of equivalent indices

        TODO: now it uses the label we might want to use coordinates instead
        (in case there was a mislabel)
        """
        # if the kpoint has no label it can"t have a repetition along the band
        # structure line object

        if self.kpoints[index].label is None:
            return [index]

        list_index_kpoints = []
        for i in range(len(self.kpoints)):
            if self.kpoints[i].label == self.kpoints[index].label:
                list_index_kpoints.append(i)

        return list_index_kpoints


Instruction:
How would you code a function in Python 3 to
return a BandStructureSymmLine object from a dict with all data for a band structure symm line.

Response:
def from_dict(cls, d):
        """
        Args:
            d (dict): A dict with all data for a band structure symm line
                object.

        Returns:
            A BandStructureSymmLine object
        """
        try:
            # Strip the label to recover initial string (see trick used in as_dict to handle $ chars)
            labels_dict = {k.strip(): v for k, v in d['labels_dict'].items()}
            projections = {}
            structure = None
            if d.get('projections'):
                if isinstance(d["projections"]['1'][0][0], dict):
                    raise ValueError("Old band structure dict format detected!")
                structure = Structure.from_dict(d['structure'])
                projections = {Spin(int(spin)): np.array(v)
                               for spin, v in d["projections"].items()}

            return BandStructureSymmLine(
                d['kpoints'], {Spin(int(k)): d['bands'][k]
                               for k in d['bands']},
                Lattice(d['lattice_rec']['matrix']), d['efermi'],
                labels_dict, structure=structure, projections=projections)
        except:
            warnings.warn("Trying from_dict failed. Now we are trying the old "
                          "format. Please convert your BS dicts to the new "
                          "format. The old format will be retired in pymatgen "
                          "5.0.")
            return BandStructureSymmLine.from_old_dict(d)


Instruction:
Can you generate a brief explanation for the following Python 3 code
def from_old_dict(cls, d):
        # Strip the label to recover initial string (see trick used in as_dict to handle $ chars)
        labels_dict = {k.strip(): v for k, v in d['labels_dict'].items()}
        projections = {}
        structure = None
        if 'projections' in d and len(d['projections']) != 0:
            structure = Structure.from_dict(d['structure'])
            projections = {}
            for spin in d['projections']:
                dd = []
                for i in range(len(d['projections'][spin])):
                    ddd = []
                    for j in range(len(d['projections'][spin][i])):
                        dddd = []
                        for k in range(len(d['projections'][spin][i][j])):
                            ddddd = []
                            orb = Orbital(k).name
                            for l in range(len(d['projections'][spin][i][j][
                                                   orb])):
                                ddddd.append(d['projections'][spin][i][j][
                                                 orb][l])
                            dddd.append(np.array(ddddd))
                        ddd.append(np.array(dddd))
                    dd.append(np.array(ddd))
                projections[Spin(int(spin))] = np.array(dd)

        return BandStructureSymmLine(
            d['kpoints'], {Spin(int(k)): d['bands'][k]
                           for k in d['bands']},
            Lattice(d['lattice_rec']['matrix']), d['efermi'],
            labels_dict, structure=structure, projections=projections)

Response: Returns a BandStructureSymmLine object from a dict with all data for a band structure symm line.


Instruction:
How would you explain what the following Python 3 function does
def apply_scissor(self, new_band_gap):
        if self.is_metal():
            # moves then the highest index band crossing the fermi level
            # find this band...
            max_index = -1000
            # spin_index = None
            for i in range(self.nb_bands):
                below = False
                above = False
                for j in range(len(self.kpoints)):
                    if self.bands[Spin.up][i][j] < self.efermi:
                        below = True
                    if self.bands[Spin.up][i][j] > self.efermi:
                        above = True
                if above and below:
                    if i > max_index:
                        max_index = i
                        # spin_index = Spin.up
                if self.is_spin_polarized:
                    below = False
                    above = False
                    for j in range(len(self.kpoints)):
                        if self.bands[Spin.down][i][j] < self.efermi:
                            below = True
                        if self.bands[Spin.down][i][j] > self.efermi:
                            above = True
                    if above and below:
                        if i > max_index:
                            max_index = i
                            # spin_index = Spin.down
            old_dict = self.as_dict()
            shift = new_band_gap
            for spin in old_dict['bands']:
                for k in range(len(old_dict['bands'][spin])):
                    for v in range(len(old_dict['bands'][spin][k])):
                        if k >= max_index:
                            old_dict['bands'][spin][k][v] = \
                                old_dict['bands'][spin][k][v] + shift
        else:

            shift = new_band_gap - self.get_band_gap()['energy']
            old_dict = self.as_dict()
            for spin in old_dict['bands']:
                for k in range(len(old_dict['bands'][spin])):
                    for v in range(len(old_dict['bands'][spin][k])):
                        if old_dict['bands'][spin][k][v] >= \
                                old_dict['cbm']['energy']:
                            old_dict['bands'][spin][k][v] = \
                                old_dict['bands'][spin][k][v] + shift
            old_dict['efermi'] = old_dict['efermi'] + shift

            return LobsterBandStructureSymmLine.from_dict(old_dict)

Response: This function applies a scissor operator to the band structure.


Instruction:
How would you code a function in Python 3 to
return a new object from a dictionary with all data for a band structure symm line.

Response:
def from_old_dict(cls, d):
        """
        Args:
            d (dict): A dict with all data for a band structure symm line
                object.
        Returns:
            A BandStructureSymmLine object
        """
        # Strip the label to recover initial string (see trick used in as_dict to handle $ chars)
        labels_dict = {k.strip(): v for k, v in d['labels_dict'].items()}
        projections = {}
        structure = None
        if 'projections' in d and len(d['projections']) != 0:
            structure = Structure.from_dict(d['structure'])
            projections = {}
            for spin in d['projections']:
                dd = []
                for i in range(len(d['projections'][spin])):
                    ddd = []
                    for j in range(len(d['projections'][spin][i])):
                        dddd = []
                        ddd.append(d['projections'][spin][i][j])
                    dd.append(np.array(ddd))
                projections[Spin(int(spin))] = np.array(dd)

        return LobsterBandStructureSymmLine(
            d['kpoints'], {Spin(int(k)): d['bands'][k]
                           for k in d['bands']},
            Lattice(d['lattice_rec']['matrix']), d['efermi'],
            labels_dict, structure=structure, projections=projections)


Instruction:
How would you explain what the following Python 3 function does
def _initial_guess(self):
        a, b, c = np.polyfit(self.volumes, self.energies, 2)
        self.eos_params = [a, b, c]

        v0 = -b/(2*a)
        e0 = a*(v0**2) + b*v0 + c
        b0 = 2 * a * v0
        b1 = 4  # b1 is usually a small number like 4

        vmin, vmax = min(self.volumes), max(self.volumes)

        if not vmin < v0 and v0 < vmax:
            raise EOSError('The minimum volume of a fitted parabola is '
                           'not in the input volumes\n.')

        return e0, b0, b1, v0

Response: Get an initial guess for the parameters.


Instruction:
Can you implement a function in Python 3 that
fits the least square algorithm to obtain the current state of the object.

Response:
def fit(self):
        """
        Do the fitting. Does least square fitting. If you want to use custom
        fitting, must override this.
        """
        # the objective function that will be minimized in the least square
        # fitting
        objective_func = lambda pars, x, y: y - self._func(x, pars)
        self._params = self._initial_guess()
        self.eos_params, ierr = leastsq(
            objective_func, self._params, args=(self.volumes, self.energies))
        # e0, b0, b1, v0
        self._params = self.eos_params
        if ierr not in [1, 2, 3, 4]:
            raise EOSError("Optimal parameters not found")


Instruction:
Explain what the following Python 3 code does
def results(self):
        return dict(e0=self.e0, b0=self.b0, b1=self.b1, v0=self.v0)

Response: Returns a summary dict.


Instruction:
Can you create a Python 3 function that
plots the equation of state.

Response:
def plot(self, width=8, height=None, plt=None, dpi=None, **kwargs):
        """
        Plot the equation of state.

        Args:
            width (float): Width of plot in inches. Defaults to 8in.
            height (float): Height of plot in inches. Defaults to width *
                golden ratio.
            plt (matplotlib.pyplot): If plt is supplied, changes will be made
                to an existing plot. Otherwise, a new plot will be created.
            dpi:
            kwargs (dict): additional args fed to pyplot.plot.
                supported keys: style, color, text, label

        Returns:
            Matplotlib plot object.
        """
        plt = pretty_plot(width=width, height=height, plt=plt, dpi=dpi)

        color = kwargs.get("color", "r")
        label = kwargs.get("label", "{} fit".format(self.__class__.__name__))
        lines = ["Equation of State: %s" % self.__class__.__name__,
                 "Minimum energy = %1.2f eV" % self.e0,
                 "Minimum or reference volume = %1.2f Ang^3" % self.v0,
                 "Bulk modulus = %1.2f eV/Ang^3 = %1.2f GPa" %
                 (self.b0, self.b0_GPa),
                 "Derivative of bulk modulus wrt pressure = %1.2f" % self.b1]
        text = "\n".join(lines)
        text = kwargs.get("text", text)

        # Plot input data.
        plt.plot(self.volumes, self.energies, linestyle="None", marker="o",
                 color=color)

        # Plot eos fit.
        vmin, vmax = min(self.volumes), max(self.volumes)
        vmin, vmax = (vmin - 0.01 * abs(vmin), vmax + 0.01 * abs(vmax))
        vfit = np.linspace(vmin, vmax, 100)

        plt.plot(vfit, self.func(vfit), linestyle="dashed", color=color,
                 label=label)

        plt.grid(True)
        plt.xlabel("Volume $\\AA^3$")
        plt.ylabel("Energy (eV)")
        plt.legend(loc="best", shadow=True)
        # Add text with fit parameters.
        plt.text(0.4, 0.5, text, transform=plt.gca().transAxes)

        return plt


Instruction:
Can you generate the documentation for the following Python 3 function
def plot_ax(self, ax=None, fontsize=12, **kwargs):
        ax, fig, plt = get_ax_fig_plt(ax=ax)

        color = kwargs.get("color", "r")
        label = kwargs.get("label", "{} fit".format(self.__class__.__name__))
        lines = ["Equation of State: %s" % self.__class__.__name__,
                 "Minimum energy = %1.2f eV" % self.e0,
                 "Minimum or reference volume = %1.2f Ang^3" % self.v0,
                 "Bulk modulus = %1.2f eV/Ang^3 = %1.2f GPa" %
                 (self.b0, self.b0_GPa),
                 "Derivative of bulk modulus wrt pressure = %1.2f" % self.b1]
        text = "\n".join(lines)
        text = kwargs.get("text", text)

        # Plot input data.
        ax.plot(self.volumes, self.energies, linestyle="None", marker="o", color=color)

        # Plot eos fit.
        vmin, vmax = min(self.volumes), max(self.volumes)
        vmin, vmax = (vmin - 0.01 * abs(vmin), vmax + 0.01 * abs(vmax))
        vfit = np.linspace(vmin, vmax, 100)

        ax.plot(vfit, self.func(vfit), linestyle="dashed", color=color, label=label)

        ax.grid(True)
        ax.set_xlabel("Volume $\\AA^3$")
        ax.set_ylabel("Energy (eV)")
        ax.legend(loc="best", shadow=True)
        # Add text with fit parameters.
        ax.text(0.5, 0.5, text, fontsize=fontsize, horizontalalignment='center',
            verticalalignment='center', transform=ax.transAxes)

        return fig

Response: Plots the equation of state on axis ax.


Instruction:
Write a Python 3 script to
compute the log - level of the log - level entry for the Intermetallic compounds.

Response:
def _func(self, volume, params):
        """
        From Intermetallic compounds: Principles and Practice, Vol. I:
        Principles Chapter 9 pages 195-210 by M. Mehl. B. Klein,
        D. Papaconstantopoulos.
        case where n=0
        """
        e0, b0, b1, v0 = tuple(params)
        return (e0
                + 9.0 / 8.0 * b0 * v0 * ((v0 / volume)**(2.0/3.0) - 1.0) ** 2
                + 9.0 / 16.0 * b0 * v0 * (b1 - 4.) *
                ((v0 / volume)**(2.0/3.0) - 1.0) ** 3)


Instruction:
Given the following Python 3 function, write the documentation
def _func(self, volume, params):
        e0, b0, b1, v0 = tuple(params)
        eta = (v0 / volume) ** (1. / 3.)
        return (e0 +
                9. * b0 * v0 / 16. * (eta ** 2 - 1)**2 *
                (6 + b1 * (eta ** 2 - 1.) - 4. * eta ** 2))

Response: Compute the BirchMurnaghan equation from PRB 70 224107


Instruction:
How would you explain what the following Python 3 function does
def _func(self, volume, params):
        e0, b0, b1, v0 = tuple(params)
        eta = (volume / v0) ** (1. / 3.)
        squiggle = -3.*np.log(eta)
        return e0 + b0 * v0 * squiggle ** 2 / 6. * (3. + squiggle * (b1 - 2))

Response: Compute the log - likelihood of a set of log - likelihoods.


Instruction:
Can you implement a function in Python 3 that
computes the parameter e0 b0 b1 and v0 and set to the _params attribute.

Response:
def _set_params(self):
        """
        Use the fit polynomial to compute the parameter e0, b0, b1 and v0
        and set to the _params attribute.
        """
        fit_poly = np.poly1d(self.eos_params)
        # the volume at min energy, used as the intial guess for the
        # optimization wrt volume.
        v_e_min = self.volumes[np.argmin(self.energies)]
        # evaluate e0, v0, b0 and b1
        min_wrt_v = minimize(fit_poly, v_e_min)
        e0, v0 = min_wrt_v.fun, min_wrt_v.x[0]
        pderiv2 = np.polyder(fit_poly, 2)
        pderiv3 = np.polyder(fit_poly, 3)
        b0 = v0 * np.poly1d(pderiv2)(v0)
        db0dv = np.poly1d(pderiv2)(v0) + v0 * np.poly1d(pderiv3)(v0)
        # db/dp
        b1 = - v0 * db0dv / b0
        self._params = [e0, b0, b1, v0]


Instruction:
Given the following Python 3 function, write the documentation
def fit(self, order=3):
        x = self.volumes**(-2./3.)
        self.eos_params = np.polyfit(x, self.energies, order)
        self._set_params()

Response: Fit the eos parameters to the energy of the local eos.


Instruction:
Implement a function in Python 3 to
set the parameters of the EOS system.

Response:
def _set_params(self):
        """
        Overriden to account for the fact the fit with volume**(2/3) instead
        of volume.
        """
        deriv0 = np.poly1d(self.eos_params)
        deriv1 = np.polyder(deriv0, 1)
        deriv2 = np.polyder(deriv1, 1)
        deriv3 = np.polyder(deriv2, 1)

        for x in np.roots(deriv1):
            if x > 0 and deriv2(x) > 0:
                v0 = x**(-3./2.)
                break
        else:
            raise EOSError("No minimum could be found")

        derivV2 = 4./9. * x**5. * deriv2(x)
        derivV3 = (-20./9. * x**(13./2.) * deriv2(x) - 8./27. *
                   x**(15./2.) * deriv3(x))
        b0 = derivV2 / x**(3./2.)
        b1 = -1 - x**(-3./2.) * derivV3 / derivV2

        # e0, b0, b1, v0
        self._params = [deriv0(v0**(-2./3.)), b0, b1, v0]


Instruction:
Implement a Python 3 function for
fitting the input data to the numerical eos model.

Response:
def fit(self, min_ndata_factor=3, max_poly_order_factor=5, min_poly_order=2):
        """
        Fit the input data to the 'numerical eos', the equation of state employed
        in the quasiharmonic Debye model described in the paper:
        10.1103/PhysRevB.90.174107.

        credits: Cormac Toher

        Args:
            min_ndata_factor (int): parameter that controls the minimum number
                of data points that will be used for fitting.
                minimum number of data points =
                    total data points-2*min_ndata_factor
            max_poly_order_factor (int): parameter that limits the max order
                of the polynomial used for fitting.
                max_poly_order = number of data points used for fitting -
                                 max_poly_order_factor
            min_poly_order (int): minimum order of the polynomial to be
                considered for fitting.
        """
        warnings.simplefilter('ignore', np.RankWarning)

        get_rms = lambda x, y: np.sqrt(np.sum((np.array(x)-np.array(y))**2)/len(x))

        # list of (energy, volume) tuples
        e_v = [(i, j) for i, j in zip(self.energies, self.volumes)]
        ndata = len(e_v)
        # minimum number of data points used for fitting
        ndata_min = max(ndata - 2 * min_ndata_factor, min_poly_order + 1)
        rms_min = np.inf
        # number of data points available for fit in each iteration
        ndata_fit = ndata
        # store the fit polynomial coefficients and the rms in a dict,
        # where the key=(polynomial order, number of data points used for
        # fitting)
        all_coeffs = {}

        # sort by energy
        e_v = sorted(e_v, key=lambda x: x[0])
        # minimum energy tuple
        e_min = e_v[0]
        # sort by volume
        e_v = sorted(e_v, key=lambda x: x[1])
        # index of minimum energy tuple in the volume sorted list
        emin_idx = e_v.index(e_min)
        # the volume lower than the volume corresponding to minimum energy
        v_before = e_v[emin_idx - 1][1]
        # the volume higher than the volume corresponding to minimum energy
        v_after = e_v[emin_idx + 1][1]
        e_v_work = deepcopy(e_v)

        # loop over the data points.
        while (ndata_fit >= ndata_min) and (e_min in e_v_work):
            max_poly_order = ndata_fit - max_poly_order_factor
            e = [ei[0] for ei in e_v_work]
            v = [ei[1] for ei in e_v_work]
            # loop over polynomial order
            for i in range(min_poly_order, max_poly_order + 1):
                coeffs = np.polyfit(v, e, i)
                pder = np.polyder(coeffs)
                a = np.poly1d(pder)(v_before)
                b = np.poly1d(pder)(v_after)
                if a * b < 0:
                    rms = get_rms(e, np.poly1d(coeffs)(v))
                    rms_min = min(rms_min, rms * i / ndata_fit)
                    all_coeffs[(i, ndata_fit)] = [coeffs.tolist(), rms]
                    # store the fit coefficients small to large,
                    # i.e a0, a1, .. an
                    all_coeffs[(i, ndata_fit)][0].reverse()
            # remove 1 data point from each end.
            e_v_work.pop()
            e_v_work.pop(0)
            ndata_fit = len(e_v_work)

        logger.info("total number of polynomials: {}".format(len(all_coeffs)))

        norm = 0.
        fit_poly_order = ndata
        # weight average polynomial coefficients.
        weighted_avg_coeffs = np.zeros((fit_poly_order,))

        # combine all the filtered polynomial candidates to get the final fit.
        for k, v in all_coeffs.items():
            # weighted rms = rms * polynomial order / rms_min / ndata_fit
            weighted_rms = v[1] * k[0] / rms_min / k[1]
            weight = np.exp(-(weighted_rms ** 2))
            norm += weight
            coeffs = np.array(v[0])
            # pad the coefficient array with zeros
            coeffs = np.lib.pad(coeffs,
                                (0, max(fit_poly_order-len(coeffs), 0)),
                                'constant')
            weighted_avg_coeffs += weight * coeffs

        # normalization
        weighted_avg_coeffs /= norm
        weighted_avg_coeffs = weighted_avg_coeffs.tolist()
        # large to small(an, an-1, ..., a1, a0) as expected by np.poly1d
        weighted_avg_coeffs.reverse()

        self.eos_params = weighted_avg_coeffs
        self._set_params()


Instruction:
Explain what the following Python 3 code does
def fit(self, volumes, energies):
        eos_fit = self.model(np.array(volumes), np.array(energies))
        eos_fit.fit()
        return eos_fit

Response: Fit volumes as function of energies.


Instruction:
Make a summary of the following Python 3 code
def dilute_solution_model(structure, e0, vac_defs, antisite_defs, T, trial_chem_pot=None, generate='plot'):

    if not check_input(vac_defs):
        raise ValueError('Vacancy energy is not defined')
    if not check_input(antisite_defs):
        raise ValueError('Antisite energy is not defined')

    formation_energies = {}
    formation_energies['vacancies'] = copy.deepcopy(vac_defs)
    formation_energies['antisites'] = copy.deepcopy(antisite_defs)
    for vac in formation_energies['vacancies']:
        del vac['energy']
    for asite in formation_energies['antisites']:
        del asite['energy']
    # Setup the system
    site_species = [vac_def['site_specie'] for vac_def in vac_defs]
    multiplicity = [vac_def['site_multiplicity'] for vac_def in vac_defs]
    m = len(set(site_species))  # distinct species
    n = len(vac_defs)  # inequivalent sites

    # Reduce the system and associated parameters such that only distinctive
    # atoms are retained
    comm_div = gcd(*tuple(multiplicity))
    multiplicity = [val / comm_div for val in multiplicity]
    e0 = e0 / comm_div
    T = Float(T)

    #c0 = np.diag(multiplicity)
    c0 = np.diag(np.ones(n))
    mu = [Symbol('mu' + i.__str__()) for i in range(m)]

    # Generate maps for hashing
    # Generate specie->mu map and use it for site->mu map
    specie_order = []  # Contains hash for site->mu map    Eg: [Al, Ni]
    site_specie_set = set()  # Eg: {Ni, Al}
    for i in range(n):
        site_specie = site_species[i]
        if site_specie not in site_specie_set:
            site_specie_set.add(site_specie)
            specie_order.append(site_specie)
    site_mu_map = []  # Eg: [mu0,mu0,mu0,mu1] where mu0->Al, and mu1->Ni
    for i in range(n):
        site_specie = site_species[i]
        j = specie_order.index(site_specie)
        site_mu_map.append(j)
    specie_site_index_map = []  # Eg: [(0,3),(3,4)] for Al & Ni
    for i in range(m):
        low_ind = site_species.index(specie_order[i])
        if i < m - 1:
            hgh_ind = site_species.index(specie_order[i + 1])
        else:
            hgh_ind = n
        specie_site_index_map.append((low_ind, hgh_ind))
    """
    dC: delta concentration matrix:
    dC[i,j,k]: Concentration change of atom i, due to presence of atom
    j on lattice site k
    Special case is [i,i,i] which is considered as vacancy
    Few cases: dC[i,i,i] = -1 due to being vacancy special case
                dC[k,k,i] = +1 due to increment in k at i lattice if i
                               lattice type is of different element
                dC[i,k,i] = -1 due to decrement of ith type atom due to
                presence of kth type atom on ith sublattice and kth type
                atom specie is different from ith sublattice atom specie
                dC[i,k,k] = 0 due to no effect on ith type atom
                dC[i,j,k] = 0 if i!=j!=k
    """
    dC = np.zeros((n, n, n), dtype=np.int)
    for i in range(n):
        for j in range(n):
            for k in range(n):
                if i == j and site_species[j] != site_species[k] and \
                        site_species[i] != site_species[k]:
                    dC[i, j, k] = 1
        for j in range(n):
            for k in range(n):
                if i == k:
                    dC[i, j, k] = -1
    for k in range(n):
        for j in range(n):
            for i in range(n):
                if i != j:
                    if site_species[j] == site_species[k]:
                        dC[i, j, k] = 0

    for ind_map in specie_site_index_map:
        if ind_map[1] - ind_map[0] > 1:
            for index1 in range(ind_map[0] + 1, ind_map[1]):
                for index2 in range(ind_map[0]):
                    for i in range(n):
                        dC[i, index1, index2] = 0
                for index2 in range(ind_map[1], n):
                    for i in range(n):
                        dC[i, index1, index2] = 0

    # dE matrix: Flip energies (or raw defect energies)
    els = [vac_def['site_specie'] for vac_def in vac_defs]
    dE = []
    for i in range(n):
        dE.append([])
    for i in range(n):
        for j in range(n):
            dE[i].append(0)

    for j in range(n):
        for i in range(n):
            if i == j:
                dE[i][j] = vac_defs[i]['energy']
            else:
                sub_specie = vac_defs[i]['site_specie']
                site_specie = vac_defs[j]['site_specie']
                if site_specie == sub_specie:
                    dE[i][j] = 0
                else:
                    for as_def in antisite_defs:
                        if int(as_def['site_index']) == j + 1 and \
                                sub_specie == as_def['substitution_specie']:
                            dE[i][j] = as_def['energy']
                            break
    dE = np.array(dE)

    # Initialization for concentrations
    # c(i,p) == presence of ith type atom on pth type site
    c = Matrix(n, n, [0] * n**2)
    for i in range(n):
        for p in range(n):
            c[i, p] = Integer(c0[i, p])
            site_flip_contribs = []
            for epi in range(n):
                sum_mu = sum([mu[site_mu_map[j]] * Integer(dC[j, epi, p]) for j in range(n)])
                flip = Integer(dC[i, epi, p]) * \
                    exp(-(dE[epi, p] - sum_mu) / (k_B * T))
                if flip not in site_flip_contribs:
                    site_flip_contribs.append(flip)
                    c[i, p] += flip

    total_c = []
    for ind in specie_site_index_map:
        val = 0
        for i in range(*ind):
            sum_i = sum([c[i, j] * multiplicity[j] for j in range(n)])
            val += sum_i
        total_c.append(val)

    c_ratio = [total_c[-1] / total_c[i] for i in range(m)]

    # Expression for Omega, the Grand Potential
    omega1 = e0 - sum([mu[site_mu_map[i]] * sum(c0[i, :]) * multiplicity[i] for i in range(n)])
    omega2 = []
    fm_en_eff = []
    used_dEs = []
    for p_r in range(n):
        for epi in range(n):
            sum_mu = sum([mu[site_mu_map[j]] * dC[j, epi, p_r] for j in range(n)])
            if p_r != epi and site_mu_map[p_r] == site_mu_map[epi]:
                continue
            if dE[epi, p_r] not in used_dEs:
                omega2.append(k_B * T * multiplicity[p_r] * exp(-(dE[epi, p_r] - sum_mu) / (k_B * T)))
                fm_en_eff.append(dE[epi, p_r] - sum_mu)
                used_dEs.append(dE[epi, p_r])
    omega = omega1 - sum(omega2)

    # Compute composition range
    li = specie_site_index_map[0][0]
    hi = specie_site_index_map[0][1]
    comp1_min = sum(multiplicity[li:hi]) / sum(multiplicity) * 100 - 1
    comp1_max = sum(multiplicity[li:hi]) / sum(multiplicity) * 100 + 1
    delta = float(comp1_max - comp1_min) / 120.0
    yvals = []
    for comp1 in np.arange(comp1_min, comp1_max + delta, delta):
        comp2 = 100 - comp1
        y = comp2 / comp1
        yvals.append(y)

    def reduce_mu():
        omega = [e0 - sum([mu[site_mu_map[i]] * sum(c0[i, :]) for i in range(n)])]
        x = solve(omega)
        return x

    def compute_mus_by_search():
        # Compute trial mu
        mu_red = reduce_mu()

        mult = multiplicity
        specie_concen = [sum(mult[ind[0]:ind[1]]) for ind in specie_site_index_map]
        y_vect = [specie_concen[-1] / specie_concen[i] for i in range(m)]
        vector_func = [y_vect[i] - c_ratio[i] for i in range(m - 1)]
        vector_func.append(omega)
        min_diff = 1e10
        mu_vals = None
        c_val = None
        m1_min = -20.0
        if e0 > 0:
            m1_max = 10  # Search space needs to be modified
        else:
            m1_max = 0
        for m1 in np.arange(m1_min, m1_max, 0.01):
            m0 = mu_red[mu[0]].subs(mu[-1], m1)

            try:
                x = nsolve(vector_func, mu, [m0, m1], module="numpy")
            except:
                continue

            c_val = c.subs(dict(zip(mu, x)))
            # if all(x >= 0 for x in c_val):
            specie_concen = []
            for ind in specie_site_index_map:
                specie_concen.append(sum([sum(c_val[i, :]) for i in range(*ind)]))
            y_comp = [specie_concen[-1] / specie_concen[i] for i in range(m)]
            diff = math.sqrt(sum([pow(abs(y_comp[i] - y_vect[i]), 2) for i in range(m)]))
            if diff < min_diff:
                min_diff = diff
                mu_vals = x

        if mu_vals:
            mu_vals = [float(mu_val) for mu_val in mu_vals]
        else:
            raise ValueError()
        return mu_vals

    def compute_def_formation_energies():
        i = 0
        for vac_def in vac_defs:
            site_specie = vac_def['site_specie']
            ind = specie_order.index(site_specie)
            uncor_energy = vac_def['energy']
            formation_energy = uncor_energy + mu_vals[ind]
            formation_energies['vacancies'][i]['formation_energy'] = formation_energy
            specie_ind = site_mu_map[i]
            indices = specie_site_index_map[specie_ind]
            specie_ind_del = indices[1] - indices[0]
            cur_ind = i - indices[0] + 1
            if not specie_ind_del - 1:
                label = '$V_{' + site_specie + '}$'
            else:
                label = '$V_{' + site_specie + '_' + str(cur_ind) + '}$'
            formation_energies['vacancies'][i]['label'] = label
            i += 1
        i = 0
        for as_def in antisite_defs:
            site_specie = as_def['site_specie']
            sub_specie = as_def['substitution_specie']
            ind1 = specie_order.index(site_specie)
            ind2 = specie_order.index(sub_specie)
            uncor_energy = as_def['energy']
            formation_energy = uncor_energy + mu_vals[ind1] - mu_vals[ind2]
            formation_energies['antisites'][i]['formation_energy'] = formation_energy
            specie_ind = site_mu_map[i]
            indices = specie_site_index_map[specie_ind]
            specie_ind_del = indices[1] - indices[0]
            cur_ind = i - indices[0] + 1
            if not specie_ind_del - 1:
                label = '$' + sub_specie + '_{' + site_specie + '}$'
            else:
                label = '$' + sub_specie + '_{' + site_specie + '_' + str(cur_ind) + '}$'
            formation_energies['antisites'][i]['label'] = label
            i += 1
        return formation_energies

    # If generate option is energy compute effective formation energies
    # at ideal stoichiometry and return the formation energies and chem pot.
    if generate == 'energy':
        if not trial_chem_pot:
            mu_vals = compute_mus_by_search()
        else:
            try:
                mu_vals = [trial_chem_pot[element] for element in specie_order]
            except:
                mu_vals = compute_mus()

        formation_energies = compute_def_formation_energies()
        mu_dict = dict(zip(specie_order, mu_vals))
        return formation_energies, mu_dict

    if not trial_chem_pot:
        # Try computing mus by assuming one of the defects is dominant at 0.01
        # concen.  First vacancy is tried and then antisite

        # Generate trial mus assuming vacancy as dominant defect
        # for specie-0 at lower yval
        li = specie_site_index_map[0][0]
        hi = specie_site_index_map[0][1]
        li1 = specie_site_index_map[1][0]
        hi1 = specie_site_index_map[1][1]
        spec_mult = [sum(multiplicity[li:hi]), sum(multiplicity[li1:hi1])]
        ln_def_conc = 4.60517
        for i in range(li, hi):
            vac_flip_en = vac_defs[i]['energy']
            mu_vals = [ln_def_conc * k_B * T - vac_flip_en]
            mu_vals.append((e0 - spec_mult[0] * mu_vals[0]) / spec_mult[1])
            comp_ratio = yvals[0]

            # Test if the trial mus are good
            vector_func = [comp_ratio - c_ratio[0]]
            vector_func.append(omega)
            try:
                mu_vals = nsolve(vector_func, mu, mu_vals)
                if mu_vals:
                    mu_vals = [float(mu_val) for mu_val in mu_vals]
                break
            except:  # Go for antisite as dominant defect
                mu_gs = [Symbol('mu_gs' + j.__str__()) for j in range(m)]

                eqs = [mu_gs[0] - mu_gs[1] - (ln_def_conc * k_B * T - antisite_defs[i]['energy'])]
                eqs.append(spec_mult[0] * mu_gs[0] + spec_mult[1] * mu_gs[1] - e0)
                x = solve(eqs, mu_gs)
                #mu_names = sorted([key.name for key in x.keys()])
                mu_vals = []
                for key in sorted(x.keys(), key=lambda inp: inp.name):
                    mu_vals.append(x[key])
                vector_func = [comp_ratio - c_ratio[0]]
                vector_func.append(omega)

                try:
                    mu_vals = nsolve(vector_func, mu, mu_vals)
                    if mu_vals:
                        mu_vals = [float(mu_val) for mu_val in mu_vals]
                    break
                except:  # Go to the default option (search the space)
                    pass
        else:
            mu_vals = compute_mus_by_search()

    else:
        try:
            mu_vals = [trial_chem_pot[element] for element in specie_order]
        except:
            mu_vals = compute_mus_by_search()

    # Compile mu's for all composition ratios in the range
    #+/- 1% from the stoichiometry
    result = {}
    i = 0
    len_y = len(yvals)
    failed_y, failed_i = [], []
    for y in yvals:
        vector_func = [y - c_ratio[0]]
        vector_func.append(omega)
        try:
            x = nsolve(vector_func, mu, mu_vals, module="numpy")
            if x:
                mu_vals = [float(mu_val) for mu_val in x]
        except:
            failed_y.append(y)
            failed_i.append(i)
            continue
        result[y] = list(mu_vals)
        x = None
        i += 1

    def get_next_mu_val(i):
        if i >= len(yvals):
            return None

        y = yvals[i + 1]
        x = result.get(y, None)
        if x:
            mu_vals = [float(mu_val) for mu_val in x]
            return mu_vals
        else:
            return get_next_mu_val(i + 1)

    def get_prev_mu_val(i):
        if i <= 0:
            return None

        y = yvals[i - 1]
        x = result.get(y, None)
        if x:
            mu_vals = [float(mu_val) for mu_val in x]
            return mu_vals
        else:
            return get_next_mu_val(i - 1)

    # Try to get better trial mus for failed cases
    for j in range(len(failed_y)):
        i = failed_i[j]

        prev_mu_val = get_prev_mu_val(i)
        if not prev_mu_val:
            continue
        next_mu_val = get_next_mu_val(i)
        if not next_mu_val:
            continue

        y = failed_y[j]
        vector_func = [y - c_ratio[0]]
        vector_func.append(omega)
        trial_mu = list(map(lambda x: float(sum(x)) / len(x), zip(prev_mu_val, next_mu_val)))
        try:
            x = nsolve(vector_func, mu, trial_mu, module="numpy")
            if x:
                mu_vals = [float(mu_val) for mu_val in x]
        except:
            continue
        result[y] = mu_vals
        x = None

    # Alternate way of calculating trial mus for failed cases
    # by taking average of trial mus at extremes.
    # for j in range(len(failed_y)):
    #    y = yvals[0]
    #    prev_mu_val = result[y]
    #    y = yvals[-1]
    #    next_mu_val = result[y]
    #
    #    trial_mu = list(map(lambda x: float(sum(x))/len(x),  \
    #            zip(prev_mu_val,next_mu_val)))
    #    y = failed_y[j]
    #    vector_func = [y-c_ratio[0]]
    #    vector_func.append(omega)
    #    try:
    #        x = nsolve(vector_func,mu,trial_mu,module="numpy")
    #        if x:
    #            mu_vals = [float(mu_val) for mu_val in x]
    #    except:
    #        continue
    #    result[y] = list(mu_vals)

    if len(result.keys()) < len(yvals) / 2:
        raise ValueError('Not sufficient data')

    res = []
    new_mu_dict = {}
    # Compute the concentrations for all the compositions
    for key in sorted(result.keys()):
        mu_val = result[key]
        total_c_val = [total_c[i].subs(dict(zip(mu, mu_val))) for i in range(len(total_c))]
        c_val = c.subs(dict(zip(mu, mu_val)))
        res1 = []
        # Concentration of first element/over total concen
        res1.append(float(total_c_val[0] / sum(total_c_val)))
        new_mu_dict[res1[0]] = mu_val
        sum_c0 = sum([c0[i, i] for i in range(n)])
        for i in range(n):
            for j in range(n):
                if i == j:  # Vacancy
                    vac_conc = float(exp(-(mu_val[site_mu_map[i]] + dE[i, i]) / (k_B * T)))
                    res1.append(vac_conc)
                else:  # Antisite
                    res1.append(float(c_val[i, j] / c0[j, j]))
        res.append(res1)

    res = np.array(res)
    dtype = [(str('x'), np.float64)] + [(str('y%d%d' % (i, j)), np.float64) for i in range(n) for j in range(n)]
    res1 = np.sort(res.view(dtype), order=[str('x')], axis=0)

    conc_data = {}
    """Because all the plots have identical x-points storing it in a
    single array"""
    conc_data['x'] = [dat[0][0] for dat in res1]  # x-axis data
    # Element whose composition is varied. For x-label
    conc_data['x_label'] = els[0] + " mole fraction"
    conc_data['y_label'] = "Point defect concentration"
    conc = []
    for i in range(n):
        conc.append([])
        for j in range(n):
            conc[i].append([])
    for i in range(n):
        for j in range(n):
            y1 = [dat[0][i * n + j + 1] for dat in res1]
            conc[i][j] = y1

    y_data = []
    for i in range(n):
        data = conc[i][i]
        specie = els[i]
        specie_ind = site_mu_map[i]
        indices = specie_site_index_map[specie_ind]
        specie_ind_del = indices[1] - indices[0]
        cur_ind = i - indices[0] + 1
        vac_string = "$Vac_{"
        if not specie_ind_del - 1:
            label = vac_string + specie + '}$'
        else:
            label = vac_string + specie + '_' + str(cur_ind) + '}$'
        # Plot data and legend info
        y_data.append({'data': data, 'name': label})

    for i in range(n):
        site_specie = els[i]
        specie_ind = site_mu_map[i]
        indices = specie_site_index_map[specie_ind]
        specie_ind_del = indices[1] - indices[0]
        cur_ind = i - indices[0] + 1
        for j in range(m):  # Antisite plot dat
            sub_specie = specie_order[j]
            if sub_specie == site_specie:
                continue
            if not specie_ind_del - 1:
                label = '$' + sub_specie + '_{' + site_specie + '}$'
            else:
                label = '$' + sub_specie + '_{' + site_specie + '_' + str(cur_ind) + '}$'
            inds = specie_site_index_map[j]
            # TODO: Investigate the value below
            data = np.sum([conc[ind][i] for ind in range(*inds)], axis=0)
            data = data.tolist()
            y_data.append({'data': data, 'name': label})

    conc_data['y'] = y_data

    # Compute the  formation energies
    def compute_vac_formation_energies(mu_vals):
        en = []
        for vac_def in vac_defs:
            site_specie = vac_def['site_specie']
            ind = specie_order.index(site_specie)
            uncor_energy = vac_def['energy']
            formation_energy = uncor_energy + mu_vals[ind]
            en.append(float(formation_energy))
        return en

    en_res = []
    for key in sorted(new_mu_dict.keys()):
        mu_val = new_mu_dict[key]
        en_res.append(compute_vac_formation_energies(mu_val))

    en_data = {'x_label': els[0] + ' mole fraction', 'x': []}
    en_data['x'] = [dat[0][0] for dat in res1]  # x-axis data

    i = 0
    y_data = []
    for vac_def in vac_defs:
        data = [data[i] for data in en_res]
        site_specie = vac_def['site_specie']
        ind = specie_order.index(site_specie)
        specie_ind = site_mu_map[i]
        indices = specie_site_index_map[specie_ind]
        specie_ind_del = indices[1] - indices[0]
        cur_ind = i - indices[0] + 1
        vac_string = "$Vac_{"
        if not specie_ind_del - 1:
            label = vac_string + site_specie + '}$'
        else:
            label = vac_string + site_specie + '_' + str(cur_ind) + '}$'
        y_data.append({'data': data, 'name': label})
        i += 1

    def compute_as_formation_energies(mu_vals):
        en = []
        for as_def in antisite_defs:
            site_specie = as_def['site_specie']
            sub_specie = as_def['substitution_specie']
            ind1 = specie_order.index(site_specie)
            ind2 = specie_order.index(sub_specie)
            uncor_energy = as_def['energy']
            form_en = uncor_energy + mu_vals[ind1] - mu_vals[ind2]
            en.append(form_en)
        return en

    en_res = []
    for key in sorted(new_mu_dict.keys()):
        mu_val = new_mu_dict[key]
        en_res.append(compute_as_formation_energies(mu_val))
    i = 0
    for as_def in antisite_defs:
        data = [data[i] for data in en_res]
        site_specie = as_def['site_specie']
        sub_specie = as_def['substitution_specie']
        ind1 = specie_order.index(site_specie)
        ind2 = specie_order.index(sub_specie)
        specie_ind = site_mu_map[i]
        indices = specie_site_index_map[specie_ind]
        specie_ind_del = indices[1] - indices[0]
        cur_ind = i - indices[0] + 1
        if not specie_ind_del - 1:
            label = '$' + sub_specie + '_{' + site_specie + '}$'
        else:
            label = '$' + sub_specie + '_{' + site_specie + '_' + str(cur_ind) + '}$'
        y_data.append({'data': data, 'name': label})
        i += 1

    en_data['y'] = y_data

    # Return chem potential as well
    mu_data = {'x_label': els[0] + ' mole fraction', 'x': []}
    mu_data['x'] = [dat[0][0] for dat in res1]  # x-axis data

    y_data = []
    for j in range(m):
        specie = specie_order[j]
        mus = [new_mu_dict[key][j] for key in sorted(new_mu_dict.keys())]
        y_data.append({'data': mus, 'name': specie})
    mu_data['y'] = y_data

    return conc_data, en_data, mu_data

Response: Compute the defect densities using dilute solution model.


Instruction:
Given the following Python 3 function, write the documentation
def compute_defect_density(structure, e0, vac_defs, antisite_defs, T=800, trial_chem_pot=None, plot_style="highcharts"):
    conc_data, en_data, mu_data = dilute_solution_model(
        structure, e0, vac_defs, antisite_defs, T, trial_chem_pot=trial_chem_pot)

    if plot_style == 'highcharts':
        "Energy data is ignored in this mode"
        hgh_chrt_data = {}
        hgh_chrt_data['xAxis'] = conc_data['x_label']
        hgh_chrt_data['yAxis'] = conc_data['y_label']

        series = []
        x = conc_data['x']
        for y_data in conc_data['y']:
            y = y_data['data']
            xy = zip(x, y)
            xy = [list(el) for el in xy]
            name = y_data['name'].strip('$')
            flds = name.split('_')
            def_string = flds[0]
            site_string = flds[1].strip('{}')
            name = def_string + "<sub>" + site_string + "</sub>"
            #series.append({'data':xy, 'name':y_data['name']})
            series.append({'data': xy, 'name': name})
        hgh_chrt_data['series'] = series
        return hgh_chrt_data
    elif plot_style == 'gnuplot':

        def data_to_rows(inp_data):
            rows = []
            labels = []
            labels.append(inp_data['x_label'])
            labels += [y['name'] for y in inp_data['y']]
            # labels.sort()
            rows.append('#' + '\t'.join(labels))
            m = len(inp_data['x'])
            for i in range(m):
                data = []
                data.append(inp_data['x'][i])
                data += [y['data'][i] for y in inp_data['y']]
                data = [float(x) for x in data]
                rows.append('\t'.join(list(map(str, data))))
            return rows

        conc_rows = data_to_rows(conc_data)
        en_rows = data_to_rows(en_data)
        mu_rows = data_to_rows(mu_data)

        return conc_rows, en_rows, mu_rows

Response: This function computes the density of the defects in the undefected system.


Instruction:
How would you explain what the following Python 3 function does
def solute_site_preference_finder(structure,
                                  e0,
                                  T,
                                  vac_defs,
                                  antisite_defs,
                                  solute_defs,
                                  solute_concen=0.01,
                                  trial_chem_pot=None):
    """
    Compute the solute defect densities using dilute solution model.
    Args:
        structure: pymatgen.core.structure.Structure object representing the
            primitive or unitcell of the crystal.
        e0: The total energy of the undefected system.
            This is E0 from VASP calculation.
        T: Temperature in Kelvin
        vac_defs: List of vacancy defect parameters in the dictionary format.
            The keys of the dict associated with each vacancy defect are
            1) site_index, 2) site_specie, 3) site_multiplicity, and
            4) energy. 1-3 can be obtained from
            pymatgen.analysis.defects.point_defects.Vacancy class.
            Site index is expected to start with 1 (fortran index).
        antisite_defs: List of antisite defect parameters in the dictionary
            format. The keys of the dict associated with each antisite
            defect are 1) site_index, 2) site_specie, 3) site_multiplicity,
            4) substitution_specie, and 5) energy. 1-3 can be obtained
            from pymatgen.analysis.defects.point_defects.Vacancy class.
        solute_defs: List of solute defect parameters in the dictionary
            format. Similary to that of antisite defs, wtih solute specie
            specified in substitution_specie
        solute_concen: Solute concentration (in fractional value)
        trial_chem_pot: Trial chemical potentials to speedup the plot
            generation. Format is {el1:mu1,...}

    Returns:
        plot_data: The data for plotting the solute defect concentration.
    """

    if not check_input(vac_defs):
        raise ValueError('Vacancy energy is not defined')
    if not check_input(antisite_defs):
        raise ValueError('Antisite energy is not defined')

    formation_energies = {}
    formation_energies['vacancies'] = copy.deepcopy(vac_defs)
    formation_energies['antisites'] = copy.deepcopy(antisite_defs)
    formation_energies['solute'] = copy.deepcopy(solute_defs)
    for vac in formation_energies['vacancies']:
        del vac['energy']
    for asite in formation_energies['antisites']:
        del asite['energy']
    for solute in formation_energies['solute']:
        del solute['energy']
    # Setup the system
    site_species = [vac_def['site_specie'] for vac_def in vac_defs]
    solute_specie = solute_defs[0]['substitution_specie']
    site_species.append(solute_specie)
    multiplicity = [vac_def['site_multiplicity'] for vac_def in vac_defs]
    m = len(set(site_species))  # distinct species
    n = len(vac_defs)  # inequivalent sites

    # Reduce the system and associated parameters such that only distinctive
    # atoms are retained
    comm_div = gcd(*tuple(multiplicity))
    multiplicity = [val / comm_div for val in multiplicity]
    multiplicity.append(0)
    e0 = e0 / comm_div
    T = Float(T)

    #c0 = np.diag(multiplicity)
    c0 = np.diag(np.ones(n + 1))
    c0[n, n] = 0
    mu = [Symbol('mu' + str(i)) for i in range(m)]

    # Generate maps for hashing
    # Generate specie->mu map and use it for site->mu map
    specie_order = []  # Contains hash for site->mu map    Eg: [Al, Ni]
    site_specie_set = set()  # Eg: {Ni, Al}
    for i in range(len(site_species)):
        site_specie = site_species[i]
        if site_specie not in site_specie_set:
            site_specie_set.add(site_specie)
            specie_order.append(site_specie)
    site_mu_map = []  # Eg: [mu0,mu0,mu0,mu1] where mu0->Al, and mu1->Ni
    for i in range(len(site_species)):
        site_specie = site_species[i]
        j = specie_order.index(site_specie)
        site_mu_map.append(j)
    specie_site_index_map = []  # Eg: [(0,3),(3,4)] for Al & Ni
    for i in range(m):
        low_ind = site_species.index(specie_order[i])
        if i < m - 1:
            hgh_ind = site_species.index(specie_order[i + 1])
        else:
            hgh_ind = len(site_species)
        specie_site_index_map.append((low_ind, hgh_ind))
    """
    dC: delta concentration matrix:
    dC[i,j,k]: Concentration change of atom i, due to presence of atom
    j on lattice site k
    Special case is [i,i,i] which is considered as vacancy
    Few cases: dC[i,i,i] = -1 due to being vacancy special case
                dC[k,k,i] = +1 due to increment in k at i lattice if i
                               lattice type is of different element
                dC[i,k,i] = -1 due to decrement of ith type atom due to
                presence of kth type atom on ith sublattice and kth type
                atom specie is different from ith sublattice atom specie
                dC[i,k,k] = 0 due to no effect on ith type atom
                dC[i,j,k] = 0 if i!=j!=k
    """
    dC = np.zeros((n + 1, n + 1, n), dtype=np.int)
    for i in range(n):
        for j in range(n):
            for k in range(n):
                if i == j and site_species[j] != site_species[k] and \
                        site_species[i] != site_species:
                    dC[i, j, k] = 1
        for j in range(n + 1):
            for k in range(n):
                if i == k:
                    dC[i, j, k] = -1
    for k in range(n):
        dC[n, n, k] = 1
    for k in range(n):
        for j in range(n):
            if i != j:
                if site_species[i] == site_species[k]:
                    dC[i, j, k] = 0

    for ind_map in specie_site_index_map:
        if ind_map[1] - ind_map[0] > 1:
            for index1 in range(ind_map[0] + 1, ind_map[1]):
                for index2 in range(ind_map[0]):
                    for i in range(n):
                        dC[i, index1, index2] = 0
                for index2 in range(ind_map[1], n):
                    for i in range(n):
                        dC[i, index1, index2] = 0

    # dE matrix: Flip energies (or raw defect energies)
    els = [vac_def['site_specie'] for vac_def in vac_defs]
    dE = []
    for i in range(n + 1):
        dE.append([])
    for i in range(n + 1):
        for j in range(n):
            dE[i].append(0)

    for j in range(n):
        for i in range(n):
            if i == j:
                dE[i][j] = vac_defs[i]['energy']
            else:
                sub_specie = vac_defs[i]['site_specie']
                site_specie = vac_defs[j]['site_specie']
                if site_specie == sub_specie:
                    dE[i][j] = 0
                else:
                    for as_def in antisite_defs:
                        if int(as_def['site_index']) == j + 1 and \
                                sub_specie == as_def['substitution_specie']:
                            dE[i][j] = as_def['energy']
                            break
        # Solute
        site_specie = vac_defs[j]['site_specie']
        for solute_def in solute_defs:
            def_site_ind = int(solute_def['site_index'])
            def_site_specie = solute_def['site_specie']
            if def_site_specie == site_specie and def_site_ind == j + 1:
                dE[n][j] = solute_def['energy']
                break

    dE = np.array(dE)
    #np.where(dE == np.array(None), 0, dE)

    # Initialization for concentrations
    # c(i,p) == presence of ith type atom on pth type site
    c = Matrix(n + 1, n, [0] * n * (n + 1))
    for i in range(n + 1):
        for p in range(n):
            c[i, p] = Integer(c0[i, p])
            site_flip_contribs = []
            for epi in range(n + 1):
                sum_mu = sum([mu[site_mu_map[j]] * Integer(dC[j, epi, p]) for j in range(n + 1)])
                flip = dC[i, epi, p] * exp(-(dE[epi, p] - sum_mu) / (k_B * T))
                if flip not in site_flip_contribs:
                    site_flip_contribs.append(flip)
                    c[i, p] += flip
    host_c = Matrix(n, n, [0] * n * n)
    for i in range(n):
        for p in range(n):
            host_c[i, p] = Integer(c0[i, p])
            site_flip_contribs = []
            for epi in range(n):
                sum_mu = sum([mu[site_mu_map[j]] * Integer(dC[j, epi, p]) for j in range(n)])
                flip = dC[i, epi, p] * exp(-(dE[epi, p] - sum_mu) / (k_B * T))
                if flip not in site_flip_contribs:
                    site_flip_contribs.append(flip)
                    host_c[i, p] += flip

    #specie_concen = [sum(mult[ind[0]:ind[1]]) for ind in specie_site_index_map]
    #total_c = [sum(c[ind[0]:ind[1]]) for ind in specie_site_index_map]
    total_c = []
    for ind in specie_site_index_map:
        val = 0
        for i in range(*ind):
            sum_i = sum([c[i, j] * multiplicity[j] for j in range(n)])
            val += sum_i
        total_c.append(val)

    c_ratio = [total_c[i] / sum(total_c) for i in range(m)]

    host_total_c = []
    for ind in specie_site_index_map[:-1]:
        val = 0
        for i in range(*ind):
            sum_i = sum([host_c[i, j] * multiplicity[j] for j in range(n)])
            val += sum_i
        host_total_c.append(val)

    host_c_ratio = [host_total_c[i] / sum(host_total_c) for i in range(m - 1)]

    # Expression for Omega, the Grand Potential
    omega1 = e0 - sum([mu[site_mu_map[i]] * sum(c0[i, :]) * multiplicity[i] for i in range(n)])
    omega = omega1

    used_dEs = []
    for p_r in range(n):
        for epi in range(n):
            sum_mu1 = sum([mu[site_mu_map[j]] * Integer(dC[j, epi, p_r]) for j in range(n)])
            sum_mu = sum_mu1 - mu[site_mu_map[n]] * dC[n, epi, p_r]
            if p_r != epi and site_mu_map[p_r] == site_mu_map[epi]:
                continue
            if dE[epi, p_r] not in used_dEs:
                omega1 -= k_B * T * multiplicity[p_r] * \
                    exp(-(dE[epi, p_r] - sum_mu1) / (k_B * T))
                omega -= k_B * T * multiplicity[p_r] * \
                    exp(-(dE[epi, p_r] - sum_mu) / (k_B * T))
                used_dEs.append(dE[epi, p_r])

    # Compute composition ranges
    max_host_specie_concen = 1 - solute_concen
    mult = multiplicity
    specie_concen = [sum(mult[ind[0]:ind[1]]) for ind in specie_site_index_map]
    host_specie_concen_ratio = [specie_concen[i] / sum(specie_concen) * max_host_specie_concen for i in range(m)]
    host_specie_concen_ratio[-1] = solute_concen
    li = specie_site_index_map[0][0]
    hi = specie_site_index_map[0][1]
    comp1_min = sum(multiplicity[li:hi]) / sum(multiplicity) * \
        max_host_specie_concen - 0.01
    comp1_max = sum(multiplicity[li:hi]) / sum(multiplicity) * \
        max_host_specie_concen + 0.01
    delta = (comp1_max - comp1_min) / 50.0

    # def reduce_mu():
    #    omega = [e0 - sum([mu[site_mu_map[i]]*sum(c0[i,:]) for i in range(n)])]
    #    x = solve(omega)
    #    return x
    def reduce_mu():
        host_concen = 1 - solute_concen
        new_c0 = c0.astype(float)
        for i in range(n):
            new_c0[i, i] = host_concen * c0[i, i]
        new_c0[n, n] = 2 * solute_concen
        omega = [e0 - sum([mu[site_mu_map[i]] * sum(new_c0[i, :]) for i in range(n + 1)])]
        x = solve(omega)
        return x

    def compute_solute_mu_by_lin_search(host_mu_vals):
        # Compute trial mu
        mu_red = reduce_mu()

        mult = multiplicity
        specie_concen = [sum(mult[ind[0]:ind[1]]) for ind in specie_site_index_map]
        max_host_specie_concen = 1 - solute_concen
        host_specie_concen_ratio = [specie_concen[i] / sum(specie_concen) * max_host_specie_concen for i in range(m)]
        host_specie_concen_ratio[-1] = solute_concen
        y_vect = host_specie_concen_ratio
        vector_func = [y_vect[i] - c_ratio[i] for i in range(m)]
        vector_func.append(omega)
        min_diff = 1e10
        mu_vals = None
        c_val = None
        m1_min = -20.0
        if e0 > 0:
            m1_max = 10  # Search space needs to be modified
        else:
            m1_max = 0
        for m1 in np.arange(m1_min, m1_max, 0.1):
            trial_mus = host_mu_vals + [m1]
            try:
                x = nsolve(vector_func, mu, trial_mus, module="numpy")
                if x:
                    mu_vals = [float(mu_val) for mu_val in x]
                break
            except:
                continue
        else:
            raise ValueError()
        return mu_vals

    def compute_mus():

        # Compute trial mu
        mu_red = reduce_mu()
        mult = multiplicity
        specie_concen = [sum(mult[ind[0]:ind[1]]) for ind in specie_site_index_map]
        max_host_specie_concen = 1 - solute_concen
        host_specie_concen_ratio = [specie_concen[i] / sum(specie_concen) * max_host_specie_concen for i in range(m)]
        host_specie_concen_ratio[-1] = solute_concen

        y_vect = host_specie_concen_ratio
        vector_func = [y_vect[i] - c_ratio[i] for i in range(m)]
        vector_func.append(omega)
        mu_vals = None
        c_val = None
        m_min = -15.0
        if e0 > 0:
            m_max = 10  # Search space needs to be modified
        else:
            m_max = 0
        for m1 in np.arange(m_min, m_max, 0.3):
            for m2 in np.arange(m_min, m_max, 0.3):
                m0 = mu_red[mu[0]].subs([(mu[1], m1), (mu[2], m2)])
                try:
                    mu_vals = nsolve(vector_func, mu, [m0, m1, m2], module="numpy")
                    # Line needs to be modified to include all mus when n > 2
                except:
                    continue
                break
            if mu_vals:
                mu_vals = [float(mu_val) for mu_val in mu_vals]
                break
        else:
            raise ValueError("Couldn't find mus")
        return mu_vals

    if not trial_chem_pot:
        # Try computing mus by assuming one of the defects is dominant at 0.01
        # concen.  First vacancy is tried and then antisite

        # Generate trial mus assuming vacancy as dominant defect
        # for specie-0 at lower yval
        li = specie_site_index_map[0][0]
        hi = specie_site_index_map[0][1]
        li1 = specie_site_index_map[1][0]
        hi1 = specie_site_index_map[1][1]
        spec_mult = [sum(multiplicity[li:hi]), sum(multiplicity[li1:hi1])]
        ln_def_conc = 4.60517
        for i in range(li, hi):
            vac_flip_en = vac_defs[i]['energy']
            mu_vals = [ln_def_conc * k_B * T - vac_flip_en]
            mu_vals.append((e0 - spec_mult[0] * mu_vals[0]) / spec_mult[1])
            comp_ratio = comp1_min

            # Test if the trial mus are good
            vector_func = [comp_ratio - host_c_ratio[0]]
            vector_func.append(omega1)
            try:
                host_mu_vals = nsolve(vector_func, mu[:-1], mu_vals)
                if host_mu_vals:
                    host_mu_vals = [float(mu_val) for mu_val in host_mu_vals]
                compute_solute_mu_by_lin_search(host_mu_vals)
                break
            except:  # Go for antisite as dominant defect
                mu_gs = [Symbol('mu_gs' + j.__str__()) for j in range(m - 1)]

                eqs = [mu_gs[0] - mu_gs[1] - (ln_def_conc * k_B * T - antisite_defs[i]['energy'])]
                eqs.append(spec_mult[0] * mu_gs[0] + spec_mult[1] * mu_gs[1] - e0)
                x = solve(eqs, mu_gs)
                host_mu_vals = []
                for key in sorted(x.keys(), key=lambda inp: inp.name):
                    host_mu_vals.append(x[key])
                vector_func = [comp_ratio - host_c_ratio[0]]
                vector_func.append(omega1)

                try:
                    host_mu_vals = nsolve(vector_func, mu[:-1], host_mu_vals)
                    if host_mu_vals:
                        host_mu_vals = [float(mu_val) for mu_val in host_mu_vals]
                    mu_vals = compute_solute_mu_by_lin_search(host_mu_vals)
                    break
                except:  # Go to the default option (search the space)
                    pass
        else:
            mu_vals = compute_mus()

    else:
        try:
            mu_vals = [trial_chem_pot[element] for element in specie_order]
        except:
            mu_vals = compute_mus()

    # Compile mu's for all composition ratios in the range
    #+/- 1% from the stoichiometry
    result = {}
    for y in np.arange(comp1_min, comp1_max + delta, delta):
        y_vect = []
        y_vect.append(y)
        y2 = max_host_specie_concen - y
        y_vect.append(y2)
        y_vect.append(solute_concen)
        vector_func = [y_vect[i] - c_ratio[i] for i in range(1, m)]
        vector_func.append(omega)

        try:
            x = nsolve(vector_func, mu, mu_vals)
            if x:
                mu_vals = [float(mu_val) for mu_val in x]
        except:
            continue
        result[y] = mu_vals

    res = []

    # Compute the concentrations for all the compositions
    for key in sorted(result.keys()):
        mu_val = result[key]
        total_c_val = [total_c[i].subs(dict(zip(mu, mu_val))) for i in range(len(total_c))]
        c_val = c.subs(dict(zip(mu, mu_val)))
        # Concentration of first element/over total concen
        res1 = []
        res1.append(float(total_c_val[0] / sum(total_c_val)))

        sum_c0 = sum([c0[i, i] for i in range(n)])
        for i in range(n + 1):
            for j in range(n):
                if i == j:  # Vacancy
                    vac_conc = float(exp(-(mu_val[site_mu_map[i]] + dE[i, i]) / (k_B * T)))
                    res1.append(vac_conc)
                else:  # Antisite
                    res1.append(float(c_val[i, j] / c0[j, j]))
        res.append(res1)

    res = np.array(res)
    dtype = [(str('x'), np.float64)] + [(str('y%d%d' % (i, j)), np.float64) for i in range(n + 1) for j in range(n)]
    res1 = np.sort(res.view(dtype), order=[str('x')], axis=0)

    conc = []
    for i in range(n + 1):
        conc.append([])
        for j in range(n):
            conc[i].append([])
    for i in range(n + 1):  # Append vacancies
        for j in range(n):
            y1 = [dat[0][i * n + j + 1] for dat in res1]
            conc[i][j] = y1

    # Compute solute site preference
    # Removing the functionality
    #site_pref_data = {}
    """Because all the plots have identical x-points storing it in a
    single array"""
    # site_pref_data['x'] = [dat[0][0] for dat in res1]         # x-axis data
    # Element whose composition is varied. For x-label
    #site_pref_data['x_label'] = els[0]+ "_mole_fraction"
    # site_pref_data['y_label'] = "$"+solute_specie+"_{"+els[0]+"}/("+\
    #    solute_specie+"_{"+els[0]+"}+"+solute_specie+"_{"+els[1]+"})$"

    #y_data = []
    #inds = specie_site_index_map[m-1]
    #data1 = np.sum([multiplicity[0]*conc[ind][0] for ind in range(*inds)],axis=0)
    #data2 = np.sum([multiplicity[1]*conc[ind][1] for ind in range(*inds)],axis=0)
    #frac_data = data1/(data1+data2)
    #frac_data = frac_data.tolist()
    # y_data.append({'data':frac_data})

    #site_pref_data['y'] = y_data

    #  Return all defect concentrations
    conc_data = {}
    """Because all the plots have identical x-points storing it in a
    single array"""
    conc_data['x'] = [dat[0][0] for dat in res1]  # x-axis data
    # Element whose composition is varied. For x-label
    conc_data['x_label'] = els[0] + " mole fraction"
    conc_data['y_label'] = "Point defect concentration"

    y_data = []
    # Vacancy
    for i in range(n):
        data = conc[i][i]
        specie = els[i]
        specie_ind = site_mu_map[i]
        indices = specie_site_index_map[specie_ind]
        specie_ind_del = indices[1] - indices[0]
        cur_ind = i - indices[0] + 1
        vac_string = "$Vac_{"
        if not specie_ind_del - 1:
            label = vac_string + specie + '}$'
        else:
            label = vac_string + specie + '_' + str(cur_ind) + '}$'
        # Plot data and legend info
        y_data.append({'data': data, 'name': label})

    # Antisites and solute
    for i in range(n):
        site_specie = els[i]
        specie_ind = site_mu_map[i]
        indices = specie_site_index_map[specie_ind]
        specie_ind_del = indices[1] - indices[0]
        cur_ind = i - indices[0] + 1
        for j in range(m):
            sub_specie = specie_order[j]
            if sub_specie == site_specie:
                continue
            if not specie_ind_del - 1:
                label = '$' + sub_specie + '_{' + site_specie + '}$'
            else:
                label = '$' + sub_specie + '_{' + site_specie + '_' + str(cur_ind) + '}$'
            inds = specie_site_index_map[j]
            # TODO: Investigate the value below
            data = np.sum([conc[ind][i] for ind in range(*inds)], axis=0)
            data = data.tolist()
            y_data.append({'data': data, 'name': label})

    conc_data['y'] = y_data
    # return site_pref_data, conc_data
    return conc_data

Response: This function calculates the solute defect densities using dilute solution model.


Instruction:
Given the following Python 3 function, write the documentation
def solute_defect_density(structure,
                          e0,
                          vac_defs,
                          antisite_defs,
                          solute_defs,
                          solute_concen=0.01,
                          T=800,
                          trial_chem_pot=None,
                          plot_style="highchargs"):
    """
    Wrapper for the solute_site_preference_finder.
    The computed plot data is prepared based on plot_style.

    Args:
        structure: pymatgen.core.structure.Structure object representing the
            primitive or unitcell of the crystal.
        e0: The total energy of the undefected system.
            This is E0 from VASP calculation.
        vac_defs: List of vacancy defect parameters in the dictionary format.
            The keys of the dict associated with each vacancy defect are
            1) site_index, 2) site_specie, 3) site_multiplicity, and
            4) energy. 1-3 can be obtained from
            pymatgen.analysis.defects.point_defects.Vacancy class.
            Site index is expected to start with 1 (fortran index).
        antisite_defs: List of antisite defect parameters in the dictionary
            format. The keys of the dict associated with each antisite defect
            are 1) site_index, 2) site_specie, 3) site_multiplicity,
            4) substitution_specie, and 5) energy. 1-3 can be obtained
            from pymatgen.analysis.defects.point_defects.Vacancy class.
        solute_defs: List of solute defect parameters in the dictionary
            format. Similary to that of antisite defs, wtih solute specie
            specified in substitution_specie
        solute_concen: Solute concentration (in fractional value)
        T: Temperature in Kelvin
        trial_chem_pot (optional): Trial chemical potentials to speedup
            the plot generation. Format is {el1:mu1,...}
        plot_style (string): Allowed options are
            1) highcharts (default)
            2) gnuplot

    Returns:
        The plot data is generated and returned in asked format.
    """

    # solute_site_pref_data, def_conc_data = solute_site_preference_finder(
    def_conc_data = solute_site_preference_finder(
        structure,
        e0,
        T,
        vac_defs,
        antisite_defs,
        solute_defs,
        solute_concen=solute_concen,
        trial_chem_pot=trial_chem_pot)

    if plot_style == 'highcharts':
        "Energy data is ignored in this mode"
        hgh_chrt_data = {}
        hgh_chrt_data['xAxis'] = def_conc_data['x_label']
        hgh_chrt_data['yAxis'] = def_conc_data['y_label']

        series = []
        x = def_conc_data['x']
        for y_data in def_conc_data['y']:
            y = y_data['data']
            xy = zip(x, y)
            xy = [list(el) for el in xy]
            name = y_data['name'].strip('$')
            flds = name.split('_')
            def_string = flds[0]
            site_string = flds[1].strip('{}')
            name = def_string + "<sub>" + site_string + "</sub>"
            #series.append({'data':xy, 'name':y_data['name']})
            series.append({'data': xy, 'name': name})
        hgh_chrt_data['series'] = series
        return hgh_chrt_data
    elif plot_style == 'gnuplot':

        def data_to_rows(inp_data, y_lbl_flg):
            rows = []
            labels = []
            labels.append(inp_data['x_label'])
            if y_lbl_flg:
                labels.append(inp_data['y_label'])
            else:
                labels += [y['name'] for y in inp_data['y']]
            rows.append('#' + '\t'.join(labels))
            m = len(inp_data['x'])
            for i in range(m):
                data = []
                data.append(inp_data['x'][i])
                data += [y['data'][i] for y in inp_data['y']]
                data = [float(x) for x in data]
                rows.append('\t'.join(list(map(str, data))))
            return rows

        #solute_site_pref_rows = data_to_rows(solute_site_pref_data, True)
        pt_def_conc_rows = data_to_rows(def_conc_data, False)
        # return solute_site_pref_rows, pt_def_conc_rows
        return pt_def_conc_rows

Response: This function calculates the solute defect density of a single object.


Instruction:
Can you generate the documentation for the following Python 3 function
def optimize_gibbs_free_energy(self):
        temperatures = np.linspace(
            self.temperature_min,  self.temperature_max,
            int(np.ceil((self.temperature_max - self.temperature_min)
            / self.temperature_step) + 1))

        for t in temperatures:
            try:
                G_opt, V_opt = self.optimizer(t)
            except:
                if len(temperatures) > 1:
                    print("EOS fitting failed, so skipping this data point, {}".
                          format(t))
                    continue
                else:
                    raise
            self.gibbs_free_energy.append(G_opt)
            self.temperatures.append(t)
            self.optimum_volumes.append(V_opt)

Response: Evaluate the gibbs free energy as a function of V T and P and store the optimum volumes wrt V for each T.


Instruction:
Write a Python 3 function that can
evaluate G at the given temperature and pressure and minimize it wrt V.

Response:
def optimizer(self, temperature):
        """
        Evaluate G(V, T, P) at the given temperature(and pressure) and
        minimize it wrt V.

        1. Compute the  vibrational helmholtz free energy, A_vib.
        2. Compute the gibbs free energy as a function of volume, temperature
            and pressure, G(V,T,P).
        3. Preform an equation of state fit to get the functional form of
            gibbs free energy:G(V, T, P).
        4. Finally G(V, P, T) is minimized with respect to V.

        Args:
            temperature (float): temperature in K

        Returns:
            float, float: G_opt(V_opt, T, P) in eV and V_opt in Ang^3.
        """
        G_V = []  # G for each volume
        # G = E(V) + PV + A_vib(V, T)
        for i, v in enumerate(self.volumes):
            G_V.append(self.energies[i] +
                       self.pressure * v * self.gpa_to_ev_ang +
                       self.vibrational_free_energy(temperature, v))

        # fit equation of state, G(V, T, P)
        eos_fit = self.eos.fit(self.volumes, G_V)
        # minimize the fit eos wrt volume
        # Note: the ref energy and the ref volume(E0 and V0) not necessarily
        # the same as minimum energy and min volume.
        volume_guess = eos_fit.volumes[np.argmin(eos_fit.energies)]
        min_wrt_vol = minimize(eos_fit.func, volume_guess)
        # G_opt=G(V_opt, T, P), V_opt
        return min_wrt_vol.fun, min_wrt_vol.x[0]


Instruction:
Implement a function in Python 3 to
return the vibrational free energy in eVova.

Response:
def vibrational_free_energy(self, temperature, volume):
        """
        Vibrational Helmholtz free energy, A_vib(V, T).
        Eq(4) in doi.org/10.1016/j.comphy.2003.12.001

        Args:
            temperature (float): temperature in K
            volume (float)

        Returns:
            float: vibrational free energy in eV
        """
        y = self.debye_temperature(volume) / temperature
        return self.kb * self.natoms * temperature * (
            9./8. * y + 3 * np.log(1 - np.exp(-y)) - self.debye_integral(y))


Instruction:
Can you tell what is the following Python 3 function doing
def vibrational_internal_energy(self, temperature, volume):
        y = self.debye_temperature(volume) / temperature
        return self.kb * self.natoms * temperature * (9./8. * y +
                                                      3*self.debye_integral(y))

Response: Returns the vibrational internal energy in eVibrational.


Instruction:
Create a Python 3 function for
calculating the debye temperature for a given volume in the species.

Response:
def debye_temperature(self, volume):
        """
        Calculates the debye temperature.
        Eq(6) in doi.org/10.1016/j.comphy.2003.12.001. Thanks to Joey.

        Eq(6) above is equivalent to Eq(3) in doi.org/10.1103/PhysRevB.37.790
        which does not consider anharmonic effects. Eq(20) in the same paper
        and Eq(18) in doi.org/10.1016/j.commatsci.2009.12.006 both consider
        anharmonic contributions to the Debye temperature through the Gruneisen
        parameter at 0K (Gruneisen constant).

        The anharmonic contribution is toggled by setting the anharmonic_contribution
        to True or False in the QuasiharmonicDebyeApprox constructor.

        Args:
            volume (float): in Ang^3

        Returns:
            float: debye temperature in K
         """
        term1 = (2./3. * (1. + self.poisson) / (1. - 2. * self.poisson))**1.5
        term2 = (1./3. * (1. + self.poisson) / (1. - self.poisson))**1.5
        f = (3. / (2. * term1 + term2))**(1. / 3.)
        debye = 2.9772e-11 * (volume / self.natoms) ** (-1. / 6.) * f * \
               np.sqrt(self.bulk_modulus/self.avg_mass)
        if self.anharmonic_contribution:
            gamma = self.gruneisen_parameter(0, self.ev_eos_fit.v0)  # 0K equilibrium Gruneisen parameter
            return debye * (self.ev_eos_fit.v0 / volume) ** (gamma)
        else:
            return debye


Instruction:
Here you have a function in Python 3, explain what it does
def thermal_conductivity(self, temperature, volume):
        gamma = self.gruneisen_parameter(temperature, volume)
        theta_d = self.debye_temperature(volume)  # K
        theta_a = theta_d * self.natoms**(-1./3.)  # K
        prefactor = (0.849 * 3 * 4**(1./3.)) / (20. * np.pi**3)
        # kg/K^3/s^3
        prefactor = prefactor * (self.kb/self.hbar)**3 * self.avg_mass
        kappa = prefactor / (gamma**2 - 0.514 * gamma + 0.228)
        # kg/K/s^3 * Ang = (kg m/s^2)/(Ks)*1e-10
        # = N/(Ks)*1e-10 = Nm/(Kms)*1e-10 = W/K/m*1e-10
        kappa = kappa * theta_a**2 * volume**(1./3.) * 1e-10
        return kappa

Response: Equation 17 in 10. 1103. 1348. 1348.


Instruction:
Write a Python 3 script for
returning a dict with a summary of the computed properties.

Response:
def get_summary_dict(self):
        """
        Returns a dict with a summary of the computed properties.
        """
        d = defaultdict(list)
        d["pressure"] = self.pressure
        d["poisson"] = self.poisson
        d["mass"] = self.mass
        d["natoms"] = int(self.natoms)
        d["bulk_modulus"] = self.bulk_modulus
        d["gibbs_free_energy"] = self.gibbs_free_energy
        d["temperatures"] = self.temperatures
        d["optimum_volumes"] = self.optimum_volumes
        for v, t in zip(self.optimum_volumes, self.temperatures):
            d["debye_temperature"].append(self.debye_temperature(v))
            d["gruneisen_parameter"].append(self.gruneisen_parameter(t, v))
            d["thermal_conductivity"].append(self.thermal_conductivity(t, v))
        return d


Instruction:
How would you explain what the following Python 3 function does
def fix_pbc(structure, matrix=None):

    spec = []
    coords = []
    if matrix is None:
        latte = Lattice(structure.lattice.matrix)
    else:
        latte = Lattice(matrix)

    for site in structure:
        spec.append(site.specie)
        coord = np.array(site.frac_coords)
        for i in range(3):
            coord[i] -= floor(coord[i])
            if np.allclose(coord[i], 1):
                coord[i] = 0
            elif np.allclose(coord[i], 0):
                coord[i] = 0
            else:
                coord[i] = round(coord[i], 7)
        coords.append(coord)

    return Structure(latte, spec, coords, site_properties=structure.site_properties)

Response: Fixes the frac_coords of the input structure within [ 0 1 )


Instruction:
Write a Python 3 function for
obtaining cubic symmetric eqivalents of the list of vectors. Args: matrix (lattice matrix, n by 3 array/matrix) Return: cubic symmetric eqivalents of the list of vectors.

Response:
def symm_group_cubic(mat):
    """
     obtain cubic symmetric eqivalents of the list of vectors.

    Args:
        matrix (lattice matrix, n by 3 array/matrix)

    Return:
        cubic symmetric eqivalents of the list of vectors.
    """
    sym_group = np.zeros([24, 3, 3])
    sym_group[0, :] = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
    sym_group[1, :] = [[1, 0, 0], [0, -1, 0], [0, 0, -1]]
    sym_group[2, :] = [[-1, 0, 0], [0, 1, 0], [0, 0, -1]]
    sym_group[3, :] = [[-1, 0, 0], [0, -1, 0], [0, 0, 1]]
    sym_group[4, :] = [[0, -1, 0], [-1, 0, 0], [0, 0, -1]]
    sym_group[5, :] = [[0, -1, 0], [1, 0, 0], [0, 0, 1]]
    sym_group[6, :] = [[0, 1, 0], [-1, 0, 0], [0, 0, 1]]
    sym_group[7, :] = [[0, 1, 0], [1, 0, 0], [0, 0, -1]]
    sym_group[8, :] = [[-1, 0, 0], [0, 0, -1], [0, -1, 0]]
    sym_group[9, :] = [[-1, 0, 0], [0, 0, 1], [0, 1, 0]]
    sym_group[10, :] = [[1, 0, 0], [0, 0, -1], [0, 1, 0]]
    sym_group[11, :] = [[1, 0, 0], [0, 0, 1], [0, -1, 0]]
    sym_group[12, :] = [[0, 1, 0], [0, 0, 1], [1, 0, 0]]
    sym_group[13, :] = [[0, 1, 0], [0, 0, -1], [-1, 0, 0]]
    sym_group[14, :] = [[0, -1, 0], [0, 0, 1], [-1, 0, 0]]
    sym_group[15, :] = [[0, -1, 0], [0, 0, -1], [1, 0, 0]]
    sym_group[16, :] = [[0, 0, 1], [1, 0, 0], [0, 1, 0]]
    sym_group[17, :] = [[0, 0, 1], [-1, 0, 0], [0, -1, 0]]
    sym_group[18, :] = [[0, 0, -1], [1, 0, 0], [0, -1, 0]]
    sym_group[19, :] = [[0, 0, -1], [-1, 0, 0], [0, 1, 0]]
    sym_group[20, :] = [[0, 0, -1], [0, -1, 0], [-1, 0, 0]]
    sym_group[21, :] = [[0, 0, -1], [0, 1, 0], [1, 0, 0]]
    sym_group[22, :] = [[0, 0, 1], [0, -1, 0], [1, 0, 0]]
    sym_group[23, :] = [[0, 0, 1], [0, 1, 0], [-1, 0, 0]]

    mat = np.atleast_2d(mat)
    all_vectors = []
    for sym in sym_group:
        for vec in mat:
            all_vectors.append(np.dot(sym, vec))
    return np.unique(np.array(all_vectors), axis=0)


Instruction:
Create a Python 3 function to
return a copy of the structure with optionally new site properties and optionally sanitized site properties.

Response:
def copy(self):
        """
        Convenience method to get a copy of the structure, with options to add
        site properties.

        Returns:
            A copy of the Structure, with optionally new site_properties and
            optionally sanitized.
        """
        return GrainBoundary(self.lattice, self.species_and_occu, self.frac_coords,
                             self.rotation_axis, self.rotation_angle, self.gb_plane,
                             self.join_plane, self.init_cell, self.vacuum_thickness,
                             self.ab_shift, self.site_properties, self.oriented_unit_cell)


Instruction:
Implement a Python 3 function for
returning a sorted copy of the structure.

Response:
def get_sorted_structure(self, key=None, reverse=False):
        """
        Get a sorted copy of the structure. The parameters have the same
        meaning as in list.sort. By default, sites are sorted by the
        electronegativity of the species. Note that Slab has to override this
        because of the different __init__ args.
        Args:
            key: Specifies a function of one argument that is used to extract
                a comparison key from each list element: key=str.lower. The
                default value is None (compare the elements directly).
            reverse (bool): If set to True, then the list elements are sorted
                as if each comparison were reversed.
        """
        sites = sorted(self, key=key, reverse=reverse)
        s = Structure.from_sites(sites)
        return GrainBoundary(s.lattice, s.species_and_occu, s.frac_coords,
                             self.rotation_axis, self.rotation_angle, self.gb_plane,
                             self.join_plane, self.init_cell, self.vacuum_thickness,
                             self.ab_shift, self.site_properties, self.oriented_unit_cell)


Instruction:
Create a Python 3 function to
return the sigma value of the current set of cell names.

Response:
def sigma(self):
        """
        This method returns the sigma value of the gb.
        If using 'quick_gen' to generate GB, this value is not valid.
        """
        return int(round(self.oriented_unit_cell.volume / self.init_cell.volume))


Instruction:
Given the following Python 3 function, write the documentation
def sigma_from_site_prop(self):
        num_coi = 0
        if None in self.site_properties['grain_label']:
            raise RuntimeError('Site were merged, this property do not work')
        for tag in self.site_properties['grain_label']:
            if 'incident' in tag:
                num_coi += 1
        return int(round(self.num_sites / num_coi))

Response: This method returns the sigma value of the gb from site properties.


Instruction:
Implement a function in Python 3 to
return the top grain of the GB.

Response:
def top_grain(self):
        """
        return the top grain (Structure) of the GB.
        """
        top_sites = []
        for i, tag in enumerate(self.site_properties['grain_label']):
            if 'top' in tag:
                top_sites.append(self.sites[i])
        return Structure.from_sites(top_sites)


Instruction:
Can you tell what is the following Python 3 function doing
def bottom_grain(self):
        bottom_sites = []
        for i, tag in enumerate(self.site_properties['grain_label']):
            if 'bottom' in tag:
                bottom_sites.append(self.sites[i])
        return Structure.from_sites(bottom_sites)

Response: Return the bottom grain of the GB.


Instruction:
Implement a Python 3 function for
returning a list of coincident sites.

Response:
def coincidents(self):
        """
        return the a list of coincident sites.
        """
        coincident_sites = []
        for i, tag in enumerate(self.site_properties['grain_label']):
            if 'incident' in tag:
                coincident_sites.append(self.sites[i])
        return coincident_sites