code: def build_posterior(
      specs,
      prior_idxs,
      prior_vals,
      obs_idxs,
      obs_vals,
      obs_loss_idxs,
      obs_loss_vals,
      oloss_gamma,
      prior_weight,
  ):
      """
      This method clones a posterior inference graph by iterating forward in
      topological order, and replacing prior random-variables (prior_idxs, prior_vals)
      with new posterior distributions (post_specs, post_idxs, post_vals) that make use
      of observations (obs_idxs, obs_vals).
  
      """
      assert all(
          isinstance(arg, pyll.Apply)
          for arg in [obs_loss_idxs, obs_loss_vals, oloss_gamma]
      )
      assert set(prior_idxs.keys()) == set(prior_vals.keys())
  
      expr = pyll.as_apply([specs, prior_idxs, prior_vals])
      nodes = pyll.dfs(expr)
  
      # build the joint posterior distribution as the values in this memo
      memo = {}
      # map prior RVs to observations
      obs_memo = {}
  
      for nid in prior_vals:
          # construct the leading args for each call to adaptive_parzen_sampler
          # which will permit the "adaptive parzen samplers" to adapt to the
          # correct samples.
          obs_below, obs_above = scope.ap_split_trials(
              obs_idxs[nid],
              obs_vals[nid],
              obs_loss_idxs,
              obs_loss_vals,
              oloss_gamma,
          )
          obs_memo[prior_vals[nid]] = [obs_below, obs_above]
      for node in nodes:
          if node not in memo:
              new_inputs = [memo[arg] for arg in node.inputs()]
              if node in obs_memo:
                  # -- this case corresponds to an observed Random Var
                  # node.name is a distribution like "normal", "randint", etc.
                  obs_below, obs_above = obs_memo[node]
                  aa = [memo[a] for a in node.pos_args]
                  fn = adaptive_parzen_samplers[node.name]
                  b_args = [obs_below, prior_weight] + aa
                  named_args = {
                      kw: memo[arg] for (kw, arg) in node.named_args
                  }
                  b_post = fn(*b_args, **named_args)
                  a_args = [obs_above, prior_weight] + aa
                  a_post = fn(*a_args, **named_args)
  
                  # fn is a function e.g ap_uniform_sampler, ap_normal_sampler, etc
                  # b_post and a_post are pyll.Apply objects that are
                  # AST (Abstract Syntax Trees).  They create the distribution,
                  # (e.g. using adaptive_parzen_normal), and then
                  # call a function to sample randomly from that distribution
                  # (e.g. using scope.GMM1) which return those samples.
                  #
                  # However we are only interested in using the samples from b_post.
                  # This code looks at the AST and grabs the function name that we used
                  # for sampling (e.g. scope.GMM1)   and modifies it, e.g. to
                  # "scope.GMM1_lpdf". It then calls this function, passing in the
                  # samples as the first parameter.a_args
                  #
                  # The result is that we are effectively calling, for example:
                  # below_llik = GMM1_lpdf( b_post, *adaptive_parzen_normal(obs_below, ...))
                  # above_llik = GMM1_lpdf( b_post, *adaptive_parzen_normal(obs_above, ...))
  
                  assert a_post.name == b_post.name
                  fn_lpdf = getattr(scope, a_post.name + "_lpdf")
                  a_kwargs = {
                      n: a
                      for n, a in a_post.named_args
                      if n not in ("rng", "size")
                  }
                  b_kwargs = {
                      n: a
                      for n, a in b_post.named_args
                      if n not in ("rng", "size")
                  }
  
                  # calculate the log likelihood of b_post under both distributions
                  below_llik = fn_lpdf(
                      *([b_post] + b_post.pos_args), **b_kwargs
                  )
                  above_llik = fn_lpdf(
                      *([b_post] + a_post.pos_args), **a_kwargs
                  )
                  # compute new_node based on below & above log likelihood
                  new_node = scope.broadcast_best(
                      b_post, below_llik, above_llik
                  )
              elif hasattr(node, "obj"):
                  # -- keep same literals in the graph
                  new_node = node
              else:
                  # -- this case is for all the other stuff in the graph
                  new_node = node.clone_from_inputs(new_inputs)
              memo[node] = new_node
  
      post_idxs = {nid: memo[idxs] for nid, idxs in prior_idxs.items()}
      post_vals = {nid: memo[vals] for nid, vals in prior_vals.items()}
      return post_idxs, post_vals
  
imports: import logging
  import time
  import numpy
  from scipy.special import erf
  from .None import pyll
  from .pyll import scope
  from .pyll.stochastic import implicit_stochastic
  from .base import miscs_to_idxs_vals
  from .base import miscs_update_idxs_vals
  from .None import rand
label_desc: Use image node_id 29 for calling a global function with example usage: build_posterior(specs, prior_idxs, prior_vals, obs_idxs, obs_vals, obs_loss_idxs, obs_loss_vals, oloss_gamma, prior_weight) and returns: post_idxs,  post_vals