Instruction:
How would you explain what the following Python 3 function does
def get_order_parameters(self, structure, n, indices_neighs=None, \
                             tol=0.0, target_spec=None):

        """
        Compute all order parameters of site n.

        Args:
            structure (Structure): input structure.
            n (int): index of site in input structure,
                for which OPs are to be
                calculated.  Note that we do not use the sites iterator
                here, but directly access sites via struct[index].
            indices_neighs ([int]): list of indices of those neighbors
                in Structure object
                structure that are to be considered for OP computation.
                This optional argument overwrites the way neighbors are
                to be determined as defined in the constructor (i.e.,
                Voronoi coordination finder via negative cutoff radius
                vs constant cutoff radius if cutoff was positive).
                We do not use information about the underlying
                structure lattice if the neighbor indices are explicitly
                provided.  This has two important consequences.  First,
                the input Structure object can, in fact, be a
                simple list of Site objects.  Second, no nearest images
                of neighbors are determined when providing an index list.
                Note furthermore that this neighbor
                determination type ignores the optional target_spec
                argument.
            tol (float): threshold of weight
                (= solid angle / maximal solid angle)
                to determine if a particular pair is
                considered neighbors; this is relevant only in the case
                when Voronoi polyhedra are used to determine coordination
            target_spec (Specie): target species to be considered
                when calculating the order
                parameters of site n; None includes all species of input
                structure.

        Returns:
            [floats]: representing order parameters.  Should it not be
            possible to compute a given OP for a conceptual reason, the
            corresponding entry is None instead of a float.  For Steinhardt
            et al.'s bond orientational OPs and the other geometric OPs
            ("tet", "oct", "bcc", etc.),
            this can happen if there is a single
            neighbor around site n in the structure because that
            does not permit calculation of angles between multiple
            neighbors.
        """

        # Do error-checking and initialization.
        if n < 0:
            raise ValueError("Site index smaller zero!")
        if n >= len(structure):
            raise ValueError("Site index beyond maximum!")
        if indices_neighs is not None:
            for index in indices_neighs:
                if index >= len(structure):
                    raise ValueError("Neighbor site index beyond maximum!")
        if tol < 0.0:
            raise ValueError("Negative tolerance for weighted solid angle!")

        left_of_unity = 1.0 - 1.0e-12
        # The following threshold has to be adapted to non-Angstrom units.
        very_small = 1.0e-12
        fac_bcc = 1.0 / exp(-0.5)

        # Find central site and its neighbors.
        # Note that we adopt the same way of accessing sites here as in
        # VoronoiNN; that is, not via the sites iterator.
        centsite = structure[n]
        if indices_neighs is not None:
            neighsites = [structure[index] for index in indices_neighs]
        elif self._voroneigh:
            vnn = VoronoiNN(tol=tol, targets=target_spec)
            neighsites = vnn.get_nn(structure, n)
        else:
            # Structure.get_sites_in_sphere --> also other periodic images
            neighsitestmp = [i[0] for i in structure.get_sites_in_sphere(
                centsite.coords, self._cutoff)]
            neighsites = []
            if centsite not in neighsitestmp:
                raise ValueError("Could not find center site!")
            else:
                neighsitestmp.remove(centsite)
            if target_spec is None:
                neighsites = list(neighsitestmp)
            else:
                neighsites[:] = [site for site in neighsitestmp \
                                 if site.specie.symbol == target_spec]
        nneigh = len(neighsites)
        self._last_nneigh = nneigh

        # Prepare angle calculations, if applicable.
        rij = []
        rjk = []
        rijnorm = []
        rjknorm = []
        dist = []
        distjk_unique = []
        distjk = []
        centvec = centsite.coords
        if self._computerijs:
            for j, neigh in enumerate(neighsites):
                rij.append((neigh.coords - centvec))
                dist.append(np.linalg.norm(rij[j]))
                rijnorm.append((rij[j] / dist[j]))
        if self._computerjks:
            for j, neigh in enumerate(neighsites):
                rjk.append([])
                rjknorm.append([])
                distjk.append([])
                kk = 0
                for k in range(len(neighsites)):
                    if j != k:
                        rjk[j].append(neighsites[k].coords - neigh.coords)
                        distjk[j].append(np.linalg.norm(rjk[j][kk]))
                        if k > j:
                            distjk_unique.append(distjk[j][kk])
                        rjknorm[j].append(rjk[j][kk] / distjk[j][kk])
                        kk = kk + 1
        # Initialize OP list and, then, calculate OPs.
        ops = [0.0 for t in self._types]
        # norms = [[[] for j in range(nneigh)] for t in self._types]

        # First, coordination number and distance-based OPs.
        for i, t in enumerate(self._types):
            if t == "cn":
                ops[i] = nneigh / self._params[i]['norm']
            elif t == "sgl_bd":
                dist_sorted = sorted(dist)
                if len(dist_sorted) == 1:
                    ops[i] = 1.0
                elif len(dist_sorted) > 1:
                    ops[i] = 1.0 - dist_sorted[0] / dist_sorted[1]

        # Then, bond orientational OPs based on spherical harmonics
        # according to Steinhardt et al., Phys. Rev. B, 28, 784-805, 1983.
        if self._boops:
            thetas = []
            phis = []
            for j, vec in enumerate(rijnorm):

                # z is North pole --> theta between vec and (0, 0, 1)^T.
                # Because vec is normalized, dot product is simply vec[2].
                thetas.append(acos(max(-1.0, min(vec[2], 1.0))))
                tmpphi = 0.0

                # Compute phi only if it is not (almost) perfectly
                # aligned with z-axis.
                if -left_of_unity < vec[2] < left_of_unity:
                    # x is prime meridian --> phi between projection of vec
                    # into x-y plane and (1, 0, 0)^T
                    tmpphi = acos(max(-1.0, min(vec[0] / (sqrt(
                        vec[0] * vec[0] + vec[1] * vec[1])), 1.0)))
                    if vec[1] < 0.0:
                        tmpphi = -tmpphi
                phis.append(tmpphi)

            # Note that None flags that we have too few neighbors
            # for calculating BOOPS.
            for i, t in enumerate(self._types):
                if t == "q2":
                    ops[i] = self.get_q2(thetas, phis) if len(
                        thetas) > 0 else None
                elif t == "q4":
                    ops[i] = self.get_q4(thetas, phis) if len(
                        thetas) > 0 else None
                elif t == "q6":
                    ops[i] = self.get_q6(thetas, phis) if len(
                        thetas) > 0 else None

        # Then, deal with the Peters-style OPs that are tailor-made
        # to recognize common structural motifs
        # (Peters, J. Chem. Phys., 131, 244103, 2009;
        #  Zimmermann et al., J. Am. Chem. Soc., under revision, 2015).
        if self._geomops:
            gaussthetak = [0.0 for t in self._types]  # not used by all OPs
            qsptheta = [[[] for j in range(nneigh)] for t in self._types]
            norms = [[[] for j in range(nneigh)] for t in self._types]
            ipi = 1.0 / pi
            piover2 = pi / 2.0
            tetangoverpi = acos(-1.0 / 3.0) * ipi  # xxx: delete
            itetangminuspihalfoverpi = 1.0 / (tetangoverpi - 0.5)
            onethird = 1.0 / 3.0
            twothird = 2.0 / 3.0
            for j in range(nneigh):  # Neighbor j is put to the North pole.
                zaxis = rijnorm[j]
                kc = 0
                for k in range(nneigh):  # From neighbor k, we construct
                    if j != k:  # the prime meridian.
                        for i in range(len(self._types)):
                            qsptheta[i][j].append(0.0)
                            norms[i][j].append(0)
                        tmp = max(
                            -1.0, min(np.inner(zaxis, rijnorm[k]), 1.0))
                        thetak = acos(tmp)
                        xaxis = gramschmidt(rijnorm[k], zaxis)
                        if np.linalg.norm(xaxis) < very_small:
                            flag_xaxis = True
                        else:
                            xaxis = xaxis / np.linalg.norm(xaxis)
                            flag_xaxis = False
                        if self._comp_azi:
                            flag_yaxis = True
                            yaxis = np.cross(zaxis, xaxis)
                            if np.linalg.norm(yaxis) > very_small:
                                yaxis = yaxis / np.linalg.norm(yaxis)
                                flag_yaxis = False

                        # Contributions of j-i-k angles, where i represents the
                        # central atom and j and k two of the neighbors.
                        for i, t in enumerate(self._types):
                            if t in ["bent", "sq_pyr_legacy"]:
                                tmp = self._params[i]['IGW_TA'] * (
                                        thetak * ipi - self._params[i]['TA'])
                                qsptheta[i][j][kc] += exp(-0.5 * tmp * tmp)
                                norms[i][j][kc] += 1
                            elif t in ["tri_plan", "tri_plan_max", "tet",
                                       "tet_max"]:
                                tmp = self._params[i]['IGW_TA'] * (
                                        thetak * ipi - self._params[i]['TA'])
                                gaussthetak[i] = exp(-0.5 * tmp * tmp)
                                if t in ["tri_plan_max", "tet_max"]:
                                    qsptheta[i][j][kc] += gaussthetak[i]
                                    norms[i][j][kc] += 1
                            elif t in ["T", "tri_pyr", "sq_pyr", "pent_pyr",
                                       "hex_pyr"]:
                                tmp = self._params[i]['IGW_EP'] * (
                                            thetak * ipi - 0.5)
                                qsptheta[i][j][kc] += exp(-0.5 * tmp * tmp)
                                norms[i][j][kc] += 1
                            elif t in ["sq_plan", "oct", "oct_legacy",
                                       "cuboct", "cuboct_max"]:
                                if thetak >= self._params[i]['min_SPP']:
                                    tmp = self._params[i]['IGW_SPP'] * (
                                            thetak * ipi - 1.0)
                                    qsptheta[i][j][kc] += (
                                            self._params[i]['w_SPP'] *
                                            exp(-0.5 * tmp * tmp))
                                    norms[i][j][kc] += self._params[i]['w_SPP']
                            elif t in ["see_saw_rect", "tri_bipyr", "sq_bipyr",
                                       "pent_bipyr", "hex_bipyr", "oct_max",
                                       "sq_plan_max", "hex_plan_max"]:
                                if thetak < self._params[i]['min_SPP']:
                                    tmp = self._params[i]['IGW_EP'] * (
                                            thetak * ipi - 0.5) if t != "hex_plan_max" else \
                                        self._params[i]['IGW_TA'] * (
                                                fabs(thetak * ipi - 0.5) -
                                                self._params[i]['TA'])
                                    qsptheta[i][j][kc] += exp(
                                        -0.5 * tmp * tmp)
                                    norms[i][j][kc] += 1
                            elif t in ["pent_plan", "pent_plan_max"]:
                                tmp = 0.4 if thetak <= self._params[i][
                                    'TA'] * pi \
                                    else 0.8
                                tmp2 = self._params[i]['IGW_TA'] * (
                                        thetak * ipi - tmp)
                                gaussthetak[i] = exp(-0.5 * tmp2 * tmp2)
                                if t == "pent_plan_max":
                                    qsptheta[i][j][kc] += gaussthetak[i]
                                    norms[i][j][kc] += 1
                            elif t == "bcc" and j < k:
                                if thetak >= self._params[i]['min_SPP']:
                                    tmp = self._params[i]['IGW_SPP'] * (
                                            thetak * ipi - 1.0)
                                    qsptheta[i][j][kc] += (
                                                self._params[i]['w_SPP'] *
                                                exp(-0.5 * tmp * tmp))
                                    norms[i][j][kc] += self._params[i]['w_SPP']
                            elif t == "sq_face_cap_trig_pris":
                                if thetak < self._params[i]['TA3']:
                                    tmp = self._params[i]['IGW_TA1'] * (
                                            thetak * ipi - self._params[i][
                                        'TA1'])
                                    qsptheta[i][j][kc] += exp(-0.5 * tmp * tmp)
                                    norms[i][j][kc] += 1

                        for m in range(nneigh):
                            if (m != j) and (m != k) and (not flag_xaxis):
                                tmp = max(
                                    -1.0, min(np.inner(zaxis, rijnorm[m]), 1.0))
                                thetam = acos(tmp)
                                xtwoaxistmp = gramschmidt(rijnorm[m], zaxis)
                                l = np.linalg.norm(xtwoaxistmp)
                                if l < very_small:
                                    flag_xtwoaxis = True
                                else:
                                    xtwoaxis = xtwoaxistmp / l
                                    phi = acos(max(
                                        -1.0,
                                        min(np.inner(xtwoaxis, xaxis), 1.0)))
                                    flag_xtwoaxis = False
                                    if self._comp_azi:
                                        phi2 = atan2(
                                            np.dot(xtwoaxis, yaxis),
                                            np.dot(xtwoaxis, xaxis))
                                        # print('{} {}'.format(180*phi/pi, 180*phi2/pi))
                                # South pole contributions of m.
                                if t in ["tri_bipyr", "sq_bipyr", "pent_bipyr",
                                         "hex_bipyr", "oct_max", "sq_plan_max",
                                         "hex_plan_max", "see_saw_rect"]:
                                    if thetam >= self._params[i]['min_SPP']:
                                        tmp = self._params[i]['IGW_SPP'] * (
                                                thetam * ipi - 1.0)
                                        qsptheta[i][j][kc] += exp(
                                            -0.5 * tmp * tmp)
                                        norms[i][j][kc] += 1

                                # Contributions of j-i-m angle and
                                # angles between plane j-i-k and i-m vector.
                                if not flag_xaxis and not flag_xtwoaxis:
                                    for i, t in enumerate(self._types):
                                        if t in ["tri_plan", "tri_plan_max",
                                                 "tet", "tet_max"]:
                                            tmp = self._params[i]['IGW_TA'] * (
                                                    thetam * ipi -
                                                    self._params[i]['TA'])
                                            tmp2 = cos(
                                                self._params[i]['fac_AA'] *
                                                phi) ** self._params[i][
                                                       'exp_cos_AA']
                                            tmp3 = 1 if t in ["tri_plan_max",
                                                              "tet_max"] \
                                                else gaussthetak[i]
                                            qsptheta[i][j][kc] += tmp3 * exp(
                                                -0.5 * tmp * tmp) * tmp2
                                            norms[i][j][kc] += 1
                                        elif t in ["pent_plan",
                                                   "pent_plan_max"]:
                                            tmp = 0.4 if thetam <= \
                                                         self._params[i][
                                                             'TA'] * pi \
                                                else 0.8
                                            tmp2 = self._params[i]['IGW_TA'] * (
                                                    thetam * ipi - tmp)
                                            tmp3 = cos(phi)
                                            tmp4 = 1 if t == "pent_plan_max" \
                                                else gaussthetak[i]
                                            qsptheta[i][j][kc] += tmp4 * exp(
                                                -0.5 * tmp2 * tmp2) * tmp3 * tmp3
                                            norms[i][j][kc] += 1
                                        elif t in ["T", "tri_pyr", "sq_pyr",
                                                   "pent_pyr", "hex_pyr"]:
                                            tmp = cos(
                                                self._params[i]['fac_AA'] *
                                                phi) ** self._params[i][
                                                      'exp_cos_AA']
                                            tmp3 = self._params[i]['IGW_EP'] * (
                                                    thetam * ipi - 0.5)
                                            qsptheta[i][j][kc] += tmp * exp(
                                                -0.5 * tmp3 * tmp3)
                                            norms[i][j][kc] += 1
                                        elif t in ["sq_plan", "oct",
                                                   "oct_legacy"]:
                                            if thetak < self._params[i][
                                                'min_SPP'] and \
                                                    thetam < self._params[i][
                                                'min_SPP']:
                                                tmp = cos(
                                                    self._params[i]['fac_AA'] *
                                                    phi) ** self._params[i][
                                                          'exp_cos_AA']
                                                tmp2 = self._params[i][
                                                           'IGW_EP'] * (
                                                               thetam * ipi - 0.5)
                                                qsptheta[i][j][kc] += tmp * exp(
                                                    -0.5 * tmp2 * tmp2)
                                                if t == "oct_legacy":
                                                    qsptheta[i][j][kc] -= tmp * \
                                                                          self._params[
                                                                              i][
                                                                              6] * \
                                                                          self._params[
                                                                              i][
                                                                              7]
                                                norms[i][j][kc] += 1
                                        elif t in ["tri_bipyr", "sq_bipyr",
                                                   "pent_bipyr",
                                                   "hex_bipyr", "oct_max",
                                                   "sq_plan_max",
                                                   "hex_plan_max"]:
                                            if thetam < self._params[i][
                                                'min_SPP']:
                                                if thetak < self._params[i][
                                                    'min_SPP']:
                                                    tmp = cos(self._params[i][
                                                                  'fac_AA'] *
                                                              phi) ** \
                                                          self._params[i][
                                                              'exp_cos_AA']
                                                    tmp2 = self._params[i][
                                                               'IGW_EP'] * (
                                                                   thetam * ipi - 0.5) if t != "hex_plan_max" else \
                                                        self._params[i][
                                                            'IGW_TA'] * (
                                                                fabs(
                                                                    thetam * ipi - 0.5) -
                                                                self._params[i][
                                                                    'TA'])
                                                    qsptheta[i][j][
                                                        kc] += tmp * exp(
                                                        -0.5 * tmp2 * tmp2)
                                                    norms[i][j][kc] += 1
                                        elif t == "bcc" and j < k:
                                            if thetak < self._params[i][
                                                'min_SPP']:
                                                if thetak > piover2:
                                                    fac = 1.0
                                                else:
                                                    fac = -1.0
                                                tmp = (thetam - piover2) / asin(
                                                    1 / 3)
                                                qsptheta[i][j][kc] += fac * cos(
                                                    3.0 * phi) * fac_bcc * \
                                                                      tmp * exp(
                                                    -0.5 * tmp * tmp)
                                                norms[i][j][kc] += 1
                                        elif t == "see_saw_rect":
                                            if thetam < self._params[i][
                                                'min_SPP']:
                                                if thetak < self._params[i][
                                                    'min_SPP'] and phi < 0.75 * pi:
                                                    tmp = cos(self._params[i][
                                                                  'fac_AA'] *
                                                              phi) ** \
                                                          self._params[i][
                                                              'exp_cos_AA']
                                                    tmp2 = self._params[i][
                                                               'IGW_EP'] * (
                                                                   thetam * ipi - 0.5)
                                                    qsptheta[i][j][kc] += tmp * \
                                                                          exp(
                                                                              -0.5 * tmp2 * tmp2)
                                                    norms[i][j][kc] += 1.0
                                        elif t in ["cuboct", "cuboct_max"]:
                                            if thetam < self._params[i][
                                                'min_SPP'] and \
                                                    thetak > self._params[i][
                                                4] and \
                                                    thetak < self._params[i][2]:
                                                if thetam > self._params[i][
                                                    4] and \
                                                        thetam < \
                                                        self._params[i][2]:
                                                    tmp = cos(phi)
                                                    tmp2 = self._params[i][
                                                               5] * (
                                                                       thetam * ipi - 0.5)
                                                    qsptheta[i][j][
                                                        kc] += tmp * tmp * exp(
                                                        -0.5 * tmp2 * tmp2)
                                                    norms[i][j][kc] += 1.0
                                                elif thetam < self._params[i][
                                                    4]:
                                                    tmp = 0.0556 * (cos(
                                                        phi - 0.5 * pi) - 0.81649658)
                                                    tmp2 = self._params[i][
                                                               6] * (
                                                                   thetam * ipi - onethird)
                                                    qsptheta[i][j][kc] += exp(
                                                        -0.5 * tmp * tmp) * \
                                                                          exp(
                                                                              -0.5 * tmp2 * tmp2)
                                                    norms[i][j][kc] += 1.0
                                                elif thetam > self._params[i][
                                                    2]:
                                                    tmp = 0.0556 * (cos(
                                                        phi - 0.5 * pi) - \
                                                                    0.81649658)
                                                    tmp2 = self._params[i][
                                                               6] * (
                                                                       thetam * ipi - \
                                                                       twothird)
                                                    qsptheta[i][j][kc] += exp(
                                                        -0.5 * tmp * tmp) * \
                                                                          exp(
                                                                              -0.5 * tmp2 * tmp2)
                                                    norms[i][j][kc] += 1.0
                                        elif t == "sq_face_cap_trig_pris" and not flag_yaxis:
                                            if thetak < self._params[i]['TA3']:
                                                if thetam < self._params[i][
                                                    'TA3']:
                                                    tmp = cos(self._params[i][
                                                                  'fac_AA1'] * \
                                                              phi2) ** \
                                                          self._params[i][
                                                              'exp_cos_AA1']
                                                    tmp2 = self._params[i][
                                                               'IGW_TA1'] * (
                                                                   thetam * ipi -
                                                                   self._params[
                                                                       i][
                                                                       'TA1'])
                                                else:
                                                    tmp = cos(self._params[i][
                                                                  'fac_AA2'] * \
                                                              (phi2 +
                                                               self._params[i][
                                                                   'shift_AA2'])) ** \
                                                          self._params[i][
                                                              'exp_cos_AA2']
                                                    tmp2 = self._params[i][
                                                               'IGW_TA2'] * (
                                                                   thetam * ipi -
                                                                   self._params[
                                                                       i][
                                                                       'TA2'])
                                                    # print("phi2 {}   phi2+shift {}   tmp {}  ".format(phi2*180/pi, 180*(phi2 + self._params[i]['shift_AA2'])/pi, tmp))
                                                qsptheta[i][j][kc] += tmp * exp(
                                                    -0.5 * tmp2 * tmp2)
                                                norms[i][j][kc] += 1

                        kc += 1

            # Normalize Peters-style OPs.
            for i, t in enumerate(self._types):
                if t in ["tri_plan", "tet", "bent", "sq_plan",
                         "oct", "oct_legacy", "cuboct", "pent_plan"]:
                    ops[i] = tmp_norm = 0.0
                    for j in range(nneigh):
                        ops[i] += sum(qsptheta[i][j])
                        tmp_norm += float(sum(norms[i][j]))
                    ops[i] = ops[i] / tmp_norm if tmp_norm > 1.0e-12 else None
                elif t in ["T", "tri_pyr", "see_saw_rect", "sq_pyr",
                           "tri_bipyr",
                           "sq_bipyr", "pent_pyr", "hex_pyr", "pent_bipyr",
                           "hex_bipyr", "oct_max", "tri_plan_max", "tet_max",
                           "sq_plan_max", "pent_plan_max", "cuboct_max",
                           "hex_plan_max",
                           "sq_face_cap_trig_pris"]:
                    ops[i] = None
                    if nneigh > 1:
                        for j in range(nneigh):
                            for k in range(len(qsptheta[i][j])):
                                qsptheta[i][j][k] = qsptheta[i][j][k] / \
                                                    norms[i][j][k] \
                                    if norms[i][j][k] > 1.0e-12 else 0.0
                            ops[i] = max(qsptheta[i][j]) if j == 0 \
                                else max(ops[i], max(qsptheta[i][j]))
                elif t == "bcc":
                    ops[i] = 0.0
                    for j in range(nneigh):
                        ops[i] += sum(qsptheta[i][j])
                    ops[i] = ops[i] / float(0.5 * float(
                        nneigh * (6 + (nneigh - 2) * (nneigh - 3)))) \
                        if nneigh > 3 else None
                elif t == "sq_pyr_legacy":
                    if nneigh > 1:
                        dmean = np.mean(dist)
                        acc = 0.0
                        for d in dist:
                            tmp = self._params[i][2] * (d - dmean)
                            acc = acc + exp(-0.5 * tmp * tmp)
                        for j in range(nneigh):
                            ops[i] = max(qsptheta[i][j]) if j == 0 \
                                else max(ops[i], max(qsptheta[i][j]))
                        ops[i] = acc * ops[i] / float(nneigh)
                        # nneigh * (nneigh - 1))
                    else:
                        ops[i] = None

        # Then, deal with the new-style OPs that require vectors between
        # neighbors.
        if self._geomops2:
            # Compute all (unique) angles and sort the resulting list.
            aij = []
            for ir, r in enumerate(rijnorm):
                for j in range(ir + 1, len(rijnorm)):
                    aij.append(
                        acos(max(-1.0, min(np.inner(r, rijnorm[j]), 1.0))))
            aijs = sorted(aij)

            # Compute height, side and diagonal length estimates.
            neighscent = np.array([0.0, 0.0, 0.0])
            for j, neigh in enumerate(neighsites):
                neighscent = neighscent + neigh.coords
            if nneigh > 0:
                neighscent = (neighscent / float(nneigh))
            h = np.linalg.norm(neighscent - centvec)
            b = min(distjk_unique) if len(distjk_unique) > 0 else 0
            dhalf = max(distjk_unique) / 2.0 if len(distjk_unique) > 0 else 0

            for i, t in enumerate(self._types):
                if t == "reg_tri" or t == "sq":
                    if nneigh < 3:
                        ops[i] = None
                    else:
                        ops[i] = 1.0
                        if t == "reg_tri":
                            a = 2.0 * asin(b / (2.0 * sqrt(h * h + (b / (
                                    2.0 * cos(3.0 * pi / 18.0))) ** 2.0)))
                            nmax = 3
                        elif t == "sq":
                            a = 2.0 * asin(
                                b / (2.0 * sqrt(h * h + dhalf * dhalf)))
                            nmax = 4
                        for j in range(min([nneigh, nmax])):
                            ops[i] = ops[i] * exp(-0.5 * ((
                                                                  aijs[j] - a) *
                                                          self._params[i][
                                                              0]) ** 2)

        return ops

