Пример #1
0
    def SetGenerationMonitor(self, monitor, new=False):
        """select a callable to monitor (x, f(x)) after each solver iteration

input::
    - a monitor instance or monitor type used to track (x, f(x)). Any data
      collected in an existing generation monitor will be prepended, unless
      new is True."""
        from mystic.monitors import Null, Monitor  #, CustomMonitor
        if monitor is None: monitor = Null()
        current = Null() if new else self._stepmon
        if current is monitor: current = Null()
        if isinstance(monitor, Monitor):  # is Monitor()
            self._stepmon = monitor
            self._stepmon.prepend(current)
        elif isinstance(monitor, Null) or monitor == Null:  # is Null() or Null
            self._stepmon = Monitor()  #XXX: don't allow Null
            self._stepmon.prepend(current)
        elif hasattr(monitor, '__module__'):  # is CustomMonitor()
            if monitor.__module__ in ['mystic._genSow']:
                self._stepmon = monitor  #FIXME: need .prepend(current)
        else:
            raise TypeError("'%s' is not a monitor instance" % monitor)
        self.energy_history = None  # sync with self._stepmon
        self.solution_history = None  # sync with self._stepmon
        return
Пример #2
0
 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
Пример #3
0
 def SetEvaluationMonitor(self, monitor, new=False):
     """select a callable to monitor (x, f(x)) after each cost function evaluation"""
     from mystic.monitors import Null, Monitor#, CustomMonitor
     if monitor is None: monitor = Null()
     current = Null() if new else self._evalmon
     if current is monitor: current = Null()
     if isinstance(monitor, (Null, Monitor) ):  # is Monitor() or Null()
         self._evalmon = monitor
         self._evalmon.prepend(current)
     elif monitor == Null:  # is Null
         self._evalmon = monitor()
         self._evalmon.prepend(current)
     elif hasattr(monitor, '__module__'):  # is CustomMonitor()
         if monitor.__module__ in ['mystic._genSow']:
             self._evalmon = monitor #FIXME: need .prepend(current)
     else:
         raise TypeError("'%s' is not a monitor instance" % monitor)
     return
Пример #4
0
    def __init__(self, bounds, model, npts=None, **kwds):
        """
        Inputs:
         bounds -- list[tuples]: (lower,upper) bound for each model input
         model -- function: y = model(x), where x is an input parameter vector
         npts -- int: number of points to sample the model

        NOTE:
          additional keywords (evalmon, stepmon, maxiter, maxfun, dist, tight,
          saveiter, state, termination, constraints, penalty, reducer, solver)
          are available for use. See mystic.ensemble for more details.
        """
        self._bounds = bounds
        self._model = model  #FIXME: if None, interpolate
        self._npts = npts

        self._evalmon = kwds.pop('evalmon', None)
        if self._evalmon is None:
            from mystic.monitors import Null
            self._evalmon = Null()
        from collections import defaultdict
        self._kwds = defaultdict(lambda: None)
        self._kwds.update(kwds)

        s = self._init_solver()
        s.SetStrictRanges(*zip(*bounds), tight=self._kwds['tight'])
        #s.SetObjective(memo) #model) #XXX: ExtraArgs: axis ???
        s.SetObjective(model)  #FIXME: ensure cached model

        # apply additional kwds
        solver = self._kwds['solver']
        if solver is not None: s.SetNestedSolver(solver)
        s.SetDistribution(self._kwds['dist'])
        s.SetEvaluationLimits(self._kwds['maxiter'], self._kwds['maxfun'])
        s.SetSaveFrequency(self._kwds['saveiter'], self._kwds['state'])
        termination = self._kwds['termination']
        if termination is not None: s.SetTermination(termination)  #XXX:?
        s.SetConstraints(self._kwds['constraints'])
        s.SetPenalty(self._kwds['penalty'])
        s.SetReducer(self._kwds['reducer'], arraylike=True)
        s.SetGenerationMonitor(self._kwds['stepmon'])  #XXX: use self._stepmon?

        # pass a copy of the monitor to all instances
        import copy
        m = copy.deepcopy(self._evalmon)  #XXX: no change with direct reference
        s.SetEvaluationMonitor(m)
        self._sampler = s
        self._npts = s._npts

        self._evals = [0] * s._npts  #XXX: collect count or monitor?
        self._iters = [0] * s._npts  #XXX: collect count or monitor?
        return
Пример #5
0
    def SetEvaluationMonitor(self, monitor, new=False):
        """select a callable to monitor (x, f(x)) after each cost function evaluation

input::
    - a monitor instance or monitor type used to track (x, f(x)). Any data
      collected in an existing evaluation monitor will be prepended, unless
      new is True."""
        from mystic.monitors import Null, Monitor  #, CustomMonitor
        if monitor is None: monitor = Null()
        current = Null() if new else self._evalmon
        if current is monitor: current = Null()
        if isinstance(monitor, (Null, Monitor)):  # is Monitor() or Null()
            self._evalmon = monitor
            self._evalmon.prepend(current)
        elif monitor == Null:  # is Null
            self._evalmon = monitor()
            self._evalmon.prepend(current)
        elif hasattr(monitor, '__module__'):  # is CustomMonitor()
            if monitor.__module__ in ['mystic._genSow']:
                self._evalmon = monitor  #FIXME: need .prepend(current)
        else:
            raise TypeError("'%s' is not a monitor instance" % monitor)
        return
Пример #6
0
 def SetGenerationMonitor(self, monitor, new=False):
     """select a callable to monitor (x, f(x)) after each solver iteration"""
     from mystic.monitors import Null, Monitor  #, CustomMonitor
     current = Null() if new else self._stepmon
     if isinstance(monitor, Monitor):  # is Monitor()
         self._stepmon = monitor
         self._stepmon.prepend(current)
     elif isinstance(monitor, Null) or monitor == Null:  # is Null() or Null
         self._stepmon = Monitor()  #XXX: don't allow Null
         self._stepmon.prepend(current)
     elif hasattr(monitor, '__module__'):  # is CustomMonitor()
         if monitor.__module__ in ['mystic._genSow']:
             self._stepmon = monitor  #FIXME: need .prepend(current)
     else:
         raise TypeError("'%s' is not a monitor instance" % monitor)
     self.energy_history = None  # sync with self._stepmon
     self.solution_history = None  # sync with self._stepmon
     return
Пример #7
0
    def __init__(self, dim, **kwds):
        """
Takes one initial input::

    dim      -- dimensionality of the problem.

Additional inputs::

    npop     -- size of the trial solution population.       [default = 1]

Important class members::

    nDim, nPop       = dim, npop
    generations      - an iteration counter.
    evaluations      - an evaluation counter.
    bestEnergy       - current best energy.
    bestSolution     - current best parameter set.           [size = dim]
    popEnergy        - set of all trial energy solutions.    [size = npop]
    population       - set of all trial parameter solutions. [size = dim*npop]
    solution_history - history of bestSolution status.       [StepMonitor.x]
    energy_history   - history of bestEnergy status.         [StepMonitor.y]
    signal_handler   - catches the interrupt signal.
        """
        NP = kwds['npop'] if 'npop' in kwds else 1

        self.nDim = dim
        self.nPop = NP
        self._init_popEnergy = inf
        self.popEnergy = [self._init_popEnergy] * NP
        self.population = [[0.0 for i in range(dim)] for j in range(NP)]
        self.trialSolution = [0.0] * dim
        self._map_solver = False
        self._bestEnergy = None
        self._bestSolution = None
        self._state = None
        self._type = self.__class__.__name__

        self.sigint_callback = None
        self._handle_sigint = False
        self._useStrictRange = False
        self._defaultMin = [-1e3] * dim
        self._defaultMax = [1e3] * dim
        self._strictMin = []
        self._strictMax = []
        self._maxiter = None
        self._maxfun = None
        self._saveiter = None
        #self._saveeval        = None

        from mystic.monitors import Null, Monitor
        self._evalmon = Null()
        self._stepmon = Monitor()
        self._fcalls = [0]
        self._energy_history = None
        self._solution_history = None
        self.id = None  # identifier (use like "rank" for MPI)

        self._constraints = lambda x: x
        self._penalty = lambda x: 0.0
        self._reducer = None
        self._cost = (None, None, None)
        #                       (cost, raw_cost, args) #,callback)
        self._collapse = False
        self._termination = lambda x, *ar, **kw: False if len(ar) < 1 or ar[
            0] is False or (kw['info'] if 'info' in kw else True
                            ) == False else ''  #XXX: better default ?
        # (get termination details with self._termination.__doc__)

        import mystic.termination as mt
        self._EARLYEXIT = mt.EARLYEXIT
        self._live = False
        return
