Example #1
0
    def get_site_description(self, site_index: int) -> str:
        """Gets a description of the geometry and bonding of a site.

        If the site likeness (order parameter) is less than ``distorted_tol``,
        "distorted" will be added to the geometry description.

        Args:
            site_index: An inequivalent site index.

        Returns:
            A description of the geometry and bonding of a site.
        """
        site = self._da.sites[site_index]

        if site["poly_formula"] and (
            self.cation_polyhedra_only or "+" in site["element"]
        ):
            desc = self._get_poly_site_description(site_index)
            tilt_desc = self.get_octahedral_tilt_description(site_index)
            if tilt_desc:
                desc += ". " + tilt_desc
        else:
            element = get_formatted_el(
                site["element"],
                self._da.sym_labels[site_index],
                use_oxi_state=self.describe_oxidation_state,
                use_sym_label=self.describe_symmetry_labels,
                fmt=self.fmt,
            )

            if site["geometry"]["likeness"] < self.distorted_tol:
                s_geometry = "distorted "
            else:
                s_geometry = ""
            s_geometry += site["geometry"]["type"]

            desc = f"{element} is bonded in {en.a(s_geometry)} geometry to "
            desc += self._get_nearest_neighbor_description(site_index)

        bond_length_desc = self._get_nearest_neighbor_bond_length_descriptions(
            site_index
        )
        if bond_length_desc:
            desc += ". " + bond_length_desc
        else:
            desc += "."

        return desc
Example #2
0
    def get_component_description(self,
                                  component_index: int,
                                  single_component: bool = False) -> str:
        """Gets the descriptions of all sites in a component.

        Args:
            component_index: The index of the component
            single_component: Whether the structure contains only a single
                component.

        Returns:
            The description for all sites in the components.
        """
        desc = []
        first_group = True
        for site_group in self._da.get_component_site_groups(component_index):

            if len(site_group.sites) == 1:
                desc.append(self.get_site_description(site_group.sites[0]))

            else:
                element = get_formatted_el(
                    site_group.element,
                    "",
                    use_oxi_state=self.describe_oxidation_state,
                    use_sym_label=False,
                    fmt=self.fmt)

                if first_group and not single_component:
                    s_there = "there"
                else:
                    s_there = "There"

                s_count = en.number_to_words(len(site_group.sites))

                desc.append("{} are {} inequivalent {} sites.".format(
                    s_there, s_count, element))

                for i, site in enumerate(site_group.sites):
                    s_ordinal = en.number_to_words(en.ordinal(i + 1))
                    desc.append("In the {} {} site,".format(
                        s_ordinal, element))
                    desc.append(self.get_site_description(site))

            first_group = False
        return " ".join(desc)