Response: This function calculates all order parameters of a given atom n in a structure.


Instruction:
Can you generate the documentation for the following Python 3 function
def get_nn_info(self, structure, n):

        nndata = self.get_nn_data(structure, n)

        if not self.weighted_cn:
            max_key = max(nndata.cn_weights, key=lambda k: nndata.cn_weights[k])
            nn = nndata.cn_nninfo[max_key]
            for entry in nn:
                entry["weight"] = 1
            return nn

        else:
            for entry in nndata.all_nninfo:
                weight = 0
                for cn in nndata.cn_nninfo:
                    for cn_entry in nndata.cn_nninfo[cn]:
                        if entry["site"] == cn_entry["site"]:
                            weight += nndata.cn_weights[cn]

                entry["weight"] = weight

            return nndata.all_nninfo

Response: Get all near - neighbor information.


Instruction:
Can you generate the documentation for the following Python 3 function
def get_nn_data(self, structure, n, length=None):

        length = length or self.fingerprint_length

        # determine possible bond targets
        target = None
        if self.cation_anion:
            target = []
            m_oxi = structure[n].specie.oxi_state
            for site in structure:
                if site.specie.oxi_state * m_oxi <= 0:  # opposite charge
                    target.append(site.specie)
            if not target:
                raise ValueError(
                    "No valid targets for site within cation_anion constraint!")

        # get base VoronoiNN targets
        cutoff = self.search_cutoff
        vnn = VoronoiNN(weight="solid_angle", targets=target, cutoff=cutoff)
        nn = vnn.get_nn_info(structure, n)

        # solid angle weights can be misleading in open / porous structures
        # adjust weights to correct for this behavior
        if self.porous_adjustment:
            for x in nn:
                x["weight"] *= x["poly_info"][
                                   "solid_angle"] / x["poly_info"]["area"]

        # adjust solid angle weight based on electronegativity difference
        if self.x_diff_weight > 0:
            for entry in nn:
                X1 = structure[n].specie.X
                X2 = entry["site"].specie.X

                if math.isnan(X1) or math.isnan(X2):
                    chemical_weight = 1
                else:
                    # note: 3.3 is max deltaX between 2 elements
                    chemical_weight = 1 + self.x_diff_weight * \
                                      math.sqrt(abs(X1 - X2) / 3.3)

                entry["weight"] = entry["weight"] * chemical_weight

        # sort nearest neighbors from highest to lowest weight
        nn = sorted(nn, key=lambda x: x["weight"], reverse=True)
        if nn[0]["weight"] == 0:
            return self.transform_to_length(self.NNData([], {0: 1.0}, {0: []}),
                                            length)

        # renormalize weights so the highest weight is 1.0
        highest_weight = nn[0]["weight"]
        for entry in nn:
            entry["weight"] = entry["weight"] / highest_weight

        # adjust solid angle weights based on distance
        if self.distance_cutoffs:
            r1 = self._get_radius(structure[n])
            for entry in nn:
                r2 = self._get_radius(entry["site"])
                if r1 > 0 and r2 > 0:
                    d = r1 + r2
                else:
                    warnings.warn(
                        "CrystalNN: cannot locate an appropriate radius, "
                        "covalent or atomic radii will be used, this can lead "
                        "to non-optimal results.")
                    d = CrystalNN._get_default_radius(structure[n]) + \
                        CrystalNN._get_default_radius(entry["site"])

                dist = np.linalg.norm(
                    structure[n].coords - entry["site"].coords)
                dist_weight = 0

                cutoff_low = d + self.distance_cutoffs[0]
                cutoff_high = d + self.distance_cutoffs[1]

                if dist <= cutoff_low:
                    dist_weight = 1
                elif dist < cutoff_high:
                    dist_weight = (math.cos((dist - cutoff_low) / (
                            cutoff_high - cutoff_low) * math.pi) + 1) * 0.5
                entry["weight"] = entry["weight"] * dist_weight

        # sort nearest neighbors from highest to lowest weight
        nn = sorted(nn, key=lambda x: x["weight"], reverse=True)
        if nn[0]["weight"] == 0:
            return self.transform_to_length(self.NNData([], {0: 1.0}, {0: []}),
                                            length)

        for entry in nn:
            entry["weight"] = round(entry["weight"], 3)
            del entry["poly_info"]  # trim

        # remove entries with no weight
        nn = [x for x in nn if x["weight"] > 0]

        # get the transition distances, i.e. all distinct weights
        dist_bins = []
        for entry in nn:
            if not dist_bins or dist_bins[-1] != entry["weight"]:
                dist_bins.append(entry["weight"])
        dist_bins.append(0)

        # main algorithm to determine fingerprint from bond weights
        cn_weights = {}  # CN -> score for that CN
        cn_nninfo = {}  # CN -> list of nearneighbor info for that CN
        for idx, val in enumerate(dist_bins):
            if val != 0:
                nn_info = []
                for entry in nn:
                    if entry["weight"] >= val:
                        nn_info.append(entry)
                cn = len(nn_info)
                cn_nninfo[cn] = nn_info
                cn_weights[cn] = self._semicircle_integral(dist_bins, idx)

        # add zero coord
        cn0_weight = 1.0 - sum(cn_weights.values())
        if cn0_weight > 0:
            cn_nninfo[0] = []
            cn_weights[0] = cn0_weight

        return self.transform_to_length(self.NNData(nn, cn_weights, cn_nninfo),
                                        length)