Пример #8
0
class AbstractSolver(object):
    """AbstractSolver base class for mystic optimizers.
    """
    def __init__(self, dim, **kwds):
        """
Takes one initial input::

    dim      -- dimensionality of the problem.

Additional inputs::

    npop     -- size of the trial solution population.       [default = 1]

Important class members::

    nDim, nPop       = dim, npop
    generations      - an iteration counter.
    evaluations      - an evaluation counter.
    bestEnergy       - current best energy.
    bestSolution     - current best parameter set.           [size = dim]
    popEnergy        - set of all trial energy solutions.    [size = npop]
    population       - set of all trial parameter solutions. [size = dim*npop]
    solution_history - history of bestSolution status.       [StepMonitor.x]
    energy_history   - history of bestEnergy status.         [StepMonitor.y]
    signal_handler   - catches the interrupt signal.
        """
        NP = kwds['npop'] if 'npop' in kwds else 1

        self.nDim = dim
        self.nPop = NP
        self._init_popEnergy = inf
        self.popEnergy = [self._init_popEnergy] * NP
        self.population = [[0.0 for i in range(dim)] for j in range(NP)]
        self.trialSolution = [0.0] * dim
        self._map_solver = False
        self._bestEnergy = None
        self._bestSolution = None
        self._state = None
        self._type = self.__class__.__name__

        self.sigint_callback = None
        self._handle_sigint = False
        self._useStrictRange = False
        self._defaultMin = [-1e3] * dim
        self._defaultMax = [1e3] * dim
        self._strictMin = []
        self._strictMax = []
        self._maxiter = None
        self._maxfun = None
        self._saveiter = None
        #self._saveeval        = None

        from mystic.monitors import Null, Monitor
        self._evalmon = Null()
        self._stepmon = Monitor()
        self._fcalls = [0]
        self._energy_history = None
        self._solution_history = None
        self.id = None  # identifier (use like "rank" for MPI)

        self._constraints = lambda x: x
        self._penalty = lambda x: 0.0
        self._reducer = None
        self._cost = (None, None, None)
        #                       (cost, raw_cost, args) #,callback)
        self._collapse = False
        self._termination = lambda x, *ar, **kw: False if len(ar) < 1 or ar[
            0] is False or (kw['info'] if 'info' in kw else True
                            ) == False else ''  #XXX: better default ?
        # (get termination details with self._termination.__doc__)

        import mystic.termination as mt
        self._EARLYEXIT = mt.EARLYEXIT
        self._live = False
        return

    def Solution(self):
        """return the best solution"""
        return self.bestSolution

    def __evaluations(self):
        """get the number of function calls"""
        return self._fcalls[0]

    def __generations(self):
        """get the number of iterations"""
        return max(0, len(self._stepmon) - 1)

    def __energy_history(self):
        """get the energy_history (default: energy_history = _stepmon._y)"""
        if self._energy_history is None: return self._stepmon._y
        return self._energy_history

    def __set_energy_history(self, energy):
        """set the energy_history (energy=None will sync with _stepmon._y)"""
        self._energy_history = energy
        return

    def __solution_history(self):
        """get the solution_history (default: solution_history = _stepmon.x)"""
        if self._solution_history is None: return self._stepmon.x
        return self._solution_history

    def __set_solution_history(self, params):
        """set the solution_history (params=None will sync with _stepmon.x)"""
        self._solution_history = params
        return

    def __bestSolution(self):
        """get the bestSolution (default: bestSolution = population[0])"""
        if self._bestSolution is None: return self.population[0]
        return self._bestSolution

    def __set_bestSolution(self, params):
        """set the bestSolution (params=None will sync with population[0])"""
        self._bestSolution = params
        return

    def __bestEnergy(self):
        """get the bestEnergy (default: bestEnergy = popEnergy[0])"""
        if self._bestEnergy is None: return self.popEnergy[0]
        return self._bestEnergy

    def __set_bestEnergy(self, energy):
        """set the bestEnergy (energy=None will sync with popEnergy[0])"""
        self._bestEnergy = energy
        return

    def SetReducer(self, reducer, arraylike=False):
        """apply a reducer function to the cost function

input::
    - a reducer function of the form: y' = reducer(yk), where yk is a results
      vector and y' is a single value.  Ideally, this method is applied to
      a cost function with a multi-value return, to reduce the output to a
      single value.  If arraylike, the reducer provided should take a single
      array as input and produce a scalar; otherwise, the reducer provided
      should meet the requirements of the python's builtin 'reduce' method 
      (e.g. lambda x,y: x+y), taking two scalars and producing a scalar."""
        if not reducer:
            self._reducer = None
        elif not isinstance(reducer, collections.Callable):
            raise TypeError("'%s' is not a callable function" % reducer)
        elif not arraylike:
            self._reducer = wrap_reducer(reducer)
        else:  #XXX: check if is arraylike?
            self._reducer = reducer
        return self._update_objective()

    def SetPenalty(self, penalty):
        """apply a penalty function to the optimization

input::
    - a penalty function of the form: y' = penalty(xk), with y = cost(xk) + y',
      where xk is the current parameter vector. Ideally, this function
      is constructed so a penalty is applied when the desired (i.e. encoded)
      constraints are violated. Equality constraints should be considered
      satisfied when the penalty condition evaluates to zero, while
      inequality constraints are satisfied when the penalty condition
      evaluates to a non-positive number."""
        if not penalty:
            self._penalty = lambda x: 0.0
        elif not isinstance(penalty, collections.Callable):
            raise TypeError("'%s' is not a callable function" % penalty)
        else:  #XXX: check for format: y' = penalty(x) ?
            self._penalty = penalty
        return self._update_objective()

    def SetConstraints(self, constraints):
        """apply a constraints function to the optimization

input::
    - a constraints function of the form: xk' = constraints(xk),
      where xk is the current parameter vector. Ideally, this function
      is constructed so the parameter vector it passes to the cost function
      will satisfy the desired (i.e. encoded) constraints."""
        if not constraints:
            self._constraints = lambda x: x
        elif not isinstance(constraints, collections.Callable):
            raise TypeError("'%s' is not a callable function" % constraints)
        else:  #XXX: check for format: x' = constraints(x) ?
            self._constraints = constraints
        return self._update_objective()

    def SetGenerationMonitor(self, monitor, new=False):
        """select a callable to monitor (x, f(x)) after each solver iteration"""
        from mystic.monitors import Null, Monitor  #, CustomMonitor
        current = Null() if new else self._stepmon
        if isinstance(monitor, Monitor):  # is Monitor()
            self._stepmon = monitor
            self._stepmon.prepend(current)
        elif isinstance(monitor, Null) or monitor == Null:  # is Null() or Null
            self._stepmon = Monitor()  #XXX: don't allow Null
            self._stepmon.prepend(current)
        elif hasattr(monitor, '__module__'):  # is CustomMonitor()
            if monitor.__module__ in ['mystic._genSow']:
                self._stepmon = monitor  #FIXME: need .prepend(current)
        else:
            raise TypeError("'%s' is not a monitor instance" % monitor)
        self.energy_history = None  # sync with self._stepmon
        self.solution_history = None  # sync with self._stepmon
        return

    def SetEvaluationMonitor(self, monitor, new=False):
        """select a callable to monitor (x, f(x)) after each cost function evaluation"""
        from mystic.monitors import Null, Monitor  #, CustomMonitor
        current = Null() if new else self._evalmon
        if isinstance(monitor, (Null, Monitor)):  # is Monitor() or Null()
            self._evalmon = monitor
            self._evalmon.prepend(current)
        elif monitor == Null:  # is Null
            self._evalmon = monitor()
            self._evalmon.prepend(current)
        elif hasattr(monitor, '__module__'):  # is CustomMonitor()
            if monitor.__module__ in ['mystic._genSow']:
                self._evalmon = monitor  #FIXME: need .prepend(current)
        else:
            raise TypeError("'%s' is not a monitor instance" % monitor)
        return

    def SetStrictRanges(self, min=None, max=None):
        """ensure solution is within bounds

input::
    - min, max: must be a sequence of length self.nDim
    - each min[i] should be <= the corresponding max[i]

note::
    SetStrictRanges(None) will remove strict range constraints"""
        if min is False or max is False:
            self._useStrictRange = False
            return self._update_objective()
        #XXX: better to use 'defaultMin,defaultMax' or '-inf,inf' ???
        if min is None: min = self._defaultMin
        if max is None: max = self._defaultMax
        # when 'some' of the bounds are given as 'None', replace with default
        for i in range(len(min)):
            if min[i] is None: min[i] = self._defaultMin[0]
            if max[i] is None: max[i] = self._defaultMax[0]

        min = asarray(min)
        max = asarray(max)
        if numpy.any((min > max), 0):
            raise ValueError("each min[i] must be <= the corresponding max[i]")
        if len(min) != self.nDim:
            raise ValueError("bounds array must be length %s" % self.nDim)
        self._useStrictRange = True
        self._strictMin = min
        self._strictMax = max
        return self._update_objective()

    def _clipGuessWithinRangeBoundary(self, x0, at=True):
        """ensure that initial guess is set within bounds

input::
    - x0: must be a sequence of length self.nDim"""
        #if len(x0) != self.nDim: #XXX: unnecessary w/ self.trialSolution
        #    raise ValueError, "initial guess must be length %s" % self.nDim
        x0 = asarray(x0)
        bounds = (self._strictMin, self._strictMax)
        if not len(self._strictMin): return x0
        # clip x0 at bounds
        settings = numpy.seterr(all='ignore')
        x_ = x0.clip(*bounds)
        numpy.seterr(**settings)
        if at: return x_
        # clip x0 within bounds
        x_ = x_ != x0
        x0[x_] = random.uniform(self._strictMin, self._strictMax)[x_]
        return x0

    def SetInitialPoints(self, x0, radius=0.05):
        """Set Initial Points with Guess (x0)

input::
    - x0: must be a sequence of length self.nDim
    - radius: generate random points within [-radius*x0, radius*x0]
        for i!=0 when a simplex-type initial guess in required"""
        x0 = asfarray(x0)
        rank = len(x0.shape)
        if rank is 0:
            x0 = asfarray([x0])
            rank = 1
        if not -1 < rank < 2:
            raise ValueError(
                "Initial guess must be a scalar or rank-1 sequence.")
        if len(x0) != self.nDim:
            raise ValueError("Initial guess must be length %s" % self.nDim)

        #slightly alter initial values for solvers that depend on randomness
        min = x0 * (1 - radius)
        max = x0 * (1 + radius)
        numzeros = len(x0[x0 == 0])
        min[min == 0] = asarray([-radius for i in range(numzeros)])
        max[max == 0] = asarray([radius for i in range(numzeros)])
        self.SetRandomInitialPoints(min, max)
        #stick initial values in population[i], i=0
        self.population[0] = x0.tolist()

    def SetRandomInitialPoints(self, min=None, max=None):
        """Generate Random Initial Points within given Bounds

input::
    - min, max: must be a sequence of length self.nDim
    - each min[i] should be <= the corresponding max[i]"""
        if min is None: min = self._defaultMin
        if max is None: max = self._defaultMax
        #if numpy.any(( asarray(min) > asarray(max) ),0):
        #    raise ValueError, "each min[i] must be <= the corresponding max[i]"
        if len(min) != self.nDim or len(max) != self.nDim:
            raise ValueError("bounds array must be length %s" % self.nDim)
        # when 'some' of the bounds are given as 'None', replace with default
        for i in range(len(min)):
            if min[i] is None: min[i] = self._defaultMin[0]
            if max[i] is None: max[i] = self._defaultMax[0]
        #generate random initial values
        for i in range(len(self.population)):
            for j in range(self.nDim):
                self.population[i][j] = random.uniform(min[j], max[j])

    def SetMultinormalInitialPoints(self, mean, var=None):
        """Generate Initial Points from Multivariate Normal.

input::
    - mean must be a sequence of length self.nDim
    - var can be...
        None: -> it becomes the identity
        scalar: -> var becomes scalar * I
        matrix: -> the variance matrix. must be the right size!
        """
        from mystic.tools import random_state
        rng = random_state(module='numpy.random')
        assert (len(mean) == self.nDim)
        if var is None:
            var = numpy.eye(self.nDim)
        else:
            try:  # scalar ?
                float(var)
            except:  # nope. var better be matrix of the right size (no check)
                pass
            else:
                var = var * numpy.eye(self.nDim)
        for i in range(len(self.population)):
            self.population[i] = rng.multivariate_normal(mean, var).tolist()
        return

    def SetSampledInitialPoints(self, dist=None):
        """Generate Random Initial Points from Distribution (dist)

input::
    - dist: a mystic.math.Distribution instance
"""
        from mystic.math import Distribution
        _dist = Distribution()
        if dist is None:
            dist = _dist
        elif type(_dist) not in dist.__class__.mro():
            dist = Distribution(dist)  #XXX: or throw error?
        for i in range(self.nPop):
            self.population[i] = dist(self.nDim)
        return

    def enable_signal_handler(self):  #, callback='*'):
        """enable workflow interrupt handler while solver is running"""
        """ #XXX: disabled, as would add state to solver
input::
    - if a callback function is provided, generate a new handler with
      the given callback.  If callback is None, do not use a callback.
      If callback is not provided, just turn on the existing handler.
"""
        ## always _generate handler on first call
        #if (self.signal_handler is None) and callback == '*':
        #    callback = None
        ## when a new callback is given, generate a new handler
        #if callback != '*':
        #    self._generateHandler(callback)
        self._handle_sigint = True

    def disable_signal_handler(self):
        """disable workflow interrupt handler while solver is running"""
        self._handle_sigint = False

    def SetSaveFrequency(self, generations=None, filename=None, **kwds):
        """set frequency for saving solver restart file

input::
    - generations = number of solver iterations before next save of state
    - filename = name of file in which to save solver state

note::
    SetSaveFrequency(None) will disable saving solver restart file"""
        self._saveiter = generations
        #self._saveeval = evaluations
        self._state = filename
        return

    def SetEvaluationLimits(self, generations=None, evaluations=None, \
                                                    new=False, **kwds):
        """set limits for generations and/or evaluations

input::
    - generations = maximum number of solver iterations (i.e. steps)
    - evaluations = maximum number of function evaluations"""
        # backward compatibility
        self._maxiter = kwds['maxiter'] if 'maxiter' in kwds else generations
        self._maxfun = kwds['maxfun'] if 'maxfun' in kwds else evaluations
        # handle if new (reset counter, instead of extend counter)
        if new:
            if generations is not None:
                self._maxiter += self.generations
            else:
                self._maxiter = "*"  #XXX: better as self._newmax = True ?
            if evaluations is not None:
                self._maxfun += self.evaluations
            else:
                self._maxfun = "*"
        return

    def _SetEvaluationLimits(self, iterscale=None, evalscale=None):
        """set the evaluation limits"""
        if iterscale is None: iterscale = 10
        if evalscale is None: evalscale = 1000
        N = len(self.population[0])  # usually self.nDim
        # if SetEvaluationLimits not applied, use the solver default
        if self._maxiter is None:
            self._maxiter = N * self.nPop * iterscale
        elif self._maxiter == "*":  # (i.e. None, but 'reset counter')
            self._maxiter = (N * self.nPop * iterscale) + self.generations
        if self._maxfun is None:
            self._maxfun = N * self.nPop * evalscale
        elif self._maxfun == "*":
            self._maxfun = (N * self.nPop * evalscale) + self.evaluations
        return

    def Terminated(self, disp=False, info=False, termination=None):
        """check if the solver meets the given termination conditions

Input::
    - disp = if True, print termination statistics and/or warnings
    - info = if True, return termination message (instead of boolean)
    - termination = termination conditions to check against

Notes::
    If no termination conditions are given, the solver's stored
    termination conditions will be used.
        """
        if termination is None:
            termination = self._termination
        # ensure evaluation limits have been imposed
        self._SetEvaluationLimits()
        # check for termination messages
        msg = termination(self, info=True)
        sig = "SolverInterrupt with %s" % {}
        lim = "EvaluationLimits with %s" % {
            'evaluations': self._maxfun,
            'generations': self._maxiter
        }

        # push solver internals to scipy.optimize.fmin interface
        if self._fcalls[0] >= self._maxfun and self._maxfun is not None:
            msg = lim  #XXX: prefer the default stop ?
            if disp:
                print("Warning: Maximum number of function evaluations has "\
                      "been exceeded.")
        elif self.generations >= self._maxiter and self._maxiter is not None:
            msg = lim  #XXX: prefer the default stop ?
            if disp:
                print(
                    "Warning: Maximum number of iterations has been exceeded")
        elif self._EARLYEXIT:
            msg = sig
            if disp:
                print(
                    "Warning: Optimization terminated with signal interrupt.")
        elif msg and disp:
            print("Optimization terminated successfully.")
            print("         Current function value: %f" % self.bestEnergy)
            print("         Iterations: %d" % self.generations)
            print("         Function evaluations: %d" % self._fcalls[0])

        if info:
            return msg
        return bool(msg)

    def SetTermination(self, termination):  # disp ?
        """set the termination conditions"""
        #XXX: validate that termination is a 'condition' ?
        self._termination = termination
        self._collapse = False
        if termination is not None:
            from mystic.termination import state
            stop = state(termination)
            stop = getattr(stop, 'iterkeys', stop.keys)()
            self._collapse = any(key.startswith('Collapse') for key in stop)
        return

    def SetObjective(self, cost, ExtraArgs=None):  # callback=None/False ?
        """decorate the cost function with bounds, penalties, monitors, etc"""
        _cost, _raw, _args = self._cost
        # check if need to 'wrap' or can return the stored cost
        if (cost is None or cost is _raw or cost is _cost) and \
           (ExtraArgs is None or ExtraArgs is _args):
            return
        # get cost and args if None was given
        if cost is None: cost = _raw
        args = _args if ExtraArgs is None else ExtraArgs
        args = () if args is None else args
        # quick validation check (so doesn't screw up internals)
        if not isvalid(cost, [0] * self.nDim, *args):
            try:
                name = cost.__name__
            except AttributeError:  # raise new error for non-callables
                cost(*args)
            validate(cost, None, *args)
        #val = len(args) + 1  #XXX: 'klepto.validate' for better error?
        #msg = '%s() invalid number of arguments (%d given)' % (name, val)
        #raise TypeError(msg)
        # hold on to the 'raw' cost function
        self._cost = (None, cost, ExtraArgs)
        self._live = False
        return

    def Collapsed(self, disp=False, info=False):
        """check if the solver meets the given collapse conditions

Input::
    - disp = if True, print details about the solver state at collapse
    - info = if True, return collapsed state (instead of boolean)
"""
        stop = getattr(self, '__stop__', self.Terminated(info=True))
        import mystic.collapse as ct
        collapses = ct.collapsed(stop) or dict()
        if collapses and disp:
            for (k, v) in getattr(collapses, 'iteritems', collapses.items)():
                print("         %s: %s" % (k.split()[0], v))
        #print("# Collapse at: Generation", self._stepmon._step-1, \
        #      "with", self.bestEnergy, "@\n#", list(self.bestSolution))
        return collapses if info else bool(collapses)

    def Collapse(self, disp=False):
        """if solver has terminated by collapse, apply the collapse"""
        collapses = self.Collapsed(disp=disp, info=True)
        if collapses:  # then stomach a bunch of module imports (yuck)
            import mystic.tools as to
            import mystic.termination as mt
            import mystic.constraints as cn
            import mystic.mask as ma
            # get collapse conditions  #XXX: efficient? 4x loops over collapses
            state = mt.state(self._termination)
            npts = getattr(self._stepmon, '_npts', None)  #XXX: default?
            #conditions = [cn.impose_at(*to.select_params(self,collapses[k])) if state[k].get('target') is None else cn.impose_at(collapses[k],state[k].get('target')) for k in collapses if k.startswith('CollapseAt')]
            #conditions += [cn.impose_as(collapses[k],state[k].get('offset')) for k in collapses if k.startswith('CollapseAs')]
            conditions = []
            _conditions = []
            for k in collapses:
                if k.startswith('CollapseAt'):
                    t = state[k]
                    t = t['target'] if 'target' in t else None
                    if t is None:
                        t = cn.impose_at(*to.select_params(self, collapses[k]))
                    else:
                        t = cn.impose_at(collapses[k], t)
                    conditions.append(t)
                elif k.startswith('CollapseAs'):
                    t = state[k]
                    t = t['offset'] if 'offset' in t else None
                    _conditions.append(cn.impose_as(collapses[k], t))
            conditions.extend(_conditions)
            del _conditions
            # get measure collapse conditions
            if npts:  #XXX: faster/better if comes first or last?
                conditions += [
                    cn.impose_measure(npts, [
                        collapses[k]
                        for k in collapses if k.startswith('CollapsePosition')
                    ], [
                        collapses[k]
                        for k in collapses if k.startswith('CollapseWeight')
                    ])
                ]

            # update termination and constraints in solver
            constraints = to.chain(*conditions)(self._constraints)
            termination = ma.update_mask(self._termination, collapses)
            self.SetConstraints(constraints)
            self.SetTermination(termination)
            #print(mt.state(self._termination).keys())
        return collapses

    def _update_objective(self):
        """decorate the cost function with bounds, penalties, monitors, etc"""
        # rewrap the cost if the solver has been run
        if False:  # trigger immediately
            self._decorate_objective(*self._cost[1:])
        else:  # delay update until _bootstrap
            self.Finalize()
        return

    def _decorate_objective(self, cost, ExtraArgs=None):
        """decorate the 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)
        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 _bootstrap_objective(self, cost=None, ExtraArgs=None):
        """HACK to enable not explicitly calling _decorate_objective"""
        _cost, _raw, _args = self._cost
        # check if need to 'wrap' or can return the stored cost
        if (cost is None or cost is _raw or cost is _cost) and \
           (ExtraArgs is None or ExtraArgs is _args) and self._live:
            return _cost
        # 'wrap' the 'new' cost function with _decorate
        self.SetObjective(cost, ExtraArgs)
        return self._decorate_objective(*self._cost[1:])
        #XXX: when _decorate called, solver._fcalls will be reset ?

    def _Step(self, cost=None, ExtraArgs=None, **kwds):
        """perform a single optimization iteration

*** this method must be overwritten ***"""
        raise NotImplementedError("an optimization algorithm was not provided")

    def SaveSolver(self, filename=None, **kwds):
        """save solver state to a restart file"""
        import dill
        fd = None
        if filename is None:  # then check if already has registered file
            if self._state is None:  # then create a new one
                import os, tempfile
                fd, self._state = tempfile.mkstemp(suffix='.pkl')
                os.close(fd)
            filename = self._state
        self._state = filename
        f = open(filename, 'wb')
        try:
            dill.dump(self, f, **kwds)
            self._stepmon.info('DUMPED("%s")' %
                               filename)  #XXX: before / after ?
        finally:
            f.close()
        return

    def __save_state(self, force=False):
        """save the solver state, if chosen save frequency is met"""
        # save the last iteration
        if force and bool(self._state):
            self.SaveSolver()
            return
        # save the zeroth iteration
        nonzero = True  #XXX: or bool(self.generations) ?
        # after _saveiter generations, then save state
        iters = self._saveiter
        saveiter = bool(iters) and not bool(self.generations % iters)
        if nonzero and saveiter:
            self.SaveSolver()
        #FIXME: if _saveeval (or more) since last check, then save state

    #save = self.evaluations % self._saveeval
        return

    def __load_state(self, solver, **kwds):
        """load solver.__dict__ into self.__dict__; override with kwds"""
        #XXX: should do some filtering on kwds ?
        self.__dict__.update(solver.__dict__, **kwds)
        return

    def Finalize(self, **kwds):
        """cleanup upon exiting the main optimization loop"""
        self._live = False
        return

    def _process_inputs(self, kwds):
        """process and activate input settings"""
        #allow for inputs that don't conform to AbstractSolver interface
        #NOTE: not sticky: callback, disp
        #NOTE: sticky: EvaluationMonitor, StepMonitor, penalty, constraints
        settings = \
       {'callback':None,     #user-supplied function, called after each step
        'disp':0}            #non-zero to print convergence messages
        [settings.update({i: j}) for (i, j) in kwds.items() if i in settings]
        # backward compatibility
        if 'EvaluationMonitor' in kwds:             \
                       self.SetEvaluationMonitor(kwds['EvaluationMonitor'])
        if 'StepMonitor' in kwds:             \
                       self.SetGenerationMonitor(kwds['StepMonitor'])
        if 'penalty' in kwds: \
           self.SetPenalty(kwds['penalty'])
        if 'constraints' in kwds: \
           self.SetConstraints(kwds['constraints'])
        return settings

    def Step(self, cost=None, termination=None, ExtraArgs=None, **kwds):
        """Take a single optimization step using the given 'cost' function.

