# (C) 2012-2015 Elke Schaper
# (C) 2014 Julia Pecerska

"""
    :synopsis: A cyclic hidden Markov model that describes sequence tandem repeats.

    .. moduleauthor:: Elke Schaper <elke.schaper@isb.sib.ch>
"""

import datetime
import logging
import numpy as np
import os
import pickle
import shutil
import subprocess
import tempfile

from tral.hmm import hmm_io, hmm_viterbi
from tral.repeat.repeat import Repeat
from tral import configuration

LOG = logging.getLogger(__name__)

CONFIG_GENERAL = configuration.Configuration.instance().config
CONFIG = CONFIG_GENERAL["hmm"]

################################### HMM class ############################


class HMM:

    """ Sequence profile hidden Markov models (HMMs) are used to model genomic
    sequence containing a tandem repeat.

    Note:
        The HMM implemented here is described in detail in

        Schaper, E., Gascuel, O. & Anisimova, M. Deep conservation of human
        protein tandem repeats within the eukaryotes.
        Molecular Biology and Evolution (2014).


        All notations used here are based on the notations introduced in

        Eddy, S. R. Profile hidden Markov models.
        Bioinformatics 14, 755–763 (1998).

    The HMM contains flanking states ("N", "C"), match states ("M1", "M2", ...)
    and insertion states ("I1", "I2", ..). As deletion states are non-emitting,
    they are merged with the other states.

    The HMM state "N" emits the sequence before the tandem repeat.
    The HMM state "C" emits the residual sequence after the tandem repeat.
    The HMM states "M1", "M2", ... can emit sequence within the tandem repeat.
    "M1" describes the first amino acid/nucleotide in the repeat unit,
    "M2" the second, and so on]. The HMM states "I1", "I2", can emit sequence
    representing insertions within the tandem repeat.

    The transition probabilities between states are stored in ``p_t``.
    The emission probabilities of every state are stored in ``p_e``.
    The probabilities for any state to be the null state are stored in ``p_0``.

    The structure and probabilities defining standard sequence profile HMMs can
    be

    * taken from HMM databases such as PFAM,
    * calculated from sequence alignments (here: an alignment of TR units) using for example the HMMER suite,
    * defined in house.

    All three ways are implemented here.

    The HMM is used to detect the maximum likelihood occurrence of a TR modeled
    by the HMM on a sequence. The Viterbi algorithm is implemented to do this
    detection.

    Attributes:
        id (str): The id describes the origin of the HMM.
                    E.g., PFAMID "PF00069".
        states (list of str): The names of all states that the HMM contains.
                              E.g. ["N", "M1", "I1", "M2", "I2", "C"]
        insertion_states (list of str): The names of all insertion states that
                                        the HMM contains. E.g. ["I1", "I2"]
        match_states (list of str): The names of all match states that the HMM
                                    contains. E.g. ["M1", "M2"]
        l_effective (int): The number of states in the HMM.
                           E.g. l_effective = 4
        alphabet (list of str): The letters that the HMM states can emit.
                                E.g. all amino acids in the LG matrix:
                                ["A", "C", ...]
        p_t (dict of dict of float): A dictionary
            {state0: {state1: transition probability, ...}, ...}
            between all `states` as log10.
        p_0 (dict of dict of float): A dictionary of the null probabilities to
                                    be in any of the `states`:
                                    {`states[0]`: 0.1, `states[1]`: 0.2, ...}
        p_e (dict of dict of float): A dictionary of emission probabilities for
                every state and every letter in `alphabet`:
                {`states[0]`: {`alphabet[0]`: emission probability, ...}, ...}
        hmmer (dict of dict of float): A dictionary of all information
                                       extracted from a Hmmer model.
    """

    def __str__(self):
        """
            Create string for HMM instance.
        """
        try:
            tmp = [self.p_e[i] for i in self.match_states]
            tmp = "".join([max(i.keys(), key=(lambda key: i[key]))
                           for i in tmp])
            hmm = "cpHMM ID: {}\ncpHMM length: {}\n".format(
                self.id,
                self.l_effective)
            hmm += "Most likely motif: {}".format(tmp)
        except:
            hmm = "<HMM instance>"
            LOG.warning("Could not create string of HMM instance.")

        return hmm

    # Use functions defined in other methods as class functions.
    # Static function (declare as @static ?):
    read = hmm_io.read

    # Function that take the HMM instance as argument:
    def viterbi(self, *args):
        return hmm_viterbi.viterbi(self, *args)

    def __init__(self, hmmer_probabilities):
        """ HMM class __init_ module.

        __init__ takes HMM parameters (including the alphabet, emission
        probabilities and transition probabilities) as input, and assigns
        them to class attributes.

        Args:
            hmmer_probabilities (dict): A dictionary with HMM parameters.
        """
        self.hmmer = hmmer_probabilities
        self.id = hmmer_probabilities['id']
        self.alphabet = self.hmmer['letters']

        self.l_effective = max([int(key) for key \
                               in hmmer_probabilities.keys() if key.isdigit()])

        # Initialise all HMM states to default value (e.g. transition to or
        # from terminal state all have the same cost 0).
        self.initialise_HMM_structure(self.l_effective)

        d_translate_states = {
            i: i[
                1:] for i in self.match_states +
            self.insertion_states}
        for i in self.terminal_states:
            d_translate_states[i] = "COMPO"

        # Store null model emission probabilities for ["N","C"]
        # from hmmer_probabilities["COMPO"]
        # Store match_state and insertion_state emission probabilities for
        # ["M0",...] from hmmer_probabilities["0"],...
        for i_state in self.match_states:
            i_emission_probabilities = self.hmmer[
                d_translate_states[i_state]]['emissions'][:len(self.alphabet)]
            self.set_emission_probability_hmmer3(
                i_state,
                i_emission_probabilities)
        for i_state in (self.insertion_states + self.terminal_states):
            i_emission_probabilities = \
                self.hmmer[d_translate_states[i_state]] \
                ['insertion_emissions'][:len(self.alphabet)]
            self.set_emission_probability_hmmer3(
                i_state,
                i_emission_probabilities)

        # Store transition probabilities
        # (m->m    m->i    m->d    i->m    i->i    d->m    d->d):
        # First: All state defined in HMMer model
        for i, i_state in enumerate(self.match_states):
            i_transition_probabilities = self.hmmer[
                d_translate_states[i_state]]['transition']
            # Completing the circle of the HMM.
            if i == self.l_effective - 1:
                i_transition_probabilities = self.set_circle_transition_probability_hmmer3(
                    i_transition_probabilities,
                    d_translate_states[i_state])
            LOG.debug("i_state: %s", i_state)
            LOG.debug(
                "set_circle_transition_probability_hmmer3: %s",
                i_transition_probabilities)
            self.set_transition_probability_hmmer3(
                i,
                i_transition_probabilities)

        LOG.debug("p_t before deletion: %s", self.p_t)

        # Translate deletion states into direct transitions from match to match
        # states.
        if self.l_effective > 1:
            p_t_d = {}
            for i, i_match_state in enumerate(self.match_states):
                p_t_d[i_match_state] = self.get_direct_transition_probabilities_for_deletions(
                    i,
                    i_match_state)
            for i_match_state in self.match_states:
                for i_goal_state, iP in p_t_d[i_match_state].items():
                    self.p_t[i_match_state][i_goal_state] = iP

        LOG.debug("p_t after deletion: %s", self.p_t)

        # As it is never likely to got from a terminal state to a deletion
        #  tate or vice versa, this transition probabilities can be ignored.
        # Thus, you may advance and:
        # Delete everything you ever knew about deletion states.

        self.states = [self.terminal_states[
            0]] + self.match_states + self.insertion_states + [self.terminal_states[1]]
        self.p_t = {i_state: {i: j for i, j in self.p_t[
            i_state].items() if not i in self.deletion_states} for i_state in self.states}
        self.p_0 = {
            i: j for i,
            j in self.p_0.items() if i not in self.deletion_states}

        del self.deletion_states

    def create(input_format, file=None, repeat=None):
        """ Creates a HMM instance from 2 possible input formats.

        A `HMM` instance is created from one of the two possible inputs:

        * HMMER3 model
        * ``Repeat`` instance

        Args:
            input_format (str): The file format of the input file
            file (str): Path to the file containing the HMM parameters
                encoded in the HMMER3 format.
            repeat (Repeat): A Repeat object with an MSA that can be
                transformed into an HMM.

        Returns:
            `HMM`: An initialized instance of the `HMM` class.

        Raises:
            Exception: if the HMMER3 file provided does not exist.
            Exception: if the repeat value provided is not an instance of the
                Repeat class.
            Exception: if no parameters are provided to create the HMM from.

        .. warning:: Will initialize all HMM's in a file but will only return
            the first one.
        .. todo:: Fix the previous warning (agree on the way how it should be
            done first)

        """

        if input_format == 'hmmer':
            # Read only first element from HMMER3 file.
            if not os.path.exists(file):
                raise Exception('HMMER3 file does not exist.')
            hmmer_probabilities = next(HMM.read(file))
        elif input_format == 'pickle':
            with open(file, 'rb') as fh:
                return pickle.load(fh)
        elif input_format == 'repeat':
            if not isinstance(repeat, Repeat):
                raise Exception('The repeat value is not a valid instance of '
                                'the Repeat class.')
            hmmer_probabilities = HMM.create_from_repeat(repeat)
        else:
            raise Exception("Unknown input format: {}.".format(input_format))

        LOG.debug(hmmer_probabilities)
        return HMM(hmmer_probabilities)

    def create_from_repeat(tandem_repeat, hmm_copy_path=None,
                           hmm_copy_id=None):
        """ Get HMM parameters (including the alphabet, emission probabilities
        and transition probabilities) from a tandem repeat.

        An HMM is created from ``tandem_repeat`` using :command:`hmmbuild`
        and is saven in the HMMER3 file format.
        Next, HMM parameters are retrieved from the HMMER3 file and returned.

        Args:
            tandem_repeat (TR):  A Repeat class instance of the TR to be
                transformed into an HMM.
            hmm_copy_path (str): Path to where a copy of the created HMM
                will be stored.
                If None, no copies are saved.
            hmm_copy_id (str): HMM id which will serve as the filename in case
                a copy should be saved.
                If None and ``hmm_copy_path`` is specified the name is
                generated from current date and time combination.

        Returns:
            HMM parameters (dict): For a sample structure please refer to
            :py:hmm_io.read:.

        .. todo:: Make sure the id is correctly read in.

        """
        timestamp = datetime.datetime.now()
        tmp_id = timestamp.strftime('%Y:%m:%d:%H:%M:%S:%f')

        # Create a temporary directory
        tmp_dir = tempfile.mkdtemp()

        # Save TR as Stockholm file
        stockholm_file = os.path.join(tmp_dir, tmp_id + ".sto")

        # O repeat_io.save_repeat_stockholm(tandem_repeat.msaD, stockholm_file)
        tandem_repeat.write(file=stockholm_file, file_format="stockholm")

        # Run HMMbuild to build a HMM model, and read model
        p = subprocess.Popen([CONFIG["hmmbuild"], "--amino", tmp_id + ".hmm",
                              tmp_id + ".sto"],
                             stdout=subprocess.PIPE, stderr=None, cwd=tmp_dir)
        p.wait()

        hmm_file = os.path.join(tmp_dir, tmp_id + ".hmm")
        if hmm_copy_path:
            if not os.path.exists(hmm_copy_path):
                LOG.critical('Specified path for the file copy does not '
                             'exist, not saving copy: %s.', hmm_copy_path)
            else:
                if hmm_copy_id:
                    shutil.copy(hmm_file, os.path.join(hmm_copy_path,
                                                       hmm_copy_id + ".hmm"))
                else:
                    shutil.copy(hmm_file, hmm_copy_path)

        hmmer_probabilities = next(
            HMM.read(
                hmm_filename=hmm_file,
                id=hmm_copy_id))

        shutil.rmtree(tmp_dir)

        return hmmer_probabilities

    def write(self, file, output_format, *args):
        """ Write ``HMM`` to file.

        Write ``HMM`` to file. Currently, only pickle is implemented.

        Args:
            file (str): Path to input file
            output_format (str):  Either "fasta", "pickle" or "stockholm"

        .. todo:: Write checks for ``format`` and ``file``.

        """

        if output_format == 'pickle':
            with open(file, 'wb') as fh:
                pickle.dump(self, fh)
        else:
            raise Exception('output_format is unknown: {}'.format(output_format))

    def hmm_example(self):
        """ .. todo:: Is this method needed?
        """
        #states = ["N", "B", "M1", "M2", "M3", "E", "C"]
        self.states = ["N", "M1", "M2", "C"]

        # Initialisation
        self.p_t = {iS: {iS2: 0 for iS2 in self.states}
                    for iS in self.states[:-1]}
        self.p_0 = {iS: 1 / 3 for iS in self.states}

        # Transition probabilities
        # Feed Values to p_t
        self.p_t["N"] = {"N": 0.5, "M1": 0.5}
        self.p_t["M1"] = {"M2": 0.5, "C": 0.5}
        self.p_t["M2"] = {"M1": 0.5, "C": 0.5}
        self.p_t["C"] = {"C": 1}

        # emissions
        self.emissions = ["A", "C", "G", "T"]

        # emission probabilities
        self.p_e = {iS: {iE: 0.25 for iE in self.emissions}
                    for iS in self.states}
        self.p_e['M1'] = {"A": 0.9, "C": 0.025, "G": 0.025, "T": 0.025}
        self.p_e['M2'] = {"A": 0.025, "C": 0.9, "G": 0.025, "T": 0.025}

    def initialise_HMM_structure(self, l_effective):
        """ Initialise the HMM with all states and standard values for
            transition and emission probabilities.

        Initialise all states
        Set transition probabilities to None/0
        Set initial probability for all states equal.

        Args:
          l_effective (int): The number of match states in the HMM.

        """

        # Build a HMM from these likelihoods.
        self.match_states = ["M{0}".format(str(i + 1)) for i in range(l_effective)]
        self.insertion_states = ["I{0}".format(str(i + 1)) for i in range(l_effective)]
        self.deletion_states = ["D{0}".format(str(i + 1)) for i in range(l_effective)]
        self.terminal_states = ["N", "C"]
        self.states = [self.terminal_states[0]] + self.match_states + \
            self.insertion_states + self.deletion_states + [self.terminal_states[1]]
        LOG.debug("HMM states: %s", self.states)

        # Initialisation
        # The transition probability is initially set to None for all states.
        self.p_t = {iS: {iS2: None for iS2 in self.states}
                    for iS in self.states}
        # The initial probability is equal in all states. (np.log10(1)=0)
        self.p_0 = {iS: 0 for iS in self.states}

        # Transition probabilities

        # First, mark which transitions are not penalised,
        # that is bear a 'probability of np.log10(1)=0':
        # - Transitions from "N" to any "M_k"
        # - Transitions from any "M_k" to "C"
        # - Transitions from "M_k" to "M_k+1" (for k`n) and "M_k" to "M_0" (for k==n)
        # - Transitions from "N" to "N"
        # - Transitions from "C" to "C"
        for i in range(l_effective):
            self.p_t["N"]["M{0}".format(i % l_effective + 1)] = 0
            self.p_t["M{0}".format(i % l_effective + 1)]["C"] = 0
            self.p_t["M{0}".format(i % l_effective + 1)] \
                    ["M{0}".format((i + 1) % l_effective + 1)] = 0
        self.p_t["N"]["N"] = 0
        self.p_t["C"]["C"] = 0

        self.p_e = {}

    def set_emission_probability_hmmer3(self, state, l_emission_probabilities):
        """ Convert and set emission probabilities from HMMER3 file to class
            attribute.

        Set p_e of states to `l_emission_probabilities` for `state` given
        `self.alphabet`.
        In HMMER3 data, emission probabilities are -ln(p).

        It is e.g.::

            self.alphabet =
            ['A', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'K', 'L', 'M', 'N', 'P', 'Q',
             'R', 'S', 'T', 'V', 'W', 'Y']

        Return log10(p), that is convert between the two. Conversion:
        p_Local = - p_HMM * log10(e)

        Args:
            state (str): The state of the HMM. E.g. the fourth match state 'M4'.
            l_emission_probabilities (list of float): E.g.
                ['3.27687', '2.31397', '3.32252', '3.12746', '2.89175',
                '3.34719', '2.28730', '3.54139', '2.53154', '2.64774',
                '3.75733', '3.23860', '3.57894', '3.26290', '2.66343',
                '2.61544', '2.91770', '3.26739', '5.09378', '3.16816']

        """

        l_emission_probabilities = \
            [-(i) * np.log10(np.exp(1)) for i in l_emission_probabilities]
        self.p_e[state] = \
            {iL: iEP for iL, iEP in \
            zip(self.alphabet, l_emission_probabilities)}

    def set_circle_transition_probability_hmmer3(
            self,
            l_transition_probabilities,
            final_state):
        """ Calculate transition probabilites between states that exist due to
            the circularity of the tandem repeat HMM.

        In HMMER3 models, no transition in circle from the last match state to
        the first match state are included.

        Values need to be found for the following transitions:
        m->m    m->i    m->d    i->m    i->i    d->m    d->d

        Compare ftp://selab.janelia.org/pub/software/hmmer3/3.0/Userguide.pdf
        pp.84-85.

        Input: -ln(p) Output: Also -ln(p')

        When the HMM only has a single match state, i.e. l_effective == 1, the
        final match state equals the only match state, and no transition
        probabilities can be calculated via averaging over all other states.
        Therefore, the its transition probabilities remain unchanged.

        Args:
          l_transition_probabilities (list of float): The first parameter.
          final_state (str): The second parameter.

        Returns:
          list of float: transition probabilities

        """

        if self.l_effective == 1:
            return l_transition_probabilities

        # In self.hmmer, besides the probabilities for states 1, 2, 3, ... data
        # for "COMPO", 'letters', and 'id' are saved and should be ignored in
        # the following.
        # Also, we ignore the transition probabilities for final_state, as our
        # goal here is to recalculate these based on the averages of the
        # transition probabilities from all other states.
        skip_states = ['COMPO', 'letters', final_state, 'id']

        # M->M, M->I, M->D: Use an average from all other transitions from
        # match states.
        LOG.debug("self.hmmer.keys(): %s", self.hmmer.items())
        LOG.debug("self.hmmer.items(): %s", self.hmmer.items())
        mean_M_M = np.mean([np.exp(-iP["transition"][0])
                            for i_state, iP in self.hmmer.items() if i_state not in skip_states])
        mean_M_I = np.mean([np.exp(-iP["transition"][1])
                            for i_state, iP in self.hmmer.items() if i_state not in skip_states])
        mean_M_D = np.mean([np.exp(-iP["transition"][2])
                            for i_state, iP in self.hmmer.items() if i_state not in skip_states])

        sum_p = np.sum([mean_M_M, mean_M_I, mean_M_D])
        l_transition_probabilities[
            :3] = [-np.log(i / sum_p) for i in [mean_M_M, mean_M_I, mean_M_D]]

        # I->M and I->I: Use an average from all other transitions from
        # insertion states.
        mean_I_M = np.mean([np.exp(-iP["transition"][3])
                            for i_state, iP in self.hmmer.items() if i_state not in skip_states])
        mean_I_I = np.mean([np.exp(-iP["transition"][4])
                            for i_state, iP in self.hmmer.items() if i_state not in skip_states])
        sum_p = np.sum([mean_I_M, mean_I_I])
        l_transition_probabilities[
            3:5] = [-np.log(i / sum_p) for i in [mean_I_M, mean_I_I]]

        # D->M and D->D: Use an average from all other transitions from
        # deletion states.
        mean_D_M = np.mean([np.exp(-iP["transition"][5])
                            for i_state, iP in self.hmmer.items() if i_state not in skip_states])
        mean_D_D = np.mean([np.exp(-iP["transition"][6])
                            for i_state, iP in self.hmmer.items() if i_state not in skip_states])
        sum_p = np.sum([mean_D_M, mean_D_D])
        l_transition_probabilities[
            5:] = [-np.log(i / sum_p) for i in [mean_D_M, mean_D_D]]

        return l_transition_probabilities

    def set_transition_probability_hmmer3(
            self,
            state_index,
            l_transition_probabilities):
        """ Convert and assign transition probabilities from a HMMER3 file to
            HMM attribute `p_t`.


        Convert and assign transition probabilities from a HMMER3 file to
        HMM attribute `p_t`.
        Set ``p_t`` of states to ``l_transition_probabilities`` for the
        ``state_index`` th state.

        In HMMER3 data, transition probabilities are -ln(p).
        Return log10(p), i.d. convert between the two.
        Conversion: p_Local = - p_HMM * log10(e)

        Args:
            state_index (int): E.g. 4
            l_transition_probabilities (list of float): A list of transition
                probabilities as found in a single row for a certain state in
                HMMER3 files. E.g. ['0.00021', '8.85487', '9.57722', '0.61958',
                '0.77255', '0.48576', '0.95510']
        """

        l_transition_probabilities = [-(i) * np.log10(np.exp(1)) for i \
                                      in l_transition_probabilities]

        # Match state state_index -> Match state state_index + 1
        iM = self.match_states[state_index]
        iM_1 = self.match_states[(state_index + 1) % self.l_effective]
        self.p_t[iM][iM_1] = l_transition_probabilities[0]

        # Match state state_index -> Insertion state state_index
        self.p_t[iM][
            self.insertion_states[state_index]] = l_transition_probabilities[1]

        # Match state state_index -> Deletion state state_index + 1
        iD_1 = self.deletion_states[(state_index + 1) % self.l_effective]
        self.p_t[iM][iD_1] = l_transition_probabilities[2]

        # Insertion state state_index ->  Match state state_index + 1
        iI = self.insertion_states[state_index]
        self.p_t[iI][iM_1] = l_transition_probabilities[3]

        # Insertion state state_index ->  Insertion state state_index
        iI = self.insertion_states[state_index]
        self.p_t[iI][iI] = l_transition_probabilities[4]

        # Deletion state state_index -> Match state state_index + 1
        iD = self.deletion_states[state_index]
        self.p_t[iD][iM_1] = l_transition_probabilities[5]

        # Deletion state state_index -> Deletion state state_index + 1
        self.p_t[iD][iD_1] = l_transition_probabilities[6]

    def get_direct_transition_probabilities_for_deletions(
            self,
            state_index,
            state):
        """ Calculate updated transition probabilities for HMMs without
            deletion states.

        Calculate updated transition probabilities for HMMs without deletion
        states. Deletion states are non-emitting states. Therefore, they can be
        merged into the remaining emitting states. E.g. M -> D -> I would be
        equivalent to M -> I. The total transition probability from M to I
        would then be the product of the transition probabilities from M to D
        and from D to I (equivalent to the sum of the log10 probabilities.).

        This method translates all deletions following `state` with
        `state_index` into direct match state to match state transitions, and
        returns the new transition probabilities.

        Args:
            state_index (int): The first parameter.
            state (str): The second parameter.

        Returns:
            {state1: float}: The transition probability from `state` with
            `state_index` to `state1` is calculated for all (?) possible
            `state1`.
        """

        transition = {}
        p_deletion_state = self.p_t[state][
            self.deletion_states[
                (state_index + 1) %
                self.l_effective]]
        for i_state_index in range(1, self.l_effective):
            i_deleted_state_index = (state_index + i_state_index) % self.l_effective
            i_deleted_state = self.deletion_states[i_deleted_state_index]
            i_goal_state_index = (state_index + i_state_index + 1) % self.l_effective
            i_goal_state = self.match_states[i_goal_state_index]
            transition[i_goal_state] = p_deletion_state + \
                self.p_t[i_deleted_state][i_goal_state]
            p_deletion_state += self.p_t[i_deleted_state][
                self.deletion_states[i_goal_state_index]]

        return transition