Response: This method computes the NN info for a specific target site.


Instruction:
Make a summary of the following Python 3 code
def get_cn(self, structure, n, use_weights=False):
        if self.weighted_cn != use_weights:
            raise ValueError("The weighted_cn parameter and use_weights "
                             "parameter should match!")

        return super().get_cn(structure, n, use_weights)

Response: Get coordination number CN of site with index n in structure.


Instruction:
Can you tell what is the following Python 3 function doing
def transform_to_length(nndata, length):

        if length is None:
            return nndata

        if length:
            for cn in range(length):
                if cn not in nndata.cn_weights:
                    nndata.cn_weights[cn] = 0
                    nndata.cn_nninfo[cn] = []

        return nndata

Response: Given a NNData and a length of a number returns a new NNData with the specified length.


Instruction:
Can you write a function in Python 3 where it
initialises a CutOffDictNN according to a preset cut - offs.

Response:
def from_preset(preset):
        """
        Initialise a CutOffDictNN according to a preset set of cut-offs.

        Args:
            preset (str): A preset name. The list of supported presets are:

                - "vesta_2019": The distance cut-offs used by the VESTA
                  visualisation program.

        Returns:
            A CutOffDictNN using the preset cut-off dictionary.
        """
        if preset == 'vesta_2019':
            cut_offs = loadfn(os.path.join(_directory, 'vesta_cutoffs.yaml'))
            return CutOffDictNN(cut_off_dict=cut_offs)
        else:
            raise ValueError("Unrecognised preset: {}".format(preset))


