0

I am working with generators in Python, and I am getting odd behavior. I am not entirely how to phrase the question...

My code performs a pattern search over an objective function. The issue I see shows up when I zip the results of my patternSearch generator with an xrange to get only the first 12 results. It seems as though the xmin component of the tuple returned by my generator is repeated over the length of the zip. What is especially weird, the ymin component of the same tuple is not repeated. When I initialize a generator object ps and call ps.next() repeatedly, the results are correct.

What could be the issue?? Thanks!

Code

#!/usr/bin/env python2.7

def obj(x):
   x1,x2 = x
   return 100*(x2 - x1**2)**2 + (1 - x1)**2

# fobj: objective function of vector x
#   x0: initial point
#    h: initial step size
#    r: reduction parameter
def patternSearch(fobj,x0,h,r):
   n = len(x0) # get dimensionality
   dims = range(0,n)
   # initialize given x0
   xmin = list(x0)
   ymin = fobj(xmin)

   yield (xmin,ymin) # send back the initial condition first

   while True:
      focus = True
      for i in dims:
         x = xmin # start from the last best point
         x[i] += h
         y = fobj(x) # eval
         if y < ymin:
            ymin = y
            xmin = x
            focus = False
            continue
         # else, y > ymin
         x[i] -= 2*h # go in the opposite direction
         y = fobj(x) # eval
         if y < ymin:
            ymin = y
            xmin = x
            focus = False
            continue
         # else, no update to this dim
      if focus:
         h *= r
      yield (xmin,ymin)

def main():
   x0 = (3,5)
   imax = 12
   print 'Initial condition: ', x0
   print 'Stop condition: i == ', imax
   print 'Iteration |     x1     |     x2     |      y     '
   for (x,y), i in zip(patternSearch(obj,x0,h=0.5,r=0.5), xrange(imax)):
      print "{:>9} | {:<10} | {:<10} | {:<11}".format(i,x[0],x[1],y)

   print 'Generator output test:'
   g = patternSearch(obj,x0,0.5,0.5)
   for i in xrange(imax):
      g.next()

Output

Initial condition:  (3, 5)
Stop condition: i ==  12
Iteration |     x1     |     x2     |      y     
        0 | 2.0        | 4.03125    | 1604       
        1 | 2.0        | 4.03125    | 58.5       
        2 | 2.0        | 4.03125    | 58.5       
        3 | 2.0        | 4.03125    | 1.953125   
        4 | 2.0        | 4.03125    | 1.953125   
        5 | 2.0        | 4.03125    | 1.2900390625
        6 | 2.0        | 4.03125    | 1.2900390625
        7 | 2.0        | 4.03125    | 1.13043212891
        8 | 2.0        | 4.03125    | 1.13043212891
        9 | 2.0        | 4.03125    | 1.06357192993
       10 | 2.0        | 4.03125    | 1.06357192993
       11 | 2.0        | 4.03125    | 1.03150010109
Generator output test:
([3, 5], 1604)
([2.5, 5.5], 58.5)
([2.0, 5.0], 58.5)
([2.25, 4.75], 1.953125)
([2.0, 4.5], 1.953125)
([2.125, 4.375], 1.2900390625)
([2.0, 4.25], 1.2900390625)
([2.0625, 4.1875], 1.13043212890625)
([2.0, 4.125], 1.13043212890625)
([2.03125, 4.09375], 1.0635719299316406)
([2.0, 4.0625], 1.0635719299316406)
([2.015625, 4.046875], 1.0315001010894775)
abatea
  • 81
  • 1
  • 8

1 Answers1

1

You reuse the list xmax and change their elements, but it is always the same list, so the zipped list consists of 12 times the same list. The generator seems to work, because the list is printed for each step.

Replace the line

x = xmin

with

x = list(xmin)
Daniel
  • 42,087
  • 4
  • 55
  • 81
  • Except the generator works fine on its own. Look at the output. – abatea Jan 09 '16 at 22:56
  • @abatea: answer extended – Daniel Jan 09 '16 at 22:58
  • Alright, thanks for the pointer! I changed my yield line to `yield(list(xmin),ymin)`, and that solved the problem. So before, each item in the zip was pointing to the same list object. – abatea Jan 09 '16 at 23:09
  • The odd thing is, shouldn't the zip be evaluating only when called, just like the generator? My code still should be printing once per iteration, rather than evaluating the entire list first, right? – abatea Jan 09 '16 at 23:12
  • @abatea it depends on the version of Python you use. In Py 3 `zip` is a generator, in Py 2 you need to rely on `itertools.izip` for that. Since there is `xrange` in your code, you are on Py 2. – Eli Korvigo Jan 09 '16 at 23:48
  • @abatea: i've shown you the line, where you have to do the correction. Otherwise, your results may be wrong either way. – Daniel Jan 10 '16 at 10:23