############################### External parameters ######################

def hmmer3_emission_probabilities(hmmer_probabilities, letters, l_match):
    '''
    Get emission probabilities from hmmer3 hmm file.
    In hmm file, emission probabilities are -ln(p).
    Return log10(p), i.d. convert between the two.
    Conversion: p_Local = - p_HMM * log10(e)

    Parameters (e.g.)::

        letters = ['A', 'C', 'E', 'D', 'G', 'F', 'I', 'H', 'K', 'M', 'L', 'N',
                   'Q', 'P', 'S', 'R', 'T', 'W', 'V', 'Y']
        l_match = ['M'+str(i) for i in range(24)]

    Return format (pseudo code):
    [{iA: np.log10(p(iA,iM)) for iA in alphabet.keys()} for iM in l_match]

    '''

    # Test: Is the number of match states in both models equal?
    if not len(hmmer_probabilities.keys()) - 2 == len(l_match):
        print('Match states HMMER: {0} Match states local: {1}'.format(
            len(hmmer_probabilities.keys()) - 2, len(l_match)))
        raise ValueError('The number of match states in HMMer model and local'
                         'model does not match')

    # Test: Are all `letters` represented in the HMMER HMM?
    if any(iL not in hmmer_probabilities['letters'] for iL in letters):
        missing = [
            iL for iL in letters if iL not in hmmer_probabilities['letters']]
        print('Missing representation in Hmmer File: {0}'.format(missing))
        raise ValueError('Some letters in the local HMM are not represented',
                         'in the HMMER HMM.')

    return [{iL: -(iP) * np.log10(np.exp(1)) for iL,
             iP in zip(hmmer_probabilities['letters'],
                       data['emissions']) if iL in letters} for key,
            data in hmmer_probabilities.items() if key not in ['letters',
                                                               'COMPO']]
