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
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)
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)²⁺")
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) ), )
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)