Uses an optimization algorithm to take one 'step' toward the minimum of a
function of one or more variables.

Args:
    cost (func, default=None): the function to be minimized: ``y = cost(x)``.
    termination (termination, default=None): termination conditions.
    ExtraArgs (tuple, default=None): extra arguments for cost.
    callback (func, default=None): function to call after each iteration. The
        interface is ``callback(xk)``, with xk the current parameter vector.
    disp (bool, default=False): if True, print convergence messages.

Returns:
    None

Notes:
    To run the solver until termination, call ``Solve()``. Alternately, use
    ``Terminated()`` as the stop condition in a while loop over ``Step``.

    If the algorithm does not meet the given termination conditions after
    the call to ``Step``, the solver may be left in an "out-of-sync" state.
    When abandoning an non-terminated solver, one should call ``Finalize()``
    to make sure the solver is fully returned to a "synchronized" state.
        """
        if 'disp' in kwds:
            disp = kwds['disp']
            del kwds['disp']
        else:
            disp = False

        # register: cost, termination, ExtraArgs
        cost = self._bootstrap_objective(cost, ExtraArgs)
        if termination is not None: self.SetTermination(termination)

        # check termination before 'stepping'
        if len(self._stepmon):
            msg = self.Terminated(disp=disp, info=True) or None
        else:
            msg = None

        # if not terminated, then take a step
        if msg is None:
            self._Step(**kwds)  #FIXME: not all kwds are given in __doc__
            if self.Terminated():  # then cleanup/finalize
                self.Finalize()

            # get termination message and log state
            msg = self.Terminated(disp=disp, info=True) or None
            if msg:
                self._stepmon.info('STOP("%s")' % msg)
                self.__save_state(force=True)
        return msg

    def Solve(self, cost=None, termination=None, ExtraArgs=None, **kwds):
        """Minimize a 'cost' function with given termination conditions.

Uses an optimization algorithm to find the minimum of a function of one or
more variables.

Args:
    cost (func, default=None): the function to be minimized: ``y = cost(x)``.
    termination (termination, default=None): termination conditions.
    ExtraArgs (tuple, default=None): extra arguments for cost.
    sigint_callback (func, default=None): callback function for signal handler.
    callback (func, default=None): function to call after each iteration. The
        interface is ``callback(xk)``, with xk the current parameter vector.
    disp (bool, default=False): if True, print convergence messages.

Returns:
    None
        """
        # process and activate input settings
        if 'sigint_callback' in kwds:
            self.sigint_callback = kwds['sigint_callback']
            del kwds['sigint_callback']
        else:
            self.sigint_callback = None
        settings = self._process_inputs(kwds)
        disp = settings['disp'] if 'disp' in settings else False

        # set up signal handler
        self._EARLYEXIT = False  #XXX: why not use EARLYEXIT singleton?

        # activate signal handler
        #import threading as thread
        #mainthread = isinstance(thread.current_thread(), thread._MainThread)
        #if mainthread: #XXX: if not mainthread, signal will raise ValueError
        import mystic._signal as signal
        if self._handle_sigint:
            signal.signal(signal.SIGINT, signal.Handler(self))

        # register: cost, termination, ExtraArgs
        cost = self._bootstrap_objective(cost, ExtraArgs)
        if termination is not None: self.SetTermination(termination)
        #XXX: self.Step(cost, termination, ExtraArgs, **settings) ?

        # the main optimization loop
        stop = False
        while not stop:
            stop = self.Step(**settings)  #XXX: remove need to pass settings?
            continue

        # if collapse, then activate any relevant collapses and continue
        self.__stop__ = stop  #HACK: avoid re-evaluation of Termination
        while self._collapse and self.Collapse(disp=disp):
            del self.__stop__  #HACK
            stop = False
            while not stop:
                stop = self.Step(**
                                 settings)  #XXX: move Collapse inside of Step?
                continue
            self.__stop__ = stop  #HACK
        del self.__stop__  #HACK

        # restore default handler for signal interrupts
        if self._handle_sigint:
            signal.signal(signal.SIGINT, signal.default_int_handler)
        return

    def __copy__(self):
        cls = self.__class__
        result = cls.__new__(cls)
        result.__dict__.update(self.__dict__)
        return result

    def __deepcopy__(self, memo):
        import copy
        import dill
        cls = self.__class__
        result = cls.__new__(cls)
        memo[id(self)] = result
        for k, v in self.__dict__.items():
            if v is self._cost:
                setattr(result, k, tuple(dill.copy(i) for i in v))
            else:
                try:  #XXX: work-around instancemethods in python2.6
                    setattr(result, k, copy.deepcopy(v, memo))
                except TypeError:
                    setattr(result, k, dill.copy(v))
        return result

    # extensions to the solver interface
    evaluations = property(__evaluations)
    generations = property(__generations)
    energy_history = property(__energy_history, __set_energy_history)
    solution_history = property(__solution_history, __set_solution_history)
    bestEnergy = property(__bestEnergy, __set_bestEnergy)
    bestSolution = property(__bestSolution, __set_bestSolution)
    pass
Пример #9
0
    def __init__(self, dim, **kwds):
        """
Takes one initial input:
    dim      -- dimensionality of the problem.

Additional inputs:
    npop     -- size of the trial solution population.       [default = 1]

Important class members:
    nDim, nPop       = dim, npop
    generations      - an iteration counter.
    evaluations      - an evaluation counter.
    bestEnergy       - current best energy.
    bestSolution     - current best parameter set.           [size = dim]
    popEnergy        - set of all trial energy solutions.    [size = npop]
    population       - set of all trial parameter solutions. [size = dim*npop]
    solution_history - history of bestSolution status.       [StepMonitor.x]
    energy_history   - history of bestEnergy status.         [StepMonitor.y]
    signal_handler   - catches the interrupt signal.
        """
        NP = 1
        if kwds.has_key('npop'): NP = kwds['npop']

        self.nDim             = dim
        self.nPop             = NP
        self._init_popEnergy  = inf
        self.popEnergy	      = [self._init_popEnergy] * NP
        self.population	      = [[0.0 for i in range(dim)] for j in range(NP)]
        self.trialSolution    = [0.0] * dim
        self._map_solver      = False
        self._bestEnergy      = None
        self._bestSolution    = None
        self._state           = None
        self._type            = self.__class__.__name__

        self.signal_handler   = None
        self._handle_sigint   = False
        self._useStrictRange  = False
        self._defaultMin      = [-1e3] * dim
        self._defaultMax      = [ 1e3] * dim
        self._strictMin       = []
        self._strictMax       = []
        self._maxiter         = None
        self._maxfun          = None
        self._saveiter        = None
       #self._saveeval        = None

        from mystic.monitors import Null, Monitor
        self._evalmon         = Null()
        self._stepmon         = Monitor()
        self._fcalls          = [0]
        self._energy_history  = None
        self._solution_history= None
        self.id               = None     # identifier (use like "rank" for MPI)

        self._constraints     = lambda x: x
        self._penalty         = lambda x: 0.0
        self._reducer         = None
        self._cost            = (None, None)
        self._termination     = lambda x, *ar, **kw: False if len(ar) < 1 or ar[0] is False or kw.get('info',True) == False else '' #XXX: better default ?
        # (get termination details with self._termination.__doc__)

        import mystic.termination
        self._EARLYEXIT       = mystic.termination.EARLYEXIT
        return
Пример #10
0
class AbstractSolver(object):
    """
AbstractSolver base class for mystic optimizers.
    """

    def __init__(self, dim, **kwds):
        """
Takes one initial input:
    dim      -- dimensionality of the problem.

Additional inputs:
    npop     -- size of the trial solution population.       [default = 1]

Important class members:
    nDim, nPop       = dim, npop
    generations      - an iteration counter.
    evaluations      - an evaluation counter.
    bestEnergy       - current best energy.
    bestSolution     - current best parameter set.           [size = dim]
    popEnergy        - set of all trial energy solutions.    [size = npop]
    population       - set of all trial parameter solutions. [size = dim*npop]
    solution_history - history of bestSolution status.       [StepMonitor.x]
    energy_history   - history of bestEnergy status.         [StepMonitor.y]
    signal_handler   - catches the interrupt signal.
        """
        NP = 1
        if kwds.has_key('npop'): NP = kwds['npop']

        self.nDim             = dim
        self.nPop             = NP
        self._init_popEnergy  = inf
        self.popEnergy	      = [self._init_popEnergy] * NP
        self.population	      = [[0.0 for i in range(dim)] for j in range(NP)]
        self.trialSolution    = [0.0] * dim
        self._map_solver      = False
        self._bestEnergy      = None
        self._bestSolution    = None
        self._state           = None
        self._type            = self.__class__.__name__

        self.signal_handler   = None
        self._handle_sigint   = False
        self._useStrictRange  = False
        self._defaultMin      = [-1e3] * dim
        self._defaultMax      = [ 1e3] * dim
        self._strictMin       = []
        self._strictMax       = []
        self._maxiter         = None
        self._maxfun          = None
        self._saveiter        = None
       #self._saveeval        = None

        from mystic.monitors import Null, Monitor
        self._evalmon         = Null()
        self._stepmon         = Monitor()
        self._fcalls          = [0]
        self._energy_history  = None
        self._solution_history= None
        self.id               = None     # identifier (use like "rank" for MPI)

        self._constraints     = lambda x: x
        self._penalty         = lambda x: 0.0
        self._reducer         = None
        self._cost            = (None, None)
        self._termination     = lambda x, *ar, **kw: False if len(ar) < 1 or ar[0] is False or kw.get('info',True) == False else '' #XXX: better default ?
        # (get termination details with self._termination.__doc__)

        import mystic.termination
        self._EARLYEXIT       = mystic.termination.EARLYEXIT
        return

    def Solution(self):
        """return the best solution"""
        return self.bestSolution

    def __evaluations(self):
        """get the number of function calls"""
        return self._fcalls[0]

    def __generations(self):
        """get the number of iterations"""
        return max(0,len(self.energy_history)-1)
       #return max(0,len(self._stepmon)-1)

    def __energy_history(self):
        """get the energy_history (default: energy_history = _stepmon.y)"""
        if self._energy_history is None: return self._stepmon.y
        return self._energy_history

    def __set_energy_history(self, energy):
        """set the energy_history (energy=None will sync with _stepmon.y)"""
        self._energy_history = energy
        return

    def __solution_history(self):
        """get the solution_history (default: solution_history = _stepmon.x)"""
        if self._solution_history is None: return self._stepmon.x
        return self._solution_history

    def __set_solution_history(self, params):
        """set the solution_history (params=None will sync with _stepmon.x)"""
        self._solution_history = params
        return

    def __bestSolution(self):
        """get the bestSolution (default: bestSolution = population[0])"""
        if self._bestSolution is None: return self.population[0]
        return self._bestSolution

    def __set_bestSolution(self, params):
        """set the bestSolution (params=None will sync with population[0])"""
        self._bestSolution = params
        return

    def __bestEnergy(self):
        """get the bestEnergy (default: bestEnergy = popEnergy[0])"""
        if self._bestEnergy is None: return self.popEnergy[0]
        return self._bestEnergy

    def __set_bestEnergy(self, energy):
        """set the bestEnergy (energy=None will sync with popEnergy[0])"""
        self._bestEnergy = energy
        return

    def SetReducer(self, reducer, arraylike=False):
        """apply a reducer function to the cost function

input::
    - a reducer function of the form: y' = reducer(yk), where yk is a results
      vector and y' is a single value.  Ideally, this method is applied to
      a cost function with a multi-value return, to reduce the output to a
      single value.  If arraylike, the reducer provided should take a single
      array as input and produce a scalar; otherwise, the reducer provided
      should meet the requirements of the python's builtin 'reduce' method 
      (e.g. lambda x,y: x+y), taking two scalars and producing a scalar."""
        if not reducer:
            self._reducer = None
        elif not callable(reducer):
            raise TypeError, "'%s' is not a callable function" % reducer
        elif not arraylike:
            self._reducer = wrap_reducer(reducer)   
        else: #XXX: check if is arraylike?
            self._reducer = reducer
        return

    def SetPenalty(self, penalty):
        """apply a penalty function to the optimization

input::
    - a penalty function of the form: y' = penalty(xk), with y = cost(xk) + y',
      where xk is the current parameter vector. Ideally, this function
      is constructed so a penalty is applied when the desired (i.e. encoded)
      constraints are violated. Equality constraints should be considered
      satisfied when the penalty condition evaluates to zero, while
      inequality constraints are satisfied when the penalty condition
      evaluates to a non-positive number."""
        if not penalty:
            self._penalty = lambda x: 0.0
        elif not callable(penalty):
            raise TypeError, "'%s' is not a callable function" % penalty
        else: #XXX: check for format: y' = penalty(x) ?
            self._penalty = penalty
        return

    def SetConstraints(self, constraints):
        """apply a constraints function to the optimization

input::
    - a constraints function of the form: xk' = constraints(xk),
      where xk is the current parameter vector. Ideally, this function
      is constructed so the parameter vector it passes to the cost function
      will satisfy the desired (i.e. encoded) constraints."""
        if not constraints:
            self._constraints = lambda x: x
        elif not callable(constraints):
            raise TypeError, "'%s' is not a callable function" % constraints
        else: #XXX: check for format: x' = constraints(x) ?
            self._constraints = constraints
        return

    def SetGenerationMonitor(self, monitor, new=False):
        """select a callable to monitor (x, f(x)) after each solver iteration"""
        from mystic.monitors import Null, Monitor#, CustomMonitor
        current = Null() if new else self._stepmon
        if isinstance(monitor, Monitor):  # is Monitor()
            self._stepmon = monitor
            self._stepmon.prepend(current)
        elif isinstance(monitor, Null) or monitor == Null: # is Null() or Null
            self._stepmon = Monitor()  #XXX: don't allow Null
            self._stepmon.prepend(current)
        elif hasattr(monitor, '__module__'):  # is CustomMonitor()
            if monitor.__module__ in ['mystic._genSow']:
                self._stepmon = monitor #FIXME: need .prepend(current)
        else:
            raise TypeError, "'%s' is not a monitor instance" % monitor
        self.energy_history   = self._stepmon.y
        self.solution_history = self._stepmon.x
        return

    def SetEvaluationMonitor(self, monitor, new=False):
        """select a callable to monitor (x, f(x)) after each cost function evaluation"""
        from mystic.monitors import Null, Monitor#, CustomMonitor
        current = Null() if new else self._evalmon
        if isinstance(monitor, (Null, Monitor) ):  # is Monitor() or Null()
            self._evalmon = monitor
            self._evalmon.prepend(current)
        elif monitor == Null:  # is Null
            self._evalmon = monitor()
            self._evalmon.prepend(current)
        elif hasattr(monitor, '__module__'):  # is CustomMonitor()
            if monitor.__module__ in ['mystic._genSow']:
                self._evalmon = monitor #FIXME: need .prepend(current)
        else:
            raise TypeError, "'%s' is not a monitor instance" % monitor
        return

    def SetStrictRanges(self, min=None, max=None):
        """ensure solution is within bounds

input::
    - min, max: must be a sequence of length self.nDim
    - each min[i] should be <= the corresponding max[i]

note::
    SetStrictRanges(None) will remove strict range constraints"""
        if min is False or max is False:
            self._useStrictRange = False
            return
        #XXX: better to use 'defaultMin,defaultMax' or '-inf,inf' ???
        if min == None: min = self._defaultMin
        if max == None: max = self._defaultMax
        # when 'some' of the bounds are given as 'None', replace with default
        for i in range(len(min)): 
            if min[i] == None: min[i] = self._defaultMin[0]
            if max[i] == None: max[i] = self._defaultMax[0]

        min = asarray(min); max = asarray(max)
        if numpy.any(( min > max ),0):
            raise ValueError, "each min[i] must be <= the corresponding max[i]"
        if len(min) != self.nDim:
            raise ValueError, "bounds array must be length %s" % self.nDim
        self._useStrictRange = True
        self._strictMin = min
        self._strictMax = max
        return

    def _clipGuessWithinRangeBoundary(self, x0): #FIXME: use self.trialSolution?
        """ensure that initial guess is set within bounds