Instruction:
Here you have a function in Python 3, explain what it does
def to_string(self):
        # define coord system, use Cartesian
        output = ["1.0 0.0 0.0",
                  "0.0 1.0 0.0",
                  "0.0 0.0 1.0"]
        # add lattice vectors
        m = self.structure.lattice.matrix
        output.append("{:6f} {:6f} {:6f}".format(*m[0]))
        output.append("{:6f} {:6f} {:6f}".format(*m[1]))
        output.append("{:6f} {:6f} {:6f}".format(*m[2]))
        # add species
        for site in self.structure:
            species_str = []
            for sp, occu in sorted(site.species.items()):
                if isinstance(sp, Specie):
                    sp = sp.element
                species_str.append("{}={}".format(sp, occu))
            species_str = ",".join(species_str)
            output.append("{:6f} {:6f} {:6f} {}".format(site.frac_coords[0],
                                                        site.frac_coords[1],
                                                        site.frac_coords[2],
                                                        species_str))

        return "\n".join(output)

Response: Returns a structure in mcsqs rndstr. in format.


Instruction:
Can you implement a function in Python 3 that
parses a rndstr. in or lat. in file into a pymatgen Structure object.

Response:
def structure_from_string(data):
        """
        Parses a rndstr.in or lat.in file into pymatgen's
        Structure format.

        :param data: contents of a rndstr.in or lat.in file
        :return: Structure object
        """

        data = data.splitlines()
        data = [x.split() for x in data if x]  # remove empty lines

        # following specification/terminology given in manual
        if len(data[0]) == 6:  # lattice parameters
            a, b, c, alpha, beta, gamma = map(float, data[0])
            coord_system = Lattice.from_parameters(a, b, c,
                                                   alpha, beta, gamma).matrix
            lattice_vecs = np.array([
                [data[1][0], data[1][1], data[1][2]],
                [data[2][0], data[2][1], data[2][2]],
                [data[3][0], data[3][1], data[3][2]]
            ], dtype=float)
            first_species_line = 4
        else:
            coord_system = np.array([
                [data[0][0], data[0][1], data[0][2]],
                [data[1][0], data[1][1], data[1][2]],
                [data[2][0], data[2][1], data[2][2]]
            ], dtype=float)
            lattice_vecs = np.array([
                [data[3][0], data[3][1], data[3][2]],
                [data[4][0], data[4][1], data[4][2]],
                [data[5][0], data[5][1], data[5][2]]
            ], dtype=float)
            first_species_line = 6

        scaled_matrix = np.matmul(coord_system, lattice_vecs)
        lattice = Lattice(scaled_matrix)

        all_coords = []
        all_species = []
        for l in data[first_species_line:]:

            all_coords.append(np.array([l[0], l[1], l[2]], dtype=float))

            species_strs = "".join(l[3:])  # join multiple strings back together
            species_strs = species_strs.replace(" ", "")  # trim any white space
            species_strs = species_strs.split(",")  # comma-delimited

            species = {}

            for species_str in species_strs:
                species_str = species_str.split('=')
                if len(species_str) == 1:
                    # assume occupancy is 1.0
                    species_str = [species_str[0], 1.0]
                try:
                    species[Specie(species_str[0])] = float(species_str[1])
                except:
                    species[DummySpecie(species_str[0])] = float(species_str[1])

            all_species.append(species)

        return Structure(lattice, all_species, all_coords)


Instruction:
Implement a Python 3 function for
returning the set of voronoi nodes in the structure.

Response:
def get_voronoi_nodes(structure, rad_dict=None, probe_rad=0.1):
    """
    Analyze the void space in the input structure using voronoi decomposition
    Calls Zeo++ for Voronoi decomposition.

    Args:
        structure: pymatgen.core.structure.Structure
        rad_dict (optional): Dictionary of radii of elements in structure.
            If not given, Zeo++ default values are used.
            Note: Zeo++ uses atomic radii of elements.
            For ionic structures, pass rad_dict with ionic radii
        probe_rad (optional): Sampling probe radius in Angstroms. Default is
            0.1 A

    Returns:
        voronoi nodes as pymatgen.core.structure.Strucutre within the
        unit cell defined by the lattice of input structure
        voronoi face centers as pymatgen.core.structure.Strucutre within the
        unit cell defined by the lattice of input structure
    """

    with ScratchDir('.'):
        name = "temp_zeo1"
        zeo_inp_filename = name + ".cssr"
        ZeoCssr(structure).write_file(zeo_inp_filename)
        rad_file = None
        rad_flag = False

        if rad_dict:
            rad_file = name + ".rad"
            rad_flag = True
            with open(rad_file, 'w+') as fp:
                for el in rad_dict.keys():
                    fp.write("{} {}\n".format(el, rad_dict[el].real))

        atmnet = AtomNetwork.read_from_CSSR(
            zeo_inp_filename, rad_flag=rad_flag, rad_file=rad_file)
        vornet, vor_edge_centers, vor_face_centers = \
            atmnet.perform_voronoi_decomposition()
        vornet.analyze_writeto_XYZ(name, probe_rad, atmnet)
        voro_out_filename = name + '_voro.xyz'
        voro_node_mol = ZeoVoronoiXYZ.from_file(voro_out_filename).molecule

    species = ["X"] * len(voro_node_mol.sites)
    coords = []
    prop = []
    for site in voro_node_mol.sites:
        coords.append(list(site.coords))
        prop.append(site.properties['voronoi_radius'])

    lattice = Lattice.from_lengths_and_angles(
        structure.lattice.abc, structure.lattice.angles)
    vor_node_struct = Structure(
        lattice, species, coords, coords_are_cartesian=True,
        to_unit_cell=True, site_properties={"voronoi_radius": prop})

    # PMG-Zeo c<->a transformation for voronoi face centers
    rot_face_centers = [(center[1], center[2], center[0]) for center in
                        vor_face_centers]
    rot_edge_centers = [(center[1], center[2], center[0]) for center in
                        vor_edge_centers]

    species = ["X"] * len(rot_face_centers)
    prop = [0.0] * len(rot_face_centers)  # Vor radius not evaluated for fc
    vor_facecenter_struct = Structure(
        lattice, species, rot_face_centers, coords_are_cartesian=True,
        to_unit_cell=True, site_properties={"voronoi_radius": prop})

    species = ["X"] * len(rot_edge_centers)
    prop = [0.0] * len(rot_edge_centers)  # Vor radius not evaluated for fc
    vor_edgecenter_struct = Structure(
        lattice, species, rot_edge_centers, coords_are_cartesian=True,
        to_unit_cell=True, site_properties={"voronoi_radius": prop})

    return vor_node_struct, vor_edgecenter_struct, vor_facecenter_struct


