1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
|
from copy import deepcopy
from threading import Thread
import os
import wx.lib.newevent
from .. import monitor
from ..fitters import FitDriver
from ..mapper import MPMapper, SerialMapper, can_pickle
from ..util import redirect_console
from .convergence_view import ConvergenceMonitor
# ==============================================================================
PROGRESS_DELAY = 5
IMPROVEMENT_DELAY = 5
(FitProgressEvent, EVT_FIT_PROGRESS) = wx.lib.newevent.NewEvent()
(FitCompleteEvent, EVT_FIT_COMPLETE) = wx.lib.newevent.NewEvent()
# NOTE: GUI monitors are running in a separate thread. They should not
# touch the problem internals.
class GUIProgressMonitor(monitor.TimedUpdate):
def __init__(self, win, problem, progress=None, improvement=None):
monitor.TimedUpdate.__init__(
self, progress=progress or PROGRESS_DELAY, improvement=improvement or IMPROVEMENT_DELAY
)
self.win = win
self.problem = problem
def show_progress(self, history):
evt = FitProgressEvent(
problem=self.problem,
message="progress",
step=history.step[0],
value=history.value[0],
point=history.point[0] + 0,
) # avoid race
wx.PostEvent(self.win, evt)
def show_improvement(self, history):
evt = FitProgressEvent(
problem=self.problem,
message="improvement",
step=history.step[0],
value=history.value[0],
point=history.point[0] + 0,
) # avoid race
wx.PostEvent(self.win, evt)
class GUIMonitor(monitor.Monitor):
"""
Generic GUI monitor for fitting.
Sends a fit progress event to the selected window every *rate*
seconds. The *monitor* is a bumps monitor which processes fit
history as it arrives. Instead of the usual *show_progress* method
to update the managing process it has a *progress* method which
returns a dictionary of progress attributes. These attributes are
added to a *FitProgressEvent* along with *problem* and *message* which
is then posted to *win*.
*problem* should be the fit problem handed to the fit thread, and not
a copy. This is because it is used for direct comparison with the current
fit object in the progress panels so that stray messages don't cause
confusion when making graphs.
*message* is a dispatch string used by the OnFitProgress event processor
in the app to determine which progress panel should receive the event.
TODO: this is a pretty silly design, especially since GUI monitor is only
used for the convergence plot, and a separate monitor class was created
for DREAM progress updates.
"""
def __init__(self, win, problem, message, monitor, rate=None):
self.time = 0
self.rate = rate # rate=0 for no progress update, only final
self.win = win
self.problem = problem
self.message = message
self.monitor = monitor
def config_history(self, history):
self.monitor.config_history(history)
history.requires(time=1)
def __call__(self, history):
self.monitor(history)
if self.rate > 0 and history.time[0] >= self.time + self.rate:
evt = FitProgressEvent(problem=self.problem, message=self.message, **self.monitor.progress())
wx.PostEvent(self.win, evt)
self.time = history.time[0]
def final(self):
"""
Close out the monitor
"""
evt = FitProgressEvent(problem=self.problem, message=self.message, **self.monitor.progress())
wx.PostEvent(self.win, evt)
# Horrible hacks:
# (1) We are grabbing uncertainty_state from the history on monitor update
# and holding onto it for the monitor final call. Need to restructure the
# history/monitor interaction so that object lifetimes are better controlled.
# (2) We set the uncertainty state directly in the GUI window. This is an
# attempt to work around a memory leak which causes problems in the GUI when
# it is updated too frequently. If the problem is that the event structure
# is too large and wx isn't cleaning up properly then this should address it,
# but if the problem lies within the DREAM plot functions or within matplotlib
# then this change won't do anything and should be reverted.
class DreamMonitor(monitor.Monitor):
def __init__(self, win, problem, message, fitter, rate=None):
self.time = 0
self.rate = rate # rate=0 for no progress update, only final
self.win = win
self.problem = problem
self.fitter = fitter
self.message = message
self.uncertainty_state = None
def config_history(self, history):
history.requires(time=1)
def __call__(self, history):
self.uncertainty_state = getattr(history, "uncertainty_state", None)
if self.rate > 0 and history.time[0] >= self.time + self.rate and self.uncertainty_state is not None:
# Note: win.uncertainty_state protected by win.fit_lock
self.time = history.time[0]
self.win.uncertainty_state = self.uncertainty_state
evt = FitProgressEvent(
problem=self.problem,
message="uncertainty_update",
# uncertainty_state=deepcopy(self.uncertainty_state),
)
wx.PostEvent(self.win, evt)
def final(self):
"""
Close out the monitor
"""
if self.uncertainty_state is not None:
# Note: win.uncertainty_state protected by win.fit_lock
self.win.uncertainty_state = self.uncertainty_state
evt = FitProgressEvent(
problem=self.problem,
message="uncertainty_final",
# uncertainty_state=deepcopy(self.uncertainty_state),
)
wx.PostEvent(self.win, evt)
# ==============================================================================
class FitThread(Thread):
"""Run the fit in a separate thread from the GUI thread."""
def __init__(
self,
win,
abort_test=None,
problem=None,
fitclass=None,
options=None,
mapper=None,
convergence_update=5,
uncertainty_update=300,
):
# base class initialization
# Process.__init__(self)
Thread.__init__(self)
self.win = win
self.abort_test = abort_test
self.problem = problem
self.fitclass = fitclass
self.options = options
self.mapper = mapper
self.convergence_update = convergence_update
self.uncertainty_update = uncertainty_update
def run(self):
# TODO: we have no interlocks on changes in problem state. What
# happens when the user changes the problem while a fit is being run?
# May want to keep a history of changes to the problem definition,
# along with a function to reverse them so we can handle undo.
# NOTE: Problem must be the original problem (not a copy) when used
# inside the GUI monitor otherwise AppPanel will not be able to
# recognize that it is the same problem when updating views.
monitors = [
GUIProgressMonitor(self.win, self.problem),
GUIMonitor(
self.win,
self.problem,
message="convergence_update",
monitor=ConvergenceMonitor(),
rate=self.convergence_update,
),
DreamMonitor(
self.win, self.problem, fitter=self.fitclass, message="uncertainty_update", rate=self.uncertainty_update
),
]
# Only use parallel if the problem can be pickled
mapper = MPMapper if can_pickle(self.problem) else SerialMapper
# Be safe and send a private copy of the problem to the fitting engine
# print "fitclass",self.fitclass
problem = deepcopy(self.problem)
# print "fitclass id",id(self.fitclass),self.fitclass,threading.current_thread()
driver = FitDriver(
self.fitclass,
problem=problem,
monitors=monitors,
abort_test=self.abort_test,
mapper=mapper.start_mapper(problem, []),
**self.options,
)
x, fx = driver.fit()
# Give final state message from monitors
for M in monitors:
if hasattr(M, "final"):
M.final()
with redirect_console() as fid:
driver.show()
captured_output = fid.getvalue()
evt = FitCompleteEvent(problem=self.problem, point=x, value=fx, info=captured_output)
wx.PostEvent(self.win, evt)
|