input::
    - x0: must be a sequence of length self.nDim"""
       #if len(x0) != self.nDim: #XXX: unnecessary w/ self.trialSolution
       #    raise ValueError, "initial guess must be length %s" % self.nDim
        x0 = asarray(x0)
        lo = self._strictMin
        hi = self._strictMax
        # crop x0 at bounds
        x0[x0<lo] = lo[x0<lo]
        x0[x0>hi] = hi[x0>hi]
        return x0

    def SetInitialPoints(self, x0, radius=0.05):
        """Set Initial Points with Guess (x0)

input::
    - x0: must be a sequence of length self.nDim
    - radius: generate random points within [-radius*x0, radius*x0]
        for i!=0 when a simplex-type initial guess in required"""
        x0 = asfarray(x0)
        rank = len(x0.shape)
        if rank is 0:
            x0 = asfarray([x0])
            rank = 1
        if not -1 < rank < 2:
            raise ValueError, "Initial guess must be a scalar or rank-1 sequence."
        if len(x0) != self.nDim:
            raise ValueError, "Initial guess must be length %s" % self.nDim

        #slightly alter initial values for solvers that depend on randomness
        min = x0*(1-radius)
        max = x0*(1+radius)
        numzeros = len(x0[x0==0])
        min[min==0] = asarray([-radius for i in range(numzeros)])
        max[max==0] = asarray([radius for i in range(numzeros)])
        self.SetRandomInitialPoints(min,max)
        #stick initial values in population[i], i=0
        self.population[0] = x0.tolist()
    
    def SetRandomInitialPoints(self, min=None, max=None):
        """Generate Random Initial Points within given Bounds

input::
    - min, max: must be a sequence of length self.nDim
    - each min[i] should be <= the corresponding max[i]"""
        if min == None: min = self._defaultMin
        if max == None: max = self._defaultMax
       #if numpy.any(( asarray(min) > asarray(max) ),0):
       #    raise ValueError, "each min[i] must be <= the corresponding max[i]"
        if len(min) != self.nDim or len(max) != self.nDim:
            raise ValueError, "bounds array must be length %s" % self.nDim
        # when 'some' of the bounds are given as 'None', replace with default
        for i in range(len(min)): 
            if min[i] == None: min[i] = self._defaultMin[0]
            if max[i] == None: max[i] = self._defaultMax[0]
        import random
        #generate random initial values
        for i in range(len(self.population)):
            for j in range(self.nDim):
                self.population[i][j] = random.uniform(min[j],max[j])

    def SetMultinormalInitialPoints(self, mean, var = None):
        """Generate Initial Points from Multivariate Normal.

input::
    - mean must be a sequence of length self.nDim
    - var can be...
        None: -> it becomes the identity
        scalar: -> var becomes scalar * I
        matrix: -> the variance matrix. must be the right size!
        """
        from numpy.random import multivariate_normal
        assert(len(mean) == self.nDim)
        if var == None:
            var = numpy.eye(self.nDim)
        else:
            try: # scalar ?
                float(var)
            except: # nope. var better be matrix of the right size (no check)
                pass
            else:
                var = var * numpy.eye(self.nDim)
        for i in range(len(self.population)):
            self.population[i] = multivariate_normal(mean, var).tolist()
        return

    def enable_signal_handler(self):
        """enable workflow interrupt handler while solver is running"""
        self._handle_sigint = True

    def disable_signal_handler(self):
        """disable workflow interrupt handler while solver is running"""
        self._handle_sigint = False

    def _generateHandler(self,sigint_callback):
        """factory to generate signal handler

Available switches::
    - sol  --> Print current best solution.
    - cont --> Continue calculation.
    - call --> Executes sigint_callback, if provided.
    - exit --> Exits with current best solution.
"""
        def handler(signum, frame):
            import inspect
            print inspect.getframeinfo(frame)
            print inspect.trace()
            while 1:
                s = raw_input(\
"""
 
 Enter sense switch.

    sol:  Print current best solution.
    cont: Continue calculation.
    call: Executes sigint_callback [%s].
    exit: Exits with current best solution.

 >>> """ % sigint_callback)
                if s.lower() == 'sol': 
                    print self.bestSolution
                elif s.lower() == 'cont': 
                    return
                elif s.lower() == 'call': 
                    # sigint call_back
                    if sigint_callback is not None:
                        sigint_callback(self.bestSolution)
                elif s.lower() == 'exit': 
                    self._EARLYEXIT = True
                    return
                else:
                    print "unknown option : %s" % s
            return
        self.signal_handler = handler
        return

    def SetSaveFrequency(self, generations=None, filename=None, **kwds):
        """set frequency for saving solver restart file

input::
    - generations = number of solver iterations before next save of state
    - filename = name of file in which to save solver state

note::
    SetSaveFrequency(None) will disable saving solver restart file"""
        self._saveiter = generations
       #self._saveeval = evaluations
        self._state = filename
        return

    def SetEvaluationLimits(self, generations=None, evaluations=None, \
                                                    new=False, **kwds):
        """set limits for generations and/or evaluations

input::
    - generations = maximum number of solver iterations (i.e. steps)
    - evaluations = maximum number of function evaluations"""
        self._maxiter = generations
        self._maxfun = evaluations
        # backward compatibility
        if kwds.has_key('maxiter'):
            self._maxiter = kwds['maxiter']
        if kwds.has_key('maxfun'):
            self._maxfun = kwds['maxfun']
        # handle if new (reset counter, instead of extend counter)
        if new:
            if generations is not None:
                self._maxiter += self.generations
            else:
                self._maxiter = "*" #XXX: better as self._newmax = True ?
            if evaluations is not None:
                self._maxfun += self.evaluations
            else:
                self._maxfun = "*"
        return

    def _SetEvaluationLimits(self, iterscale=None, evalscale=None):
        """set the evaluation limits"""
        if iterscale is None: iterscale = 10
        if evalscale is None: evalscale = 1000
        N = len(self.population[0]) # usually self.nDim
        # if SetEvaluationLimits not applied, use the solver default
        if self._maxiter is None:
            self._maxiter = N * self.nPop * iterscale
        elif self._maxiter == "*": # (i.e. None, but 'reset counter') 
            self._maxiter = (N * self.nPop * iterscale) + self.generations
        if self._maxfun is None:
            self._maxfun = N * self.nPop * evalscale
        elif self._maxiter == "*":
            self._maxfun = (N * self.nPop * evalscale) + self.evaluations
        return

    def CheckTermination(self, disp=False, info=False, termination=None):
        """check if the solver meets the given termination conditions

Input::
    - disp = if True, print termination statistics and/or warnings
    - info = if True, return termination message (instead of boolean)
    - termination = termination conditions to check against

Note::
    If no termination conditions are given, the solver's stored
    termination conditions will be used.
        """
        if termination == None:
            termination = self._termination
        # check for termination messages
        msg = termination(self, info=True)
        lim = "EvaluationLimits with %s" % {'evaluations':self._maxfun,
                                            'generations':self._maxiter}

        # push solver internals to scipy.optimize.fmin interface
        if self._fcalls[0] >= self._maxfun and self._maxfun is not None:
            msg = lim #XXX: prefer the default stop ?
            if disp:
                print "Warning: Maximum number of function evaluations has "\
                      "been exceeded."
        elif self.generations >= self._maxiter and self._maxiter is not None:
            msg = lim #XXX: prefer the default stop ?
            if disp:
                print "Warning: Maximum number of iterations has been exceeded"
        elif msg and disp:
            print "Optimization terminated successfully."
            print "         Current function value: %f" % self.bestEnergy
            print "         Iterations: %d" % self.generations
            print "         Function evaluations: %d" % self._fcalls[0]

        if info:
            return msg
        return bool(msg)

    def SetTermination(self, termination):
        """set the termination conditions"""
        #XXX: validate that termination is a 'condition' ?
        self._termination = termination
        return

    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)
        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' cost function
        self._cost = (cost, ExtraArgs)
        return cost

    def _bootstrap_decorate(self, cost=None, ExtraArgs=None):
        """HACK to enable not explicitly calling _RegisterObjective"""
        args = None
        if cost == None: # 'use existing cost'
            cost,args = self._cost # use args, unless override with ExtraArgs
        if ExtraArgs != None: args = ExtraArgs
        if self._cost[0] == None: # '_RegisterObjective not yet called'
            if args is None: args = ()
            cost = self._RegisterObjective(cost, args)
        return cost

    def Step(self, cost=None, ExtraArgs=None, **kwds):
        """perform a single optimization iteration

*** this method must be overwritten ***"""
        raise NotImplementedError, "an optimization algorithm was not provided"

    def SaveSolver(self, filename=None, **kwds):
        """save solver state to a restart file"""
        import dill
        if filename == None: # then check if already has registered file
            if self._state == None: # then create a new one
                import tempfile
                self._state = tempfile.mkstemp(suffix='.pkl')[-1]
            filename = self._state
        self._state = filename
        f = file(filename, 'wb')
        try:
            dill.dump(self, f, **kwds)
            self._stepmon.info('DUMPED("%s")' % filename) #XXX: before / after ?
        finally:
            f.close()
        return

    def __save_state(self, force=False):
        """save the solver state, if chosen save frequency is met"""
        # save the last iteration
        if force and bool(self._state):
            self.SaveSolver()
            return
        # save the zeroth iteration
        nonzero = True #XXX: or bool(self.generations) ?
        # after _saveiter generations, then save state
        iters = self._saveiter
        saveiter = bool(iters) and not bool(self.generations % iters)
        if nonzero and saveiter:
            self.SaveSolver()
        #FIXME: if _saveeval (or more) since last check, then save state
       #save = self.evaluations % self._saveeval
        return

    def __load_state(self, solver, **kwds):
        """load solver.__dict__ into self.__dict__; override with kwds"""
        #XXX: should do some filtering on kwds ?
        self.__dict__.update(solver.__dict__, **kwds)
        return

    def _exitMain(self, **kwds):
        """cleanup upon exiting the main optimization loop"""
        pass

    def _process_inputs(self, kwds):
        """process and activate input settings"""
        #allow for inputs that don't conform to AbstractSolver interface
        settings = \
       {'callback':None,     #user-supplied function, called after each step
        'disp':0}            #non-zero to print convergence messages
        [settings.update({i:j}) for (i,j) in kwds.items() if i in settings]
        # backward compatibility
        if kwds.has_key('EvaluationMonitor'): \
           self.SetEvaluationMonitor(kwds.get('EvaluationMonitor'))
        if kwds.has_key('StepMonitor'): \
           self.SetGenerationMonitor(kwds.get('StepMonitor'))
        if kwds.has_key('penalty'): \
           self.SetPenalty(kwds.get('penalty'))
        if kwds.has_key('constraints'): \
           self.SetConstraints(kwds.get('constraints'))
        return settings

    def Solve(self, cost=None, termination=None, sigint_callback=None,
                                                 ExtraArgs=None, **kwds):
        """Minimize a 'cost' function with given termination conditions.

Description:

    Uses an optimization algorith to find the minimum of
    a function of one or more variables.

Inputs:

    cost -- the Python function or method to be minimized.

Additional Inputs:

    termination -- callable object providing termination conditions.
    sigint_callback -- callback function for signal handler.
    ExtraArgs -- extra arguments for cost.

Further Inputs:

    callback -- an optional user-supplied function to call after each
        iteration.  It is called as callback(xk), where xk is
        the current parameter vector.  [default = None]
    disp -- non-zero to print convergence messages.
        """
        # HACK to enable not explicitly calling _RegisterObjective
        cost = self._bootstrap_decorate(cost, ExtraArgs)
        # process and activate input settings
        settings = self._process_inputs(kwds)
        for key in settings:
            exec "%s = settings['%s']" % (key,key)

        # set up signal handler
        import signal
        self._EARLYEXIT = False
        self._generateHandler(sigint_callback) 
        if self._handle_sigint: signal.signal(signal.SIGINT, self.signal_handler)

       ## decorate cost function with bounds, penalties, monitors, etc
       #self._RegisterObjective(cost, ExtraArgs)    #XXX: SetObjective ?
        # register termination function
        if termination is not None:
            self.SetTermination(termination)

        # the initital optimization iteration
        if not len(self._stepmon): # do generation = 0
            self.Step()
            if callback is not None:
                callback(self.bestSolution)
         
            # initialize termination conditions, if needed
            self._termination(self) #XXX: call at generation 0 or always?
        # impose the evaluation limits
        self._SetEvaluationLimits()

        # the main optimization loop
        while not self.CheckTermination() and not self._EARLYEXIT:
            self.Step(**settings)
            if callback is not None:
                callback(self.bestSolution)
        else: self._exitMain()

        # handle signal interrupts
        signal.signal(signal.SIGINT,signal.default_int_handler)

        # log any termination messages
        msg = self.CheckTermination(disp=disp, info=True)
        if msg: self._stepmon.info('STOP("%s")' % msg)
        # save final state
        self.__save_state(force=True)
        return

    # extensions to the solver interface
    evaluations = property(__evaluations )
    generations = property(__generations )
    energy_history = property(__energy_history,__set_energy_history )
    solution_history = property(__solution_history,__set_solution_history )
    bestEnergy = property(__bestEnergy,__set_bestEnergy )
    bestSolution = property(__bestSolution,__set_bestSolution )
    pass
Пример #11
0
class AbstractSolver(object):
    """AbstractSolver base class for mystic optimizers.
    """

    def __init__(self, dim, **kwds):
        """
Takes one initial input::

    dim      -- dimensionality of the problem.

Additional inputs::

    npop     -- size of the trial solution population.       [default = 1]

Important class members::

    nDim, nPop       = dim, npop
    generations      - an iteration counter.
    evaluations      - an evaluation counter.
    bestEnergy       - current best energy.
    bestSolution     - current best parameter set.           [size = dim]
    popEnergy        - set of all trial energy solutions.    [size = npop]
    population       - set of all trial parameter solutions. [size = dim*npop]
    solution_history - history of bestSolution status.       [StepMonitor.x]
    energy_history   - history of bestEnergy status.         [StepMonitor.y]
    signal_handler   - catches the interrupt signal.
        """
        NP = kwds['npop'] if 'npop' in kwds else 1

        self.nDim             = dim
        self.nPop             = NP
        self._init_popEnergy  = inf
        self.popEnergy	      = [self._init_popEnergy] * NP
        self.population	      = [[0.0 for i in range(dim)] for j in range(NP)]
        self.trialSolution    = [0.0] * dim
        self._map_solver      = False
        self._bestEnergy      = None
        self._bestSolution    = None
        self._state           = None
        self._type            = self.__class__.__name__

        self.sigint_callback  = None
        self._handle_sigint   = False
        self._useStrictRange  = False
        self._defaultMin      = [-1e3] * dim
        self._defaultMax      = [ 1e3] * dim
        self._strictMin       = []
        self._strictMax       = []
        self._maxiter         = None
        self._maxfun          = None
        self._saveiter        = None
       #self._saveeval        = None

        from mystic.monitors import Null, Monitor
        self._evalmon         = Null()
        self._stepmon         = Monitor()
        self._fcalls          = [0]
        self._energy_history  = None
        self._solution_history= None
        self.id               = None     # identifier (use like "rank" for MPI)

        self._constraints     = lambda x: x
        self._penalty         = lambda x: 0.0
        self._reducer         = None
        self._cost            = (None, None, None)
        #                       (cost, raw_cost, args) #,callback)
        self._collapse        = False
        self._termination     = lambda x, *ar, **kw: False if len(ar) < 1 or ar[0] is False or (kw['info'] if 'info' in kw else True) == False else '' #XXX: better default ?
        # (get termination details with self._termination.__doc__)

        import mystic.termination as mt
        self._EARLYEXIT       = mt.EARLYEXIT
        self._live            = False 
        return

    def Solution(self):
        """return the best solution"""
        return self.bestSolution

    def __evaluations(self):
        """get the number of function calls"""
        return self._fcalls[0]

    def __generations(self):
        """get the number of iterations"""
        return max(0,len(self._stepmon)-1)

    def __energy_history(self):
        """get the energy_history (default: energy_history = _stepmon._y)"""
        if self._energy_history is None: return self._stepmon._y
        return self._energy_history

    def __set_energy_history(self, energy):
        """set the energy_history (energy=None will sync with _stepmon._y)"""
        self._energy_history = energy
        return

    def __solution_history(self):
        """get the solution_history (default: solution_history = _stepmon.x)"""
        if self._solution_history is None: return self._stepmon.x
        return self._solution_history

    def __set_solution_history(self, params):
        """set the solution_history (params=None will sync with _stepmon.x)"""
        self._solution_history = params
        return

    def __bestSolution(self):
        """get the bestSolution (default: bestSolution = population[0])"""
        if self._bestSolution is None: return self.population[0]
        return self._bestSolution

    def __set_bestSolution(self, params):
        """set the bestSolution (params=None will sync with population[0])"""
        self._bestSolution = params
        return

    def __bestEnergy(self):
        """get the bestEnergy (default: bestEnergy = popEnergy[0])"""
        if self._bestEnergy is None: return self.popEnergy[0]
        return self._bestEnergy

    def __set_bestEnergy(self, energy):
        """set the bestEnergy (energy=None will sync with popEnergy[0])"""
        self._bestEnergy = energy
        return

    def SetReducer(self, reducer, arraylike=False):
        """apply a reducer function to the cost function

