2

I wrote an artificial life simulation. each creature is an object of the class "Animal" that i defined, with some properties. I defined a function "reproduce" outside the Animal class:

def reproduce(parent):
    child = Animal()
    child.brain.w= parent.brain.w[:]
    child.brain.ix= parent.brain.ix[:]
    child.x,child.y = random.randint(0,width),random.randint(0,height)
    child.age = 0
    child.fitness= 9 + parent.fitness/10 #parent.fitness/2

    mutation = random.choice([0,1,1,1,1,1,1,1,1,2,3,4,5])
    for b in range(mutation):
      child.brain.mutate()
    animals.append(child)

As can be seen, each animal has a brain, which is an object from different class: for each animal I defined animals[i].brain = Brain(). the "mutate" part in the reproduce function ensures that the child's brain is not identical to the parent's brain.

However, the problem is that when I apply this function on some animal from the list, the child indeed get slightly new brain, but the parent's brain becomes identical to the child's new brain. When I use reproduce(copy.deepcopy(animals[i])) instead of reproduce(animals[i]) that does not happen. What is the reason?

Thanks!

Emmett Butler
  • 5,969
  • 2
  • 29
  • 47
user1767774
  • 1,775
  • 3
  • 24
  • 32
  • 4
    I think you've going to need to post the code for `Animal`. – Daniel Roseman Mar 02 '13 at 22:53
  • 4
    Just a guess, but maybe `brain.w` or `brain.ix` is a list of lists? Then `w[:]` gives you one new list that still contains the same old sublists. – Armin Rigo Mar 02 '13 at 22:55
  • 4
    using [:] should make a copy of the parent brain. Can you post more code? Personally, I would make reproduce() a method of the Animal class. – Alex Mar 02 '13 at 22:57
  • 1
    My first stab at fixing this would be to replace the two lines assigning to `child.brain.w` and `child.brain.ix` with the single assignment `child.brain = copy.deepcopy(parent.brain)`. In fact, I'd like to know why you don't simply replace the first three lines of your function with `child = copy.deepcopy(parent)`. – kjo Mar 02 '13 at 23:57
  • 1
    Without more info, we're really fishing in the dark. My [answer below](http://stackoverflow.com/a/15180841/709852) is a stab at an area where this sort of problem crops up, but I can't get the deepcopy bit. – Henry Gomersall Mar 03 '13 at 00:17
  • Armin Rigo, can you be more specific please? indeed brain.w and brain.ix are list of lists, but why should it make this problem? – user1767774 Mar 03 '13 at 04:54

2 Answers2

2

Edit: Contrary to the initial assertion, the following doesn't exhibit the deepcopy behaviour which I missed in the description. Still, I'll leave it up in case there is anything useful.

A bit of a guess, but the following exhibits the behaviour you are encountering:

import random

width = 5
height = 5

class Brain(object):

    def __init__(self):
        self.w = [1]
        self.ix = [1]
        self.state = 1

    def mutate(self):
        self.state += 1

class Animal(object):
    brain = Brain()
    x = random.randint(0, width)
    y = random.randint(0,height)
    age = 0
    fitness = 10

def reproduce(parent):
    child = Animal()
    child.brain.w= parent.brain.w[:]
    child.brain.ix= parent.brain.ix[:]
    child.x,child.y = random.randint(0,width),random.randint(0,height)
    child.age = 0
    child.fitness= 9 + parent.fitness/10 #parent.fitness/2

    mutation = random.choice([0,1,1,1,1,1,1,1,1,2,3,4,5])
    for b in range(mutation):
      child.brain.mutate()
    animals.append(child)

animals = []
parent = Animal()

animals.append(parent)
print parent.brain.state
reproduce(parent)

for each in animals:
    print each.brain.state

When you run it, you get out:

1
2
2

If this is the case, the problem is because animal.brain is an attribute of the class, not the instance. The consequence of this is that all instances share the attribute.

It's simple to correct:

class Animal(object):
    x = random.randint(0, width)
    y = random.randint(0,height)
    age = 0
    fitness = 10

    def __init__(self):
        self.brain = Brain()

With that fix, you'll output the correct value for the parent, and the child will change. For example:

1
1
3

(according to how many times mutate() is called).

You'll also likely encounter similar problems with the other properties of Brain, but that can be an exercise for the reader.

Henry Gomersall
  • 8,434
  • 3
  • 31
  • 54
2

Another stab based on @Armin's comment. This does exhibit the relevant deepcopy behaviour:

import random

width = 5
height = 5

class Brain(object):

    def __init__(self):
        self.w = [[1]]
        self.ix = [[1]]

    def mutate(self):
        self.w[0].append(1)

class Animal(object):

    def __init__(self):
        self.brain = Brain()
        self.x = random.randint(0, width)
        self.y = random.randint(0, height)
        self.age = 0
        self.fitness = 10

def reproduce(parent):
    child = Animal()
    child.brain.w= parent.brain.w[:]
    child.brain.ix= parent.brain.ix[:]
    child.x,child.y = random.randint(0,width),random.randint(0,height)
    child.age = 0
    child.fitness= 9 + parent.fitness/10 #parent.fitness/2

    mutation = random.choice([0,1,1,1,1,1,1,1,1,2,3,4,5])
    for b in range(mutation):
      child.brain.mutate()
    animals.append(child)

animals = []
parent = Animal()

animals.append(parent)
print parent.brain.w
#reproduce(parent)
import copy
reproduce(copy.deepcopy(parent))

for each in animals:
    print each.brain.w

The fix here is to not have the state values stored in a mutable type that you're copying between objects; in this case a list, but it could be any mutable object.

Edit: What you're doing in the original code is to copy the contents of parent.brain.w into child.brain.w. Python has the property that assignments are to the original object, not copies of the object or the contents (unless you use the copy module). The docs cover this well. Tersely, this means the following is true:

>>> a = [1, 2, 3, 4, 5]
>>> b = a
>>> b.append(6)
>>> b
[1, 2, 3, 4, 5, 6]
>>> a
[1, 2, 3, 4, 5, 6]
>>> a is b
True

That is, both a and b are the same list. That isn't quite what you are doing; you're copying a list into an object, but that is equivalent:

>>> a = [[1, 2, 3]]
>>> b = []
>>> b = a[:] # What you are doing
>>> b is a
False
>>> b[0] is a[0]
True
>>> b[0].append(4)
>>> b[0]
[1, 2, 3, 4]
>>> a[0]
[1, 2, 3, 4]

If your type is not mutable, then when you modify it, a new object is created. Consider, for example, a somewhat equivalent list of tuples (which are immutable):

>>> a = [(1, 2, 3)]
>>> b = []
>>> b = a[:]
>>> b is a
False
>>> b[0] is a[0] # Initially the objects are the same
True
>>> b[0] += (4,) # Now a new object is created and overwrites b[0]
>>> b[0] is a[0]
False
>>> b[0]
(1, 2, 3, 4)
>>> a[0]
(1, 2, 3)
Henry Gomersall
  • 8,434
  • 3
  • 31
  • 54
  • THank you for the answer. I think you found the problem. But why can't I store the state as a list of lists? why there is a problem with copying such a list? – user1767774 Mar 03 '13 at 04:50