Returns orbital projections from a band structure.

    Args:
        bs (:obj:`~pymatgen.electronic_structure.bandstructure.BandStructureSymmLine`):
            The band structure.
        selection (list): A list of :obj:`tuple` or :obj:`string`
            identifying which projections to return. Projections can be
            specified by both element and orbital, for example::

                [('Bi', 's'), ('Bi', 'p'), ('S', 'p')]

            If just the element is specified then all the orbitals of
            that element are combined. For example, the following will combine
            all the S orbitals into a single projection::

                [('Bi', 's'), ('Bi', 'p'), 'S']

            Particular orbitals can also be combined, for example::

                [('Bi', 's'), ('Bi', 'p'), ('S', ('s', 'p', 'd'))]

        normalise (:obj:`str`, optional): Normalisation the projections.
            Options are:

              * ``'all'``: Projections normalised against the sum of all
                   other projections.
              * ``'select'``: Projections normalised against the sum of the
                   selected projections.
              * ``None``: No normalisation performed.

            Defaults to ``None``.

    Returns:
        list: A ``list`` of orbital projections, in the same order as specified
        in ``selection``, with the format::

            [ {spin: projections}, {spin: projections} ... ]

        Where spin is a :obj:`pymatgen.electronic_structure.core.Spin`
        object and projections is a :obj:`numpy.array` of::

            projections[band_index][kpoint_index]

        If there are no projections in the band structure, then an array of
        zeros is returned for each spin.

def get_projections(bs, selection, normalise=None):
    """Returns orbital projections from a band structure.

    Args:
        bs (:obj:`~pymatgen.electronic_structure.bandstructure.BandStructureSymmLine`):
            The band structure.
        selection (list): A list of :obj:`tuple` or :obj:`string`
            identifying which projections to return. Projections can be
            specified by both element and orbital, for example::

                [('Bi', 's'), ('Bi', 'p'), ('S', 'p')]

            If just the element is specified then all the orbitals of
            that element are combined. For example, the following will combine
            all the S orbitals into a single projection::

                [('Bi', 's'), ('Bi', 'p'), 'S']

            Particular orbitals can also be combined, for example::

                [('Bi', 's'), ('Bi', 'p'), ('S', ('s', 'p', 'd'))]

        normalise (:obj:`str`, optional): Normalisation the projections.
            Options are:

              * ``'all'``: Projections normalised against the sum of all
                   other projections.
              * ``'select'``: Projections normalised against the sum of the
                   selected projections.
              * ``None``: No normalisation performed.

            Defaults to ``None``.

    Returns:
        list: A ``list`` of orbital projections, in the same order as specified
        in ``selection``, with the format::

            [ {spin: projections}, {spin: projections} ... ]

        Where spin is a :obj:`pymatgen.electronic_structure.core.Spin`
        object and projections is a :obj:`numpy.array` of::

            projections[band_index][kpoint_index]

        If there are no projections in the band structure, then an array of
        zeros is returned for each spin.
    """
    spins = bs.bands.keys()
    nbands = bs.nb_bands
    nkpts = len(bs.kpoints)

    # if we are to normalise the data later we need access to all projections
    elements = bs.structure.symbol_set
    all_orbitals = ['s', 'p', 'd', 'f']

    # dictio has the form: {'el1': [s, p, d, f], 'el2': [s, p, d, f]...}
    dictio = dict(zip(elements, [all_orbitals] * len(elements)))

    # bs.get_projection_on_elements_and_orbitals() returns the data in a
    # really frustrating format, namely:
    #     {spin: [band_index][kpoint_index]{element: {orbital: projection}}}
    all_proj = bs.get_projections_on_elements_and_orbitals(dictio)

    # Make a defaultdict of defaultdicts
    dict_proj = defaultdict(lambda: defaultdict(dict))
    sum_proj = dict(zip(spins, [np.zeros((nbands, nkpts))] * len(spins)))

    # store the projections for all elements and orbitals in a useable format
    for spin, element, orbital in it.product(spins, elements, all_orbitals):

        # convert data to [nb][nk][projection]
        el_orb_proj = [[all_proj[spin][nb][nk][element][orbital]
                        for nk in range(nkpts)] for nb in range(nbands)]

        dict_proj[element][orbital][spin] = np.array(el_orb_proj)

        if normalise == 'all':
            sum_proj[spin] += el_orb_proj

    # now go through the selected orbitals and extract what's needed
    spec_proj = []
    for spec in selection:

        if isinstance(spec, str):
            # spec is just an element type, therefore sum all orbitals
            element = spec
            orbitals = all_orbitals
        else:
            element, orbitals = spec
            # even if there is only one orbital, make sure we can loop over it
            orbitals = tuple(orbitals)

        proj = dict(zip(spins, [np.zeros((nbands, nkpts))] * len(spins)))
        for spin, orbital in it.product(spins, orbitals):
            proj[spin] += dict_proj[element][orbital][spin]

            if normalise == 'select':
                sum_proj[spin] += dict_proj[element][orbital][spin]

        spec_proj.append(proj)

    if normalise:
        # to prevent warnings/errors relating to divide by zero,
        # catch warnings and surround divide with np.nan_to_num
        with np.errstate(divide='ignore', invalid='ignore'):
            for spin, i in it.product(spins, range(len(spec_proj))):
                spec_proj[i][spin] = np.nan_to_num(spec_proj[i][spin] /
                                                   sum_proj[spin])
    return spec_proj