Instruction:
Implement a Python 3 function for
analyzing the void space in the input structure using high accuracy voronoi decomposition. Calls Zeo++ for Voronoi decomposition. Args: structure: pymatgen.core.structure.Structure rad_dict (optional): Dictionary of radii of elements in structure. If not given, Zeo++ default values are used. Note: Zeo++ uses atomic radii of elements. For ionic structures, pass rad_dict with ionic radii probe_rad (optional): Sampling probe radius in Angstroms. Default is 0.1 A Returns: voronoi nodes as pymatgen.core.structure.Strucutre within the unit cell defined by the lattice of input structure voronoi face centers as pymatgen.core.structure.Strucutre within the unit cell defined by the lattice of input structure

Response:
def get_high_accuracy_voronoi_nodes(structure, rad_dict, probe_rad=0.1):
    """
    Analyze the void space in the input structure using high accuracy
    voronoi decomposition.
    Calls Zeo++ for Voronoi decomposition.

    Args:
        structure: pymatgen.core.structure.Structure
        rad_dict (optional): Dictionary of radii of elements in structure.
            If not given, Zeo++ default values are used.
            Note: Zeo++ uses atomic radii of elements.
            For ionic structures, pass rad_dict with ionic radii
        probe_rad (optional): Sampling probe radius in Angstroms.
            Default is 0.1 A

    Returns:
        voronoi nodes as pymatgen.core.structure.Strucutre within the
        unit cell defined by the lattice of input structure
        voronoi face centers as pymatgen.core.structure.Strucutre within the
        unit cell defined by the lattice of input structure
    """

    with ScratchDir('.'):
        name = "temp_zeo1"
        zeo_inp_filename = name + ".cssr"
        ZeoCssr(structure).write_file(zeo_inp_filename)
        rad_flag = True
        rad_file = name + ".rad"
        with open(rad_file, 'w+') as fp:
            for el in rad_dict.keys():
                print("{} {}".format(el, rad_dict[el].real), file=fp)

        atmnet = AtomNetwork.read_from_CSSR(
            zeo_inp_filename, rad_flag=rad_flag, rad_file=rad_file)
        # vornet, vor_edge_centers, vor_face_centers = \
        #        atmnet.perform_voronoi_decomposition()
        red_ha_vornet = \
            prune_voronoi_network_close_node(atmnet)
        # generate_simplified_highaccuracy_voronoi_network(atmnet)
        # get_nearest_largest_diameter_highaccuracy_vornode(atmnet)
        red_ha_vornet.analyze_writeto_XYZ(name, probe_rad, atmnet)
        voro_out_filename = name + '_voro.xyz'
        voro_node_mol = ZeoVoronoiXYZ.from_file(voro_out_filename).molecule

    species = ["X"] * len(voro_node_mol.sites)
    coords = []
    prop = []
    for site in voro_node_mol.sites:
        coords.append(list(site.coords))
        prop.append(site.properties['voronoi_radius'])

    lattice = Lattice.from_lengths_and_angles(
        structure.lattice.abc, structure.lattice.angles)
    vor_node_struct = Structure(
        lattice, species, coords, coords_are_cartesian=True,
        to_unit_cell=True, site_properties={"voronoi_radius": prop})

    return vor_node_struct


Instruction:
Write a Python 3 function for
analyzing the void space in the input structure using voronoi decomposition Calls Zeo++ for Voronoi decomposition. Args: structure: pymatgen.core.structure.Structure rad_dict (optional): Dictionary of radii of elements in structure. If not given, Zeo++ default values are used. Note: Zeo++ uses atomic radii of elements. For ionic structures, pass rad_dict with ionic radii probe_rad (optional): Sampling probe radius in Angstroms. Default is 0.1 A Returns: voronoi nodes as pymatgen.core.structure.Strucutre within the unit cell defined by the lattice of input structure voronoi face centers as pymatgen.core.structure.Strucutre within the unit cell defined by the lattice of input structure

Response:
def get_free_sphere_params(structure, rad_dict=None, probe_rad=0.1):
    """
    Analyze the void space in the input structure using voronoi decomposition
    Calls Zeo++ for Voronoi decomposition.

    Args:
        structure: pymatgen.core.structure.Structure
        rad_dict (optional): Dictionary of radii of elements in structure.
            If not given, Zeo++ default values are used.
            Note: Zeo++ uses atomic radii of elements.
            For ionic structures, pass rad_dict with ionic radii
        probe_rad (optional): Sampling probe radius in Angstroms. Default is
            0.1 A

    Returns:
        voronoi nodes as pymatgen.core.structure.Strucutre within the
        unit cell defined by the lattice of input structure
        voronoi face centers as pymatgen.core.structure.Strucutre within the
        unit cell defined by the lattice of input structure
    """

    with ScratchDir('.'):
        name = "temp_zeo1"
        zeo_inp_filename = name + ".cssr"
        ZeoCssr(structure).write_file(zeo_inp_filename)
        rad_file = None
        rad_flag = False

        if rad_dict:
            rad_file = name + ".rad"
            rad_flag = True
            with open(rad_file, 'w+') as fp:
                for el in rad_dict.keys():
                    fp.write("{} {}\n".format(el, rad_dict[el].real))

        atmnet = AtomNetwork.read_from_CSSR(
            zeo_inp_filename, rad_flag=rad_flag, rad_file=rad_file)
        out_file = "temp.res"
        atmnet.calculate_free_sphere_parameters(out_file)
        if os.path.isfile(out_file) and os.path.getsize(out_file) > 0:
            with open(out_file, "rt") as fp:
                output = fp.readline()
        else:
            output = ""
    fields = [val.strip() for val in output.split()][1:4]
    if len(fields) == 3:
        fields = [float(field) for field in fields]
        free_sphere_params = {'inc_sph_max_dia': fields[0],
                              'free_sph_max_dia': fields[1],
                              'inc_sph_along_free_sph_path_max_dia': fields[2]}
    return free_sphere_params


Instruction:
Create a Python 3 function to
compute the volume and surface area of an isolated void using Zeo ++.

Response:
def get_void_volume_surfarea(structure, rad_dict=None, chan_rad=0.3,
                             probe_rad=0.1):
    """
    Computes the volume and surface area of isolated void using Zeo++.
    Useful to compute the volume and surface area of vacant site.

    Args:
        structure: pymatgen Structure containing vacancy
        rad_dict(optional): Dictionary with short name of elements and their
            radii.
        chan_rad(optional): Minimum channel Radius.
        probe_rad(optional): Probe radius for Monte Carlo sampling.

    Returns:
        volume: floating number representing the volume of void
    """
    with ScratchDir('.'):
        name = "temp_zeo"
        zeo_inp_filename = name + ".cssr"
        ZeoCssr(structure).write_file(zeo_inp_filename)

        rad_file = None
        if rad_dict:
            rad_file = name + ".rad"
            with open(rad_file, 'w') as fp:
                for el in rad_dict.keys():
                    fp.write("{0}     {1}".format(el, rad_dict[el]))

        atmnet = AtomNetwork.read_from_CSSR(zeo_inp_filename, True, rad_file)
        vol_str = volume(atmnet, 0.3, probe_rad, 10000)
        sa_str = surface_area(atmnet, 0.3, probe_rad, 10000)
        vol = None
        sa = None
        for line in vol_str.split("\n"):
            if "Number_of_pockets" in line:
                fields = line.split()
                if float(fields[1]) > 1:
                    vol = -1.0
                    break
                if float(fields[1]) == 0:
                    vol = -1.0
                    break
                vol = float(fields[3])
        for line in sa_str.split("\n"):
            if "Number_of_pockets" in line:
                fields = line.split()
                if float(fields[1]) > 1:
                    # raise ValueError("Too many voids")
                    sa = -1.0
                    break
                if float(fields[1]) == 0:
                    sa = -1.0
                    break
                sa = float(fields[3])

    if not vol or not sa:
        raise ValueError("Error in zeo++ output stream")
    return vol, sa


Instruction:
Create a Python 3 function for
reading a string representation of a ZeoCSSR object.

Response:
def from_string(string):
        """
        Reads a string representation to a ZeoCssr object.

        Args:
            string: A string representation of a ZeoCSSR.

        Returns:
            ZeoCssr object.
        """
        lines = string.split("\n")
        toks = lines[0].split()
        lengths = [float(i) for i in toks]
        toks = lines[1].split()
        angles = [float(i) for i in toks[0:3]]
        # Zeo++ takes x-axis along a and pymatgen takes z-axis along c
        a = lengths.pop(-1)
        lengths.insert(0, a)
        alpha = angles.pop(-1)
        angles.insert(0, alpha)
        latt = Lattice.from_lengths_and_angles(lengths, angles)
        sp = []
        coords = []
        chrg = []
        for l in lines[4:]:
            m = re.match(r'\d+\s+(\w+)\s+([0-9\-\.]+)\s+([0-9\-\.]+)\s+' +
                         r'([0-9\-\.]+)\s+(?:0\s+){8}([0-9\-\.]+)', l.strip())
            if m:
                sp.append(m.group(1))
                # coords.append([float(m.group(i)) for i in xrange(2, 5)])
                # Zeo++ takes x-axis along a and pymatgen takes z-axis along c
                coords.append([float(m.group(i)) for i in [3, 4, 2]])
                chrg.append(m.group(5))
        return ZeoCssr(
            Structure(latt, sp, coords, site_properties={'charge': chrg})
        )