Example #3
0
    def _get_nearest_neighbor_description(self, site_index: int) -> str:
        """Gets a description of a sites nearest neighbors.

        Note: This function is intended to be run directly after
        :meth:`get_site_description`, as the output will not form a complete
        sentence on its own.

        Args:
            site_index: An inequivalent site index.

        Returns:
            A description of the nearest neighbors.
        """
        nn_details = self._da.get_nearest_neighbor_details(
            site_index, group=not self.describe_symmetry_labels
        )

        last_count = 0
        nn_descriptions = []
        for nn_site in nn_details:
            element = get_formatted_el(
                nn_site.element,
                nn_site.sym_label,
                use_oxi_state=self.describe_oxidation_state,
                use_sym_label=self.describe_symmetry_labels,
                fmt=self.fmt,
            )

            if len(nn_site.sites) == 1 and nn_site.count != 1:
                s_equivalent = " equivalent "
            else:
                s_equivalent = " "

            nn_descriptions.append(
                "{}{}{}".format(
                    en.number_to_words(nn_site.count), s_equivalent, element
                )
            )
            last_count = nn_site.count

        s_atoms = "atom" if last_count == 1 else "atoms"
        return f"{en.join(nn_descriptions)} {s_atoms}"
    def test_get_formatted_el(self):
        """Test getting formatted element strings."""
        specie = get_el_sp("Sn2+")
        form_el = get_formatted_el(specie, "")
        self.assertEqual(form_el, "Sn2+")

        element = get_el_sp("Sn")
        form_el = get_formatted_el(element, "")
        self.assertEqual(form_el, "Sn")

        form_el = get_formatted_el(get_el(50), "")
        self.assertEqual(form_el, "Sn")

        form_el = get_formatted_el("Sn2+",
                                   "(1,2)",
                                   use_oxi_state=True,
                                   use_sym_label=True,
                                   fmt="raw")
        self.assertEqual(form_el, "Sn(1,2)2+")

        form_el = get_formatted_el("Sn2+",
                                   "(1,2)",
                                   use_oxi_state=False,
                                   use_sym_label=True,
                                   fmt="raw")
        self.assertEqual(form_el, "Sn(1,2)")

        form_el = get_formatted_el("Sn2+",
                                   "(1,2)",
                                   use_oxi_state=True,
                                   use_sym_label=False,
                                   fmt="raw")
        self.assertEqual(form_el, "Sn2+")

        form_el = get_formatted_el("Sn2+",
                                   "(1,2)",
                                   use_oxi_state=False,
                                   use_sym_label=False,
                                   fmt="raw")
        self.assertEqual(form_el, "Sn")

        form_el = get_formatted_el("Sn2+",
                                   "(1,2)",
                                   use_oxi_state=True,
                                   use_sym_label=True,
                                   fmt="latex")
        self.assertEqual(form_el, "Sn(1,2)^{2+}")

        form_el = get_formatted_el("Sn2+",
                                   "(1,2)",
                                   use_oxi_state=True,
                                   use_sym_label=True,
                                   fmt="html")
        self.assertEqual(form_el, "Sn(1,2)<sup>2+</sup>")

        form_el = get_formatted_el("Sn2+",
                                   "(1,2)",
                                   use_oxi_state=True,
                                   use_sym_label=True,
                                   fmt="unicode")
        self.assertEqual(form_el, "Sn(1,2)²⁺")
Example #5
0
    def get_bond_length_description(self, from_site: int, to_sites: List[int]) -> str:
        """Gets a description of the bond lengths between two sets of sites.

        Args:
            from_site: An inequivalent site index.
            to_sites: A :obj:`list` of site indices. The site indices should
                all be for the same element.

        Returns:
            A description of the bond lengths or an empty string if
            :attr:`StructureDescriber.only_describe_bonds_once` is ``True`` and
            all all bond lengths have already been described.
        """
        if self.only_describe_bonds_once:
            to_sites = self._filter_seen_bonds(from_site, to_sites)

            if not to_sites:
                return ""

        from_element = get_formatted_el(
            self._da.elements[from_site],
            self._da.sym_labels[from_site],
            use_oxi_state=False,
            use_sym_label=self.describe_symmetry_labels,
        )
        to_element = get_formatted_el(
            self._da.elements[to_sites[0]],
            self._da.get_sym_label(to_sites),
            use_oxi_state=False,
            use_sym_label=self.describe_symmetry_labels,
        )

        dists = self._da.get_distance_details(from_site, to_sites)

        # if only one bond length
        if len(dists) == 1:
            return "The {}–{} bond length is {}.".format(
                from_element, to_element, self._distance_to_string(dists[0])
            )

        discrete_bond_lengths = self._rounded_bond_lengths(dists)

        # if multiple bond lengths but they are all the same
        if len(set(discrete_bond_lengths)) == 1:
            s_intro = "Both" if len(discrete_bond_lengths) == 2 else "All"
            return "{} {}–{} bond lengths are {}.".format(
                s_intro, from_element, to_element, self._distance_to_string(dists[0])
            )

        # if two sets of bond lengths
        if len(set(discrete_bond_lengths)) == 2:
            small = min(discrete_bond_lengths)
            s_small_count = en.number_to_words(discrete_bond_lengths.count(small))
            big = max(discrete_bond_lengths)
            s_big_count = en.number_to_words(discrete_bond_lengths.count(big))

            s_length = en.plural("length", s_big_count)

            return (
                "There {} {} shorter ({}) and {} " "longer ({}) {}–{} bond {}."
            ).format(
                en.plural_verb("is", s_small_count),
                s_small_count,
                self._distance_to_string(small),
                s_big_count,
                self._distance_to_string(big),
                from_element,
                to_element,
                s_length,
            )

        # otherwise just detail the spread of bond lengths
        return (
            "There are a spread of {}–{} bond distances ranging from " "{}."
        ).format(
            from_element,
            to_element,
            self._distance_range_to_string(
                min(discrete_bond_lengths), max(discrete_bond_lengths)
            ),
        )
