code: def eval_nodes(self, memo=None):
      if memo is None:
          memo = {}
      else:
          memo = dict(memo)
  
      # TODO: optimize dfs to not recurse past the items in memo
      #       this is especially important for evaluating Lambdas
      #       which cause rec_eval to recurse
      #
      # N.B. that Lambdas may expand the graph during the evaluation
      #      so that this iteration may be an incomplete
      if self.memo_gc:
          clients = self.clients = {}
          for aa in pyll.dfs(self.expr):
              clients.setdefault(aa, set())
              for ii in aa.inputs():
                  clients.setdefault(ii, set()).add(aa)
  
      todo = deque([self.expr])
      while todo:
          if len(todo) > self.max_program_len:
              raise RuntimeError("Probably infinite loop in document")
          node = todo.pop()
  
          if node in memo:
              # -- we've already computed this, move on.
              continue
  
          # -- different kinds of nodes are treated differently:
          if node.name == "switch":
              waiting_on = self.on_switch(memo, node)
              if waiting_on is None:
                  continue
          elif isinstance(node, pyll.Literal):
              # -- constants go straight into the memo
              self.set_in_memo(memo, node, node.obj)
              continue
          else:
              # -- normal instruction-type nodes have inputs
              waiting_on = [v for v in node.inputs() if v not in memo]
  
          if waiting_on:
              # -- Necessary inputs have yet to be evaluated.
              #    push the node back in the queue, along with the
              #    inputs it still needs
              todo.append(node)
              todo.extend(waiting_on)
          else:
              rval = self.on_node(memo, node)
              if isinstance(rval, pyll.Apply):
                  # -- if an instruction returns a Pyll apply node
                  # it means evaluate that too. Lambdas do this.
                  #
                  # XXX: consider if it is desirable, efficient, buggy
                  #      etc. to keep using the same memo dictionary.
                  #      I think it is OK because by using the same
                  #      dictionary all of the nodes are stored in the memo
                  #      so all keys are preserved until the entire outer
                  #      function returns
                  evaluator = self.__class__(
                      rval,
                      self.deep_copy_inputs,
                      self.max_program_len,
                      self.memo_gc,
                  )
                  foo = evaluator(memo)
                  self.set_in_memo(memo, node, foo)
              else:
                  self.set_in_memo(memo, node, rval)
      return memo
  
file_path: auptimizer/src/aup/Proposer/hyperopt/algobase.py
imports: from __future__ import print_function
  from builtins import object
  import copy
  from collections import deque
  import numpy
  from .None import pyll
  from .base import miscs_update_idxs_vals
label_desc: Use image node_id 2 for calling the ExprEvaluator obj's underlying member method code with example usage: obj.eval_nodes(memo) and returns: memo