'Evaluate mixture of experts.
We provide a convenient debugging tool for determining the set of examples
that we passed to each expert.  The caller may provide a `Tensor` of
"identifiers", of any type whose first dimension matches the number of
input examples. The function will then return a list
"expert_to_identifiers", with one `Tensor` for each expert containing the
identifiers for all examples assigned to that expert.  A parallel list of
`Tensor`s, "expert_to_gates", is also returned, containing the
corresponding gate values.
Args:
x: a `Tensor` of shape `[batch_size, input_size]`
train: a boolean Scalar.  Are we in training mode?
per_example_multiplier: an optional `Tensor` of shape `[batch_size]` which
gets multiplied into the gate values.  If this LocalMixtureOfExperts
represents one secondary MoE in a hierarchical MoE, then we pass in
in the gate values from the primary gating function here.  This causes
the computed values (`y`, `importance` and `expert_to_gates`) to also
reflect the primary gate values.
summaries: an boolean.  Enable summaries.
identifiers: an optional `Tensor` whose first dimension is equal to
batch_size.
Returns:
y: a `Tensor` of shape `[batch_size, output_size]`.  Output of the MoE.
importance: a `Tensor` of shape `[n]`.  Batchwise sum of gates.
load: a `Tensor` of shape `[n]`.  Smooth estimator of the number of
examples passed to each expert.  This is useful for load-balancing,
as any gradient on this `Tensor` will back-propagate to the gating
network.
expert_to_identifiers:  if `identifiers` was passed in, a list of
length `num_experts`.  Each element is a `Tensor` whose shape matches
that of `identifiers` in all but the first dimension.  Contains the
slices of `identifiers` corresponding to the batch elements that were
dispatched to that expert.
expert_to_gates:  A list of length `num_experts`.  Each element contains
a 1-dimensional tensor'

def Eval(self, x, train=True, per_example_multiplier=None, summaries=False, identifiers=None):

    gating_hp = self._gating_hp
    (gates, load) = self._gating.Eval(x, train, summaries)
    if (per_example_multiplier is not None):
        gates *= tf.expand_dims(per_example_multiplier, 1)
    dispatcher = SparseDispatcher(gating_hp.num_experts, gates)
    expert_input = dispatcher.Dispatch(x)
    expert_output = [self._experts[i].Eval(expert_input[i]) for i in xrange(gating_hp.num_experts)]
    y = dispatcher.Combine(expert_output)
    if (identifiers is not None):
        expert_to_identifiers = dispatcher.Dispatch(identifiers)
    else:
        expert_to_identifiers = None
    return (y, tf.reduce_sum(gates, 0), load, expert_to_identifiers, dispatcher.ExpertToGates())
