class_docstr: Based on
  https://github.com/pytorch/examples/blob/master/word_language_model/model.py
  
  TODO(brendan): RL controllers do not necessarily have much to do with
  language models.
  
  Base the controller RNN on the GRU from:
  https://github.com/ikostrikov/pytorch-a2c-ppo-acktr/blob/master/model.py
docstr: Samples a set of `args.num_blocks` many computational nodes from the
  controller, where each node is made up of an activation function, and
  each node except the last also includes a previous node.
docstr_tok: ["Samples","a","set","of","`","args.num_blocks","`","many","computational","nodes","from","the","controller",",","where","each","node","is","made","up","of","an","activation","function",",","and","each","node","except","the","last","also","includes","a","previous","node","."]
code: def sample(self, batch_size=1, with_details=False, save_dir=None):
      """Samples a set of `args.num_blocks` many computational nodes from the
      controller, where each node is made up of an activation function, and
      each node except the last also includes a previous node.
      """
      if batch_size < 1:
          raise Exception(f"Wrong batch_size: {batch_size} < 1")
  
      # [B, L, H]
      inputs = self.static_inputs[batch_size]
      hidden = self.static_init_hidden[batch_size]
  
      activations = []
      entropies = []
      log_probs = []
      prev_nodes = []
      # NOTE(brendan): The RNN controller alternately outputs an activation,
      # followed by a previous node, for each block except the last one,
      # which only gets an activation function. The last node is the output
      # node, and its previous node is the average of all leaf nodes.
      for block_idx in range(2 * (self.args.num_blocks - 1) + 1):
          logits, hidden = self.forward(
              inputs, hidden, block_idx, is_embed=(block_idx == 0)
          )
  
          probs = F.softmax(logits, dim=-1)
          log_prob = F.log_softmax(logits, dim=-1)
          # TODO(brendan): .mean() for entropy?
          entropy = -(log_prob * probs).sum(1, keepdim=False)
  
          action = probs.multinomial(num_samples=1).data
          selected_log_prob = log_prob.gather(
              1, utils.get_variable(action, requires_grad=False)
          )
  
          # TODO(brendan): why the [:, 0] here? Should it be .squeeze(), or
          # .view()? Same below with `action`.
          entropies.append(entropy)
          log_probs.append(selected_log_prob[:, 0])
  
          # 0: function, 1: previous node
          mode = block_idx % 2
          inputs = utils.get_variable(
              action[:, 0] + sum(self.num_tokens[:mode]),
              requires_grad=False,
          )
  
          if mode == 0:
              activations.append(action[:, 0])
          elif mode == 1:
              prev_nodes.append(action[:, 0])
  
      prev_nodes = torch.stack(prev_nodes).transpose(0, 1)
      activations = torch.stack(activations).transpose(0, 1)
  
      dags = _construct_dags(
          prev_nodes, activations, self.func_names, self.args.num_blocks
      )
  
      if save_dir is not None:
          for idx, dag in enumerate(dags):
              utils.draw_network(
                  dag, os.path.join(save_dir, f"graph{idx}.png")
              )
  
      if with_details:
          return dags, torch.cat(log_probs), torch.cat(entropies)
  
      return dags
  
file_path: ENAS-pytorch/models/controller.py
imports: import collections
  import os
  import torch
  import torch.nn.functional
  import utils
  from utils import Node
label_desc: Use image node_id 4 for calling the Controller obj's underlying member method code with example usage: obj.sample(batch_size, with_details, save_dir) and returns: dags, dags,  torch,  torch