input::
    - a reducer function of the form: y' = reducer(yk), where yk is a results
      vector and y' is a single value.  Ideally, this method is applied to
      a cost function with a multi-value return, to reduce the output to a
      single value.  If arraylike, the reducer provided should take a single
      array as input and produce a scalar; otherwise, the reducer provided
      should meet the requirements of the python's builtin 'reduce' method 
      (e.g. lambda x,y: x+y), taking two scalars and producing a scalar."""
        if not reducer:
            self._reducer = None
        elif not isinstance(reducer, collections.Callable):
            raise TypeError("'%s' is not a callable function" % reducer)
        elif not arraylike:
            self._reducer = wrap_reducer(reducer)   
        else: #XXX: check if is arraylike?
            self._reducer = reducer
        return self._update_objective()

    def SetPenalty(self, penalty):
        """apply a penalty function to the optimization

input::
    - a penalty function of the form: y' = penalty(xk), with y = cost(xk) + y',
      where xk is the current parameter vector. Ideally, this function
      is constructed so a penalty is applied when the desired (i.e. encoded)
      constraints are violated. Equality constraints should be considered
      satisfied when the penalty condition evaluates to zero, while
      inequality constraints are satisfied when the penalty condition
      evaluates to a non-positive number."""
        if not penalty:
            self._penalty = lambda x: 0.0
        elif not isinstance(penalty, collections.Callable):
            raise TypeError("'%s' is not a callable function" % penalty)
        else: #XXX: check for format: y' = penalty(x) ?
            self._penalty = penalty
        return self._update_objective()

    def SetConstraints(self, constraints):
        """apply a constraints function to the optimization

input::
    - a constraints function of the form: xk' = constraints(xk),
      where xk is the current parameter vector. Ideally, this function
      is constructed so the parameter vector it passes to the cost function
      will satisfy the desired (i.e. encoded) constraints."""
        if not constraints:
            self._constraints = lambda x: x
        elif not isinstance(constraints, collections.Callable):
            raise TypeError("'%s' is not a callable function" % constraints)
        else: #XXX: check for format: x' = constraints(x) ?
            self._constraints = constraints
        return self._update_objective()

    def SetGenerationMonitor(self, monitor, new=False):
        """select a callable to monitor (x, f(x)) after each solver iteration"""
        from mystic.monitors import Null, Monitor#, CustomMonitor
        current = Null() if new else self._stepmon
        if isinstance(monitor, Monitor):  # is Monitor()
            self._stepmon = monitor
            self._stepmon.prepend(current)
        elif isinstance(monitor, Null) or monitor == Null: # is Null() or Null
            self._stepmon = Monitor()  #XXX: don't allow Null
            self._stepmon.prepend(current)
        elif hasattr(monitor, '__module__'):  # is CustomMonitor()
            if monitor.__module__ in ['mystic._genSow']:
                self._stepmon = monitor #FIXME: need .prepend(current)
        else:
            raise TypeError("'%s' is not a monitor instance" % monitor)
        self.energy_history   = None # sync with self._stepmon
        self.solution_history = None # sync with self._stepmon
        return

    def SetEvaluationMonitor(self, monitor, new=False):
        """select a callable to monitor (x, f(x)) after each cost function evaluation"""
        from mystic.monitors import Null, Monitor#, CustomMonitor
        current = Null() if new else self._evalmon
        if isinstance(monitor, (Null, Monitor) ):  # is Monitor() or Null()
            self._evalmon = monitor
            self._evalmon.prepend(current)
        elif monitor == Null:  # is Null
            self._evalmon = monitor()
            self._evalmon.prepend(current)
        elif hasattr(monitor, '__module__'):  # is CustomMonitor()
            if monitor.__module__ in ['mystic._genSow']:
                self._evalmon = monitor #FIXME: need .prepend(current)
        else:
            raise TypeError("'%s' is not a monitor instance" % monitor)
        return

    def SetStrictRanges(self, min=None, max=None):
        """ensure solution is within bounds

input::
    - min, max: must be a sequence of length self.nDim
    - each min[i] should be <= the corresponding max[i]

note::
    SetStrictRanges(None) will remove strict range constraints"""
        if min is False or max is False:
            self._useStrictRange = False
            return self._update_objective()
        #XXX: better to use 'defaultMin,defaultMax' or '-inf,inf' ???
        if min is None: min = self._defaultMin
        if max is None: max = self._defaultMax
        # when 'some' of the bounds are given as 'None', replace with default
        for i in range(len(min)): 
            if min[i] is None: min[i] = self._defaultMin[0]
            if max[i] is None: max[i] = self._defaultMax[0]

        min = asarray(min); max = asarray(max)
        if numpy.any(( min > max ),0):
            raise ValueError("each min[i] must be <= the corresponding max[i]")
        if len(min) != self.nDim:
            raise ValueError("bounds array must be length %s" % self.nDim)
        self._useStrictRange = True
        self._strictMin = min
        self._strictMax = max
        return self._update_objective()

    def _clipGuessWithinRangeBoundary(self, x0, at=True):
        """ensure that initial guess is set within bounds

input::
    - x0: must be a sequence of length self.nDim"""
       #if len(x0) != self.nDim: #XXX: unnecessary w/ self.trialSolution
       #    raise ValueError, "initial guess must be length %s" % self.nDim
        x0 = asarray(x0)
        bounds = (self._strictMin,self._strictMax)
        if not len(self._strictMin): return x0
        # clip x0 at bounds
        settings = numpy.seterr(all='ignore')
        x_ = x0.clip(*bounds)
        numpy.seterr(**settings)
        if at: return x_
        # clip x0 within bounds
        x_ = x_ != x0
        x0[x_] = random.uniform(self._strictMin,self._strictMax)[x_]
        return x0

    def SetInitialPoints(self, x0, radius=0.05):
        """Set Initial Points with Guess (x0)

input::
    - x0: must be a sequence of length self.nDim
    - radius: generate random points within [-radius*x0, radius*x0]
        for i!=0 when a simplex-type initial guess in required"""
        x0 = asfarray(x0)
        rank = len(x0.shape)
        if rank is 0:
            x0 = asfarray([x0])
            rank = 1
        if not -1 < rank < 2:
            raise ValueError("Initial guess must be a scalar or rank-1 sequence.")
        if len(x0) != self.nDim:
            raise ValueError("Initial guess must be length %s" % self.nDim)

        #slightly alter initial values for solvers that depend on randomness
        min = x0*(1-radius)
        max = x0*(1+radius)
        numzeros = len(x0[x0==0])
        min[min==0] = asarray([-radius for i in range(numzeros)])
        max[max==0] = asarray([radius for i in range(numzeros)])
        self.SetRandomInitialPoints(min,max)
        #stick initial values in population[i], i=0
        self.population[0] = x0.tolist()
    
    def SetRandomInitialPoints(self, min=None, max=None):
        """Generate Random Initial Points within given Bounds

input::
    - min, max: must be a sequence of length self.nDim
    - each min[i] should be <= the corresponding max[i]"""
        if min is None: min = self._defaultMin
        if max is None: max = self._defaultMax
       #if numpy.any(( asarray(min) > asarray(max) ),0):
       #    raise ValueError, "each min[i] must be <= the corresponding max[i]"
        if len(min) != self.nDim or len(max) != self.nDim:
            raise ValueError("bounds array must be length %s" % self.nDim)
        # when 'some' of the bounds are given as 'None', replace with default
        for i in range(len(min)): 
            if min[i] is None: min[i] = self._defaultMin[0]
            if max[i] is None: max[i] = self._defaultMax[0]
        #generate random initial values
        for i in range(len(self.population)):
            for j in range(self.nDim):
                self.population[i][j] = random.uniform(min[j],max[j])

    def SetMultinormalInitialPoints(self, mean, var=None):
        """Generate Initial Points from Multivariate Normal.

input::
    - mean must be a sequence of length self.nDim
    - var can be...
        None: -> it becomes the identity
        scalar: -> var becomes scalar * I
        matrix: -> the variance matrix. must be the right size!
        """
        from mystic.tools import random_state
        rng = random_state(module='numpy.random')
        assert(len(mean) == self.nDim)
        if var is None:
            var = numpy.eye(self.nDim)
        else:
            try: # scalar ?
                float(var)
            except: # nope. var better be matrix of the right size (no check)
                pass
            else:
                var = var * numpy.eye(self.nDim)
        for i in range(len(self.population)):
            self.population[i] = rng.multivariate_normal(mean, var).tolist()
        return

    def SetSampledInitialPoints(self, dist=None):
        """Generate Random Initial Points from Distribution (dist)

input::
    - dist: a mystic.math.Distribution instance
"""
        from mystic.math import Distribution
        _dist = Distribution()
        if dist is None:
            dist = _dist
        elif type(_dist) not in dist.__class__.mro():
            dist = Distribution(dist) #XXX: or throw error?
        for i in range(self.nPop):
            self.population[i] = dist(self.nDim)
        return

    def enable_signal_handler(self):#, callback='*'):
        """enable workflow interrupt handler while solver is running"""
        """ #XXX: disabled, as would add state to solver
input::
    - if a callback function is provided, generate a new handler with
      the given callback.  If callback is None, do not use a callback.
      If callback is not provided, just turn on the existing handler.
"""
       ## always _generate handler on first call
       #if (self.signal_handler is None) and callback == '*':
       #    callback = None
       ## when a new callback is given, generate a new handler
       #if callback != '*':
       #    self._generateHandler(callback)
        self._handle_sigint = True

    def disable_signal_handler(self):
        """disable workflow interrupt handler while solver is running"""
        self._handle_sigint = False

    def SetSaveFrequency(self, generations=None, filename=None, **kwds):
        """set frequency for saving solver restart file

input::
    - generations = number of solver iterations before next save of state
    - filename = name of file in which to save solver state

note::
    SetSaveFrequency(None) will disable saving solver restart file"""
        self._saveiter = generations
       #self._saveeval = evaluations
        self._state = filename
        return

    def SetEvaluationLimits(self, generations=None, evaluations=None, \
                                                    new=False, **kwds):
        """set limits for generations and/or evaluations

input::
    - generations = maximum number of solver iterations (i.e. steps)
    - evaluations = maximum number of function evaluations"""
        # backward compatibility
        self._maxiter = kwds['maxiter'] if 'maxiter' in kwds else generations
        self._maxfun = kwds['maxfun'] if 'maxfun' in kwds else evaluations
        # handle if new (reset counter, instead of extend counter)
        if new:
            if generations is not None:
                self._maxiter += self.generations
            else:
                self._maxiter = "*" #XXX: better as self._newmax = True ?
            if evaluations is not None:
                self._maxfun += self.evaluations
            else:
                self._maxfun = "*"
        return

    def _SetEvaluationLimits(self, iterscale=None, evalscale=None):
        """set the evaluation limits"""
        if iterscale is None: iterscale = 10
        if evalscale is None: evalscale = 1000
        N = len(self.population[0]) # usually self.nDim
        # if SetEvaluationLimits not applied, use the solver default
        if self._maxiter is None:
            self._maxiter = N * self.nPop * iterscale
        elif self._maxiter == "*": # (i.e. None, but 'reset counter') 
            self._maxiter = (N * self.nPop * iterscale) + self.generations
        if self._maxfun is None:
            self._maxfun = N * self.nPop * evalscale
        elif self._maxfun == "*":
            self._maxfun = (N * self.nPop * evalscale) + self.evaluations
        return

    def Terminated(self, disp=False, info=False, termination=None, **kwds):
        """check if the solver meets the given termination conditions

Input::
    - disp = if True, print termination statistics and/or warnings
    - info = if True, return termination message (instead of boolean)
    - termination = termination conditions to check against

Notes::
    If no termination conditions are given, the solver's stored
    termination conditions will be used.
        """
        if termination is None:
            termination = self._termination
        # ensure evaluation limits have been imposed
        self._SetEvaluationLimits()
        # check for termination messages
        msg = termination(self, info=True)
        sig = "SolverInterrupt with %s" % {}
        lim = "EvaluationLimits with %s" % {'evaluations':self._maxfun,
                                            'generations':self._maxiter}

        # push solver internals to scipy.optimize.fmin interface
        if self._fcalls[0] >= self._maxfun and self._maxfun is not None:
            msg = lim #XXX: prefer the default stop ?
            if disp:
                print("Warning: Maximum number of function evaluations has "\
                      "been exceeded.")
        elif self.generations >= self._maxiter and self._maxiter is not None:
            msg = lim #XXX: prefer the default stop ?
            if disp:
                print("Warning: Maximum number of iterations has been exceeded")
        elif self._EARLYEXIT:
            msg = sig
            if disp:
                print("Warning: Optimization terminated with signal interrupt.")
        elif msg and disp:
            print("Optimization terminated successfully.")
            print("         Current function value: %f" % self.bestEnergy)
            print("         Iterations: %d" % self.generations)
            print("         Function evaluations: %d" % self._fcalls[0])

        if info:
            return msg
        return bool(msg)

    def SetTermination(self, termination): # disp ?
        """set the termination conditions"""
        #XXX: validate that termination is a 'condition' ?
        self._termination = termination
        self._collapse = False
        if termination is not None:
            from mystic.termination import state
            stop = state(termination)
            stop = getattr(stop, 'iterkeys', stop.keys)()
            self._collapse = any(key.startswith('Collapse') for key in stop)
        return

    def SetObjective(self, cost, ExtraArgs=None):  # callback=None/False ?
        """decorate the cost function with bounds, penalties, monitors, etc"""
        _cost,_raw,_args = self._cost
        # check if need to 'wrap' or can return the stored cost
        if (cost is None or cost is _raw or cost is _cost) and \
           (ExtraArgs is None or ExtraArgs is _args):
            return
        # get cost and args if None was given
        if cost is None: cost = _raw
        args = _args if ExtraArgs is None else ExtraArgs
        args = () if args is None else args
        # quick validation check (so doesn't screw up internals)
        if not isvalid(cost, [0]*self.nDim, *args):
            try: name = cost.__name__
            except AttributeError: # raise new error for non-callables
                cost(*args)
            validate(cost, None, *args)
           #val = len(args) + 1  #XXX: 'klepto.validate' for better error?
           #msg = '%s() invalid number of arguments (%d given)' % (name, val)
           #raise TypeError(msg)
        # hold on to the 'raw' cost function
        self._cost = (None, cost, ExtraArgs)
        self._live = False
        return

    def Collapsed(self, disp=False, info=False):
        """check if the solver meets the given collapse conditions

Input::
    - disp = if True, print details about the solver state at collapse
    - info = if True, return collapsed state (instead of boolean)
