def _decorate_objective(self, cost, ExtraArgs=None): """decorate the cost function with bounds, penalties, monitors, etc""" #print("@%r %r %r" % (cost, ExtraArgs, max)) evalmon = self._evalmon raw = cost if ExtraArgs is None: ExtraArgs = () self._fcalls, cost = wrap_function(cost, ExtraArgs, evalmon) if self._useStrictRange: if self.generations: #NOTE: pop[0] was best, may not be after resetting simplex for i, j in enumerate(self._setSimplexWithinRangeBoundary()): self.population[i + 1] = self.population[0].copy() self.population[i + 1][i] = j else: self.population[0] = self._clipGuessWithinRangeBoundary( self.population[0]) cost = wrap_bounds(cost, self._strictMin, self._strictMax) cost = wrap_penalty(cost, self._penalty) cost = wrap_nested(cost, self._constraints) if self._reducer: #cost = reduced(*self._reducer)(cost) # was self._reducer = (f,bool) cost = reduced(self._reducer, arraylike=True)(cost) # hold on to the 'wrapped' and 'raw' cost function self._cost = (cost, raw, ExtraArgs) self._live = True return cost
def _decorate_objective(self, cost, ExtraArgs=None): """decorate cost function with bounds, penalties, monitors, etc""" #print("@%r %r %r" % (cost, ExtraArgs, max)) raw = cost if ExtraArgs is None: ExtraArgs = () from mystic.python_map import python_map if self._map != python_map: #FIXME: EvaluationMonitor fails for MPI, throws error for 'pp' from mystic.monitors import Null evalmon = Null() else: evalmon = self._evalmon fcalls, cost = wrap_function(cost, ExtraArgs, evalmon) if self._useStrictRange: indx = list(self.popEnergy).index(self.bestEnergy) ngen = self.generations #XXX: no random if generations=0 ? for i in range(self.nPop): self.population[i] = self._clipGuessWithinRangeBoundary( self.population[i], (not ngen) or (i is indx)) cost = wrap_bounds(cost, self._strictMin, self._strictMax) cost = wrap_penalty(cost, self._penalty) if self._reducer: #cost = reduced(*self._reducer)(cost) # was self._reducer = (f,bool) cost = reduced(self._reducer, arraylike=True)(cost) # hold on to the 'wrapped' and 'raw' cost function self._cost = (cost, raw, ExtraArgs) self._live = True return cost
def _decorate_objective(self, cost, ExtraArgs=None): """decorate cost function with bounds, penalties, monitors, etc""" #print ("@", cost, ExtraArgs, max) raw = cost if ExtraArgs is None: ExtraArgs = () from python_map import python_map if self._map != python_map: #FIXME: EvaluationMonitor fails for MPI, throws error for 'pp' from mystic.monitors import Null evalmon = Null() else: evalmon = self._evalmon fcalls, cost = wrap_function(cost, ExtraArgs, evalmon) if self._useStrictRange: indx = list(self.popEnergy).index(self.bestEnergy) ngen = self.generations #XXX: no random if generations=0 ? for i in range(self.nPop): self.population[i] = self._clipGuessWithinRangeBoundary(self.population[i], (not ngen) or (i is indx)) cost = wrap_bounds(cost, self._strictMin, self._strictMax) cost = wrap_penalty(cost, self._penalty) if self._reducer: #cost = reduced(*self._reducer)(cost) # was self._reducer = (f,bool) cost = reduced(self._reducer, arraylike=True)(cost) # hold on to the 'wrapped' and 'raw' cost function self._cost = (cost, raw, ExtraArgs) self._live = True return cost
def _decorate_objective(self, cost, ExtraArgs=None): """decorate the cost function with bounds, penalties, monitors, etc input:: - cost is the objective function, of the form y = cost(x, *ExtraArgs), where x is a candidate solution, and ExtraArgs is the tuple of positional arguments required to evaluate the objective.""" #print("@%r %r %r" % (cost, ExtraArgs, max)) evalmon = self._evalmon raw = cost if ExtraArgs is None: ExtraArgs = () self._fcalls, cost = wrap_function(cost, ExtraArgs, evalmon) if self._useStrictRange: indx = list(self.popEnergy).index(self.bestEnergy) ngen = self.generations #XXX: no random if generations=0 ? for i in range(self.nPop): self.population[i] = self._clipGuessWithinRangeBoundary( self.population[i], (not ngen) or (i == indx)) cost = wrap_bounds(cost, self._strictMin, self._strictMax) #XXX: remove? from mystic.constraints import and_ constraints = and_(self._constraints, self._strictbounds, onfail=self._strictbounds) else: constraints = self._constraints cost = wrap_penalty(cost, self._penalty) cost = wrap_nested(cost, constraints) if self._reducer: #cost = reduced(*self._reducer)(cost) # was self._reducer = (f,bool) cost = reduced(self._reducer, arraylike=True)(cost) # hold on to the 'wrapped' and 'raw' cost function self._cost = (cost, raw, ExtraArgs) self._live = True return cost
def _RegisterObjective(self, cost, ExtraArgs=None): """decorate cost function with bounds, penalties, monitors, etc""" if ExtraArgs == None: ExtraArgs = () self._fcalls, cost = wrap_function(cost, ExtraArgs, self._evalmon) if self._useStrictRange: for i in range(self.nPop): self.population[i] = self._clipGuessWithinRangeBoundary(self.population[i]) cost = wrap_bounds(cost, self._strictMin, self._strictMax) cost = wrap_penalty(cost, self._penalty) if self._reducer: #cost = reduced(*self._reducer)(cost) # was self._reducer = (f,bool) cost = reduced(self._reducer, arraylike=True)(cost) # hold on to the 'wrapped' cost function self._cost = (cost, ExtraArgs) return cost
def _RegisterObjective(self, cost, ExtraArgs=None): """decorate cost function with bounds, penalties, monitors, etc""" if ExtraArgs == None: ExtraArgs = () #FIXME: EvaluationMonitor fails for MPI, throws error for 'pp' from python_map import python_map if self._map != python_map: self._fcalls = [0] #FIXME: temporary patch for removing the following line else: self._fcalls, cost = wrap_function(cost, ExtraArgs, self._evalmon) if self._useStrictRange: for i in range(self.nPop): self.population[i] = self._clipGuessWithinRangeBoundary(self.population[i]) cost = wrap_bounds(cost, self._strictMin, self._strictMax) cost = wrap_penalty(cost, self._penalty) if self._reducer: #cost = reduced(*self._reducer)(cost) # was self._reducer = (f,bool) cost = reduced(self._reducer, arraylike=True)(cost) # hold on to the 'wrapped' cost function self._cost = (cost, ExtraArgs) return cost
def _decorate_objective(self, cost, ExtraArgs=None): """decorate cost function with bounds, penalties, monitors, etc""" #print ("@", cost, ExtraArgs, max) raw = cost if ExtraArgs is None: ExtraArgs = () self._fcalls, cost = wrap_function(cost, ExtraArgs, self._evalmon) if self._useStrictRange: indx = list(self.popEnergy).index(self.bestEnergy) ngen = self.generations #XXX: no random if generations=0 ? for i in range(self.nPop): self.population[i] = self._clipGuessWithinRangeBoundary(self.population[i], (not ngen) or (i is indx)) cost = wrap_bounds(cost, self._strictMin, self._strictMax) cost = wrap_penalty(cost, self._penalty) if self._reducer: #cost = reduced(*self._reducer)(cost) # was self._reducer = (f,bool) cost = reduced(self._reducer, arraylike=True)(cost) # hold on to the 'wrapped' and 'raw' cost function self._cost = (cost, raw, ExtraArgs) self._live = True return cost
def _decorate_objective(self, cost, ExtraArgs=None): """decorate cost function with bounds, penalties, monitors, etc""" #print("@%r %r %r" % (cost, ExtraArgs, max)) raw = cost if ExtraArgs is None: ExtraArgs = () self._fcalls, cost = wrap_function(cost, ExtraArgs, self._evalmon) if self._useStrictRange: indx = list(self.popEnergy).index(self.bestEnergy) ngen = self.generations #XXX: no random if generations=0 ? for i in range(self.nPop): self.population[i] = self._clipGuessWithinRangeBoundary(self.population[i], (not ngen) or (i is indx)) cost = wrap_bounds(cost, self._strictMin, self._strictMax) cost = wrap_penalty(cost, self._penalty) if self._reducer: #cost = reduced(*self._reducer)(cost) # was self._reducer = (f,bool) cost = reduced(self._reducer, arraylike=True)(cost) # hold on to the 'wrapped' and 'raw' cost function self._cost = (cost, raw, ExtraArgs) self._live = True return cost
def _decorate_objective(self, cost, ExtraArgs=None): """decorate the cost function with bounds, penalties, monitors, etc input:: - cost is the objective function, of the form y = cost(x, *ExtraArgs), where x is a candidate solution, and ExtraArgs is the tuple of positional arguments required to evaluate the objective.""" #print("@%r %r %r" % (cost, ExtraArgs, max)) evalmon = self._evalmon raw = cost if ExtraArgs is None: ExtraArgs = () self._fcalls, cost = wrap_function(cost, ExtraArgs, evalmon) if self._useStrictRange: if self.generations: #NOTE: pop[0] was best, may not be after resetting simplex for i, j in enumerate(self._setSimplexWithinRangeBoundary()): self.population[i + 1] = self.population[0].copy() self.population[i + 1][i] = j else: self.population[0] = self._clipGuessWithinRangeBoundary( self.population[0]) cost = wrap_bounds(cost, self._strictMin, self._strictMax) #XXX: remove? from mystic.constraints import and_ constraints = and_(self._constraints, self._strictbounds, onfail=self._strictbounds) else: constraints = self._constraints cost = wrap_penalty(cost, self._penalty) cost = wrap_nested(cost, constraints) if self._reducer: #cost = reduced(*self._reducer)(cost) # was self._reducer = (f,bool) cost = reduced(self._reducer, arraylike=True)(cost) # hold on to the 'wrapped' and 'raw' cost function self._cost = (cost, raw, ExtraArgs) self._live = True return cost
def _decorate_objective(self, cost, ExtraArgs=None): """decorate the cost function with bounds, penalties, monitors, etc""" #print ("@", cost, ExtraArgs, max) raw = cost if ExtraArgs is None: ExtraArgs = () self._fcalls, cost = wrap_function(cost, ExtraArgs, self._evalmon) if self._useStrictRange: if self.generations: #NOTE: pop[0] was best, may not be after resetting simplex for i,j in enumerate(self._setSimplexWithinRangeBoundary()): self.population[i+1] = self.population[0].copy() self.population[i+1][i] = j else: self.population[0] = self._clipGuessWithinRangeBoundary(self.population[0]) cost = wrap_bounds(cost, self._strictMin, self._strictMax) cost = wrap_penalty(cost, self._penalty) cost = wrap_nested(cost, self._constraints) if self._reducer: #cost = reduced(*self._reducer)(cost) # was self._reducer = (f,bool) cost = reduced(self._reducer, arraylike=True)(cost) # hold on to the 'wrapped' and 'raw' cost function self._cost = (cost, raw, ExtraArgs) self._live = True return cost
def model_plotter(model, logfile=None, **kwds): """ generate surface contour plots for model, specified by full import path generate model trajectory from logfile (or solver restart file), if provided Available from the command shell as: mystic_model_plotter.py model (filename) [options] or as a function call as: mystic.model_plotter(model, filename=None, **options) The option "bounds" takes an indicator string, where the bounds should be given as comma-separated slices. For example, using bounds = "-1:10, 0:20" will set the 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" would set the bounds as above, but use increments of .1 along x and the default step along y. For models with > 2D, the bounds can be used to specify 2 dimensions plus fixed values for remaining dimensions. Thus, "-1:10, 0:20, 1.0" would plot the 2D surface where the z-axis was 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 "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. Required Inputs: model full import path for the model (e.g. mystic.models.rosen) Additional Inputs: filename name of the convergence logfile (e.g. log.txt) """ #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 global __quit __quit = False _model = None _reducer = None _solver = None instance = 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, basestring) 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) # special case: bounds passed as list of slices if not isinstance(bounds, (basestring, 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 = '' if callable(reduce): _reducer, reduce = reduce, None # special case: model passed as model instance #model.__doc__.split('using::')[1].split()[0].strip() if callable(model): _model, model = model, "None" # handle logfile if given if logfile: if isinstance(logfile, basestring): 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 ' else: cmdargs = ' ' + cmdargs cmdargs = model.split() + shlex.split(cmdargs) #XXX: note that 'argparse' is new as of python2.7 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") # import sys # if 'mystic_model_plotter.py' not in sys.argv: from StringIO import StringIO 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 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' 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 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 _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 reducer: reducer = _reducer or _get_instance(reducer) 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) ## 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) 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? 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? p = [p[int(i)] for i in select[:2]] px, py = p # _draw_trajectory requires two parameters # ignore everything after 'stop' _x = eval('px[%s]' % stop) _y = eval('py[%s]' % stop) _c = eval('c[%s]' % stop) if surface else None 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.gca(**kwds) ax.set_xlabel(label[0]) ax.set_ylabel(label[1]) if surface: ax.set_zlabel(label[2]) if not parsed_opts.out: plt.show() else: fig.savefig(parsed_opts.out)
def model_plotter(model, logfile=None, **kwds): """ generate surface contour plots for model, specified by full import path generate model trajectory from logfile (or solver restart file), if provided Available from the command shell as: mystic_model_plotter.py model (filename) [options] or as a function call as: mystic.model_plotter(model, filename=None, **options) The option "bounds" takes an indicator string, where the bounds should be given as comma-separated slices. For example, using bounds = "-1:10, 0:20" will set the 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" would set the bounds as above, but use increments of .1 along x and the default step along y. For models with > 2D, the bounds can be used to specify 2 dimensions plus fixed values for remaining dimensions. Thus, "-1:10, 0:20, 1.0" would plot the 2D surface where the z-axis was 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 "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. Required Inputs: model full import path for the model (e.g. mystic.models.rosen) Additional Inputs: filename name of the convergence logfile (e.g. log.txt) """ #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 try: basestring from StringIO import StringIO except NameError: basestring = str from io import StringIO global __quit __quit = False _model = None _reducer = None _solver = None instance = 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, basestring) 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) # special case: bounds passed as list of slices if not isinstance(bounds, (basestring, 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 = '' if isinstance(reduce, collections.Callable): _reducer, reduce = reduce, None # special case: model passed as model instance #model.__doc__.split('using::')[1].split()[0].strip() if isinstance(model, collections.Callable): _model, model = model, "None" # handle logfile if given if logfile: if isinstance(logfile, basestring): 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 ' else: cmdargs = ' ' + cmdargs cmdargs = model.split() + shlex.split(cmdargs) #XXX: note that 'argparse' is new as of python2.7 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") # import sys # if 'mystic_model_plotter.py' not in sys.argv: if PY3: 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() else: if 'Options:' not in model_plotter.__doc__: model_plotter.__doc__ += '\nOptions:%s' % parser.format_help().split('Options:')[-1] 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 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' 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 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 _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 reducer: reducer = _reducer or _get_instance(reducer) 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) ## 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) 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? 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? p = [p[int(i)] for i in select[:2]] 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 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.gca(**kwds) ax.set_xlabel(label[0]) ax.set_ylabel(label[1]) if surface: ax.set_zlabel(label[2]) if not parsed_opts.out: plt.show() else: fig.savefig(parsed_opts.out)
from mystic.tools import reduced, masked, partial # process inputs 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 reducer: reducer = get_instance(reducer) if solver and (not source or not model): raise RuntimeError('a model and results filename are required') elif not source and not model: raise RuntimeError('a model or a results file is required') if model: model = get_instance(model) # 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 = 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 itermon = VerboseLoggingMonitor(filename=source, new=True) # explicitly constrain parameters model = partial(mask)(model) # solve
from mystic.tools import reduced, masked, partial # process inputs 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 reducer: reducer = get_instance(reducer) if solver and (not source or not model): raise RuntimeError('a model and results filename are required') elif not source and not model: raise RuntimeError('a model or a results file is required') if model: model = get_instance(model) # 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 = 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 itermon = VerboseLoggingMonitor(filename=source, new=True) # explicitly constrain parameters model = partial(mask)(model) # solve