class_docstr: Nested sampler with reactive exploration strategy.
  
  Storage & resume capable, optionally MPI parallelised.
class_docstr_tok: ["Nested","sampler","with","reactive","exploration","strategy",".","Storage","&","resume","capable",",","optionally","MPI","parallelised","."]
docstr: Iterate towards convergence.
  
  Use as an iterator like so::
  
      for result in sampler.run_iter(...):
          print('lnZ = %(logz).2f +- %(logzerr).2f' % result)
  
  Parameters as described in run() method.
  
  Yields
  ------
  results: dict
docstr_tok: ["Iterate","towards","convergence",".","Use","as","an","iterator","like","so",":",":","for","result","in","sampler.run_iter","(","...",")",":","print","(","'lnZ","=","%","(","logz",")",".2f","+-","%","(","logzerr",")",".2f","'","%","result",")","Parameters","as","described","in","run","(",")","method",".","Yields","--","--","--","results",":","dict"]
code: def run_iter(
      self,
      update_interval_volume_fraction=0.8,
      update_interval_ncall=None,
      log_interval=None,
      dlogz=0.5,
      dKL=0.5,
      frac_remain=0.01,
      Lepsilon=0.001,
      min_ess=400,
      max_iters=None,
      max_ncalls=None,
      max_num_improvement_loops=-1,
      min_num_live_points=400,
      cluster_num_live_points=40,
      show_status=True,
      viz_callback="auto",
      insertion_test_window=10000,
      insertion_test_zscore_threshold=2,
      region_class=MLFriends,
      widen_before_initial_plateau_num_warn=10000,
      widen_before_initial_plateau_num_max=50000,
  ):
      """Iterate towards convergence.
  
      Use as an iterator like so::
  
          for result in sampler.run_iter(...):
              print('lnZ = %(logz).2f +- %(logzerr).2f' % result)
  
      Parameters as described in run() method.
  
      Yields
      ------
      results: dict
      """
      # frac_remain=1  means 1:1 -> dlogz=log(0.5)
      # frac_remain=0.1 means 1:10 -> dlogz=log(0.1)
      # dlogz_min = log(1./(1 + frac_remain))
      # dlogz_min = -log1p(frac_remain)
      if -np.log1p(frac_remain) > dlogz:
          raise ValueError(
              "To achieve the desired logz accuracy, set frac_remain to a value much smaller than %s (currently: %s)"
              % (exp(-dlogz) - 1, frac_remain)
          )
  
      # the error is approximately dlogz = sqrt(iterations) / Nlive
      # so we need a minimum, which depends on the number of iterations
      # fewer than 1000 iterations is quite unlikely
      if min_num_live_points < 1000**0.5 / dlogz:
          min_num_live_points = int(np.ceil(1000**0.5 / dlogz))
          if self.log:
              self.logger.info(
                  "To achieve the desired logz accuracy, min_num_live_points was increased to %d"
                  % (min_num_live_points)
              )
  
      if self.log_to_pointstore:
          if len(self.pointstore.stack) > 0:
              self.logger.info(
                  "Resuming from %d stored points",
                  len(self.pointstore.stack),
              )
          self.use_point_stack = not self.pointstore.stack_empty
      else:
          self.use_point_stack = False
  
      assert min_num_live_points >= cluster_num_live_points, (
          "min_num_live_points(%d) cannot be less than cluster_num_live_points(%d)"
          % (min_num_live_points, cluster_num_live_points)
      )
      self.min_num_live_points = min_num_live_points
      self.cluster_num_live_points = cluster_num_live_points
      self.sampling_slow_warned = False
      self.build_tregion = True
      self.region_class = region_class
      update_interval_volume_log_fraction = log(
          update_interval_volume_fraction
      )
  
      if viz_callback == "auto":
          viz_callback = get_default_viz_callback()
  
      self._widen_roots_beyond_initial_plateau(
          min_num_live_points,
          widen_before_initial_plateau_num_warn,
          widen_before_initial_plateau_num_max,
      )
  
      Llo, Lhi = -np.inf, np.inf
      Lmax = -np.inf
      strategy_stale = True
      minimal_widths = []
      target_min_num_children = {}
      improvement_it = 0
  
      assert max_iters is None or max_iters > 0, (
          "Invalid value for max_iters: %s. Set to None or positive number"
          % max_iters
      )
      assert max_ncalls is None or max_ncalls > 0, (
          "Invalid value for max_ncalls: %s. Set to None or positive number"
          % max_ncalls
      )
  
      if self.log:
          self.logger.debug(
              "run_iter dlogz=%.1f, dKL=%.1f, frac_remain=%.2f, Lepsilon=%.4f, min_ess=%d"
              % (dlogz, dKL, frac_remain, Lepsilon, min_ess)
          )
          self.logger.debug(
              "max_iters=%d, max_ncalls=%d, max_num_improvement_loops=%d, min_num_live_points=%d, cluster_num_live_points=%d"
              % (
                  max_iters if max_iters else -1,
                  max_ncalls if max_ncalls else -1,
                  max_num_improvement_loops,
                  min_num_live_points,
                  cluster_num_live_points,
              )
          )
  
      self.results = None
  
      while True:
          roots = self.root.children
  
          nroots = len(roots)
  
          if update_interval_ncall is None:
              update_interval_ncall = nroots
  
          if log_interval is None:
              log_interval = max(1, round(0.1 * nroots))
          else:
              log_interval = round(log_interval)
              if log_interval < 1:
                  raise ValueError("log_interval must be >= 1")
  
          explorer = BreadthFirstIterator(roots)
          # Integrating thing
          main_iterator = MultiCounter(
              nroots=len(roots),
              nbootstraps=max(1, self.num_bootstraps // self.mpi_size),
              random=False,
              check_insertion_order=False,
          )
          main_iterator.Lmax = max(Lmax, max(n.value for n in roots))
          insertion_test = UniformOrderAccumulator()
          insertion_test_runs = []
          insertion_test_quality = np.inf
          insertion_test_direction = 0
  
          self.transformLayer = None
          self.region = None
          self.tregion = None
          self.live_points_healthy = True
          it_at_first_region = 0
          self.ib = 0
          self.samples = []
          if self.draw_multiple:
              ndraw = self.ndraw_min
          else:
              ndraw = 40
          self.pointstore.reset()
          if self.log_to_pointstore:
              self.use_point_stack = not self.pointstore.stack_empty
          else:
              self.use_point_stack = False
          if self.use_mpi:
              self.use_point_stack = self.comm.bcast(
                  self.use_point_stack, root=0
              )
  
          if self.log and (np.isfinite(Llo) or np.isfinite(Lhi)):
              self.logger.info(
                  "Exploring (in particular: L=%.2f..%.2f) ...",
                  Llo,
                  Lhi,
              )
          region_sequence = []
          minimal_widths_sequence = _sequentialize_width_sequence(
              minimal_widths, self.min_num_live_points
          )
          if self.log:
              self.logger.debug(
                  "minimal_widths_sequence: %s", minimal_widths_sequence
              )
  
          saved_nodeids = []
          saved_logl = []
          it = 0
          ncall_at_run_start = self.ncall
          ncall_region_at_run_start = self.ncall_region
          next_update_interval_volume = 1
          last_status = time.time()
  
          # we go through each live point (regardless of root) by likelihood value
          while True:
              next_node = explorer.next_node()
              if next_node is None:
                  break
              (
                  rootid,
                  node,
                  (_, active_rootids, active_values, active_node_ids),
              ) = next_node
              assert not isinstance(rootid, float)
              # this is the likelihood level we have to improve upon
              self.Lmin = Lmin = node.value
  
              # if within suggested range, expand
              if (
                  strategy_stale
                  or not (Lmin <= Lhi)
                  or not np.isfinite(Lhi)
                  or (active_values == Lmin).all()
              ):
                  # check with advisor if we want to expand this node
                  Llo, Lhi = self._adaptive_strategy_advice(
                      Lmin,
                      active_values,
                      main_iterator,
                      minimal_widths,
                      frac_remain,
                      Lepsilon=Lepsilon,
                  )
                  # when we are going to the peak, numerical accuracy
                  # can become an issue. We should try not to get stuck there
                  strategy_stale = Lhi - Llo < max(Lepsilon, 0.01)
  
              expand_node = self._should_node_be_expanded(
                  it,
                  Llo,
                  Lhi,
                  minimal_widths_sequence,
                  target_min_num_children,
                  node,
                  active_values,
                  max_ncalls,
                  max_iters,
                  self.live_points_healthy,
              )
  
              region_fresh = False
              if expand_node:
                  # sample a new point above Lmin
                  active_u = self.pointpile.getu(active_node_ids)
                  active_p = self.pointpile.getp(active_node_ids)
                  nlive = len(active_u)
                  # first we check that the region is up-to-date
                  if (
                      main_iterator.logVolremaining
                      < next_update_interval_volume
                  ):
                      if self.region is None:
                          it_at_first_region = it
                      region_fresh = self._update_region(
                          active_u=active_u,
                          active_p=active_p,
                          active_node_ids=active_node_ids,
                          active_rootids=active_rootids,
                          bootstrap_rootids=main_iterator.rootids[1:,],
                          nbootstraps=self.num_bootstraps,
                          minvol=exp(main_iterator.logVolremaining),
                      )
  
                      if region_fresh and self.stepsampler is not None:
                          self.stepsampler.region_changed(
                              active_values, self.region
                          )
  
                      _, cluster_sizes = np.unique(
                          self.region.transformLayer.clusterids,
                          return_counts=True,
                      )
                      nclusters = (cluster_sizes > 1).sum()
                      region_sequence.append(
                          (
                              Lmin,
                              nlive,
                              nclusters,
                              np.max(active_values),
                          )
                      )
  
                      # next_update_interval_ncall = self.ncall + (update_interval_ncall or nlive)
                      next_update_interval_volume = (
                          main_iterator.logVolremaining
                          + update_interval_volume_log_fraction
                      )
  
                      # provide nice output to follow what is going on
                      # but skip if we are resuming
                      #  and (self.ncall != ncall_at_run_start and it_at_first_region == it)
                      if self.log and viz_callback:
                          viz_callback(
                              points=dict(
                                  u=active_u,
                                  p=active_p,
                                  logl=active_values,
                              ),
                              info=dict(
                                  it=it,
                                  ncall=self.ncall,
                                  logz=main_iterator.logZ,
                                  logz_remain=main_iterator.logZremain,
                                  logvol=main_iterator.logVolremaining,
                                  paramnames=self.paramnames
                                  + self.derivedparamnames,
                                  paramlims=self.transform_limits,
                                  order_test_correlation=insertion_test_quality,
                                  order_test_direction=insertion_test_direction,
                              ),
                              region=self.region,
                              transformLayer=self.transformLayer,
                              region_fresh=region_fresh,
                          )
                      if self.log:
                          self.pointstore.flush()
  
                  if (
                      nlive < cluster_num_live_points * nclusters
                      and improvement_it < max_num_improvement_loops
                  ):
                      # make wider here
                      if self.log:
                          self.logger.info(
                              "Found %d clusters, but only have %d live points, want %d.",
                              self.region.transformLayer.nclusters,
                              nlive,
                              cluster_num_live_points * nclusters,
                          )
                      break
  
                  # sample point
                  u, p, L = self._create_point(
                      Lmin=Lmin,
                      ndraw=ndraw,
                      active_u=active_u,
                      active_values=active_values,
                  )
                  child = self.pointpile.make_node(L, u, p)
                  main_iterator.Lmax = max(main_iterator.Lmax, L)
                  if (
                      np.isfinite(insertion_test_zscore_threshold)
                      and nlive > 1
                  ):
                      insertion_test.add(
                          (active_values < L).sum(), nlive
                      )
                      if (
                          abs(insertion_test.zscore)
                          > insertion_test_zscore_threshold
                      ):
                          insertion_test_runs.append(insertion_test.N)
                          insertion_test_quality = insertion_test.N
                          insertion_test_direction = np.sign(
                              insertion_test.zscore
                          )
                          insertion_test.reset()
                      elif insertion_test.N > insertion_test_window:
                          insertion_test_quality = np.inf
                          insertion_test_direction = 0
                          insertion_test.reset()
  
                  # identify which point is being replaced (from when we built the region)
                  worst = np.where(self.region_nodes == node.id)[0]
                  self.region_nodes[worst] = child.id
                  # if we keep the region informed about the new live points
                  # then the region follows the live points even if maxradius is not updated
                  self.region.u[worst] = u
                  self.region.unormed[
                      worst
                  ] = self.region.transformLayer.transform(u)
                  # move also the ellipsoid
                  self.region.ellipsoid_center = np.mean(
                      self.region.u, axis=0
                  )
                  if self.tregion:
                      self.tregion.update_center(
                          np.mean(active_p, axis=0)
                      )
  
                  # if we track the cluster assignment, then in the next round
                  # the ids with the same members are likely to have the same id
                  # this is imperfect
                  # transformLayer.clusterids[worst] = transformLayer.clusterids[father[ib]]
                  # so we just mark the replaced ones as "unassigned"
                  self.transformLayer.clusterids[worst] = 0
  
                  node.children.append(child)
  
                  if self.log and (
                      region_fresh
                      or it % log_interval == 0
                      or time.time() > last_status + 0.1
                  ):
                      last_status = time.time()
                      # the number of proposals asked from region
                      ncall_region_here = (
                          self.ncall_region - ncall_region_at_run_start
                      )
                      # the number of proposals returned by the region
                      ncall_here = self.ncall - ncall_at_run_start
                      # the number of likelihood evaluations above threshold
                      it_here = it - it_at_first_region
  
                      if show_status:
                          if Lmin < -1e8:
                              txt = "Z=%.1g(%.2f%%) | Like=%.2g..%.2g [%.4g..%.4g]%s| it/evals=%d/%d eff=%.4f%% N=%d \r"
                          elif Llo < -1e8:
                              txt = "Z=%.1f(%.2f%%) | Like=%.2f..%.2f [%.4g..%.4g]%s| it/evals=%d/%d eff=%.4f%% N=%d \r"
                          else:
                              txt = "Z=%.1f(%.2f%%) | Like=%.2f..%.2f [%.4f..%.4f]%s| it/evals=%d/%d eff=%.4f%% N=%d \r"
                          sys.stdout.write(
                              txt
                              % (
                                  main_iterator.logZ,
                                  100
                                  * (
                                      1
                                      - main_iterator.remainder_fraction
                                  ),
                                  Lmin,
                                  main_iterator.Lmax,
                                  Llo,
                                  Lhi,
                                  "*" if strategy_stale else " ",
                                  it,
                                  self.ncall,
                                  np.inf
                                  if ncall_here == 0
                                  else it_here * 100 / ncall_here,
                                  nlive,
                              )
                          )
                          sys.stdout.flush()
                      self.logger.debug(
                          "iteration=%d, ncalls=%d, regioncalls=%d, ndraw=%d, logz=%.2f, remainder_fraction=%.4f%%, Lmin=%.2f, Lmax=%.2f"
                          % (
                              it,
                              self.ncall,
                              self.ncall_region,
                              ndraw,
                              main_iterator.logZ,
                              100 * main_iterator.remainder_fraction,
                              Lmin,
                              main_iterator.Lmax,
                          )
                      )
  
                      # if efficiency becomes low, bulk-process larger arrays
                      if self.draw_multiple:
                          # inefficiency is the number of (region) proposals per successful number of iterations
                          # but improves by parallelism (because we need only the per-process inefficiency)
                          # sampling_inefficiency = (self.ncall - ncall_at_run_start + 1) / (it + 1) / self.mpi_size
                          sampling_inefficiency = (
                              (ncall_region_here + 1)
                              / (it_here + 1)
                              / self.mpi_size
                          )
  
                          # smooth update:
                          ndraw_next = (
                              0.04 * sampling_inefficiency
                              + ndraw * 0.96
                          )
                          ndraw = max(
                              self.ndraw_min,
                              min(
                                  self.ndraw_max,
                                  round(ndraw_next),
                                  ndraw * 100,
                              ),
                          )
  
                          if (
                              sampling_inefficiency > 100000
                              and it >= it_at_first_region + 10
                          ):
                              # if the efficiency is poor, there are enough samples in each iteration
                              # to estimate the inefficiency
                              ncall_at_run_start = self.ncall
                              it_at_first_region = it
                              ncall_region_at_run_start = (
                                  self.ncall_region
                              )
  
              else:
                  # we do not want to count iterations without work
                  # otherwise efficiency becomes > 1
                  it_at_first_region += 1
  
              saved_nodeids.append(node.id)
              saved_logl.append(Lmin)
  
              # inform iterators (if it is their business) about the arc
              main_iterator.passing_node(
                  rootid, node, active_rootids, active_values
              )
              if len(node.children) == 0 and self.region is not None:
                  # the region radius needs to increase if nlive decreases
                  # radius is not reliable, so set to inf
                  # (heuristics do not work in practice)
                  self.region.maxradiussq = None
                  # ask for the region to be rebuilt
                  next_update_interval_volume = 1
  
              it += 1
              explorer.expand_children_of(rootid, node)
  
          if self.log:
              self.logger.info("Explored until L=%.1g  ", node.value)
          # print_tree(roots[::10])
  
          self.pointstore.flush()
          self._update_results(main_iterator, saved_logl, saved_nodeids)
          yield self.results
  
          if max_ncalls is not None and self.ncall >= max_ncalls:
              if self.log:
                  self.logger.info(
                      "Reached maximum number of likelihood calls (%d > %d)...",
                      self.ncall,
                      max_ncalls,
                  )
              break
  
          improvement_it += 1
          if (
              max_num_improvement_loops >= 0
              and improvement_it > max_num_improvement_loops
          ):
              if self.log:
                  self.logger.info(
                      "Reached maximum number of improvement loops."
                  )
              break
  
          if ncall_at_run_start == self.ncall and improvement_it > 1:
              if self.log:
                  self.logger.info(
                      "No changes made. "
                      "Probably the strategy was to explore in the remainder, "
                      "but it is irrelevant already; try decreasing frac_remain."
                  )
              break
  
          Lmax = main_iterator.Lmax
          if len(region_sequence) > 0:
              Lmin, nlive, nclusters, Lhi = region_sequence[-1]
              nnodes_needed = cluster_num_live_points * nclusters
              if nlive < nnodes_needed:
                  (
                      Llo,
                      _,
                      target_min_num_children_new,
                  ) = self._expand_nodes_before(
                      Lmin,
                      nnodes_needed,
                      update_interval_ncall or nlive,
                  )
                  target_min_num_children.update(
                      target_min_num_children_new
                  )
                  # if self.log:
                  #     print_tree(self.root.children[::10])
                  minimal_widths.append((Llo, Lhi, nnodes_needed))
                  Llo, Lhi = -np.inf, np.inf
                  continue
  
          if self.log:
              # self.logger.info('  logZ = %.4f +- %.4f (main)' % (main_iterator.logZ, main_iterator.logZerr))
              self.logger.info(
                  "  logZ = %.4g +- %.4g",
                  main_iterator.logZ_bs,
                  main_iterator.logZerr_bs,
              )
  
          saved_logl = np.asarray(saved_logl)
          # reactive nested sampling: see where we have to improve
          (
              dlogz_min_num_live_points,
              (Llo_KL, Lhi_KL),
              (Llo_ess, Lhi_ess),
          ) = self._find_strategy(
              saved_logl,
              main_iterator,
              dlogz=dlogz,
              dKL=dKL,
              min_ess=min_ess,
          )
          Llo = min(Llo_ess, Llo_KL)
          Lhi = max(Lhi_ess, Lhi_KL)
          # to avoid numerical issues when all likelihood values are the same
          Lhi = min(Lhi, saved_logl.max() - 0.001)
  
          if self.use_mpi:
              recv_Llo = self.comm.gather(Llo, root=0)
              recv_Llo = self.comm.bcast(recv_Llo, root=0)
              recv_Lhi = self.comm.gather(Lhi, root=0)
              recv_Lhi = self.comm.bcast(recv_Lhi, root=0)
              recv_dlogz_min_num_live_points = self.comm.gather(
                  dlogz_min_num_live_points, root=0
              )
              recv_dlogz_min_num_live_points = self.comm.bcast(
                  recv_dlogz_min_num_live_points, root=0
              )
  
              Llo = min(recv_Llo)
              Lhi = max(recv_Lhi)
              dlogz_min_num_live_points = max(
                  recv_dlogz_min_num_live_points
              )
  
          if dlogz_min_num_live_points > self.min_num_live_points:
              # more live points needed throughout to reach target
              self.min_num_live_points = dlogz_min_num_live_points
              self._widen_roots_beyond_initial_plateau(
                  self.min_num_live_points,
                  widen_before_initial_plateau_num_warn,
                  widen_before_initial_plateau_num_max,
              )
  
          elif Llo <= Lhi:
              # if self.log:
              #     print_tree(roots, title="Tree before forking:")
              parents, parent_weights = find_nodes_before(
                  self.root, Llo
              )
              # double the width / live points:
              _, width = count_tree_between(
                  self.root.children, Llo, Lhi
              )
              nnodes_needed = width * 2
              if self.log:
                  self.logger.info(
                      "Widening from %d to %d live points before L=%.1g...",
                      len(parents),
                      nnodes_needed,
                      Llo,
                  )
  
              if len(parents) == 0:
                  Llo = -np.inf
              else:
                  Llo = min(n.value for n in parents)
              self.pointstore.reset()
              target_min_num_children.update(
                  self._widen_nodes(
                      parents,
                      parent_weights,
                      nnodes_needed,
                      update_interval_ncall,
                  )
              )
              minimal_widths.append((Llo, Lhi, nnodes_needed))
              # if self.log:
              #     print_tree(roots, title="Tree after forking:")
              # print('tree size:', count_tree(roots))
          else:
              break
  
imports: from __future__ import print_function, division
  import os
  import sys
  import csv
  import json
  import operator
  import time
  import warnings
  from numpy import log, exp, logaddexp
  import numpy
  from .utils import create_logger, make_run_dir, resample_equal, vol_prefactor, vectorize, listify
  from .utils import is_affine_transform, normalised_kendall_tau_distance, distributed_work_chunk_size
  from ultranest.mlfriends import MLFriends, AffineLayer, ScalingLayer, find_nearby, WrappingEllipsoid, RobustEllipsoidRegion
  from .store import HDF5PointStore, TextPointStore, NullPointStore
  from .viz import get_default_viz_callback
  from .ordertest import UniformOrderAccumulator
  from .netiter import PointPile, SingleCounter, MultiCounter, BreadthFirstIterator, TreeNode, count_tree_between, find_nodes_before, logz_sequence
  from .netiter import dump_tree, combine_results
  from .hotstart import get_auxiliary_contbox_parameterization
label_desc: Use image node_id 16 for calling the ReactiveNestedSampler obj's underlying member method code with example usage: obj.run_iter(update_interval_volume_fraction, update_interval_ncall, log_interval, dlogz, dKL, frac_remain, Lepsilon, min_ess, max_iters, max_ncalls, max_num_improvement_loops, min_num_live_points, cluster_num_live_points, show_status, viz_callback, insertion_test_window, insertion_test_zscore_threshold, region_class, widen_before_initial_plateau_num_warn, widen_before_initial_plateau_num_max) without return types