"""
        stop = getattr(self, '__stop__', self.Terminated(info=True))
        import mystic.collapse as ct
        collapses = ct.collapsed(stop) or dict()
        if collapses and disp:
            for (k,v) in getattr(collapses, 'iteritems', collapses.items)():
                print("         %s: %s" % (k.split()[0],v))
           #print("# Collapse at: Generation", self._stepmon._step-1, \
           #      "with", self.bestEnergy, "@\n#", list(self.bestSolution))
        return collapses if info else bool(collapses) 

    def Collapse(self, disp=False):
        """if solver has terminated by collapse, apply the collapse
        (unless both collapse and "stop" are simultaneously satisfied)
        """
       #XXX: return True for "collapse and continue" and False otherwise?
        collapses = self.Collapsed(disp=disp, info=True)
        if collapses: # stop if any Termination is not from Collapse
            stop = getattr(self, '__stop__', self.Terminated(info=True))
            stop = not all(k.startswith("Collapse") for k in stop.split("; "))
            if stop: return {} #XXX: self._collapse = False ?
        else: stop = True
        if collapses: # then stomach a bunch of module imports (yuck)
            import mystic.tools as to
            import mystic.termination as mt
            import mystic.constraints as cn
            import mystic.mask as ma
            # get collapse conditions  #XXX: efficient? 4x loops over collapses
            state = mt.state(self._termination)
            npts = getattr(self._stepmon, '_npts', None)  #XXX: default?
           #conditions = [cn.impose_at(*to.select_params(self,collapses[k])) if state[k].get('target') is None else cn.impose_at(collapses[k],state[k].get('target')) for k in collapses if k.startswith('CollapseAt')]
           #conditions += [cn.impose_as(collapses[k],state[k].get('offset')) for k in collapses if k.startswith('CollapseAs')]
            #randomize = False
            conditions = []; _conditions = []; conditions_ = []
            for k in collapses:
                #FIXME: these should be encapsulted in termination instance
                if k.startswith('CollapseAt'):
                    t = state[k]
                    t = t['target'] if 'target' in t else None
                    if t is None:
                        t = cn.impose_at(*to.select_params(self,collapses[k]))
                    else:
                        t = cn.impose_at(collapses[k],t)
                    conditions.append(t)
                elif k.startswith('CollapseAs'):
                    t = state[k]
                    t = t['offset'] if 'offset' in t else None
                    _conditions.append(cn.impose_as(collapses[k],t))
                elif k.startswith(('CollapseCost','CollapseGrad')):
                    t = state[k]
                    t = t['clip'] if 'clip' in t else True
                    conditions_.append(cn.impose_bounds(collapses[k],clip=t))
                    #randomize = True
            conditions.extend(_conditions)
            conditions.extend(conditions_)
            del _conditions; del conditions_
            # get measure collapse conditions
            if npts: #XXX: faster/better if comes first or last?
                conditions += [cn.impose_measure( npts, [collapses[k] for k in collapses if k.startswith('CollapsePosition')], [collapses[k] for k in collapses if k.startswith('CollapseWeight')] )]

            # update termination and constraints in solver
            constraints = to.chain(*conditions)(self._constraints)
            termination = ma.update_mask(self._termination, collapses)
            self.SetConstraints(constraints)
            self.SetTermination(termination)
            #if randomize: self.SetInitialPoints(self.population[0])
            #print(mt.state(self._termination).keys())
       #return bool(collapses) and not stop
        return collapses

    def _update_objective(self):
        """decorate the cost function with bounds, penalties, monitors, etc"""
        # rewrap the cost if the solver has been run
        if False: # trigger immediately
            self._decorate_objective(*self._cost[1:])
        else: # delay update until _bootstrap
            self.Finalize()
        return

    def _decorate_objective(self, cost, ExtraArgs=None):
        """decorate the 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)
        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 _bootstrap_objective(self, cost=None, ExtraArgs=None):
        """HACK to enable not explicitly calling _decorate_objective"""
        _cost,_raw,_args = self._cost
        # check if need to 'wrap' or can return the stored cost
        if (cost is None or cost is _raw or cost is _cost) and \
           (ExtraArgs is None or ExtraArgs is _args) and self._live:
            return _cost
        # 'wrap' the 'new' cost function with _decorate
        self.SetObjective(cost, ExtraArgs)
        return self._decorate_objective(*self._cost[1:])
        #XXX: when _decorate called, solver._fcalls will be reset ?

    def _Step(self, cost=None, ExtraArgs=None, **kwds):
        """perform a single optimization iteration

*** this method must be overwritten ***"""
        raise NotImplementedError("an optimization algorithm was not provided")

    def SaveSolver(self, filename=None, **kwds):
        """save solver state to a restart file"""
        import dill
        fd = None
        if filename is None: # then check if already has registered file
            if self._state is None: # then create a new one
                import os, tempfile
                fd, self._state = tempfile.mkstemp(suffix='.pkl')
                os.close(fd)
            filename = self._state
        self._state = filename
        f = open(filename, 'wb')
        try:
            dill.dump(self, f, **kwds)
            self._stepmon.info('DUMPED("%s")' % filename) #XXX: before / after ?
        finally:
            f.close()
        return

    def __save_state(self, force=False):
        """save the solver state, if chosen save frequency is met"""
        # save the last iteration
        if force and bool(self._state):
            self.SaveSolver()
            return
        # save the zeroth iteration
        nonzero = True #XXX: or bool(self.generations) ?
        # after _saveiter generations, then save state
        iters = self._saveiter
        saveiter = bool(iters) and not bool(self.generations % iters)
        if nonzero and saveiter:
            self.SaveSolver()
        #FIXME: if _saveeval (or more) since last check, then save state
       #save = self.evaluations % self._saveeval
        return

    def __load_state(self, solver, **kwds):
        """load solver.__dict__ into self.__dict__; override with kwds"""
        #XXX: should do some filtering on kwds ?
        self.__dict__.update(solver.__dict__, **kwds)
        return

    def Finalize(self, **kwds):
        """cleanup upon exiting the main optimization loop"""
        self._live = False
        return

    def _process_inputs(self, kwds):
        """process and activate input settings"""
        #allow for inputs that don't conform to AbstractSolver interface
        #NOTE: not sticky: callback, disp
        #NOTE: sticky: EvaluationMonitor, StepMonitor, penalty, constraints
        settings = \
       {'callback':None,     #user-supplied function, called after each step
        'disp':0}            #non-zero to print convergence messages
        [settings.update({i:j}) for (i,j) in kwds.items() if i in settings]
        # backward compatibility
        if 'EvaluationMonitor' in kwds: \
           self.SetEvaluationMonitor(kwds['EvaluationMonitor'])
        if 'StepMonitor' in kwds: \
           self.SetGenerationMonitor(kwds['StepMonitor'])
        if 'penalty' in kwds: \
           self.SetPenalty(kwds['penalty'])
        if 'constraints' in kwds: \
           self.SetConstraints(kwds['constraints'])
        return settings

    def Step(self, cost=None, termination=None, ExtraArgs=None, **kwds):
        """Take a single optimization step using the given 'cost' function.

Uses an optimization algorithm to take one 'step' toward the minimum of a
function of one or more variables.

Args:
    cost (func, default=None): the function to be minimized: ``y = cost(x)``.
    termination (termination, default=None): termination conditions.
    ExtraArgs (tuple, default=None): extra arguments for cost.
    callback (func, default=None): function to call after each iteration. The
        interface is ``callback(xk)``, with xk the current parameter vector.
    disp (bool, default=False): if True, print convergence messages.

Returns:
    None

Notes:
    To run the solver until termination, call ``Solve()``. Alternately, use
    ``Terminated()`` as the stop condition in a while loop over ``Step``.

    If the algorithm does not meet the given termination conditions after
    the call to ``Step``, the solver may be left in an "out-of-sync" state.
    When abandoning an non-terminated solver, one should call ``Finalize()``
    to make sure the solver is fully returned to a "synchronized" state.
        """
        if 'disp' in kwds:
            disp = bool(kwds['disp'])#; del kwds['disp']
        else: disp = False

        # register: cost, termination, ExtraArgs
        cost = self._bootstrap_objective(cost, ExtraArgs)
        if termination is not None: self.SetTermination(termination)

        # check termination before 'stepping'
        if len(self._stepmon):
            msg = self.Terminated(disp=disp, info=True) or None
        else: msg = None

        # if not terminated, then take a step
        if msg is None:
            self._Step(**kwds) #FIXME: not all kwds are given in __doc__
            if self.Terminated(): # then cleanup/finalize
                self.Finalize()

            # get termination message and log state
            msg = self.Terminated(disp=disp, info=True) or None
            if msg:
                self._stepmon.info('STOP("%s")' % msg)
                self.__save_state(force=True)
        return msg

    def _Solve(self, cost, ExtraArgs, **settings):
        """Run the optimizer to termination, using the given settings.

Args:
    cost (func): the function to be minimized: ``y = cost(x)``.
    ExtraArgs (tuple): tuple of extra arguments for ``cost``.
    settings (dict): optimizer settings (produced by _process_inputs)

Returns:
    None
        """
        disp = settings['disp'] if 'disp' in settings else False

        # the main optimization loop
        stop = False
        while not stop: 
            stop = self.Step(**settings) #XXX: remove need to pass settings?
            continue

        # if collapse, then activate any relevant collapses and continue
        self.__stop__ = stop  #HACK: avoid re-evaluation of Termination
        while self._collapse and self.Collapse(disp=disp):
            del self.__stop__ #HACK
            stop = False
            while not stop:
                stop = self.Step(**settings) #XXX: move Collapse inside of Step?
                continue
            self.__stop__ = stop  #HACK
        del self.__stop__ #HACK
        return

    def Solve(self, cost=None, termination=None, ExtraArgs=None, **kwds):
        """Minimize a 'cost' function with given termination conditions.

Uses an optimization algorithm to find the minimum of a function of one or
more variables.

Args:
    cost (func, default=None): the function to be minimized: ``y = cost(x)``.
    termination (termination, default=None): termination conditions.
    ExtraArgs (tuple, default=None): extra arguments for cost.
    sigint_callback (func, default=None): callback function for signal handler.
    callback (func, default=None): function to call after each iteration. The
        interface is ``callback(xk)``, with xk the current parameter vector.
    disp (bool, default=False): if True, print convergence messages.

Returns:
    None
        """
        # process and activate input settings
        if 'sigint_callback' in kwds:
            self.sigint_callback = kwds['sigint_callback']
            del kwds['sigint_callback']
        else: self.sigint_callback = None
        settings = self._process_inputs(kwds)

        # set up signal handler #FIXME: sigint doesn't behave well in parallel
        self._EARLYEXIT = False  #XXX: why not use EARLYEXIT singleton?

        # activate signal handler
       #import threading as thread
       #mainthread = isinstance(thread.current_thread(), thread._MainThread)
       #if mainthread: #XXX: if not mainthread, signal will raise ValueError
        import mystic._signal as signal
        if self._handle_sigint:
            signal.signal(signal.SIGINT, signal.Handler(self))

        # register: cost, termination, ExtraArgs
        cost = self._bootstrap_objective(cost, ExtraArgs)
        if termination is not None: self.SetTermination(termination)
        #XXX: self.Step(cost, termination, ExtraArgs, **settings) ?

        # run the optimizer to termination
        self._Solve(cost, ExtraArgs, **settings)

        # restore default handler for signal interrupts
        if self._handle_sigint:
            signal.signal(signal.SIGINT, signal.default_int_handler)
        return

    def __copy__(self):
        cls = self.__class__
        result = cls.__new__(cls)
        result.__dict__.update(self.__dict__)
        return result

    def __deepcopy__(self, memo):
        import copy
        import dill
        cls = self.__class__
        result = cls.__new__(cls)
        memo[id(self)] = result
        for k, v in self.__dict__.items():
            if v is self._cost:
                setattr(result, k, tuple(dill.copy(i) for i in v))
            else:
                try: #XXX: work-around instancemethods in python2.6
                    setattr(result, k, copy.deepcopy(v, memo))
                except TypeError:
                    setattr(result, k, dill.copy(v))
        return result

    # extensions to the solver interface
    evaluations = property(__evaluations )
    generations = property(__generations )
    energy_history = property(__energy_history,__set_energy_history )
    solution_history = property(__solution_history,__set_solution_history )
    bestEnergy = property(__bestEnergy,__set_bestEnergy )
    bestSolution = property(__bestSolution,__set_bestSolution )
    pass
    def Solve(self, cost, termination=None, ExtraArgs=(), **kwds):
        """Minimize a 'cost' function with given termination conditions.

Uses an ensemble of optimizers to find the minimum of a function of one or
more variables.

Args:
    cost (func, default=None): the function to be minimized: ``y = cost(x)``.
    termination (termination, default=None): termination conditions.
    ExtraArgs (tuple, default=None): extra arguments for cost.
    sigint_callback (func, default=None): callback function for signal handler.
    callback (func, default=None): function to call after each iteration. The
        interface is ``callback(xk)``, with xk the current parameter vector.
    disp (bool, default=False): if True, print convergence messages.

Returns:
    None
        """
        # process and activate input settings
        if 'sigint_callback' in kwds:
            self.sigint_callback = kwds['sigint_callback']
            del kwds['sigint_callback']
        else:
            self.sigint_callback = None
        settings = self._process_inputs(kwds)
        disp = settings['disp'] if 'disp' in settings else False
        echo = settings['callback'] if 'callback' in settings else None
        #       for key in settings:
        #           exec "%s = settings['%s']" % (key,key)
        if disp in ['verbose', 'all']: verbose = True
        else: verbose = False
        #-------------------------------------------------------------

        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)

        # set up signal handler
        #self._EARLYEXIT = False

        # activate signal_handler
        #import threading as thread
        #mainthread = isinstance(thread.current_thread(), thread._MainThread)
        #if mainthread: #XXX: if not mainthread, signal will raise ValueError
        import mystic._signal as signal
        if self._handle_sigint:
            signal.signal(signal.SIGINT, signal.Handler(self))

        # register termination function
        if termination is not None: self.SetTermination(termination)

        # get the nested solver instance
        solver = self._AbstractEnsembleSolver__get_solver_instance()
        #-------------------------------------------------------------

        # generate starting points
        initial_values = self._InitialPoints()

        # run optimizer for each grid point
        from copy import deepcopy as _copy
        op = [_copy(solver) for i in range(len(initial_values))]
        #cf = [cost for i in range(len(initial_values))]
        vb = [verbose for i in range(len(initial_values))]
        cb = [echo for i in range(len(initial_values))]  #XXX: remove?
        at = self.id if self.id else 0  # start at self.id
        id = range(at, at + len(initial_values))

        # generate the local_optimize function
        def local_optimize(solver, x0, rank=None, disp=False, callback=None):
            from copy import deepcopy as _copy
            from mystic.tools import isNull
            solver.id = rank
            solver.SetInitialPoints(x0)
            if solver._useStrictRange:  #XXX: always, settable, or sync'd ?
                solver.SetStrictRanges(min=solver._strictMin, \
                                       max=solver._strictMax) # or lower,upper ?
            solver.Solve(cost, disp=disp, callback=callback)
            sm = solver._stepmon
            em = solver._evalmon
            if isNull(sm): sm = ([], [], [], [])
            else:
                sm = (_copy(sm._x), _copy(sm._y), _copy(sm._id),
                      _copy(sm._info))
            if isNull(em): em = ([], [], [], [])
            else:
                em = (_copy(em._x), _copy(em._y), _copy(em._id),
                      _copy(em._info))
            return solver, sm, em

        # map:: solver = local_optimize(solver, x0, id, verbose)
        results = list(self._map(local_optimize, op, initial_values, id, \
                                                 vb, cb, **self._mapconfig))

        # save initial state
        self._AbstractSolver__save_state()
        #XXX: HACK TO GET CONTENT OF ALL MONITORS
        # reconnect monitors; save all solvers
        from mystic.monitors import Monitor
        while results:  #XXX: option to not save allSolvers? skip this and _copy
            _solver, _stepmon, _evalmon = results.pop()
            sm = Monitor()
            sm._x, sm._y, sm._id, sm._info = _stepmon
            _solver._stepmon.extend(sm)
            del sm
            em = Monitor()
            em._x, em._y, em._id, em._info = _evalmon
            _solver._evalmon.extend(em)
            del em
            self._allSolvers[len(results)] = _solver
        del results, _solver, _stepmon, _evalmon
        #XXX: END HACK

        # get the results with the lowest energy
        self._bestSolver = self._allSolvers[0]
        bestpath = self._bestSolver._stepmon
        besteval = self._bestSolver._evalmon
        self._total_evals = self._bestSolver.evaluations
        for solver in self._allSolvers[1:]:
            self._total_evals += solver.evaluations  # add func evals
            if solver.bestEnergy < self._bestSolver.bestEnergy:
                self._bestSolver = solver
                bestpath = solver._stepmon
                besteval = solver._evalmon

        # return results to internals
        self.population = self._bestSolver.population  #XXX: pointer? copy?
        self.popEnergy = self._bestSolver.popEnergy  #XXX: pointer? copy?
        self.bestSolution = self._bestSolver.bestSolution  #XXX: pointer? copy?
        self.bestEnergy = self._bestSolver.bestEnergy
        self.trialSolution = self._bestSolver.trialSolution  #XXX: pointer? copy?
        self._fcalls = self._bestSolver._fcalls  #XXX: pointer? copy?
        self._maxiter = self._bestSolver._maxiter
        self._maxfun = self._bestSolver._maxfun

        # write 'bests' to monitors  #XXX: non-best monitors may be useful too
        self._stepmon = bestpath  #XXX: pointer? copy?
        self._evalmon = besteval  #XXX: pointer? copy?
        self.energy_history = None
        self.solution_history = None
        #from mystic.tools import isNull
        #if isNull(bestpath):
        #    self._stepmon = bestpath
        #else:
        #    for i in range(len(bestpath.y)):
        #        self._stepmon(bestpath.x[i], bestpath.y[i], self.id)
        #        #XXX: could apply callback here, or in exec'd code
        #if isNull(besteval):
        #    self._evalmon = besteval
        #else:
        #    for i in range(len(besteval.y)):
        #        self._evalmon(besteval.x[i], besteval.y[i])
        #-------------------------------------------------------------

        # restore default handler for signal interrupts
        if self._handle_sigint:
            signal.signal(signal.SIGINT, signal.default_int_handler)

        # log any termination messages
        msg = self.Terminated(disp=disp, info=True)
        if msg: self._stepmon.info('STOP("%s")' % msg)
        # save final state
        self._AbstractSolver__save_state(force=True)
        return
