code: def model_plotter(model, logfile=None, **kwds):
      """
      generate surface contour plots for model, specified by full import path; and
      generate model trajectory from logfile (or solver restart file), if provided
  
      Available from the command shell as::
  
          mystic_model_plotter model (logfile) [options]
  
      or as a function call::
  
          mystic.model_plotter(model, logfile=None, **options)
  
      Args:
          model (str): full import path for the model (e.g. ``mystic.models.rosen``)
          logfile (str, default=None): name of 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 *bounds* takes an indicator string, where bounds are given as
            comma-separated slices. For example, using ``bounds = "-1:10, 0:20"``
            will set lower and upper bounds for x to be (-1,10) and y to be (0,20).
            The "step" can also be given, to control the number of lines plotted in
            the grid. Thus ``"-1:10:.1, 0:20"`` sets the bounds as above, but uses
            increments of .1 along x and the default step along y.  For models > 2D,
            the bounds can be used to specify 2 dimensions plus fixed values for
            remaining dimensions. Thus, ``"-1:10, 0:20, 1.0"`` plots the 2D surface
            where the z-axis is fixed at z=1.0. When called from a script, slice
            objects can be used instead of a string, thus ``"-1:10:.1, 0:20, 1.0"``
            becomes ``(slice(-1,10,.1), slice(20), 1.0)``.
          - The option *label* takes comma-separated strings. For example,
            ``label = "x,y,"`` will place 'x' on the x-axis, 'y' on the y-axis, and
            nothing on the z-axis.  LaTeX is also accepted. For example,
            ``label = "$ h $, $ {\\alpha}$, $ v$"`` will label the axes with standard
            LaTeX math formatting. Note that the leading space is required, while a
            trailing space aligns the text with the axis instead of the plot frame.
          - The option *nid* takes an integer of the nth simultaneous points to plot.
          - The option *iter* takes an integer of the largest iteration to plot.
          - The option *reduce* can be given to reduce the output of a model to a
            scalar, thus converting ``model(params)`` to ``reduce(model(params))``.
            A reducer is given by the import path (e.g. ``numpy.add``).
          - The option *scale* will convert the plot to log-scale, and scale the
            cost by ``z=log(4*z*scale+1)+2``. This is useful for visualizing small
            contour changes around the minimium.
          - If using log-scale produces negative numbers, the option *shift* can be
            used to shift the cost by ``z=z+shift``. Both *shift* and *scale* are
            intended to help visualize contours.
          - The option *fill* takes a boolean, to plot using filled contours.
          - The option *depth* takes a boolean, to plot contours in 3D.
          - The option *dots* takes a boolean, to show trajectory points in the plot.
          - The option *join* takes a boolean, to connect trajectory points.
          - The option *verb* takes a boolean, to print the model documentation.
          - The option *kernel* can be given to transform the input of a model from
            nD to 2D, where ``params' = model(params)`` with ``params'`` being 2D.
            A kernel is given by the import path (e.g. ``mymodule.kernel``). Using
            kernel can be slow, as it may calcuate inverse transform at each point.
          - The option *tol* takes a float of max distance of dots from surface.
            For finer control, provide an array[float] the same length as ``params``.
      """
      # FIXME: should be able to:
      # - apply a constraint as a region of NaN -- apply when 'xx,yy=x[ij],y[ij]'
      # - apply a penalty by shifting the surface (plot w/alpha?) -- as above
      # - build an appropriately-sized default grid (from logfile info)
      # - move all mulit-id param/cost reading into read_history
      # FIXME: current issues:
      # - 1D slice and projection work for 2D function, but aren't "pretty"
      # - 1D slice and projection for 1D function, is it meaningful and correct?
      # - should be able to plot from solver.genealogy (multi-monitor?) [1D,2D,3D?]
      # - should be able to scale 'z-axis' instead of scaling 'z' itself
      #   (see https://github.com/matplotlib/matplotlib/issues/209)
      # - if trajectory outside contour grid, will increase bounds
      #   (see support_hypercube.py for how to fix bounds)
      import shlex
      from io import StringIO
  
      global __quit
      __quit = False
      _model = None
      _reducer = None
      _solver = None
      _kernel = None
      _out = False
  
      instance = None
      # handle the special case there is no model
      if model is None or model == "":
          model = "None"
      # handle the special case where list is provided by sys.argv
      if isinstance(model, (list, tuple)) and not logfile and not kwds:
          cmdargs = (
              model  # (above is used by script to parse command line)
          )
      elif isinstance(model, str) and not logfile and not kwds:
          cmdargs = shlex.split(model)
      # 'everything else' is essentially the functional interface
      else:
          cmdargs = kwds.get("kwds", "")
          if not cmdargs:
              out = kwds.get("out", None)
              bounds = kwds.get("bounds", None)
              label = kwds.get("label", None)
              nid = kwds.get("nid", None)
              iter = kwds.get("iter", None)
              reduce = kwds.get("reduce", None)
              scale = kwds.get("scale", None)
              shift = kwds.get("shift", None)
              fill = kwds.get("fill", False)
              depth = kwds.get("depth", False)
              dots = kwds.get("dots", False)
              join = kwds.get("join", False)
              verb = kwds.get("verb", False)
              kernel = kwds.get("kernel", None)
              tol = kwds.get("tol", None)
  
              # special case: bounds passed as list of slices
              if not isinstance(bounds, (str, type(None))):
                  cmdargs = ""
                  for b in bounds:
                      if isinstance(b, slice):
                          cmdargs += "{}:{}:{}, ".format(
                              b.start, b.stop, b.step
                          )
                      else:
                          cmdargs += "{}, ".format(b)
                  bounds = cmdargs[:-2]
                  cmdargs = ""
  
              # special case: tol passed as tuple of floats
              if not isinstance(tol, (str, type(None))):
                  if hasattr(tol, "__len__"):
                      cmdargs = ""
                      for t in tol:
                          cmdargs += "{}, ".format(t)
                      tol = cmdargs[:-2]
                      cmdargs = ""
  
              if isinstance(reduce, _Callable):
                  _reducer, reduce = reduce, None
              if isinstance(kernel, _Callable):
                  _kernel, kernel = kernel, None
              if isinstance(out, bool):
                  _out, out = out, None
  
          # special case: model passed as model instance
          # model.__doc__.split('using::')[1].split()[0].strip()
          if isinstance(model, _Callable):
              _model, model = model, "None"
  
          # handle logfile if given
          if logfile:
              if isinstance(logfile, str):
                  model += " " + logfile
              else:  # special case of passing in monitor instance
                  instance = logfile
  
          # process "commandline" arguments
          if not cmdargs:
              cmdargs = ""
              cmdargs += "" if out is None else "--out={} ".format(out)
              cmdargs += (
                  ""
                  if bounds is None
                  else '--bounds="{}" '.format(bounds)
              )
              cmdargs += (
                  "" if label is None else "--label={} ".format(label)
              )
              cmdargs += "" if nid is None else "--nid={} ".format(nid)
              cmdargs += (
                  "" if iter is None else "--iter={} ".format(iter)
              )
              cmdargs += (
                  ""
                  if reduce is None
                  else "--reduce={} ".format(reduce)
              )
              cmdargs += (
                  "" if scale is None else "--scale={} ".format(scale)
              )
              cmdargs += (
                  "" if shift is None else "--shift={} ".format(shift)
              )
              cmdargs += "" if fill == False else "--fill "
              cmdargs += "" if depth == False else "--depth "
              cmdargs += "" if dots == False else "--dots "
              cmdargs += "" if join == False else "--join "
              cmdargs += "" if verb == False else "--verb "
              cmdargs += (
                  ""
                  if kernel is None
                  else "--kernel={} ".format(kernel)
              )
              cmdargs += (
                  "" if tol is None else '--tol="{}" '.format(tol)
              )
          else:
              cmdargs = " " + cmdargs
          cmdargs = model.split() + 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=model_plotter.__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(
          "-b",
          "--bounds",
          action="store",
          dest="bounds",
          metavar="STR",
          default="-5:5:.1, -5:5:.1",
          help="indicator string to set plot bounds and density",
      )
      parser.add_option(
          "-l",
          "--label",
          action="store",
          dest="label",
          metavar="STR",
          default=",,",
          help="string to assign label to axis",
      )
      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(
          "-i",
          "--iter",
          action="store",
          dest="stop",
          metavar="STR",
          default=":",
          help="string for smallest:largest iterations to plot",
      )
      parser.add_option(
          "-r",
          "--reduce",
          action="store",
          dest="reducer",
          metavar="STR",
          default="None",
          help="import path of output reducer function",
      )
      parser.add_option(
          "-x",
          "--scale",
          action="store",
          dest="zscale",
          metavar="INT",
          default=0.0,
          help="scale plotted cost by z=log(4*z*scale+1)+2",
      )
      parser.add_option(
          "-z",
          "--shift",
          action="store",
          dest="zshift",
          metavar="INT",
          default=0.0,
          help="shift plotted cost by z=z+shift",
      )
      parser.add_option(
          "-f",
          "--fill",
          action="store_true",
          dest="fill",
          default=False,
          help="plot using filled contours",
      )
      parser.add_option(
          "-d",
          "--depth",
          action="store_true",
          dest="surface",
          default=False,
          help="plot contours showing depth in 3D",
      )
      parser.add_option(
          "-o",
          "--dots",
          action="store_true",
          dest="dots",
          default=False,
          help="show trajectory points in plot",
      )
      parser.add_option(
          "-j",
          "--join",
          action="store_true",
          dest="line",
          default=False,
          help="connect trajectory points in plot",
      )
      parser.add_option(
          "-v",
          "--verb",
          action="store_true",
          dest="verbose",
          default=False,
          help="print model documentation string",
      )
      parser.add_option(
          "-k",
          "--kernel",
          action="store",
          dest="kernel",
          metavar="STR",
          default="None",
          help="import path of kernel transform function",
      )
      parser.add_option(
          "-t",
          "--tol",
          action="store",
          dest="tol",
          metavar="STR",
          default="None",
          help="max distance from surface to draw dots",
      )
  
      #   import sys
      #   if 'mystic_model_plotter.py' not in sys.argv:
      f = StringIO()
      parser.print_help(file=f)
      f.seek(0)
      if "Options:" not in model_plotter.__doc__:
          model_plotter.__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
  
      # get the import path for the model
      model = parsed_args[0]  # e.g. 'mystic.models.rosen'
      if "None" == model:
          model = None
  
      try:  # get the name of the parameter log file
          source = parsed_args[1]  # e.g. 'log.txt'
      except:
          source = None
  
      try:  # select the bounds
          options = (
              parsed_opts.bounds
          )  # format is "-1:10:.1, -1:10:.1, 1.0"
      except:
          options = "-5:5:.1, -5:5:.1"
  
      try:  # plot using filled contours
          fill = parsed_opts.fill
      except:
          fill = False
  
      try:  # plot contours showing depth in 3D
          surface = parsed_opts.surface
      except:
          surface = False
  
      # XXX: can't do '-x' with no argument given  (use T/F instead?)
      try:  # scale plotted cost by z=log(4*z*scale+1)+2
          scale = float(parsed_opts.zscale)
          if not scale:
              scale = False
      except:
          scale = False
  
      # XXX: can't do '-z' with no argument given
      try:  # shift plotted cost by z=z+shift
          shift = float(parsed_opts.zshift)
          if not shift:
              shift = False
      except:
          shift = False
  
      try:  # import path of output reducer function
          reducer = parsed_opts.reducer  # e.g. 'numpy.add'
          if "None" == reducer:
              reducer = None
      except:
          reducer = None
  
      try:  # import path of kernel transform function
          kernel = parsed_opts.kernel  # e.g. 'mymodule.kernel'
          if "None" == kernel:
              kernel = None
      except:
          kernel = None
  
      try:  # path of plot output file
          out = parsed_opts.out  # e.g. 'myplot.png'
          if "None" == out:
              out = None
      except:
          out = None
  
      try:  # max distance from surface to draw dots
          tol = parsed_opts.tol
          if "None" == tol:
              tol = None
      except:
          tol = None
  
      vistol = False  # ignore visual filter
      style = "-"  # default linestyle
      if parsed_opts.dots:
          mark = "o"  # marker=mark
          # when using 'dots', also can turn off 'line'
          if not parsed_opts.line:
              style = ""  # linestyle='None'
              vistol = True  # apply visual filter
      else:
          mark = ""
      color = "w" if fill else "k"
      style = color + style + mark
  
      try:  # select labels for the axes
          label = parsed_opts.label.split(",")  # format is "x, y, z"
      except:
          label = ["", "", ""]
  
      try:  # select which 'id' to plot results for
          ids = (
              int(parsed_opts.id),
          )  # XXX: allow selecting more than one id ?
      except:
          ids = None  # i.e. 'all'
  
      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 whether to be verbose about model documentation
          verbose = bool(parsed_opts.verbose)
      except:
          verbose = False
  
      #################################################
      solver = None  # set to 'mystic.solvers.fmin' (or similar) for 'live' fits
      # NOTE: 'live' runs constrain params explicitly in the solver, then reduce
      #      dimensions appropriately so results can be 2D contour plotted.
      #      When working with legacy results that have more than 2 params,
      #      the trajectory WILL NOT follow the masked surface generated
      #      because the masked params were NOT fixed when the solver was run.
      #################################################
  
      from mystic.tools import reduced, masked, partial
  
      # process inputs
      if _model:
          model = _model
      if _reducer:
          reducer = _reducer
      if _kernel:
          kernel = _kernel
      if _out:
          out = _out
      if _solver:
          solver = _solver
      select, spec, mask = _parse_input(options)
      x, y = _parse_axes(spec, grid=True)  # grid=False for 1D plots
      # FIXME: does grid=False still make sense here...?
      if kernel:
          kernel = _kernel or _get_instance(kernel)
      if reducer:
          reducer = _reducer or _get_instance(reducer)
      if out:
          out = _out or out
      if solver and (not source or not model):  # XXX: not instance?
          raise RuntimeError(
              "a model and results filename are required"
          )
      elif not source and not model and not instance:
          raise RuntimeError("a model or a results file is required")
      if model:
          model = _model or _get_instance(model)
          if verbose:
              print(model.__doc__)
          # need a reducer if model returns an array
          if reducer:
              model = reduced(reducer, arraylike=False)(model)
  
      if solver:
          # if 'live'... pick a solver
          solver = "mystic.solvers.fmin"
          solver = _solver or _get_instance(solver)
          xlen = len(select) + len(mask)
          if solver.__name__.startswith("diffev"):
              initial = [(-1, 1)] * xlen
          else:
              initial = [0] * xlen
          from mystic.monitors import VerboseLoggingMonitor
  
          if instance:
              itermon = VerboseLoggingMonitor(new=True)
              itermon.prepend(instance)
          else:
              itermon = VerboseLoggingMonitor(filename=source, new=True)
          # explicitly constrain parameters
          model = partial(mask)(model)
          # solve
          sol = solver(model, x0=initial, itermon=itermon)
  
          # -OVERRIDE-INPUTS-#
          import numpy
  
          # read trajectories from monitor (comment out to use logfile)
          source = itermon
          # if negative minimum, shift by the 'solved minimum' plus an epsilon
          shift = max(-numpy.min(itermon.y), 0.0) + 0.5  # a good guess
          # -----------------#
  
      if model:  # for plotting, implicitly constrain by reduction
          model = masked(mask)(model)
          kernel_ = masked(mask)(kernel) if kernel else None
  
          ## plot the surface in 1D
          # if solver: v=sol[-1]
          # elif source: v=cost[-1]
          # else: v=None
          # fig0 = _draw_slice(model, x=x, y=v, scale=scale, shift=shift)
          # plot the surface in 2D or 3D
          fig = _draw_contour(
              model,
              x,
              y,
              surface=surface,
              fill=fill,
              scale=scale,
              shift=shift,
              kernel=kernel_,
          )  # XXX: ensure x,y cover bounds of source?
      else:
          # fig0 = None
          fig = None
  
      if instance:
          source = instance
      if source:
          # params are the parameter trajectories
          # cost is the solution trajectory
          params, cost = _get_history(source, ids)
          if len(cost) > 1:
              style = style[1:]  # 'auto-color' #XXX: or grayscale?
  
          if vistol:
              tols = _parse_tol(tol, select)
              params, cost = _visual_filter(
                  options, params, cost, *tols
              )
              del tols
          if not kernel:
              kernel = lambda p: [p[int(i)] for i in select[:2]]
  
          for p, c in zip(params, cost):
              ## project trajectory on a 1D slice of model surface #XXX: useful?
              # s = select[0] if len(select) else 0
              # px = p[int(s)] # _draw_projection requires one parameter
              ## ignore everything after 'stop'
              # _c = eval('c[%s]' % stop)
              # _x = eval('px[%s]' % stop)
              # fig0 = _draw_projection(_x,_c, style=style, scale=scale, shift=shift, figure=fig0)
  
              # plot the trajectory on the model surface (2D or 3D)
              # get two selected params #XXX: what if len(select)<2? or len(p)<2?
              import numpy
  
              p = numpy.array(p)
              try:  # XXX: needs better testing for all relevant cases
                  p = kernel(p)
              except:  # kernel doesn't work with arrays
                  p = numpy.array([kernel(i) for i in p.T[0]]).reshape(
                      2, -1, 1
                  )
              px, py = p  # _draw_trajectory requires two parameters
              # ignore everything after 'stop'
              locals = dict(px=px, py=py, c=c)
              _x = eval("px[%s]" % stop, locals)
              _y = eval("py[%s]" % stop, locals)
              _c = eval("c[%s]" % stop, locals) if surface else None
              del locals
              fig = _draw_trajectory(
                  _x,
                  _y,
                  _c,
                  style=style,
                  scale=scale,
                  shift=shift,
                  figure=fig,
              )
  
      import matplotlib.pyplot as plt
      from mpl_toolkits.mplot3d import axes3d
  
      # add labels to the axes
      if surface:
          kwds = {"projection": "3d"}  # 3D
      else:
          kwds = {}  # 2D
      ax = fig.axes[0] if fig.axes else plt.axes(**kwds)
      ax.set_xlabel(label[0])
      ax.set_ylabel(label[1])
      if surface:
          ax.set_zlabel(label[2])
  
      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 26 for calling a global function with example usage: model_plotter(model, logfile) and returns: fig