I have a Python script that handles Modbus communications. One feature I added was a "graph" that shows the response times along with a color coded line that indicates if the response was successful, had an exception, or an error. The graph is just a scrollable canvas widget from Tkinter.
After graphing a certain number of lines old lines will be deleted and then a new one will be added to the end. For this example I have it set to 10, which means there will never be more than 10 lines on the canvas at once.
The code works correctly but there is a memory leak somewhere in this function. I let it run for about 24 hours and it took about 6x more memory after 24 hours. The function is part of a larger class.
My current guess is that my code causes the canvas size to constantly "expand," which slowly eats up the memory.
self.lineList = []
self.xPos = 0
def UpdateResponseTimeGraph(self):
if not self.graphQueue.empty():
temp = self.graphQueue.get() #pull from queue. A separate thread handles calculating the length and color of the line.
self.graphQueue.task_done()
lineName = temp[0] #assign queue values to variables
lineLength = temp[1]
lineColor = temp[2]
if len(self.lineList) >= 10: #if more than 10 lines are on the graph, delete the first one.
self.responseTimeCanvas.delete(self.lineList[0])
del self.lineList[0]
#Add line to canvas and a list so it can be referenced.
self.lineList.append(self.responseTimeCanvas.create_rectangle(self.xPos, self.responseWidth, self.xPos + 4, self.responseWidth-lineLength,
fill=lineColor, outline=''))
self.xPos += 5 #will cause the next line to start 5 pixels later. MEMORY LEAK HERE?
self.responseTimeCanvas.config(scrollregion=self.responseTimeCanvas.bbox(ALL))
self.responseTimeCanvas.xview_moveto(1.0) #move to the end of the canvas which is scrollable.
self.graphFrame.after(10, self.UpdateResponseTimeGraph)
One solution could be loop back to the start of the graph once a limit is reached but I would rather not do this since it may be confusing where the graph starts. Usually I have far more responses than 10.
EDIT:
I'm still doing to trail and error stuff but it looks like the memory leak can be eliminated with Bryan's suggestion as long as the line attributes are not changed via itemconfig. The code below should be able to run as is, if you're on python 2.7 change the import statement from tkinter to Tkinter (lower case vs uppercase t). This code will have the memory leak in it. Comment out the itemconfig line and it will be eliminated.
import tkinter
from tkinter import Tk, Frame, Canvas, ALL
import random
def RGB(r, g, b):
return '#{:02x}{:02x}{:02x}'.format(r, g, b)
class MainUI:
def __init__(self, master):
self.master = master
self.lineList = []
self.xPos = 0
self.maxLine = 122
self.responseIndex = 0
self.responseWidth = 100
self.responseTimeCanvas = Canvas(self.master, height=self.responseWidth)
self.responseTimeCanvas.pack()
self.UpdateResponseTimeGraph()
def UpdateResponseTimeGraph(self):
self.lineLength = random.randint(10,99)
if len(self.lineList) >= self.maxLine:
self.lineLength = random.randint(5,95)
self.responseTimeCanvas.coords(self.lineList[self.responseIndex % self.maxLine], self.xPos, self.responseWidth, self.xPos + 4, self.responseWidth-self.lineLength)
#if i comment out the line below the memory leak goes away.
self.responseTimeCanvas.itemconfig(self.lineList[self.responseIndex % self.maxLine], fill=RGB(random.randint(0,255), random.randint(0,255), random.randint(0,255)))
else:
self.lineList.append(self.responseTimeCanvas.create_rectangle(self.xPos, self.responseWidth, self.xPos + 4, self.responseWidth-self.lineLength,
fill=RGB(random.randint(0,255), random.randint(0,255), random.randint(0,255)), outline=''))
self.xPos += 5 #will cause the next line to start 5 pixels later. MEMORY LEAK HERE?
self.responseIndex += 1
self.responseTimeCanvas.config(scrollregion=self.responseTimeCanvas.bbox(ALL))
self.responseTimeCanvas.xview_moveto(1.0) #move to the end of the canvas which is scrollable.
self.responseTimeCanvas.after(10, self.UpdateResponseTimeGraph)
mw = Tk()
mainUI = MainUI(mw)
mw.mainloop()
The underlying tk canvas doesn't reuse or recycle object identifiers. Whenever you create a new object, a new identifier is generated. The memory of these objects is never reclaimed.
Note: this is memory inside the embedded tcl interpreter, rather than memory managed by python.
The solution is to reconfigure old, no longer used elements rather than deleting them and creating new ones.
Here's the code with no memory leak. The original source of the leak was me deleting the old line then creating a new one. This solution moves the first the line to the end then change's its attributes as necessary. I had a second 'leak' in my example code where I was picking a random color each time which lead to the number of colors used eating up a lot of memory. This code just prints green lines but the length will be random.
import tkinter
from tkinter import Tk, Frame, Canvas, ALL
import random
def RGB(r, g, b):
return '#{:02x}{:02x}{:02x}'.format(r, g, b)
class MainUI:
def __init__(self, master):
self.master = master
self.lineList = []
self.xPos = 0
self.maxLine = 122
self.responseIndex = 0
self.responseWidth = 100
self.responseTimeCanvas = Canvas(self.master, height=self.responseWidth)
self.responseTimeCanvas.pack()
self.UpdateResponseTimeGraph()
def UpdateResponseTimeGraph(self):
self.lineLength = random.randint(10,99)
if len(self.lineList) >= self.maxLine:
self.lineLength = random.randint(5,95)
self.responseTimeCanvas.coords(self.lineList[self.responseIndex % self.maxLine], self.xPos, self.responseWidth, self.xPos + 4, self.responseWidth-self.lineLength)
self.responseTimeCanvas.itemconfig(self.lineList[self.responseIndex % self.maxLine], fill=RGB(100, 255, 100))
else:
self.lineList.append(self.responseTimeCanvas.create_rectangle(self.xPos, self.responseWidth, self.xPos + 4, self.responseWidth-self.lineLength,
fill=RGB(100, 255, 100), outline=''))
self.xPos += 5 #will cause the next line to start 5 pixels later.
self.responseIndex += 1
self.responseTimeCanvas.config(scrollregion=self.responseTimeCanvas.bbox(ALL))
self.responseTimeCanvas.xview_moveto(1.0) #move to the end of the canvas which is scrollable.
self.responseTimeCanvas.after(10, self.UpdateResponseTimeGraph)
mw = Tk()
mainUI = MainUI(mw)
mw.mainloop()
Related
this is the code that i used to load a gif into a label object in tkinter
class ImageLabel(tk.Label):
"""a label that displays images, and plays them if they are gifs"""
def load(self, im):
if isinstance(im, str):
im = Image.open(im)
print(im.is_animated)
print(im.n_frames)
self.loc = 0
self.frames = []
try:
for i in count(1):
self.frames.append(ImageTk.PhotoImage(im.copy()))
im.seek(i)
except EOFError:
pass
try:
self.delay = im.info['duration']
except:
self.delay = 900
if len(self.frames) == 1:
self.config(image=self.frames[0])
else:
self.next_frame()
def unload(self):
self.config(image="")
self.frames = None
def next_frame(self):
if self.frames:
self.loc += 1
self.loc %= len(self.frames)
self.config(image=self.frames[self.loc])
self.after(self.delay, self.next_frame)
my aim is to load the gif in only a single loop based on the number of frames like lets say there are 5 frames in an image it only loops through that and stops
can someone help me with this.
if i change the
for i in count(im.n_frames):
it only loads the first frame and stops after that.
there are two things that would be required to make this work in this code snippet
Number 1 change the loc intially to -1
secondly change the next_frame function to
def next_frame(self):
if self.frames:
self.loc += 1
self.config(image=self.frames[self.loc])
self.after(self.delay, self.next_frame)
This is line for line an answer provided as to how to get tkinter to loop a gif indefinitely (except that you changed the duration of the delay).
I'm not sure you realize what count is doing here. Count, imported from itertools, is going to infintely count (acting as a "while(true)" but incrementing a number) unless given a barrier. It accepts two parameters (start= , stop= ) but if only given one, it defaults to start. So you have initiated a count at the value of im.n_frames.
What's happening is that you are loading the first frame, and starting the count at the last frame. When it then goes to find the next frame, you're hitting EOF, and starting the whole thing over again.
If the images are indexed starting at 1, try
for i in range(1, im.n_frames+1):
If you want to play the animation of GIF image only once, you need to modify next_frame() not to call .after() when the last frame has been shown.
Below is the modified ImageLabel class:
class ImageLabel(tk.Label):
"""a label that displays images, and plays them if they are gifs"""
def load(self, im):
if isinstance(im, str):
im = Image.open(im)
print(im.is_animated)
print(im.n_frames)
self.delay = im.info.get('duration', 900)
# load all the frames inside the image
self.frames = []
for i in range(im.n_frames):
im.seek(i)
self.frames.append(ImageTk.PhotoImage(im.copy()))
# start the animation
self.next_frame()
def unload(self):
self.config(image="")
self.frames = None
# modified to play the animation only once
def next_frame(self, loc=0):
self.config(image=self.frames[loc])
if loc < len(self.frames)-1:
self.after(self.delay, self.next_frame, loc+1)
I tried to make a Clicker and I used an infinite loop, so I would raise my Variable every second. But every time I use the Button, my program crashes.
Do you have any advice how I prevent that, because I have no idea what is really happening.
import time
from tkinter import *
class Clicker :
#updates the Label
def AK_CLabel(self):
self.ClickerLabel.configure(text="Du hast " + str(self.Clicks))
#Generates Clicks
def Klicken(self):
self.Clicks += 1
self.AK_CLabel()
#raises price of Helping Elf and raises the clicks per second
def HElf(self) :
if(self.Clicks >= self.priceHElf) :
self.Clicks -= self.priceHElf
self.priceHElf = self.priceHElf * 1.2
self.Elfs += 1
self.Elfhilft()
self.AK_CLabel()
#Should make the Clicks go up by the amount of Elfs, but if I use the Button the Programm shuts down
def Elfhilft(self):
while (not time.sleep(5)):
self.Clicks = self.Bitcoins1 + self.Elfs
time.sleep(1)
def __init__(self, master):
self.master = master
self.master.title = "Der Klicker"
self.Elfs = 0
self.priceHElf = 30
self.Clicks = 30
#Buttons and Label
self.DerKnopf = Button(text = "Clicks", command = self.Klicken)
self.ClickerLabel = Label(text = "You have " +str(self.Clicks))
self.HelferElf = Button(text = "A helping Fairy", command = self.HElf)
self.DerKnopf.pack()
self.ClickerLabel.pack()
self.HelferElf.pack()
root = Tk()
my_gui = Clicker(root)
root.mainloop()
Firstly, in your example bitcoins1 is undeclared. I assume this is just a variable name you forgot to change before posting, so I renamed it to clicks in order to replicate your issue.
Second, you have your Elfhilft() function using sleep(), which is causing issues with your Tkinter app. Tkinter uses its own loop system to handle real-time stuff, and sleep will cause that loop to stall in most cases. I suggest you use an implementation of after (How to create a timer using tkinter?) in order to replicate the autoclicker-esque function I assume you're trying to implement. As an example:
def autoclick(self):
self.clicks = self.clicks + self.Elfs
#In main app / __init__()
root.after(1000, self.autoclick) # updates auto-clicks every second
I have a huge list that I need to process, which takes some time, so I divide it into 4 pieces and multiprocess each piece with some function. It still takes a bit of time to run with 4 cores, so I figured I would add some progress bar to the function, so that it could tell me where each processor is at in processing the list.
My dream was to have something like this:
erasing close atoms, cpu0 [######..............................] 13%
erasing close atoms, cpu1 [#######.............................] 15%
erasing close atoms, cpu2 [######..............................] 13%
erasing close atoms, cpu3 [######..............................] 14%
with each bar moving as the loop in the function progresses. But instead, I get a continuous flow:
etc, filling my terminal window.
Here is the main python script that calls the function:
from eraseCloseAtoms import *
from readPDB import *
import multiprocessing as mp
from vectorCalc import *
prot, cell = readPDB('file')
atoms = vectorCalc(cell)
output = mp.Queue()
# setup mp to erase grid atoms that are too close to the protein (dmin = 2.5A)
cpuNum = 4
tasks = len(atoms)
rangeSet = [tasks / cpuNum for i in range(cpuNum)]
for i in range(tasks % cpuNum):
rangeSet[i] += 1
rangeSet = np.array(rangeSet)
processes = []
for c in range(cpuNum):
na, nb = (int(np.sum(rangeSet[:c] + 1)), int(np.sum(rangeSet[:c + 1])))
processes.append(mp.Process(target=eraseCloseAtoms, args=(prot, atoms[na:nb], cell, 2.7, 2.5, output)))
for p in processes:
p.start()
results = [output.get() for p in processes]
for p in processes:
p.join()
atomsNew = results[0] + results[1] + results[2] + results[3]
Below is the function eraseCloseAtoms():
import numpy as np
import click
def eraseCloseAtoms(protein, atoms, cell, spacing=2, dmin=1.4, output=None):
print 'just need to erase close atoms'
if dmin > spacing:
print 'the spacing needs to be larger than dmin'
return
grid = [int(cell[0] / spacing), int(cell[1] / spacing), int(cell[2] / spacing)]
selected = list(atoms)
with click.progressbar(length=len(atoms), label='erasing close atoms') as bar:
for i, atom in enumerate(atoms):
bar.update(i)
erased = False
coord = np.array(atom[6])
for ix in [-1, 0, 1]:
if erased:
break
for iy in [-1, 0, 1]:
if erased:
break
for iz in [-1, 0, 1]:
if erased:
break
for j in protein:
protCoord = np.array(protein[int(j)][6])
trueDist = getMinDist(protCoord, coord, cell, vectors)
if trueDist <= dmin:
selected.remove(atom)
erased = True
break
if output is None:
return selected
else:
output.put(selected)
accepted answer says it's impossible with click and it'd require 'non trivial amount of code to make it work'.
While it's true, there is another module with this functionality out of the box: tqdm
https://github.com/tqdm/tqdm which does exatly what you need.
You can do nested progress bars in docs https://github.com/tqdm/tqdm#nested-progress-bars etc.
I see two issues in your code.
The first one explains why your progress bars are often showing 100% rather than their real progress. You're calling bar.update(i) which advances the bar's progress by i steps, when I think you want to be updating by one step. A better approach would be to pass the iterable to the progressbar function and let it do the updating automatically:
with click.progressbar(atoms, label='erasing close atoms') as bar:
for atom in bar:
erased = False
coord = np.array(atom[6])
# ...
However, this still won't work with multiple processes iterating at once, each with its own progress bar due to the second issue with your code. The click.progressbar documentation states the following limitation:
No printing must happen or the progress bar will be unintentionally destroyed.
This means that whenever one of your progress bars updates itself, it will break all of the other active progress bars.
I don't think there is an easy fix for this. It's very hard to interactively update a multiple-line console output (you basically need to be using curses or a similar "console GUI" library with support from your OS). The click module does not have that capability, it can only update the current line. Your best hope would probably be to extend the click.progressbar design to output multiple bars in columns, like:
CPU1: [###### ] 52% CPU2: [### ] 30% CPU3: [######## ] 84%
This would require a non-trivial amount of code to make it work (especially when the updates are coming from multiple processes), but it's not completely impractical.
For anybody coming to this later. I created this which seems to work okay. It overrides click.ProgressBar fairly minimally, although I had to override an entire method for only a few lines of code at the bottom of the method. This is using \x1b[1A\x1b[2K to clear the progress bars before rewriting them so may be environment dependent.
#!/usr/bin/env python
import time
from typing import Dict
import click
from click._termui_impl import ProgressBar as ClickProgressBar, BEFORE_BAR
from click._compat import term_len
class ProgressBar(ClickProgressBar):
def render_progress(self, in_collection=False):
# This is basically a copy of the default render_progress with the addition of in_collection
# param which is only used at the very bottom to determine how to echo the bar
from click.termui import get_terminal_size
if self.is_hidden:
return
buf = []
# Update width in case the terminal has been resized
if self.autowidth:
old_width = self.width
self.width = 0
clutter_length = term_len(self.format_progress_line())
new_width = max(0, get_terminal_size()[0] - clutter_length)
if new_width < old_width:
buf.append(BEFORE_BAR)
buf.append(" " * self.max_width)
self.max_width = new_width
self.width = new_width
clear_width = self.width
if self.max_width is not None:
clear_width = self.max_width
buf.append(BEFORE_BAR)
line = self.format_progress_line()
line_len = term_len(line)
if self.max_width is None or self.max_width < line_len:
self.max_width = line_len
buf.append(line)
buf.append(" " * (clear_width - line_len))
line = "".join(buf)
# Render the line only if it changed.
if line != self._last_line and not self.is_fast():
self._last_line = line
click.echo(line, file=self.file, color=self.color, nl=in_collection)
self.file.flush()
elif in_collection:
click.echo(self._last_line, file=self.file, color=self.color, nl=in_collection)
self.file.flush()
class ProgressBarCollection(object):
def __init__(self, bars: Dict[str, ProgressBar], bar_template=None, width=None):
self.bars = bars
if bar_template or width:
for bar in self.bars.values():
if bar_template:
bar.bar_template = bar_template
if width:
bar.width = width
def __enter__(self):
self.render_progress()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.render_finish()
def render_progress(self, clear=False):
if clear:
self._clear_bars()
for bar in self.bars.values():
bar.render_progress(in_collection=True)
def render_finish(self):
for bar in self.bars.values():
bar.render_finish()
def update(self, bar_name: str, n_steps: int):
self.bars[bar_name].make_step(n_steps)
self.render_progress(clear=True)
def _clear_bars(self):
for _ in range(0, len(self.bars)):
click.echo('\x1b[1A\x1b[2K', nl=False)
def progressbar_collection(bars: Dict[str, ProgressBar]):
return ProgressBarCollection(bars, bar_template="%(label)s [%(bar)s] %(info)s", width=36)
#click.command()
def cli():
with click.progressbar(length=10, label='bar 0') as bar:
for i in range(0, 10):
time.sleep(1)
bar.update(1)
click.echo('------')
with ProgressBar(iterable=None, length=10, label='bar 1', bar_template="%(label)s [%(bar)s] %(info)s") as bar:
for i in range(0, 10):
time.sleep(1)
bar.update(1)
click.echo('------')
bar2 = ProgressBar(iterable=None, length=10, label='bar 2')
bar3 = ProgressBar(iterable=None, length=10, label='bar 3')
with progressbar_collection({'bar2': bar2, 'bar3': bar3}) as bar_collection:
for i in range(0, 10):
time.sleep(1)
bar_collection.update('bar2', 1)
for i in range(0, 10):
time.sleep(1)
bar_collection.update('bar3', 1)
if __name__ == "__main__":
cli()
It may not be the same as your dream, but you can use imap_unordered with click.progressbar to integrate with multiprocessing.
import multiprocessing as mp
import click
import time
def proc(arg):
time.sleep(arg)
return True
def main():
p = mp.Pool(4)
args = range(4)
results = p.imap_unordered(proc, args)
with click.progressbar(results, length=len(args)) as bar:
for result in bar:
pass
if __name__ == '__main__:
main()
Something like this will work if you are okay with having one progress bar:
import click
import threading
import numpy as np
reallybiglist = []
numthreads = 4
def myfunc(listportion, bar):
for item in listportion:
# do a thing
bar.update(1)
with click.progressbar(length=len(reallybiglist), show_pos=True) as bar:
threads = []
for listportion in np.split(reallybiglist, numthreads):
thread = threading.Thread(target=myfunc, args=(listportion, bar))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
I'm new to Qt (PySide), and I'm trying to draw a 'grid map' efficiently. However my solution slows down to a halt with 10k+ QGraphicsRectItem.
Currently it works like so:
class GridMapView(QObject, QGraphicsItemGroup):
def __init__(self, mapWidth, mapHeight, cellSize):
QObject.__init__(self)
QGraphicsItemGroup.__init__(self)
self.mapWidth = mapWidth
self.mapHeight = mapHeight
self.cellSize = cellSize
self.graphicCells = []
#Create cells.
for x in range(self.mapWidth / self.cellSize):
self.graphicCells.append([])
for y in range(self.mapHeight / self.cellSize):
self.graphicCells[x].append(QGraphicsRectItem(x * self.cellSize, y * self.cellSize, self.cellSize, self.cellSize))
self.graphicCells[x][-1].setBrush(QBrush(QColor('grey')))
self.addToGroup(self.graphicCells[x][-1])
self.setPos(-mapWidth/2, -mapHeight/2)
#Slot(Point, int)
def onCellUpdated(self, index, state):
cell = self.graphicCells[index.x][index.y]
if state == CellStates.UNKNOWN:
cell.setBrush(QBrush(QColor('grey')))
cell.setVisible(True)
elif state == CellStates.FREE:
cell.setVisible(False)
elif state == CellStates.OCCUPIED:
cell.setBrush(QBrush(QColor('black')))
cell.setVisible(True)
The initial grid is populated during creation. When the appropriate signal is fired, a specific cell will be updated. This updating is fairly infrequent, and my assumption was that Qt only draws what changes.
The entire 'map' is visible in my viewport, and disabling the rendering makes my application run perfectly fine.
I've tried setting QGraphicsView.NoViewportUpdate, yet it still updates the entire view. I hoped it would require me to call '.update()'.
Is this approach flawed from the start? Thanks in advance.
I am trying to do an animation of a Particle Swarm Optimization using Python and Mayavi2.
The animation is working fine, my problem is that it is not possible to interact with the plot while it is animating the movement. Specifically i would like to turn the graph and zoom. Maybe someone has experience doing animations?
The way i do it is first to calculate the positions of the particles and then to store them. After the calculation is finished i plot the positions of the particle at the first instace of time with point3d() and then i iterate through time updating the data using the set() method.
Is there a way to make it possible to turn the graph? I have heard about something with threads, disabeling the the rendering, but i could not figure out how to do it in my code. Besides lots of other stuff, I have read:
http://code.enthought.com/projects/mayavi//docs/development/html/mayavi/mlab_animating.html
http://code.enthought.com/projects/mayavi//docs/development/html/mayavi/tips.html#acceleration-mayavi-scripts
but it can't see how to use it.
Any suggestions?
Here is my code:
#!/usr/bin/env python
'''
#author rt
'''
import pylab as plt
from numpy import *
from mayavi import mlab
from threading import Thread # making plotting faster?
import ackley as ac
class Swarm(Thread, object):
'''
constructor for the swarm
initializes all instance variables
'''
def __init__(self,objective_function):
Thread.__init__(self)
# optimization options
self.omega = 0.9 # inertial constant
self.c1 = 0.06 # cognitive/private constant
self.c2 = 0.06 # social constant
self.objective = objective_function # function object
self.max_iteration = 100 # maximal number of iterations
# Swarm stuff
self.number = 0
self.best = [] # gbest; the global best position
self.particles = [] # empty list for particles
# temporary
self.min = self.objective.min
self.max = self.objective.max
self.best_evolution = []
# self.dimensions = 2 # dimensions NB!
'''
add particles to the swarm
find the best position of particle in swarm to set global best
'''
def add_particles(self, n):
for i in range(n):
particle = Particle(self)
if i == 0: # initialize self.best
self.best = particle.position
if particle.eval() < self._eval(): # check if there is a better and if, set it
self.best = copy(particle.position)
self.particles.append(particle) # append the particle to the swarm
def _eval(self):
return self.objective.evaluate(self.best)
def plot(self):
for i in range(self.max_iteration):
pos_x = []
pos_y = []
pos_z = []
#print pos_x
for particle in self.particles:
[x,y,z] = particle.trail[i]
pos_x.append(x)
pos_y.append(y)
pos_z.append(z)
#print pos_x
if i ==0:
g = mlab.points3d(pos_x, pos_y,pos_z, scale_factor=0.5)
ms =g.mlab_source
ms.anti_aliasing_frames = 0
ms.set(x=pos_x, y = pos_y, z = pos_z,scale_factor=0.5) #updating y value
#print pos_y
#ms.set(x=pos_x) # update x values
#ms.set(y=pos_y) #updating y value
#ms.set(z=pos_z) #updating y value
#for p in self.particles:
#p.plot()
def plot_objective(self):
delta = 0.1
v = mgrid[self.min:self.max:delta,self.min:self.max:delta]
z = self.objective.evaluate(v)
#mlab.mesh(v[0],v[1],z)
mlab.surf(v[0],v[1],z) # surf creates a more efficient data structure than mesh
mlab.xlabel('x-axis', object=None)
mlab.ylabel('y-axis', object=None)
mlab.zlabel('z-axis', object=None)
def _info(self):
self.plot()
print '----------------------------'
print 'The best result is:'
print 'Coordinates:', self.best
print 'Value: ', self._eval()
#print 'with ', nreval, 'evaluations'
print 'nr of particles: ', len(self.particles)
print '----------------------------'
def run(self):
self.plot_objective()
self.best = self.particles[0].get_position()
iteration = 0
while iteration < self.max_iteration:
#if iteration!= 0: obj.scene.disable_render = True
#disable_render = True
for particle in self.particles:
rnd_c1 = array([random.uniform(0,1),random.uniform(0,1)])
rnd_c2 = array([random.uniform(0,1),random.uniform(0,1)])
particle.velocity = self.omega * array(particle.velocity) + \
self.c1 * rnd_c1 * (array(particle.best) - array(particle.position)) + \
self.c2 * rnd_c2 * (array(self.best) - array(particle.position)) # TODO: change so independent rnd for components
particle.position = array(particle.position) + particle.velocity
if particle.eval() < particle.best_eval():
particle.best = copy(particle.position)
if particle.eval() < self._eval():
self.best = copy(particle.position)
particle.update() # add the point to the trail
iteration +=1
self.best_evolution.append(self._eval())
#obj.scene.disable_render = False
print 'finished: ', iteration
self._info()
'''
Class modeling particle
'''
class Particle():
def __init__(self, swarm):
self.swarm = swarm
x_rand = random.uniform(self.swarm.min,self.swarm.max)
y_rand = random.uniform(self.swarm.min,self.swarm.max)
self.position = array([x_rand,y_rand])
v_x_rand = random.uniform(self.swarm.min,self.swarm.max)
v_y_rand = random.uniform(self.swarm.min,self.swarm.max)
self.velocity = array([v_x_rand, v_y_rand])
self.size = 0.5
self.best = self.position
# visualization
self.trail = []
def plot(self):
[x,y] = self.position
z = self.eval()
mlab.points3d(x,y,z,scale_factor=self.size)
def eval(self):
return self.swarm.objective.evaluate(self.position)
def best_eval(self):
return self.swarm.objective.evaluate(self.best)
def get_position(self):
return self.position
def update(self):
[x,y] = self.position
z = self.eval()
#print [x,y,z]
self.trail.append([x,y,z])
def plot_trail(self,index):
[x,y,z] = self.trail[index]
mlab.points3d(x,y,z,scale_factor=self.size)
# Make the animation
mlab.figure(1, bgcolor=(0, 0, 0), size=(1300, 700)) # create a new figure with black background and size 1300x700
objective = ac.Ackley() # make an objective function
swarm = pso.Swarm(objective) # create a swarm
nr_of_particles = 25 # nr of particles in swarm
swarm.add_particles(nr_of_particles)
swarm.run()
#swarm.start()
mlab.show()
print '------------------------------------------------------'
print 'Particle Swarm Optimization'
#objective.info()
print 'Objective function to minimize has dimension = ', objective.get_dimension()
print '# of iterations = ', 1000
print '# of particles in swarm = ', nr_of_particles
print '------------------------------------------------------'
In my case, even though I was somewhat able to do what Brandon Rhodes suggested for a mock program (https://stackoverflow.com/questions/16617814/interacting-with-mlab-scene-while-it-is-being-drawn), I could not manage to convert my already existing larger program.
Then I found this link: http://wiki.wxpython.org/LongRunningTasks
So, I just sprinkled a lot of wx.Yield() s inside my loops. This way I did not need to change my program structure, and I am able to interact with the window. I think better ways are explained in the link.
Your problem is that the wx event loop, which runs the Mayavi GUI window and listens for mouse clicking and dragging and responds by moving the scene, is not getting any time to run during your animation because you are keeping Python captive in your loop without ever letting it return control.
Instead of keeping control of the program with a loop of your own, you need to create a wx.Timer class that advances the scene by one frame update, and that then returns control to the wx event loop after scheduling itself again. It will look something like this:
import wx
...
class Animator(wx.Timer):
def Notify(self):
"""When a wx.Timer goes off, it calls its Notify() method."""
if (...the animation is complete...):
return
# Otherwise, update all necessary data to advance one step
# in the animation; you might need to keep a counter or
# other state as an instance variable on `self`
# [DATA UPDATE GOES HERE]
# Schedule ourselves again, giving the wx event loop time to
# process any pending mouse motion.
self.Start(0, oneShot=True) # "in zero milliseconds, call me again!"
I played with slightly higher values like 1 for the number of milliseconds that wx gets to run the UI with, but could not really tell a difference between that and just choosing 0 and having control returned "immediately".