Пример #13
0
    def _Solve(self, cost, ExtraArgs, **settings):  #XXX: self._cost?
        """Run the optimizer to termination, using the given settings.

Args:
    cost (func): the function to be minimized: ``y = cost(x)``.
    ExtraArgs (tuple): tuple of extra arguments for ``cost``.
    settings (dict): optimizer settings (produced by _process_inputs)

Returns:
    None
        """
        #FIXME: 'step' is undocumented (in Solve)
        #NOTE: if Step once, will ensure always uses step=True, unless...
        #TODO: if step=False passed after Step taken... this is still TODO!
        step = settings['step'] if 'step' in settings else False
        if step:  #FIXME: use abstract_solver _Solve
            super(AbstractEnsembleSolver, self)._Solve(cost, ExtraArgs,
                                                       **settings)
            return
            #FIXME: evalmon wrapped around _cost doesn't work (__class__()?)
        #FIXME: fix the above... remove HACK below (and previous evalmon HACKs)
        #FIXME: HACK evalmon wrapped around _cost is disabled (bc it works!)
        evalmon, self._evalmon = self._evalmon, Null()  #XXX: fcalls start=0
        self._live = False  #XXX: redo wrap objective with dummy evalmon
        cost = self._bootstrap_objective(self._cost[1], ExtraArgs)
        self._evalmon = evalmon
        #FIXME: HACK (end) to disable evalmon wrapped around cost

        disp = settings['disp'] if 'disp' in settings else False
        echo = settings['callback'] if 'callback' in settings else None
        if disp in ['verbose', 'all']: verbose = True
        else: verbose = False

        # generate starting points
        if self._is_new(): iv = self._InitialPoints()
        else: iv = [None] * len(self._allSolvers)
        op = self._AbstractEnsembleSolver__init_allSolvers()
        vb = [verbose] * len(op)
        cb = [echo] * len(op)  #XXX: remove?

        # generate the _solve function
        def _solve(solver, x0, disp=False, callback=None):
            from copy import deepcopy as _copy
            from mystic.tools import isNull
            if x0 is not None:
                solver.SetInitialPoints(x0)
                if solver._useStrictRange:  #XXX: always, settable, or sync'd ?
                    solver.SetStrictRanges(solver._strictMin,
                                           solver._strictMax)
            _term = (solver._live is False) and solver.Terminated()
            if _term is True:
                solver._live = True  #XXX: HACK don't reset _fcalls
            if solver._cost[1] is None:  #XXX: HACK for configured NestedSolver
                solver.SetObjective(cost, ExtraArgs=ExtraArgs)
            solver.Solve(disp=disp, callback=callback)
            if _term is True: solver._live = False
            sm = solver._stepmon
            em = solver._evalmon
            if isNull(sm): sm = ([], [], [], [])
            else:
                sm = _copy(sm)
                sm = (sm._x, sm._y, sm._id, sm._info)
            if isNull(em): em = ([], [], [], [])
            else:
                em = _copy(em)
                em = (em._x, em._y, em._id, em._info)
            return solver, sm, em

        # map:: solver = _solve(solver, x0, id, verbose)
        results = list(self._map(_solve, op, iv, vb, cb, **self._mapconfig))
        del op, iv, vb, cb

        # save initial state
        self._AbstractSolver__save_state()
        # save results to allSolvers
        self._AbstractEnsembleSolver__update_allSolvers(results)
        del results
        # update state from bestSolver
        self._AbstractEnsembleSolver__update_state()

        # log any termination messages
        msg = self.Terminated(disp=disp, info=True)
        if msg: self._stepmon.info('STOP("%s")' % msg)
        # save final state
        self._AbstractSolver__save_state(force=True)
        return
Пример #14
0
class AbstractSolver(object):
    """
AbstractSolver base class for mystic optimizers.
    """
    def __init__(self, dim, **kwds):
        """
Takes one initial input:
    dim      -- dimensionality of the problem.

Additional inputs:
    npop     -- size of the trial solution population.       [default = 1]

Important class members:
    nDim, nPop       = dim, npop
    generations      - an iteration counter.
    evaluations      - an evaluation counter.
    bestEnergy       - current best energy.
    bestSolution     - current best parameter set.           [size = dim]
    popEnergy        - set of all trial energy solutions.    [size = npop]
    population       - set of all trial parameter solutions. [size = dim*npop]
    solution_history - history of bestSolution status.       [StepMonitor.x]
    energy_history   - history of bestEnergy status.         [StepMonitor.y]
    signal_handler   - catches the interrupt signal.
        """
        NP = 1
        if kwds.has_key('npop'): NP = kwds['npop']

        self.nDim = dim
        self.nPop = NP
        self._init_popEnergy = inf
        self.popEnergy = [self._init_popEnergy] * NP
        self.population = [[0.0 for i in range(dim)] for j in range(NP)]
        self.trialSolution = [0.0] * dim
        self._map_solver = False
        self._bestEnergy = None
        self._bestSolution = None
        self._state = None
        self._type = self.__class__.__name__

        self.signal_handler = None
        self._handle_sigint = False
        self._useStrictRange = False
        self._defaultMin = [-1e3] * dim
        self._defaultMax = [1e3] * dim
        self._strictMin = []
        self._strictMax = []
        self._maxiter = None
        self._maxfun = None
        self._saveiter = None
        #self._saveeval        = None

        from mystic.monitors import Null, Monitor
        self._evalmon = Null()
        self._stepmon = Monitor()
        self._fcalls = [0]
        self._energy_history = None
        self._solution_history = None
        self.id = None  # identifier (use like "rank" for MPI)

        self._constraints = lambda x: x
        self._penalty = lambda x: 0.0
        self._cost = (None, None)
        self._termination = lambda x, *ar, **kw: False if len(ar) < 1 or ar[
            0] is False or kw.get('info', True
                                  ) == False else ''  #XXX: better default ?
        # (get termination details with self._termination.__doc__)

        import mystic.termination
        self._EARLYEXIT = mystic.termination.EARLYEXIT
        return

    def Solution(self):
        """return the best solution"""
        return self.bestSolution

    def __evaluations(self):
        """get the number of function calls"""
        return self._fcalls[0]

    def __generations(self):
        """get the number of iterations"""
        return max(0, len(self.energy_history) - 1)

    #return max(0,len(self._stepmon)-1)

    def __energy_history(self):
        """get the energy_history (default: energy_history = _stepmon.y)"""
        if self._energy_history is None: return self._stepmon.y
        return self._energy_history

    def __set_energy_history(self, energy):
        """set the energy_history (energy=None will sync with _stepmon.y)"""
        self._energy_history = energy
        return

    def __solution_history(self):
        """get the solution_history (default: solution_history = _stepmon.x)"""
        if self._solution_history is None: return self._stepmon.x
        return self._solution_history

    def __set_solution_history(self, params):
        """set the solution_history (params=None will sync with _stepmon.x)"""
        self._solution_history = params
        return

    def __bestSolution(self):
        """get the bestSolution (default: bestSolution = population[0])"""
        if self._bestSolution is None: return self.population[0]
        return self._bestSolution

    def __set_bestSolution(self, params):
        """set the bestSolution (params=None will sync with population[0])"""
        self._bestSolution = params
        return

    def __bestEnergy(self):
        """get the bestEnergy (default: bestEnergy = popEnergy[0])"""
        if self._bestEnergy is None: return self.popEnergy[0]
        return self._bestEnergy

    def __set_bestEnergy(self, energy):
        """set the bestEnergy (energy=None will sync with popEnergy[0])"""
        self._bestEnergy = energy
        return

    def SetPenalty(self, penalty):
        """apply a penalty function to the optimization

input::
    - a penalty function of the form: y' = penalty(xk), with y = cost(xk) + y',
      where xk is the current parameter vector. Ideally, this function
      is constructed so a penalty is applied when the desired (i.e. encoded)
      constraints are violated. Equality constraints should be considered
      satisfied when the penalty condition evaluates to zero, while
      inequality constraints are satisfied when the penalty condition
      evaluates to a non-positive number."""
        if not penalty:
            self._penalty = lambda x: 0.0
        elif not callable(penalty):
            raise TypeError, "'%s' is not a callable function" % penalty
        else:  #XXX: check for format: y' = penalty(x) ?
            self._penalty = penalty
        return

    def SetConstraints(self, constraints):
        """apply a constraints function to the optimization

input::
    - a constraints function of the form: xk' = constraints(xk),
      where xk is the current parameter vector. Ideally, this function
      is constructed so the parameter vector it passes to the cost function
      will satisfy the desired (i.e. encoded) constraints."""
        if not constraints:
            self._constraints = lambda x: x
        elif not callable(constraints):
            raise TypeError, "'%s' is not a callable function" % constraints
        else:  #XXX: check for format: x' = constraints(x) ?
            self._constraints = constraints
        return

    def SetGenerationMonitor(self, monitor, new=False):
        """select a callable to monitor (x, f(x)) after each solver iteration"""
        from mystic.monitors import Null, Monitor  #, CustomMonitor
        current = Null() if new else self._stepmon
        if isinstance(monitor, Monitor):  # is Monitor()
            self._stepmon = monitor
            self._stepmon.prepend(current)
        elif isinstance(monitor, Null) or monitor == Null:  # is Null() or Null
            self._stepmon = Monitor()  #XXX: don't allow Null
            self._stepmon.prepend(current)
        elif hasattr(monitor, '__module__'):  # is CustomMonitor()
            if monitor.__module__ in ['mystic._genSow']:
                self._stepmon = monitor  #FIXME: need .prepend(current)
        else:
            raise TypeError, "'%s' is not a monitor instance" % monitor
        self.energy_history = self._stepmon.y
        self.solution_history = self._stepmon.x
        return

    def SetEvaluationMonitor(self, monitor, new=False):
        """select a callable to monitor (x, f(x)) after each cost function evaluation"""
        from mystic.monitors import Null, Monitor  #, CustomMonitor
        current = Null() if new else self._evalmon
        if isinstance(monitor, (Null, Monitor)):  # is Monitor() or Null()
            self._evalmon = monitor
            self._evalmon.prepend(current)
        elif monitor == Null:  # is Null
            self._evalmon = monitor()
            self._evalmon.prepend(current)
        elif hasattr(monitor, '__module__'):  # is CustomMonitor()
            if monitor.__module__ in ['mystic._genSow']:
                self._evalmon = monitor  #FIXME: need .prepend(current)
        else:
            raise TypeError, "'%s' is not a monitor instance" % monitor
        return

    def SetStrictRanges(self, min=None, max=None):
        """ensure solution is within bounds

input::
    - min, max: must be a sequence of length self.nDim
    - each min[i] should be <= the corresponding max[i]

note::
    SetStrictRanges(None) will remove strict range constraints"""
        if min is False or max is False:
            self._useStrictRange = False
            return
        #XXX: better to use 'defaultMin,defaultMax' or '-inf,inf' ???
        if min == None: min = self._defaultMin
        if max == None: max = self._defaultMax
        # when 'some' of the bounds are given as 'None', replace with default
        for i in range(len(min)):
            if min[i] == None: min[i] = self._defaultMin[0]
            if max[i] == None: max[i] = self._defaultMax[0]

        min = asarray(min)
        max = asarray(max)
        if numpy.any((min > max), 0):
            raise ValueError, "each min[i] must be <= the corresponding max[i]"
        if len(min) != self.nDim:
            raise ValueError, "bounds array must be length %s" % self.nDim
        self._useStrictRange = True
        self._strictMin = min
        self._strictMax = max
        return

    def _clipGuessWithinRangeBoundary(self,
                                      x0):  #FIXME: use self.trialSolution?
        """ensure that initial guess is set within bounds

input::
    - x0: must be a sequence of length self.nDim"""
        #if len(x0) != self.nDim: #XXX: unnecessary w/ self.trialSolution
        #    raise ValueError, "initial guess must be length %s" % self.nDim
        x0 = asarray(x0)
        lo = self._strictMin
        hi = self._strictMax
        # crop x0 at bounds
        x0[x0 < lo] = lo[x0 < lo]
        x0[x0 > hi] = hi[x0 > hi]
        return x0

    def SetInitialPoints(self, x0, radius=0.05):
        """Set Initial Points with Guess (x0)

input::
    - x0: must be a sequence of length self.nDim
    - radius: generate random points within [-radius*x0, radius*x0]
        for i!=0 when a simplex-type initial guess in required"""
        x0 = asfarray(x0)
        rank = len(x0.shape)
        if rank is 0:
            x0 = asfarray([x0])
            rank = 1
        if not -1 < rank < 2:
            raise ValueError, "Initial guess must be a scalar or rank-1 sequence."
        if len(x0) != self.nDim:
            raise ValueError, "Initial guess must be length %s" % self.nDim

        #slightly alter initial values for solvers that depend on randomness
        min = x0 * (1 - radius)
        max = x0 * (1 + radius)
        numzeros = len(x0[x0 == 0])
        min[min == 0] = asarray([-radius for i in range(numzeros)])
        max[max == 0] = asarray([radius for i in range(numzeros)])
        self.SetRandomInitialPoints(min, max)
        #stick initial values in population[i], i=0
        self.population[0] = x0.tolist()

    def SetRandomInitialPoints(self, min=None, max=None):
        """Generate Random Initial Points within given Bounds

input::
    - min, max: must be a sequence of length self.nDim
    - each min[i] should be <= the corresponding max[i]"""
        if min == None: min = self._defaultMin
        if max == None: max = self._defaultMax
        #if numpy.any(( asarray(min) > asarray(max) ),0):
        #    raise ValueError, "each min[i] must be <= the corresponding max[i]"
        if len(min) != self.nDim or len(max) != self.nDim:
            raise ValueError, "bounds array must be length %s" % self.nDim
        # when 'some' of the bounds are given as 'None', replace with default
        for i in range(len(min)):
            if min[i] == None: min[i] = self._defaultMin[0]
            if max[i] == None: max[i] = self._defaultMax[0]
        import random
        #generate random initial values
        for i in range(len(self.population)):
            for j in range(self.nDim):
                self.population[i][j] = random.uniform(min[j], max[j])

    def SetMultinormalInitialPoints(self, mean, var=None):
        """Generate Initial Points from Multivariate Normal.

input::
    - mean must be a sequence of length self.nDim
    - var can be...
        None: -> it becomes the identity
        scalar: -> var becomes scalar * I
        matrix: -> the variance matrix. must be the right size!
        """
        from numpy.random import multivariate_normal
        assert (len(mean) == self.nDim)
        if var == None:
            var = numpy.eye(self.nDim)
        else:
            try:  # scalar ?
                float(var)
            except:  # nope. var better be matrix of the right size (no check)
                pass
            else:
                var = var * numpy.eye(self.nDim)
        for i in range(len(self.population)):
            self.population[i] = multivariate_normal(mean, var).tolist()
        return

    def enable_signal_handler(self):
        """enable workflow interrupt handler while solver is running"""
        self._handle_sigint = True

    def disable_signal_handler(self):
        """disable workflow interrupt handler while solver is running"""
        self._handle_sigint = False

    def _generateHandler(self, sigint_callback):
        """factory to generate signal handler

Available switches::
    - sol  --> Print current best solution.
    - cont --> Continue calculation.
    - call --> Executes sigint_callback, if provided.
    - exit --> Exits with current best solution.
"""
        def handler(signum, frame):
            import inspect
            print inspect.getframeinfo(frame)
            print inspect.trace()
            while 1:
                s = raw_input(\
"""
 
 Enter sense switch.

    sol:  Print current best solution.
    cont: Continue calculation.
    call: Executes sigint_callback [%s].
    exit: Exits with current best solution.

 >>> """ % sigint_callback)
                if s.lower() == 'sol':
                    print self.bestSolution
                elif s.lower() == 'cont':
                    return
                elif s.lower() == 'call':
                    # sigint call_back
                    if sigint_callback is not None:
                        sigint_callback(self.bestSolution)
                elif s.lower() == 'exit':
                    self._EARLYEXIT = True
                    return
                else:
                    print "unknown option : %s" % s
            return

        self.signal_handler = handler
        return

    def SetSaveFrequency(self, generations=None, filename=None, **kwds):
        """set frequency for saving solver restart file

input::
    - generations = number of solver iterations before next save of state
    - filename = name of file in which to save solver state

note::
    SetSaveFrequency(None) will disable saving solver restart file"""
        self._saveiter = generations
        #self._saveeval = evaluations
        self._state = filename
        return

    def SetEvaluationLimits(self, generations=None, evaluations=None, \
                                                    new=False, **kwds):
        """set limits for generations and/or evaluations

input::
    - generations = maximum number of solver iterations (i.e. steps)
    - evaluations = maximum number of function evaluations"""
        self._maxiter = generations
        self._maxfun = evaluations
        # backward compatibility
        if kwds.has_key('maxiter'):
            self._maxiter = kwds['maxiter']
        if kwds.has_key('maxfun'):
            self._maxfun = kwds['maxfun']
        # handle if new (reset counter, instead of extend counter)
        if new:
            if generations is not None:
                self._maxiter += self.generations
            else:
                self._maxiter = "*"  #XXX: better as self._newmax = True ?
            if evaluations is not None:
                self._maxfun += self.evaluations
            else:
                self._maxfun = "*"
        return

    def _SetEvaluationLimits(self, iterscale=None, evalscale=None):
        """set the evaluation limits"""
        if iterscale is None: iterscale = 10
        if evalscale is None: evalscale = 1000
        N = len(self.population[0])  # usually self.nDim
        # if SetEvaluationLimits not applied, use the solver default
        if self._maxiter is None:
            self._maxiter = N * self.nPop * iterscale
        elif self._maxiter == "*":  # (i.e. None, but 'reset counter')
            self._maxiter = (N * self.nPop * iterscale) + self.generations
        if self._maxfun is None:
            self._maxfun = N * self.nPop * evalscale
        elif self._maxiter == "*":
            self._maxfun = (N * self.nPop * evalscale) + self.evaluations
        return

    def CheckTermination(self, disp=False, info=False, termination=None):
        """check if the solver meets the given termination conditions

Input::
    - disp = if True, print termination statistics and/or warnings
    - info = if True, return termination message (instead of boolean)
    - termination = termination conditions to check against

Note::
    If no termination conditions are given, the solver's stored
    termination conditions will be used.
        """
        if termination == None:
            termination = self._termination
        # check for termination messages
        msg = termination(self, info=True)
        lim = "EvaluationLimits with %s" % {
            'evaluations': self._maxfun,
            'generations': self._maxiter
        }

        # push solver internals to scipy.optimize.fmin interface
        if self._fcalls[0] >= self._maxfun and self._maxfun is not None:
            msg = lim  #XXX: prefer the default stop ?
            if disp:
                print "Warning: Maximum number of function evaluations has "\
                      "been exceeded."
        elif self.generations >= self._maxiter and self._maxiter is not None:
            msg = lim  #XXX: prefer the default stop ?
            if disp:
                print "Warning: Maximum number of iterations has been exceeded"
        elif msg and disp:
            print "Optimization terminated successfully."
            print "         Current function value: %f" % self.bestEnergy
            print "         Iterations: %d" % self.generations
            print "         Function evaluations: %d" % self._fcalls[0]

        if info:
            return msg
        return bool(msg)

    def SetTermination(self, termination):
        """set the termination conditions"""
        #XXX: validate that termination is a 'condition' ?
        self._termination = termination
        return

    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)
        cost = wrap_nested(cost, self._constraints)
        # hold on to the 'wrapped' cost function
        self._cost = (cost, ExtraArgs)
        return cost

    def _bootstrap_decorate(self, cost=None, ExtraArgs=None):
        """HACK to enable not explicitly calling _RegisterObjective"""
        args = None
        if cost == None:  # 'use existing cost'
            cost, args = self._cost  # use args, unless override with ExtraArgs
        if ExtraArgs != None: args = ExtraArgs
        if self._cost[0] == None:  # '_RegisterObjective not yet called'
            if args is None: args = ()
            cost = self._RegisterObjective(cost, args)
        return cost

    def Step(self, cost=None, ExtraArgs=None, **kwds):
        """perform a single optimization iteration

*** this method must be overwritten ***"""
        raise NotImplementedError, "an optimization algorithm was not provided"

    def SaveSolver(self, filename=None, **kwds):
        """save solver state to a restart file"""
        import dill
        if filename == None:  # then check if already has registered file
            if self._state == None:  # then create a new one
                import tempfile
                self._state = tempfile.mkstemp(suffix='.pkl')[-1]
            filename = self._state
        self._state = filename
        f = file(filename, 'wb')
        try:
            dill.dump(self, f, **kwds)
            self._stepmon.info('DUMPED("%s")' %
                               filename)  #XXX: before / after ?
        finally:
            f.close()
        return

    def __save_state(self, force=False):
        """save the solver state, if chosen save frequency is met"""
        # save the last iteration
        if force and bool(self._state):
            self.SaveSolver()
            return
        # save the zeroth iteration
        nonzero = True  #XXX: or bool(self.generations) ?
        # after _saveiter generations, then save state
        iters = self._saveiter
        saveiter = bool(iters) and not bool(self.generations % iters)
        if nonzero and saveiter:
            self.SaveSolver()
        #FIXME: if _saveeval (or more) since last check, then save state

    #save = self.evaluations % self._saveeval
        return

    def __load_state(self, solver, **kwds):
        """load solver.__dict__ into self.__dict__; override with kwds"""
        #XXX: should do some filtering on kwds ?
        self.__dict__.update(solver.__dict__, **kwds)
        return

    def _exitMain(self, **kwds):
        """cleanup upon exiting the main optimization loop"""
        pass

    def _process_inputs(self, kwds):
        """process and activate input settings"""
        #allow for inputs that don't conform to AbstractSolver interface
        settings = \
       {'callback':None,     #user-supplied function, called after each step
        'disp':0}            #non-zero to print convergence messages
        [settings.update({i: j}) for (i, j) in kwds.items() if i in settings]
        # backward compatibility
        if kwds.has_key('EvaluationMonitor'):             \
                       self.SetEvaluationMonitor(kwds.get('EvaluationMonitor'))
        if kwds.has_key('StepMonitor'):             \
                       self.SetGenerationMonitor(kwds.get('StepMonitor'))
        if kwds.has_key('penalty'): \
           self.SetPenalty(kwds.get('penalty'))
        if kwds.has_key('constraints'):             \
                       self.SetConstraints(kwds.get('constraints'))
        return settings

    def Solve(self,
              cost=None,
              termination=None,
              sigint_callback=None,
              ExtraArgs=None,
              **kwds):
        """Minimize a 'cost' function with given termination conditions.

Description:

    Uses an optimization algorith to find the minimum of
    a function of one or more variables.

Inputs:

    cost -- the Python function or method to be minimized.

Additional Inputs:

    termination -- callable object providing termination conditions.
    sigint_callback -- callback function for signal handler.
    ExtraArgs -- extra arguments for cost.

Further Inputs:

    callback -- an optional user-supplied function to call after each
        iteration.  It is called as callback(xk), where xk is
        the current parameter vector.  [default = None]
    disp -- non-zero to print convergence messages.
        """
        # HACK to enable not explicitly calling _RegisterObjective
        cost = self._bootstrap_decorate(cost, ExtraArgs)
        # process and activate input settings
        settings = self._process_inputs(kwds)
        for key in settings:
            exec "%s = settings['%s']" % (key, key)

        # set up signal handler
        import signal
        self._EARLYEXIT = False
        self._generateHandler(sigint_callback)
        if self._handle_sigint:
            signal.signal(signal.SIGINT, self.signal_handler)

        ## decorate cost function with bounds, penalties, monitors, etc
        #self._RegisterObjective(cost, ExtraArgs)    #XXX: SetObjective ?
        # register termination function
        if termination is not None:
            self.SetTermination(termination)

        # the initital optimization iteration
        if not len(self._stepmon):  # do generation = 0
            self.Step()
            if callback is not None:
                callback(self.bestSolution)

            # initialize termination conditions, if needed
            self._termination(self)  #XXX: call at generation 0 or always?
        # impose the evaluation limits
        self._SetEvaluationLimits()

        # the main optimization loop
        while not self.CheckTermination() and not self._EARLYEXIT:
            self.Step(**settings)
            if callback is not None:
                callback(self.bestSolution)
        else:
            self._exitMain()

        # handle signal interrupts
        signal.signal(signal.SIGINT, signal.default_int_handler)

        # log any termination messages
        msg = self.CheckTermination(disp=disp, info=True)
        if msg: self._stepmon.info('STOP("%s")' % msg)
        # save final state
        self.__save_state(force=True)
        return

    # extensions to the solver interface
    evaluations = property(__evaluations)
    generations = property(__generations)
    energy_history = property(__energy_history, __set_energy_history)
    solution_history = property(__solution_history, __set_solution_history)
    bestEnergy = property(__bestEnergy, __set_bestEnergy)
    bestSolution = property(__bestSolution, __set_bestSolution)
    pass