Example #6
0
    def _get_poly_site_description(self, site_index: int):
        """Gets a description of a connected polyhedral site.

        If the site likeness (order parameter) is less than ``distorted_tol``,
        "distorted" will be added to the geometry description.

        Args:
            site_index: An inequivalent site index.

        Returns:
            A description the a polyhedral site, including connectivity.
        """
        site = self._da.sites[site_index]
        nnn_details = self._da.get_next_nearest_neighbor_details(
            site_index, group=not self.describe_symmetry_labels
        )

        from_element = get_formatted_el(
            site["element"],
            self._da.sym_labels[site_index],
            use_oxi_state=self.describe_oxidation_state,
            use_sym_label=self.describe_symmetry_labels,
            fmt=self.fmt,
        )

        from_poly_formula = site["poly_formula"]
        if self.fmt == "latex":
            from_poly_formula = latexify(from_poly_formula)
        elif self.fmt == "unicode":
            from_poly_formula = unicodeify(from_poly_formula)
        elif self.fmt == "html":
            from_poly_formula = htmlify(from_poly_formula)

        s_from_poly_formula = get_el(site["element"]) + from_poly_formula

        if site["geometry"]["likeness"] < self.distorted_tol:
            s_distorted = "distorted "
        else:
            s_distorted = ""
        s_polyhedra = geometry_to_polyhedra[site["geometry"]["type"]]
        s_polyhedra = polyhedra_plurals[s_polyhedra]

        nn_desc = self._get_nearest_neighbor_description(site_index)
        desc = f"{from_element} is bonded to {nn_desc} to form "

        # handle the case we were are connected to the same type of polyhedra
        if (
            nnn_details[0].element == site["element"]
            and len(
                {(nnn_site.element, nnn_site.poly_formula) for nnn_site in nnn_details}
            )
        ) == 1:
            connectivities = list({nnn_site.connectivity for nnn_site in nnn_details})
            s_mixture = "a mixture of " if len(connectivities) != 1 else ""
            s_connectivities = en.join(connectivities)

            desc += "{}{}{}-sharing {} {}".format(
                s_mixture,
                s_distorted,
                s_connectivities,
                s_from_poly_formula,
                s_polyhedra,
            )
            return desc

        # otherwise loop through nnn connectivities and describe individually
        desc += "{}{} {} that share ".format(
            s_distorted, s_from_poly_formula, s_polyhedra
        )
        nnn_descriptions = []
        for nnn_site in nnn_details:
            to_element = get_formatted_el(
                nnn_site.element,
                nnn_site.sym_label,
                use_oxi_state=False,
                use_sym_label=self.describe_symmetry_labels,
            )

            to_poly_formula = nnn_site.poly_formula
            if self.fmt == "latex":
                to_poly_formula = latexify(to_poly_formula)
            elif self.fmt == "unicode":
                to_poly_formula = unicodeify(to_poly_formula)
            elif self.fmt == "html":
                to_poly_formula = htmlify(to_poly_formula)

            to_poly_formula = to_element + to_poly_formula
            to_shape = geometry_to_polyhedra[nnn_site.geometry]

            if len(nnn_site.sites) == 1 and nnn_site.count != 1:
                s_equivalent = " equivalent "
            else:
                s_equivalent = " "

            if nnn_site.count == 1:
                s_an = f" {en.an(nnn_site.connectivity)}"
            else:
                s_an = ""
                to_shape = polyhedra_plurals[to_shape]

            nnn_descriptions.append(
                "{}{} with {}{}{} {}".format(
                    s_an,
                    en.plural(nnn_site.connectivity, nnn_site.count),
                    en.number_to_words(nnn_site.count),
                    s_equivalent,
                    to_poly_formula,
                    to_shape,
                )
            )

        return desc + en.join(nnn_descriptions)