Reputation: 1321
I'm attempting to place multiple DecimalNumber
s in Manim, each with a random starting value, so that when I use a ValueTracker
on each of them they will seemingly output arbitrary arrangements. Yet when I implement this code:
all_d, all_t = [], []
for tile in tiles:
start_number = random()
k = ValueTracker(start_number)
updateFunction = lambda i: i.set_value(k.get_value())
d = DecimalNumber(num_decimal_places=2).add_updater(updateFunction)
d.move_to(tile)
all_d.append(d)
all_t.append(k)
print([k.get_value() for k in all_t]) # OUTPUT ValueTracker starting values
self.play(*[Create(d) for d in all_d])
self.play(*[k.animate.set_value(0) for k in all_t], run_time = 5)
The print statement outputs random numbers:
[0.27563412131212717,..., 0.9962578393535727]
, but all of the DecimalNumber
s on the screen output the same value, starting with 1.00 and ending with 0.00 synchronously. And if I add the same print statement after all of my code, it results in all 0's: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
.
I've tried adding: k.set_value(start_number)
after initializing the ValueTracker
, but that proved fruitless. I've also tried using this code instead:
all_d, all_t = [], []
for i, tile in enumerate(tiles):
start_number = random()
d = DecimalNumber(num_decimal_places=2)
d.move_to(tile)
all_d.append((i, d, start_number))
all_t.append(ValueTracker(start_number))
self.play(*[all_t[i].animate.set_value(start_number + 1) for i, _, start_number in all_d],
*[UpdateFromFunc(d, lambda m: m.set_value(all_t[i].get_value())) for i, d, _ in all_d],
run_time = 5)
But all of them still match up, and they go from 1.00 to 2.00.
How can I remedy this?
Upvotes: 2
Views: 281
Reputation: 1453
It is a tricky problem related to the fact that your lambda is accessing to an external variable. In particular, look at these two lines, inside your for
loop:
# for ...
k = ValueTracker(start_number)
updateFunction = lambda i: i.set_value(k.get_value())
# ...
all_t.append(k)
Inside lambda
you use k
, and that k
is no more than a reference to an external variable. While that lambda is being defined, the value of k
is not read. It is read however when the lambda is ejecuted later by manim, as an updater function.
The problem is that this lambda, as well as all the lambdas created in the loop, refer to the same k
, because that k
is kind of a "global" variable for the lambdas, since it is external to them.
The value of k
changes in each iteration of the loop, but that does not affect to the lambdas, until they are run. At that time k
is a reference to the last ValueTracker()
created in the loop. All other ValueTrackers are stored in the list all_t
, but k
refers only to the last one.
This explains why, when you loop onall_t
to print the values you get different ones, but the updaters (the lambdas) use all the same one (the last one in the list).
The solution looks like a hack, but it actually makes perfect sense if you think on it:
k = ValueTracker(start_number)
updateFunction = lambda i, k=k: i.set_value(k.get_value())
Note the small addition k=k
in the lambda definition. What does this do?
k
for the lambda. So when you use k
inside it no longer refers to the external variable, but to the local variable (parameter) for that particular lambda. Each lambda has his own k
k
, only to i
k
at that iteration of the loop. Python evaluates the expression that assigns the default value, and the result of that evaluation is what is used as default.In other words, in k=k
, the k
at the left is a local variable for the lambda. The k
at the right is replaced for the current value of k
and assigned to the internal k
as default value.
This solves your problem.
Upvotes: 3