Instruction:
How would you explain what the following Python 3 function does
def from_string(contents):
        lines = contents.split("\n")
        num_sites = int(lines[0])
        coords = []
        sp = []
        prop = []
        coord_patt = re.compile(
            r"(\w+)\s+([0-9\-\.]+)\s+([0-9\-\.]+)\s+([0-9\-\.]+)\s+" +
            r"([0-9\-\.]+)"
        )
        for i in range(2, 2 + num_sites):
            m = coord_patt.search(lines[i])
            if m:
                sp.append(m.group(1))  # this is 1-indexed
                # coords.append(map(float, m.groups()[1:4]))  # this is 0-indexed
                coords.append([float(j)
                               for j in [m.group(i) for i in [3, 4, 2]]])
                prop.append(float(m.group(5)))
        return ZeoVoronoiXYZ(
            Molecule(sp, coords, site_properties={'voronoi_radius': prop})
        )

Response: Creates Zeo ++ Voronoi XYZ object from a string.


Instruction:
How would you explain what the following Python 3 function does
def set_velocities(self, velocities):
        assert len(velocities) == len(self.mol)
        self.params["velocity"] = velocities

Response:
:param velocities (au): list of list of atom velocities
        :return:


Instruction:
Make a summary of the following Python 3 code
def set_memory(self, total=None, static=None):
        if total:
            self.params["rem"]["mem_total"] = total
        if static:
            self.params["rem"]["mem_static"] = static

Response: Set the maxium allowed memory.


Instruction:
How would you explain what the following Python 3 function does
def set_scf_algorithm_and_iterations(self, algorithm="diis",
                                         iterations=50):
        """
        Set algorithm used for converging SCF and max number of SCF iterations.

        Args:
            algorithm: The algorithm used for converging SCF. (str)
            iterations: The max number of SCF iterations. (Integer)
        """
        available_algorithms = {"diis", "dm", "diis_dm", "diis_gdm", "gdm",
                                "rca", "rca_diis", "roothaan"}
        if algorithm.lower() not in available_algorithms:
            raise ValueError("Algorithm " + algorithm +
                             " is not available in QChem")
        self.params["rem"]["scf_algorithm"] = algorithm.lower()
        self.params["rem"]["max_scf_cycles"] = iterations

Response: Set the algorithm used for converging SCF and max number of SCF iterations.


Instruction:
Create a Python 3 function to
set the grid for DFT numerical integrations.

Response:
def set_dft_grid(self, radical_points=128, angular_points=302,
                     grid_type="Lebedev"):
        """
        Set the grid for DFT numerical integrations.

        Args:
            radical_points: Radical points. (Integer)
            angular_points: Angular points. (Integer)
            grid_type: The type of of the grid. There are two standard grids:
                SG-1 and SG-0. The other two supported grids are "Lebedev" and
                "Gauss-Legendre"
        """
        available_lebedev_angular_points = {6, 18, 26, 38, 50, 74, 86, 110, 146,
                                            170, 194, 230, 266, 302, 350, 434,
                                            590, 770, 974, 1202, 1454, 1730,
                                            2030, 2354, 2702, 3074, 3470, 3890,
                                            4334, 4802, 5294}
        if grid_type.lower() == "sg-0":
            self.params["rem"]["xc_grid"] = 0
        elif grid_type.lower() == "sg-1":
            self.params["rem"]["xc_grid"] = 1
        elif grid_type.lower() == "lebedev":
            if angular_points not in available_lebedev_angular_points:
                raise ValueError(str(angular_points) + " is not a valid "
                                 "Lebedev angular points number")
            self.params["rem"]["xc_grid"] = "{rp:06d}{ap:06d}".format(
                rp=radical_points, ap=angular_points)
        elif grid_type.lower() == "gauss-legendre":
            self.params["rem"]["xc_grid"] = "-{rp:06d}{ap:06d}".format(
                rp=radical_points, ap=angular_points)
        else:
            raise ValueError("Grid type " + grid_type + " is not supported "
                                                        "currently")


Instruction:
Implement a function in Python 3 to
set initial guess method to be used for SCF

Response:
def set_scf_initial_guess(self, guess="SAD"):
        """
        Set initial guess method to be used for SCF

        Args:
            guess: The initial guess method. (str)
        """
        availabel_guesses = {"core", "sad", "gwh", "read", "fragmo"}
        if guess.lower() not in availabel_guesses:
            raise ValueError("The guess method " + guess + " is not supported "
                                                           "yet")
        self.params["rem"]["scf_guess"] = guess.lower()


Instruction:
Can you create a Python 3 function that
sets the coordinates system used in geometry optimization.

Response:
def set_geom_opt_coords_type(self, coords_type="internal_switch"):
        """
        Set the coordinates system used in geometry optimization.
        "cartesian"       --- always cartesian coordinates.
        "internal"        --- always internal coordinates.
        "internal-switch" --- try internal coordinates first, if fails, switch
        to cartesian coordinates.
        "z-matrix"        --- always z-matrix coordinates.
        "z-matrix-switch" --- try z-matrix first, if fails, switch to
        cartesian coordinates.

        Args:
            coords_type: The type of the coordinates. (str)
        """
        coords_map = {"cartesian": 0, "internal": 1, "internal-switch": -1,
                      "z-matrix": 2, "z-matrix-switch": -2}
        if coords_type.lower() not in set(coords_map.keys()):
            raise ValueError("Coodinate system " + coords_type + " is not "
                             "supported yet")
        else:
            self.params["rem"]["geom_opt_coords"] = \
                coords_map[coords_type.lower()]


Instruction:
Write a Python 3 script to
adjust the convergence criteria of geometry optimization.

Response:
def scale_geom_opt_threshold(self, gradient=0.1, displacement=0.1,
                                 energy=0.1):
        """
        Adjust the convergence criteria of geometry optimization.

        Args:
            gradient: the scale factor for gradient criteria. If less than
                1.0, you are tightening the threshold. The base value is
                300 × 10E−6
            displacement: the scale factor for atomic displacement. If less
                then 1.0, you are tightening the threshold. The base value is
                1200 × 10E−6
            energy: the scale factor for energy change between successive
                iterations. If less than 1.0, you are tightening the
                threshold. The base value is 100 × 10E−8.
        """
        if gradient < 1.0/(300-1) or displacement < 1.0/(1200-1) or \
                energy < 1.0/(100-1):
            raise ValueError("The geometry optimization convergence criteria "
                             "is too tight")
        self.params["rem"]["geom_opt_tol_gradient"] = int(gradient * 300)
        self.params["rem"]["geom_opt_tol_displacement"] = int(displacement *
                                                              1200)
        self.params["rem"]["geom_opt_tol_energy"] = int(energy * 100)


Instruction:
Make a summary of the following Python 3 code
def use_pcm(self, pcm_params=None, solvent_key="solvent", solvent_params=None,
                radii_force_field=None):
        """
        Set the solvent model to PCM. Default parameters are trying to comply to
        gaussian default value

        Args:
            pcm_params (dict): The parameters of "$pcm" section.
            solvent_key (str): for versions < 4.2 the section name is "pcm_solvent"
            solvent_params (dict): The parameters of solvent_key section
            radii_force_field (str): The force fied used to set the solute
                radii. Default to UFF.
        """
        self.params["pcm"] = dict()
        self.params[solvent_key] = dict()
        default_pcm_params = {"Theory": "SSVPE",
                              "vdwScale": 1.1,
                              "Radii": "UFF"}
        if not solvent_params:
            solvent_params = {"Dielectric": 78.3553}
        if pcm_params:
            for k, v in pcm_params.items():
                self.params["pcm"][k.lower()] = v.lower() \
                    if isinstance(v, str) else v

        for k, v in default_pcm_params.items():
            if k.lower() not in self.params["pcm"].keys():
                self.params["pcm"][k.lower()] = v.lower() \
                    if isinstance(v, str) else v
        for k, v in solvent_params.items():
            self.params[solvent_key][k.lower()] = v.lower() \
                if isinstance(v, str) else copy.deepcopy(v)
        self.params["rem"]["solvent_method"] = "pcm"
        if radii_force_field:
            self.params["pcm"]["radii"] = "bondi"
            self.params["rem"]["force_fied"] = radii_force_field.lower()

Response: Sets the solvent model to PCM.