Пример #15
0
    def Solve(self, cost, termination=None, ExtraArgs=(), **kwds):
        """Minimize a function using batch grid optimization.

Description:

    Uses parallel mapping of solvers on a regular grid to find the
    minimum of a function of one or more variables.

Inputs:

    cost -- the Python function or method to be minimized.

Additional Inputs:

    termination -- callable object providing termination conditions.
    ExtraArgs -- extra arguments for cost.

Further Inputs:

    sigint_callback -- callback function for signal handler.
    callback -- an optional user-supplied function to call after each
        iteration.  It is called as callback(xk), where xk is the
        current parameter vector.                           [default = None]
    disp -- non-zero to print convergence messages.         [default = 0]
        """
        # process and activate input settings
        sigint_callback = kwds.pop('sigint_callback', None)
        settings = self._process_inputs(kwds)
        disp = settings['disp'] if 'disp' in settings else False
        echo = settings['callback'] if 'callback' in settings else None
        #       for key in settings:
        #           exec "%s = settings['%s']" % (key,key)
        if disp in ['verbose', 'all']: verbose = True
        else: verbose = False
        #-------------------------------------------------------------

        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)

        # set up signal handler
        #self._EARLYEXIT = False
        self._generateHandler(sigint_callback)

        # activate signal_handler
        import signal
        if self._handle_sigint:
            signal.signal(signal.SIGINT, self.signal_handler)

        # register termination function
        if termination is not None:
            self.SetTermination(termination)

        # get the nested solver instance
        solver = self._AbstractEnsembleSolver__get_solver_instance()
        #-------------------------------------------------------------

        nbins = self._nbins
        if len(self._strictMax): upper = list(self._strictMax)
        else:
            upper = list(self._defaultMax)
        if len(self._strictMin): lower = list(self._strictMin)
        else:
            lower = list(self._defaultMin)

        # generate arrays of points defining a grid in parameter space
        grid_dimensions = self.nDim
        bins = []
        for i in range(grid_dimensions):
            step = abs(upper[i] - lower[i]) / nbins[i]
            bins.append([lower[i] + (j + 0.5) * step for j in range(nbins[i])])

        # build a grid of starting points
        from mystic.math import gridpts
        initial_values = gridpts(bins)

        # run optimizer for each grid point
        from copy import deepcopy as _copy
        op = [_copy(solver) for i in range(len(initial_values))]
        #cf = [cost for i in range(len(initial_values))]
        vb = [verbose for i in range(len(initial_values))]
        cb = [echo for i in range(len(initial_values))]  #XXX: remove?
        at = self.id if self.id else 0  # start at self.id
        id = range(at, at + len(initial_values))

        # generate the local_optimize function
        def local_optimize(solver, x0, rank=None, disp=False, callback=None):
            from copy import deepcopy as _copy
            from mystic.tools import isNull
            solver.id = rank
            solver.SetInitialPoints(x0)
            if solver._useStrictRange:  #XXX: always, settable, or sync'd ?
                solver.SetStrictRanges(min=solver._strictMin, \
                                       max=solver._strictMax) # or lower,upper ?
            solver.Solve(cost, disp=disp, callback=callback)
            sm = solver._stepmon
            em = solver._evalmon
            if isNull(sm): sm = ([], [], [], [])
            else:
                sm = (_copy(sm._x), _copy(sm._y), _copy(sm._id),
                      _copy(sm._info))
            if isNull(em): em = ([], [], [], [])
            else:
                em = (_copy(em._x), _copy(em._y), _copy(em._id),
                      _copy(em._info))
            return solver, sm, em

        # map:: solver = local_optimize(solver, x0, id, verbose)
        results = self._map(local_optimize, op, initial_values, id, \
                                            vb, cb, **self._mapconfig)

        # save initial state
        self._AbstractSolver__save_state()
        #XXX: HACK TO GET CONTENT OF ALL MONITORS
        # reconnect monitors; save all solvers
        from mystic.monitors import Monitor
        while results:  #XXX: option to not save allSolvers? skip this and _copy
            _solver, _stepmon, _evalmon = results.pop()
            sm = Monitor()
            sm._x, sm._y, sm._id, sm._info = _stepmon
            _solver._stepmon.extend(sm)
            del sm
            em = Monitor()
            em._x, em._y, em._id, em._info = _evalmon
            _solver._evalmon.extend(em)
            del em
            self._allSolvers[len(results)] = _solver
        del results, _solver, _stepmon, _evalmon
        #XXX: END HACK

        # get the results with the lowest energy
        self._bestSolver = self._allSolvers[0]
        bestpath = self._bestSolver._stepmon
        besteval = self._bestSolver._evalmon
        self._total_evals = self._bestSolver.evaluations
        for solver in self._allSolvers[1:]:
            self._total_evals += solver.evaluations  # add func evals
            if solver.bestEnergy < self._bestSolver.bestEnergy:
                self._bestSolver = solver
                bestpath = solver._stepmon
                besteval = solver._evalmon

        # return results to internals
        self.population = self._bestSolver.population  #XXX: pointer? copy?
        self.popEnergy = self._bestSolver.popEnergy  #XXX: pointer? copy?
        self.bestSolution = self._bestSolver.bestSolution  #XXX: pointer? copy?
        self.bestEnergy = self._bestSolver.bestEnergy
        self.trialSolution = self._bestSolver.trialSolution  #XXX: pointer? copy?
        self._fcalls = self._bestSolver._fcalls  #XXX: pointer? copy?
        self._maxiter = self._bestSolver._maxiter
        self._maxfun = self._bestSolver._maxfun

        # write 'bests' to monitors  #XXX: non-best monitors may be useful too
        self._stepmon = bestpath  #XXX: pointer? copy?
        self._evalmon = besteval  #XXX: pointer? copy?
        self.energy_history = None
        self.solution_history = None
        #from mystic.tools import isNull
        #if isNull(bestpath):
        #    self._stepmon = bestpath
        #else:
        #    for i in range(len(bestpath.y)):
        #        self._stepmon(bestpath.x[i], bestpath.y[i], self.id)
        #        #XXX: could apply callback here, or in exec'd code
        #if isNull(besteval):
        #    self._evalmon = besteval
        #else:
        #    for i in range(len(besteval.y)):
        #        self._evalmon(besteval.x[i], besteval.y[i])
        #-------------------------------------------------------------

        # restore default handler for signal interrupts
        signal.signal(signal.SIGINT, signal.default_int_handler)

        # log any termination messages
        msg = self.Terminated(disp=disp, info=True)
        if msg: self._stepmon.info('STOP("%s")' % msg)
        # save final state
        self._AbstractSolver__save_state(force=True)
        return