Apply our input structure to our list of transformations and output a list
        of ordered structures that have been pruned for duplicates and for those
        with low symmetry (optional).

        Args:
            sanitized_input_structure: A sanitized input structure
            (_sanitize_input_structure)
            transformations: A dict of transformations (values) and name of
            enumeration strategy (key), the enumeration strategy name is just
            for record keeping

        Returns: None (sets self.ordered_structures
        and self.ordered_structures_origins instance variables)

def _generate_ordered_structures(self, sanitized_input_structure, transformations):
        """
        Apply our input structure to our list of transformations and output a list
        of ordered structures that have been pruned for duplicates and for those
        with low symmetry (optional).

        Args:
            sanitized_input_structure: A sanitized input structure
            (_sanitize_input_structure)
            transformations: A dict of transformations (values) and name of
            enumeration strategy (key), the enumeration strategy name is just
            for record keeping

        Returns: None (sets self.ordered_structures
        and self.ordered_structures_origins instance variables)

        """

        ordered_structures = self.ordered_structures
        ordered_structures_origins = self.ordered_structure_origins

        # utility function to combine outputs from several transformations
        def _add_structures(
            ordered_structures, ordered_structures_origins, structures_to_add, origin=""
        ):
            """
            Transformations with return_ranked_list can return either
            just Structures or dicts (or sometimes lists!) -- until this
            is fixed, we use this function to concat structures given
            by the transformation.
            """
            if structures_to_add:
                # type conversion
                if isinstance(structures_to_add, Structure):
                    structures_to_add = [structures_to_add]
                structures_to_add = [
                    s["structure"] if isinstance(s, dict) else s
                    for s in structures_to_add
                ]
                # concatenation
                ordered_structures += structures_to_add
                ordered_structures_origins += [origin] * len(structures_to_add)
                self.logger.info(
                    "Adding {} ordered structures: {}".format(
                        len(structures_to_add), origin
                    )
                )

            return ordered_structures, ordered_structures_origins

        for origin, trans in self.transformations.items():
            structures_to_add = trans.apply_transformation(
                self.sanitized_structure, return_ranked_list=self.num_orderings
            )
            ordered_structures, ordered_structures_origins = _add_structures(
                ordered_structures,
                ordered_structures_origins,
                structures_to_add,
                origin=origin,
            )

        # in case we've introduced duplicates, let's remove them
        self.logger.info("Pruning duplicate structures.")
        structures_to_remove = []
        for idx, ordered_structure in enumerate(ordered_structures):
            if idx not in structures_to_remove:
                duplicate_checker = CollinearMagneticStructureAnalyzer(
                    ordered_structure, overwrite_magmom_mode="none"
                )
                for check_idx, check_structure in enumerate(ordered_structures):
                    if check_idx not in structures_to_remove and check_idx != idx:
                        if duplicate_checker.matches_ordering(check_structure):
                            structures_to_remove.append(check_idx)

        if len(structures_to_remove):
            self.logger.info(
                "Removing {} duplicate ordered structures".format(
                    len(structures_to_remove)
                )
            )
            ordered_structures = [
                s
                for idx, s in enumerate(ordered_structures)
                if idx not in structures_to_remove
            ]
            ordered_structures_origins = [
                o
                for idx, o in enumerate(ordered_structures_origins)
                if idx not in structures_to_remove
            ]

        # also remove low symmetry structures
        if self.truncate_by_symmetry:

            # by default, keep structures with 5 most symmetric space groups
            if not isinstance(self.truncate_by_symmetry, int):
                self.truncate_by_symmetry = 5

            self.logger.info("Pruning low symmetry structures.")

            # first get a list of symmetries present
            symmetry_int_numbers = [
                s.get_space_group_info()[1] for s in ordered_structures
            ]

            # then count the number of symmetry operations for that space group
            num_sym_ops = [
                len(SpaceGroup.from_int_number(n).symmetry_ops)
                for n in symmetry_int_numbers
            ]

            # find the largest values...
            max_symmetries = sorted(list(set(num_sym_ops)), reverse=True)

            # ...and decide which ones to keep
            if len(max_symmetries) > self.truncate_by_symmetry:
                max_symmetries = max_symmetries[0:5]
            structs_to_keep = [
                (idx, num)
                for idx, num in enumerate(num_sym_ops)
                if num in max_symmetries
            ]

            # sort so that highest symmetry structs are first
            structs_to_keep = sorted(
                structs_to_keep, key=lambda x: (x[1], -x[0]), reverse=True
            )

            self.logger.info(
                "Removing {} low symmetry "
                "ordered structures".format(
                    len(ordered_structures) - len(structs_to_keep)
                )
            )

            ordered_structures = [ordered_structures[i] for i, _ in structs_to_keep]
            ordered_structures_origins = [
                ordered_structures_origins[i] for i, _ in structs_to_keep
            ]

            # and ensure fm is always at index 0
            fm_index = ordered_structures_origins.index("fm")
            ordered_structures.insert(0, ordered_structures.pop(fm_index))
            ordered_structures_origins.insert(
                0, ordered_structures_origins.pop(fm_index)
            )

        # if our input structure isn't in our generated structures,
        # let's add it manually and also keep a note of which structure
        # is our input: this is mostly for book-keeping/benchmarking
        self.input_index = None
        self.input_origin = None
        if self.input_analyzer.ordering != Ordering.NM:
            matches = [
                self.input_analyzer.matches_ordering(s) for s in ordered_structures
            ]
            if not any(matches):
                ordered_structures.append(self.input_analyzer.structure)
                ordered_structures_origins.append("input")
                self.logger.info(
                    "Input structure not present in enumerated structures, adding..."
                )
            else:
                self.logger.info(
                    "Input structure was found in enumerated "
                    "structures at index {}".format(matches.index(True))
                )
                self.input_index = matches.index(True)
                self.input_origin = ordered_structures_origins[self.input_index]

        self.ordered_structures = ordered_structures
        self.ordered_structure_origins = ordered_structures_origins