Instruction:
Here you have a function in Python 3, explain what it does
def from_string(cls, contents):
        mol = None
        charge = None
        spin_multiplicity = None
        params = dict()
        lines = contents.split('\n')
        parse_section = False
        section_name = None
        section_text = []
        ghost_atoms = None
        for line_num, line in enumerate(lines):
            l = line.strip().lower()

            if len(l) == 0:
                continue
            if (not parse_section) and (l == "$end" or not l.startswith("$")):
                raise ValueError("Format error, parsing failed")
            if parse_section and l != "$end":
                section_text.append(line)
            if l.startswith("$") and not parse_section:
                parse_section = True
                section_name = l[1:]
                available_sections = ["comment", "molecule", "rem"] + \
                    sorted(list(cls.optional_keywords_list))
                if section_name not in available_sections:
                    raise ValueError("Unrecognized keyword " + line.strip() +
                                     " at line " + str(line_num))
                if section_name in params:
                    raise ValueError("duplicated keyword " + line.strip() +
                                     "at line " + str(line_num))
            if parse_section and l == "$end":
                func_name = "_parse_" + section_name
                if func_name not in QcTask.__dict__:
                    raise Exception(func_name + " is not implemented yet, "
                                    "please implement it")
                parse_func = QcTask.__dict__[func_name].__get__(None, QcTask)
                if section_name == "molecule":
                    mol, charge, spin_multiplicity, ghost_atoms = parse_func(section_text)
                else:
                    d = parse_func(section_text)
                    params[section_name] = d
                parse_section = False
                section_name = None
                section_text = []
        if parse_section:
            raise ValueError("Format error. " + section_name + " is not "
                             "terminated")
        jobtype = params["rem"]["jobtype"]
        title = params.get("comment", None)
        exchange = params["rem"].get("exchange", "hf")
        method = params["rem"].get("method", None)
        correlation = params["rem"].get("correlation", None)
        basis_set = params["rem"]["basis"]
        aux_basis_set = params["rem"].get("aux_basis", None)
        ecp = params["rem"].get("ecp", None)
        optional_params = None
        op_keys = set(params.keys()) - {"comment", "rem"}
        if len(op_keys) > 0:
            optional_params = dict()
            for k in op_keys:
                optional_params[k] = params[k]
        return QcTask(molecule=mol, charge=charge,
                      spin_multiplicity=spin_multiplicity,
                      jobtype=jobtype, title=title,
                      exchange=exchange, correlation=correlation,
                      basis_set=basis_set, aux_basis_set=aux_basis_set,
                      ecp=ecp, rem_params=params["rem"],
                      optional_params=optional_params,
                      ghost_atoms=ghost_atoms,
                      method=method)

Response: Creates a new object from a string.


Instruction:
Here you have a function in Python 3, explain what it does
def magnetic_deformation(structure_A, structure_B):

    # retrieve orderings of both input structures
    ordering_a = CollinearMagneticStructureAnalyzer(
        structure_A, overwrite_magmom_mode="none"
    ).ordering
    ordering_b = CollinearMagneticStructureAnalyzer(
        structure_B, overwrite_magmom_mode="none"
    ).ordering

    # get a type string, this is either 'NM-FM' for between non-magnetic
    # and ferromagnetic, as in Bocarsly paper, or e.g. 'FM-AFM'
    type_str = "{}-{}".format(ordering_a.value, ordering_b.value)

    lattice_a = structure_A.lattice.matrix.T
    lattice_b = structure_B.lattice.matrix.T
    lattice_a_inv = np.linalg.inv(lattice_a)
    p = np.dot(lattice_a_inv, lattice_b)
    eta = 0.5 * (np.dot(p.T, p) - np.identity(3))
    w, v = np.linalg.eig(eta)
    deformation = 100 * (1.0 / 3.0) * np.sqrt(w[0] ** 2 + w[1] ** 2 + w[2] ** 2)

    MagneticDeformation = namedtuple("MagneticDeformation", "type deformation")

    return MagneticDeformation(deformation=deformation, type=type_str)

Response: Calculates the magnetic deformation between two structures.


Instruction:
Can you generate a brief explanation for the following Python 3 code
def _round_magmoms(magmoms, round_magmoms_mode: Union[int, float]):

        if isinstance(round_magmoms_mode, int):

            # simple rounding to number of decimal places
            magmoms = np.around(magmoms, decimals=round_magmoms_mode)

        elif isinstance(round_magmoms_mode, float):

            try:

                # get range of possible magmoms, pad by 50% just to be safe
                range_m = max([max(magmoms), abs(min(magmoms))]) * 1.5

                # construct kde, here "round_magmoms_mode" is the width of the kde
                kernel = gaussian_kde(magmoms, bw_method=round_magmoms_mode)

                # with a linearly spaced grid 1000x finer than width
                xgrid = np.linspace(
                    -range_m, range_m, 1000 * range_m / round_magmoms_mode
                )

                # and evaluate the kde on this grid, extracting the maxima of the kde peaks
                kernel_m = kernel.evaluate(xgrid)
                extrema = xgrid[argrelextrema(kernel_m, comparator=np.greater)]

                # round magmoms to these extrema
                magmoms = [extrema[(np.abs(extrema - m)).argmin()] for m in magmoms]

            except Exception as e:

                # TODO: typically a singular matrix warning, investigate this
                warnings.warn(
                    "Failed to round magmoms intelligently, "
                    "falling back to simple rounding."
                )
                warnings.warn(e)

            # and finally round roughly to the number of significant figures in our kde width
            num_decimals = len(str(round_magmoms_mode).split(".")[1]) + 1
            magmoms = np.around(magmoms, decimals=num_decimals)

        return magmoms

Response: Round the magmoms in the current number of modulation of the kde.


Instruction:
Create a Python 3 function to
return a Structure with species decorated with spin values instead of using magmom site properties.

Response:
def get_structure_with_spin(self):
        """
        Returns a Structure with species decorated with spin values instead
        of using magmom site properties.
        :return: Structure
        """

        structure = self.structure.copy()
        structure.add_spin_by_site(structure.site_properties["magmom"])
        structure.remove_site_property("magmom")

        return structure


Instruction:
Explain what the following Python 3 code does
def get_structure_with_only_magnetic_atoms(self, make_primitive=True):

        sites = [site for site in self.structure if abs(site.properties["magmom"]) > 0]

        structure = Structure.from_sites(sites)

        if make_primitive:
            structure = structure.get_primitive_structure(use_site_props=True)

        return structure

Response: Returns a Structure with only magnetic atoms present.


Instruction:
Given the following Python 3 function, write the documentation
def get_nonmagnetic_structure(self, make_primitive=True):

        structure = self.structure.copy()
        structure.remove_site_property("magmom")

        if make_primitive:
            structure = structure.get_primitive_structure()

        return structure

Response: Returns a Structure with no magnetic moments defined.


Instruction:
Here you have a function in Python 3, explain what it does
def get_ferromagnetic_structure(self, make_primitive=True):

        structure = self.structure.copy()

        structure.add_site_property("magmom", [abs(m) for m in self.magmoms])

        if make_primitive:
            structure = structure.get_primitive_structure(use_site_props=True)

        return structure

Response:
Returns a Structure with all magnetic moments positive
        or zero.


Instruction:
How would you explain what the following Python 3 function does
def number_of_unique_magnetic_sites(self, symprec=1e-3, angle_tolerance=5):

        structure = self.get_nonmagnetic_structure()

        sga = SpacegroupAnalyzer(
            structure, symprec=symprec, angle_tolerance=angle_tolerance
        )

        symm_structure = sga.get_symmetrized_structure()

        num_unique_mag_sites = 0

        for group_of_sites in symm_structure.equivalent_sites:
            if group_of_sites[0].specie in self.types_of_magnetic_specie:
                num_unique_mag_sites += 1

        return num_unique_mag_sites

Response: returns the number of unique magnetic sites present in the structure


Instruction:
How would you implement a function in Python 3 that
determines the magnetic ordering for a collinear structure.

Response:
def ordering(self):
        """
        Applies heuristics to return a magnetic ordering for a collinear
        magnetic structure. Result is not guaranteed for correctness.
        :return: Ordering Enum ('FiM' is used as the abbreviation for
        ferrimagnetic)
        """

        if not self.is_collinear:
            warnings.warn(
                "Detecting ordering in non-collinear structures not yet implemented."
            )
            return Ordering.Unknown

        if "magmom" not in self.structure.site_properties:
            # maybe this was a non-spin-polarized calculation, or we've
            # lost the magnetic moment information
            return Ordering.Unknown

        magmoms = self.magmoms

        max_magmom = max(magmoms)

        total_magnetization = abs(sum(magmoms))

        is_potentially_ferromagnetic = np.all(magmoms >= 0) or np.all(magmoms <= 0)

        if total_magnetization > 0 and is_potentially_ferromagnetic:
            return Ordering.FM
        elif total_magnetization > 0:
            return Ordering.FiM
        elif max_magmom > 0:
            return Ordering.AFM
        else:
            return Ordering.NM


Instruction:
Write a Python 3 function that can
return the spacegroup symbol and international number of the exchange energy of the system.

Response:
def get_exchange_group_info(self, symprec=1e-2, angle_tolerance=5.0):
        """
        Returns the information on the symmetry of the Hamiltonian
        describing the exchange energy of the system, taking into
        account relative direction of magnetic moments but not their
        absolute direction.

        This is not strictly accurate (e.g. some/many atoms will
        have zero magnetic moments), but defining symmetry this
        way is a useful way of keeping track of distinct magnetic
        orderings within pymatgen.

        :param symprec: same as SpacegroupAnalyzer
        :param angle_tolerance: same as SpacegroupAnalyzer
        :return: spacegroup_symbol, international_number
        """

        structure = self.get_structure_with_spin()

        return structure.get_space_group_info(
            symprec=symprec, angle_tolerance=angle_tolerance
        )


Instruction:
How would you implement a function in Python 3 that
compares the magnetic orderings of one structure with another structure.

