code: def log_reader(filename, **kwds):
      """
      generate parameter convergence plot from convergence logfile
  
      Available from the command shell as::
  
          mystic_log_reader filename [options]
  
      or as a function call::
  
          mystic.log_reader(filename, **options)
  
      Args:
          filename (str): name of the convergence logfile (e.g ``log.txt``).
  
      Returns:
          None
  
      Notes:
          - The option *out* takes a string of the filepath for the generated plot.
            If ``out = True``, return the Figure object instead of generating a plot.
          - The option *dots* takes a boolean, and will show data points in the plot.
          - The option *line* takes a boolean, and will connect the data with a line.
          - The option *iter* takes an integer of the largest iteration to plot.
          - The option *legend* takes a boolean, and will display the legend.
          - The option *nid* takes an integer of the nth simultaneous points to plot.
          - The option *param* takes an indicator string. The indicator string is
            built from comma-separated array slices. For example, ``param = ":"``
            will plot all parameters.  Alternatively, ``param = ":2, 3:"`` will plot
            all parameters except for the third parameter, while ``param = "0"``
            will only plot the first parameter."""
      import shlex
      from io import StringIO
  
      global __quit
      __quit = False
      _out = False
  
      instance = None
      # handle the special case where list is provided by sys.argv
      if isinstance(filename, (list, tuple)) and not kwds:
          cmdargs = filename  # (above is used by script to parse command line)
      elif isinstance(filename, str) and not kwds:
          cmdargs = shlex.split(filename)
      # 'everything else' is essentially the functional interface
      else:
          cmdargs = kwds.get("kwds", "")
          if not cmdargs:
              out = kwds.get("out", None)
              dots = kwds.get("dots", False)
              line = kwds.get("line", False)
              iter = kwds.get("iter", None)
              legend = kwds.get("legend", False)
              nid = kwds.get("nid", None)
              param = kwds.get("param", None)
  
              if isinstance(out, bool):
                  _out, out = out, None
  
              # process "commandline" arguments
              cmdargs = ""
              cmdargs += "" if out is None else "--out={} ".format(out)
              cmdargs += "" if dots == False else "--dots "
              cmdargs += "" if line == False else "--line "
              cmdargs += (
                  "" if iter is None else "--iter={} ".format(iter)
              )
              cmdargs += "" if legend == False else "--legend "
              cmdargs += "" if nid is None else "--nid={} ".format(nid)
              cmdargs += (
                  "" if param is None else '--param="{}" '.format(param)
              )
          else:
              cmdargs = " " + cmdargs
          if isinstance(filename, str):
              cmdargs = filename.split() + shlex.split(cmdargs)
          else:  # special case of passing in monitor instance
              instance = filename
              cmdargs = shlex.split(cmdargs)
  
      # XXX: replace with 'argparse'?
      from optparse import OptionParser
  
      def _exit(self, errno=None, msg=None):
          global __quit
          __quit = True
          if errno or msg:
              msg = msg.split(": error: ")[-1].strip()
              raise IOError(msg)
  
      OptionParser.exit = _exit
  
      parser = OptionParser(
          usage=log_reader.__doc__.split("\n\nOptions:")[0]
      )
      parser.add_option(
          "-u",
          "--out",
          action="store",
          dest="out",
          metavar="STR",
          default=None,
          help="filepath to save generated plot",
      )
      parser.add_option(
          "-d",
          "--dots",
          action="store_true",
          dest="dots",
          default=False,
          help="show data points in plot",
      )
      parser.add_option(
          "-l",
          "--line",
          action="store_true",
          dest="line",
          default=False,
          help="connect data points in plot with a line",
      )
      parser.add_option(
          "-i",
          "--iter",
          action="store",
          dest="stop",
          metavar="STR",
          default=":",
          help="string for smallest:largest iterations to plot",
      )
      parser.add_option(
          "-g",
          "--legend",
          action="store_true",
          dest="legend",
          default=False,
          help="show the legend",
      )
      parser.add_option(
          "-n",
          "--nid",
          action="store",
          dest="id",
          metavar="INT",
          default=None,
          help="id # of the nth simultaneous points to plot",
      )
      parser.add_option(
          "-p",
          "--param",
          action="store",
          dest="param",
          metavar="STR",
          default=":",
          help="indicator string to select parameters",
      )
      # parser.add_option("-f","--file",action="store",dest="filename",metavar="FILE",\
      #                  default='log.txt',help="log file name")
  
      #   import sys
      #   if 'mystic_log_reader.py' not in sys.argv:
      f = StringIO()
      parser.print_help(file=f)
      f.seek(0)
      if "Options:" not in log_reader.__doc__:
          log_reader.__doc__ += (
              "\nOptions:%s" % f.read().split("Options:")[-1]
          )
      f.close()
  
      try:
          parsed_opts, parsed_args = parser.parse_args(cmdargs)
      except UnboundLocalError:
          pass
      if __quit:
          return
  
      style = "-"  # default linestyle
      if parsed_opts.dots:
          mark = "o"
          # when using 'dots', also can turn off 'line'
          if not parsed_opts.line:
              style = "None"
      else:
          mark = ""
  
      try:  # get logfile name
          if instance:
              filename = instance
          else:
              filename = parsed_args[0]
      except:
          raise IOError("please provide log file name")
  
      try:  # path of plot output file
          out = parsed_opts.out  # e.g. 'myplot.png'
          if "None" == out:
              out = None
      except:
          out = None
  
      try:  # select which iteration to stop plotting at
          stop = parsed_opts.stop  # format is "1:10:1"
          stop = (
              stop
              if ":" in stop
              else (stop + ":" if "-" in stop else ":" + stop)
          )
      except:
          stop = ":"
  
      try:  # select which 'id' to plot results for
          runs = (
              int(parsed_opts.id),
          )  # XXX: allow selecting more than one id ?
      except:
          runs = None  # i.e. 'all' **or** use id=0, which should be 'best' energy ?
  
      try:  # select which parameters to plot
          select = parsed_opts.param.split(
              ","
          )  # format is ":2, 2:4, 5, 6:"
      except:
          select = [":"]
  
      # ensure all terms of select have a ":"
      for i in range(len(select)):
          if isinstance(select[i], int):
              select[i] = str(select[i])
          if select[i] == "-1":
              select[i] = "len(params)-1:len(params)"
          elif not select[i].count(":"):
              select[i] += ":" + str(int(select[i]) + 1)
  
      # == Possible results ==
      # iter = (i,id) or (i,)
      # split => { (i,) then (i+1,) } or { (i,) then (0,) }
      # y x = { float list } or { list [list1, ...] }
  
      # == Use Cases ==
      # (i,id) + { (i,) then (i+1,) } + { float list }
      # (i,) + { (i,) then (i+1,) } + { float list }
      # (i,id) + { (i,) then (i+1,) } + { list [list1, ...] }
      # (i,) + { (i,) then (i+1,) } + { list [list1, ...] }
      # (i,id) + { (i,) then (0,) } + { float list }
      # (i,) + { (i,) then (0,) } + { float list }
      # (i,id) + { (i,) then (0,) } + { list [list1, ...] }
      # (i,) + { (i,) then (0,) } + { list [list1, ...] }
      # NOTES:
      #   Legend is different for list versus [list1,...]
      #   Plot should be discontinuous for (i,) then (0,)
  
      # parse file contents to get (i,id), cost, and parameters
      try:
          instance = instance if instance else filename
          from mystic.munge import read_trajectories
  
          step, param, cost = read_trajectories(instance, iter=True)
      except SyntaxError:
          from mystic.munge import read_raw_file
  
          read_raw_file(filename)
          msg = (
              "incompatible file format, try 'support_convergence %s'"
              % filename
          )
          raise SyntaxError(msg)
  
      # ignore everything after 'stop'
      locals = dict(step=step, cost=cost, param=param)
      step = eval("step[%s]" % stop, locals)
      cost = eval("cost[%s]" % stop, locals)
      param = eval("param[%s]" % stop, locals)
      del locals
  
      # split (i,id) into iteration and id
      if step:  # XXX: ignore non-intger ids
          multinode = (
              len(step[0]) - 1 if isinstance(step[0][-1], int) else 0
          )
      else:
          multinode = 0  # XXX: no step info, so give up
      iter = [i[0] for i in step]
      if multinode:
          id = [i[1] for i in step]
      else:
          id = [0 for i in step]
  
      # build the list of selected parameters
      params = list(range(len(param[0])))
      selected = []
      locals = dict(params=params)
      for i in select:
          selected.extend(eval("params[%s]" % i, locals))
      selected = list(set(selected))
  
      if runs is not None:
          maxid = max(id) + 1
          runs = [maxid + i if i < 0 else i for i in runs]
  
      results = [[] for i in range(max(id) + 1)]
  
      # populate results for each id with the corresponding (iter,cost,param)
      for i in range(len(id)):
          if (
              runs is None or id[i] in runs
          ):  # take only the selected 'id'
              results[id[i]].append((iter[i], cost[i], param[i]))
      # NOTE: for example...  results = [[(0,...)],[(0,...),(1,...)],[],[(0,...)]]
  
      # build list of parameter (and cost) convergences for each id
      conv = []
      cost_conv = []
      iter_conv = []
      for i in range(len(results)):
          conv.append(
              []
          )  # ; cost_conv.append([]); iter_conv.append([])
          if len(results[i]):
              for k in range(len(results[i][0][2])):
                  conv[i].append(
                      [
                          results[i][j][2][k]
                          for j in range(len(results[i]))
                      ]
                  )
              cost_conv.append(
                  [results[i][j][1] for j in range(len(results[i]))]
              )
              iter_conv.append(
                  [results[i][j][0] for j in range(len(results[i]))]
              )
          else:
              conv[i] = [[] for k in range(len(param[0]))]
              cost_conv.append([])
              iter_conv.append([])
  
      # print("iter_conv = %s" % iter_conv)
      # print("cost_conv = %s" % cost_conv)
      # print("conv = %s" % conv)
  
      import matplotlib.pyplot as plt
  
      fig = plt.figure()
  
      # FIXME: These may fail when conv[i][j] = [[],[],[]] and cost = []. Verify this.
      ax1 = fig.add_subplot(2, 1, 1)
      for i in range(len(conv)):
          if runs is None or i in runs:  # take only the selected 'id'
              for j in range(len(param[0])):
                  if j in selected:  # take only the selected 'params'
                      tag = "%d,%d" % (j, i)  # label is 'parameter,id'
                      ax1.plot(
                          iter_conv[i],
                          conv[i][j],
                          label="%s" % tag,
                          marker=mark,
                          linestyle=style,
                      )
      if parsed_opts.legend:
          plt.legend()
  
      ax2 = fig.add_subplot(2, 1, 2)
      for i in range(len(conv)):
          if runs is None or i in runs:  # take only the selected 'id'
              tag = "%d" % i  # label is 'cost id'
              ax2.plot(
                  iter_conv[i],
                  cost_conv[i],
                  label="cost %s" % tag,
                  marker=mark,
                  linestyle=style,
              )
      if parsed_opts.legend:
          plt.legend()
  
      # process inputs
      if _out:
          out = _out
      if out:
          out = _out or out
  
      if not out:
          plt.show()
      elif out is True:
          return (
              fig  # XXX: better?: fig.axes if len(fig.axes) > 1 else ax
          )
      else:
          fig.savefig(out)
  
imports: from collections.abc import Callable
label_desc: Use image node_id 28 for calling a global function with example usage: log_reader(filename) and returns: fig