Response:
def matches_ordering(self, other):
        """
        Compares the magnetic orderings of one structure with another.
        :param other: Structure
        :return (bool):
        """

        a = CollinearMagneticStructureAnalyzer(
            self.structure, overwrite_magmom_mode="normalize"
        ).get_structure_with_spin()

        # sign of spins doesn't matter, so we're comparing both
        # positive and negative versions of the structure
        # this code is possibly redundant, but is included out of
        # an abundance of caution
        b_positive = CollinearMagneticStructureAnalyzer(
            other, overwrite_magmom_mode="normalize", make_primitive=False
        )

        b_negative = b_positive.structure.copy()
        b_negative.add_site_property(
            "magmom", np.multiply(-1, b_negative.site_properties["magmom"])
        )

        b_negative = CollinearMagneticStructureAnalyzer(
            b_negative, overwrite_magmom_mode="normalize", make_primitive=False
        )

        b_positive = b_positive.get_structure_with_spin()
        b_negative = b_negative.get_structure_with_spin()

        if a.matches(b_positive) or a.matches(
            b_negative
        ):  # sometimes returns None (bug?)
            return True
        else:
            return False


Instruction:
Here you have a function in Python 3, explain what it does
def _sanitize_input_structure(input_structure):

        input_structure = input_structure.copy()

        # remove any annotated spin
        input_structure.remove_spin()

        # sanitize input structure: first make primitive ...
        input_structure = input_structure.get_primitive_structure(use_site_props=False)

        # ... and strip out existing magmoms, which can cause conflicts
        # with later transformations otherwise since sites would end up
        # with both magmom site properties and Specie spins defined
        if "magmom" in input_structure.site_properties:
            input_structure.remove_site_property("magmom")

        return input_structure

Response:
Sanitize our input structure by removing magnetic information
        and making primitive.


Instruction:
Can you implement a function in Python 3 that
generates a dictionary of transformation class instances and names of the unique orderings that are used to generate the new magnetic orderings for the given structure.

Response:
def _generate_transformations(self, structure):
        """
        The central problem with trying to enumerate magnetic orderings is
        that we have to enumerate orderings that might plausibly be magnetic
        ground states, while not enumerating orderings that are physically
        implausible. The problem is that it is not always obvious by e.g.
        symmetry arguments alone which orderings to prefer. Here, we use a
        variety of strategies (heuristics) to enumerate plausible orderings,
        and later discard any duplicates that might be found by multiple
        strategies. This approach is not ideal, but has been found to be
        relatively robust over a wide range of magnetic structures.

        Args:
            structure: A sanitized input structure (_sanitize_input_structure)

        Returns: A dict of a transformation class instance (values) and name of
        enumeration strategy (keys)
        """

        formula = structure.composition.reduced_formula
        transformations = {}

        # analyzer is used to obtain information on sanitized input
        analyzer = CollinearMagneticStructureAnalyzer(
            structure,
            default_magmoms=self.default_magmoms,
            overwrite_magmom_mode="replace_all",
        )

        if not analyzer.is_magnetic:
            raise ValueError(
                "Not detected as magnetic, add a new default magmom for the "
                "element you believe may be magnetic?"
            )

        # now we can begin to generate our magnetic orderings
        self.logger.info("Generating magnetic orderings for {}".format(formula))

        mag_species_spin = analyzer.magnetic_species_and_magmoms
        types_mag_species = sorted(
            analyzer.types_of_magnetic_specie,
            key=lambda sp: analyzer.default_magmoms.get(str(sp), 0),
            reverse=True,
        )
        num_mag_sites = analyzer.number_of_magnetic_sites
        num_unique_sites = analyzer.number_of_unique_magnetic_sites()

        # enumerations become too slow as number of unique sites (and thus
        # permutations) increase, 8 is a soft limit, this can be increased
        # but do so with care
        if num_unique_sites > self.max_unique_sites:
            raise ValueError("Too many magnetic sites to sensibly perform enumeration.")

        # maximum cell size to consider: as a rule of thumb, if the primitive cell
        # contains a large number of magnetic sites, perhaps we only need to enumerate
        # within one cell, whereas on the other extreme if the primitive cell only
        # contains a single magnetic site, we have to create larger supercells
        if "max_cell_size" not in self.transformation_kwargs:
            # TODO: change to 8 / num_mag_sites ?
            self.transformation_kwargs["max_cell_size"] = max(1, int(4 / num_mag_sites))
        self.logger.info(
            "Max cell size set to {}".format(
                self.transformation_kwargs["max_cell_size"]
            )
        )

        # when enumerating ferrimagnetic structures, it's useful to detect
        # symmetrically distinct magnetic sites, since different
        # local environments can result in different magnetic order
        # (e.g. inverse spinels)
        # initially, this was done by co-ordination number, but is
        # now done by a full symmetry analysis
        sga = SpacegroupAnalyzer(structure)
        structure_sym = sga.get_symmetrized_structure()
        wyckoff = ["n/a"] * len(structure)
        for indices, symbol in zip(
            structure_sym.equivalent_indices, structure_sym.wyckoff_symbols
        ):
            for index in indices:
                wyckoff[index] = symbol
        is_magnetic_sites = [
            True if site.specie in types_mag_species else False for site in structure
        ]
        # we're not interested in sites that we don't think are magnetic,
        # set these symbols to None to filter them out later
        wyckoff = [
            symbol if is_magnetic_site else "n/a"
            for symbol, is_magnetic_site in zip(wyckoff, is_magnetic_sites)
        ]
        structure.add_site_property("wyckoff", wyckoff)
        wyckoff_symbols = set(wyckoff) - {"n/a"}

        # if user doesn't specifically request ferrimagnetic_Cr2NiO4 orderings,
        # we apply a heuristic as to whether to attempt them or not
        if self.automatic:
            if (
                "ferrimagnetic_by_motif" not in self.strategies
                and len(wyckoff_symbols) > 1
                and len(types_mag_species) == 1
            ):
                self.strategies += ("ferrimagnetic_by_motif",)

            if (
                "antiferromagnetic_by_motif" not in self.strategies
                and len(wyckoff_symbols) > 1
                and len(types_mag_species) == 1
            ):
                self.strategies += ("antiferromagnetic_by_motif",)

            if (
                "ferrimagnetic_by_species" not in self.strategies
                and len(types_mag_species) > 1
            ):
                self.strategies += ("ferrimagnetic_by_species",)

        # we start with a ferromagnetic ordering
        if "ferromagnetic" in self.strategies:

            # TODO: remove 0 spins !

            fm_structure = analyzer.get_ferromagnetic_structure()
            # store magmom as spin property, to be consistent with output from
            # other transformations
            fm_structure.add_spin_by_site(fm_structure.site_properties["magmom"])
            fm_structure.remove_site_property("magmom")

            # we now have our first magnetic ordering...
            self.ordered_structures.append(fm_structure)
            self.ordered_structure_origins.append("fm")

        # we store constraint(s) for each strategy first,
        # and then use each to perform a transformation later
        all_constraints = {}

        # ...to which we can add simple AFM cases first...
        if "antiferromagnetic" in self.strategies:

            constraint = MagOrderParameterConstraint(
                0.5,
                # TODO: update MagOrderParameterConstraint in pymatgen to take types_mag_species directly
                species_constraints=list(map(str, types_mag_species)),
            )
            all_constraints["afm"] = [constraint]

            # allows for non-magnetic sublattices
            if len(types_mag_species) > 1:
                for sp in types_mag_species:

                    constraints = [
                        MagOrderParameterConstraint(0.5, species_constraints=str(sp))
                    ]

                    all_constraints["afm_by_{}".format(sp)] = constraints

        # ...and then we also try ferrimagnetic orderings by motif if a
        # single magnetic species is present...
        if "ferrimagnetic_by_motif" in self.strategies and len(wyckoff_symbols) > 1:

            # these orderings are AFM on one local environment, and FM on the rest
            for symbol in wyckoff_symbols:

                constraints = [
                    MagOrderParameterConstraint(
                        0.5, site_constraint_name="wyckoff", site_constraints=symbol
                    ),
                    MagOrderParameterConstraint(
                        1.0,
                        site_constraint_name="wyckoff",
                        site_constraints=list(wyckoff_symbols - {symbol}),
                    ),
                ]

                all_constraints["ferri_by_motif_{}".format(symbol)] = constraints

        # and also try ferrimagnetic when there are multiple magnetic species
        if "ferrimagnetic_by_species" in self.strategies:

            sp_list = [str(site.specie) for site in structure]
            num_sp = {sp: sp_list.count(str(sp)) for sp in types_mag_species}
            total_mag_sites = sum(num_sp.values())

            for sp in types_mag_species:

                # attempt via a global order parameter
                all_constraints["ferri_by_{}".format(sp)] = num_sp[sp] / total_mag_sites

                # attempt via afm on sp, fm on remaining species

                constraints = [
                    MagOrderParameterConstraint(0.5, species_constraints=str(sp)),
                    MagOrderParameterConstraint(
                        1.0,
                        species_constraints=list(
                            map(str, set(types_mag_species) - {sp})
                        ),
                    ),
                ]

                all_constraints["ferri_by_{}_afm".format(sp)] = constraints

        # ...and finally, we can try orderings that are AFM on one local
        # environment, and non-magnetic on the rest -- this is less common
        # but unless explicitly attempted, these states are unlikely to be found
        if "antiferromagnetic_by_motif" in self.strategies:

            for symbol in wyckoff_symbols:

                constraints = [
                    MagOrderParameterConstraint(
                        0.5, site_constraint_name="wyckoff", site_constraints=symbol
                    )
                ]

                all_constraints["afm_by_motif_{}".format(symbol)] = constraints

        # and now construct all our transformations for each strategy
        transformations = {}
        for name, constraints in all_constraints.items():

            trans = MagOrderingTransformation(
                mag_species_spin,
                order_parameter=constraints,
                **self.transformation_kwargs
            )

            transformations[name] = trans

        return transformations