lsst.meas.modelfit  15.0-3-g150fc43+8
optimizerDisplay.py
Go to the documentation of this file.
1 from builtins import range
2 from builtins import object
3 #
4 # LSST Data Management System
5 # Copyright 2008-2013 LSST Corporation.
6 #
7 # This product includes software developed by the
8 # LSST Project (http://www.lsst.org/).
9 #
10 # This program is free software: you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation, either version 3 of the License, or
13 # (at your option) any later version.
14 #
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
19 #
20 # You should have received a copy of the LSST License Statement and
21 # the GNU General Public License along with this program. If not,
22 # see <http://www.lsstcorp.org/LegalNotices/>.
23 #
24 
25 import numpy
26 import matplotlib
27 import matplotlib.colors
28 from mpl_toolkits.axes_grid1 import make_axes_locatable
29 
30 from .densityPlot import hide_xticklabels, hide_yticklabels
31 from .. import modelfitLib
32 
33 __all__ = ("OptimizerDisplay", )
34 
35 
37 
38  def __init__(self, parent, sample):
39  self.parent = parent
40  self.sample = sample
41  # scale unit grid by trust radius
42  self.grid = parent.unitGrid * sample.get(parent.recorder.trust)
43  # offset grid to center it on the current parameter point
44  self.grid += sample.get(parent.recorder.parameters).reshape((1,)*parent.ndim + (parent.ndim,))
45  self._objectiveValues = None
46  self._objectiveModel = None
47  self.rejected = []
48 
49  def __getattr__(self, name):
50  # look for keys on the recorder and lookup fields for unknown attributes
51  return self.sample.get(getattr(self.parent.recorder, name))
52 
53  @property
54  def objectiveValues(self):
55  if self._objectiveValues is None:
56  self._objectiveValues = numpy.zeros(self.grid.shape[:-1], dtype=float)
57  self.parent.objective.fillObjectiveValueGrid(self.grid.reshape(-1, self.parent.ndim),
58  self._objectiveValues.reshape(-1))
59  good = numpy.isfinite(self._objectiveValues)
60  self._objectiveValues[numpy.logical_not(good)] = self._objectiveValues[good].max()
61  return self._objectiveValues
62 
63  @property
64  def objectiveModel(self):
65  if self._objectiveModel is None:
66  self._objectiveModel = numpy.zeros(self.grid.shape[:-1], dtype=float)
67  self.parent.recorder.fillObjectiveModelGrid(self.sample,
68  self.grid.reshape(-1, self.parent.ndim),
69  self._objectiveModel.reshape(-1))
70  return self._objectiveModel
71 
72 
73 class OptimizerDisplay(object):
74 
75  def __init__(self, history, model, objective, steps=11):
76  self.recorder = modelfitLib.OptimizerHistoryRecorder(history.schema)
77  # len(dimensions) == N in comments below
78  self.dimensions = list(model.getNonlinearNames()) + list(model.getAmplitudeNames())
79  self.ndim = len(self.dimensions)
80  self.track = []
81  self.objective = objective
82  # This creates a array with shape [steps, ..., steps, N] (a total of N+1 array dimensions):
83  # this is an N-dimensional grid, with the last dimension of the grid array giving the coordinates
84  # of each grid point.
85  # We slice mgrid to generate the basic grid, which is [N, steps, ..., steps]
86  mgridArgs = (slice(-1.0, 1.0, steps*1j),) * self.ndim
87  # We'll index the result of mgrid with these args to make first dimension last
88  transposeArgs = tuple(list(range(1, self.ndim+1)) + [0])
89  self.unitGrid = numpy.mgrid[mgridArgs].transpose(transposeArgs).copy()
90  current = None
91  for sample in history:
92  if sample.get(self.recorder.state) & modelfitLib.Optimizer.STATUS_STEP_REJECTED:
93  assert current is not None
94  current.rejected.append(sample)
95  continue
96  current = OptimizerIterationDisplay(self, sample)
97  self.track.append(current)
98 
99  def plot(self, xDim, yDim, n=0):
100  return OptimizerDisplayFigure(self, xDim=xDim, yDim=yDim, n=n)
101 
102 
104 
105  def __init__(self, parent, xDim, yDim, n=0):
106  self.parent = parent
107  self.xDim = xDim
108  self.yDim = yDim
109  self.j = self.parent.dimensions.index(self.xDim)
110  self.i = self.parent.dimensions.index(self.yDim)
111  self.yKey = self.parent.recorder.parameters[self.i]
112  self.xKey = self.parent.recorder.parameters[self.j]
113  self.zKey = self.parent.recorder.objective
114  # grid slice indices corresponding to the dimensions we're plotting
115  self.slice2d = [s//2 for s in self.parent.unitGrid.shape[:-1]]
116  self.slice2d[self.i] = slice(None)
117  self.slice2d[self.j] = slice(None)
118  self.slice2d = tuple(self.slice2d)
119  self.sliceX = [s//2 for s in self.parent.unitGrid.shape[:-1]]
120  self.sliceX[self.j] = slice(None)
121  self.sliceX = tuple(self.sliceX)
122  self.sliceY = [s//2 for s in self.parent.unitGrid.shape[:-1]]
123  self.sliceY[self.i] = slice(None)
124  self.sliceY = tuple(self.sliceY)
125  self.track = dict(
126  x=numpy.array([iteration.sample.get(self.xKey) for iteration in self.parent.track]),
127  y=numpy.array([iteration.sample.get(self.yKey) for iteration in self.parent.track]),
128  z=numpy.array([iteration.sample.get(self.zKey) for iteration in self.parent.track]),
129  )
130  self.n = n
131  self.figure = matplotlib.pyplot.figure("%s vs %s" % (xDim, yDim), figsize=(16, 8))
132  self.figure.subplots_adjust(left=0.025, right=0.975, bottom=0.08, top=0.95, wspace=0.12)
133  self.axes3d = self.figure.add_subplot(1, 2, 1, projection='3d')
134  self.axes3d.autoscale(False)
135  self.axes3d.set_xlabel(self.xDim)
136  self.axes3d.set_ylabel(self.yDim)
137  self.axes2d = self.figure.add_subplot(1, 2, 2)
138  self.axes2d.set_xlabel(self.xDim)
139  self.axes2d.set_ylabel(self.yDim)
140  self.axes2d.autoscale(False)
141  divider = make_axes_locatable(self.axes2d)
142  self.axesX = divider.append_axes("top", 1.5, pad=0.1, sharex=self.axes2d)
143  self.axesX.autoscale(False, axis='x')
144  hide_xticklabels(self.axesX)
145  self.axesY = divider.append_axes("right", 1.5, pad=0.1, sharey=self.axes2d)
146  self.axesY.autoscale(False, axis='y')
147  hide_yticklabels(self.axesY)
148  self.artists = []
149  self.guessExtent()
150  self.plotTrack()
151  self.plotRejected()
152  self.plotSurfaces()
153 
154  @property
155  def xlim(self):
156  return self._extent[:2]
157 
158  @property
159  def ylim(self):
160  return self._extent[2:4]
161 
162  @property
163  def zlim(self):
164  return self._extent[4:]
165 
166  def guessExtent(self):
167  current = self.parent.track[self.n]
168  x = current.sample.get(self.xKey)
169  y = current.sample.get(self.yKey)
170  zMin1 = current.objectiveValues[self.slice2d].min()
171  zMax1 = current.objectiveValues[self.slice2d].max()
172  zMin2 = current.objectiveModel[self.slice2d].min()
173  zMax2 = current.objectiveModel[self.slice2d].max()
174  self.setExtent(x0=x - current.trust, x1=x + current.trust,
175  y0=y - current.trust, y1=y + current.trust,
176  z0=min(zMin1, zMin2), z1=max(zMax1, zMax2), lock=False)
177 
178  def setExtent(self, x0=None, x1=None, y0=None, y1=None, z0=None, z1=None, lock=True):
179  if x0 is None:
180  x0 = self._extent[0]
181  if x1 is None:
182  x1 = self._extent[1]
183  if y0 is None:
184  y0 = self._extent[2]
185  if y1 is None:
186  y1 = self._extent[3]
187  if z0 is None:
188  z0 = self._extent[4]
189  if z1 is None:
190  z1 = self._extent[5]
191  self._extent = (x0, x1, y0, y1, z0, z1)
192  self._lock = lock
193  self.axes3d.set_xlim(*self.xlim)
194  self.axes3d.set_ylim(*self.ylim)
195  self.axes3d.set_zlim(*self.zlim)
196  self.axes2d.set_xlim(*self.xlim)
197  self.axes2d.set_ylim(*self.ylim)
198  self.axesX.set_ylim(*self.zlim)
199  self.axesY.set_xlim(*self.zlim)
200 
201  def _clipZ(self, x, y, z):
202  # clipping is currently disabled; more trouble than it's worth
203  if False:
204  mask = numpy.logical_or.reduce([x < self.xlim[0], x > self.xlim[1],
205  y < self.ylim[0], y > self.ylim[1],
206  z < self.zlim[0], z > self.zlim[1]],
207  axis=0)
208 
209  z[mask] = numpy.nan
210  return numpy.logical_not(mask).astype(int).sum() > 4
211  return True
212 
213  def _contour(self, axes, *args, **kwds):
214  self.artists.extend(axes.contour(*args, **kwds).collections)
215 
216  def plotTrack(self):
217  kwds = dict(markeredgewidth=0, markerfacecolor='g', color='g', marker='o')
218  self.axes3d.plot(self.track['x'], self.track['y'], self.track['z'], **kwds)
219  self.axes2d.plot(self.track['x'], self.track['y'], **kwds)
220  self.axesX.plot(self.track['x'], self.track['z'], **kwds)
221  self.axesY.plot(self.track['z'], self.track['y'], **kwds)
222 
223  def plotRejected(self):
224  kwds = dict(markeredgewidth=0, markerfacecolor='r', color='r', marker='v')
225  current = self.parent.track[self.n]
226  cx = current.sample.get(self.xKey)
227  cy = current.sample.get(self.yKey)
228  cz = current.sample.get(self.zKey)
229  for r in current.rejected:
230  x = [cx, r.get(self.xKey)]
231  y = [cy, r.get(self.yKey)]
232  z = [cz, r.get(self.zKey)]
233  self.artists.extend(self.axes3d.plot(x, y, z, **kwds))
234  self.artists.extend(self.axes2d.plot(x, y, **kwds))
235  self.artists.extend(self.axesX.plot(x, z, **kwds))
236  self.artists.extend(self.axesY.plot(z, y, **kwds))
237 
238  def plotSurfaces(self):
239  current = self.parent.track[self.n]
240 
241  # Start with 2-d and 3-d surfaces
242  x = current.grid[self.slice2d + (self.j,)]
243  y = current.grid[self.slice2d + (self.i,)]
244  z1 = current.objectiveValues[self.slice2d].copy()
245  z2 = current.objectiveModel[self.slice2d].copy()
246  norm = matplotlib.colors.Normalize(vmin=self.zlim[0], vmax=self.zlim[1])
247 
248  self._contour(self.axes2d, x, y, z1, cmap=matplotlib.cm.spring, norm=norm)
249  self._contour(self.axes2d, x, y, z2, cmap=matplotlib.cm.winter, norm=norm)
250 
251  # matplotlib doesn't do clipping in 3d, so we'll do that manually
252  if self._clipZ(x, y, z1):
253  self._contour(self.axes3d, x, y, z1, cmap=matplotlib.cm.spring, norm=norm)
254  self.artists.append(self.axes3d.plot_surface(x, y, z1, rstride=1, cstride=1,
255  cmap=matplotlib.cm.spring, norm=norm,
256  linewidth=0, antialiased=1, alpha=0.5))
257  if self._clipZ(x, y, z2):
258  self._contour(self.axes3d, x, y, z2, cmap=matplotlib.cm.winter, norm=norm)
259  self.artists.append(self.axes3d.plot_surface(x, y, z2, rstride=1, cstride=1,
260  cmap=matplotlib.cm.winter, norm=norm,
261  linewidth=0, antialiased=1, alpha=0.5))
262 
263  # Now the 1-d surfaces
264  self.artists.extend(self.axesX.plot(current.grid[self.sliceX + (self.j,)],
265  current.objectiveValues[self.sliceX], 'm-'))
266  self.artists.extend(self.axesX.plot(current.grid[self.sliceX + (self.j,)],
267  current.objectiveModel[self.sliceX], 'c-'))
268  self.artists.extend(self.axesY.plot(current.objectiveValues[self.sliceY],
269  current.grid[self.sliceY + (self.i,)], 'm-'))
270  self.artists.extend(self.axesY.plot(current.objectiveModel[self.sliceY],
271  current.grid[self.sliceY + (self.i,)], 'c-'))
272 
273  def move(self, n):
274  self.n = n
275  if not self._lock:
276  self.guessExtent()
277  for artist in self.artists:
278  try:
279  artist.remove()
280  except TypeError:
281  # sometimes matplotlib throws an exception even though everything worked fine
282  pass
283  self.artists = []
284  self.plotSurfaces()
285  self.plotRejected()
286  self.figure.canvas.draw()
def __init__(self, history, model, objective, steps=11)
def setExtent(self, x0=None, x1=None, y0=None, y1=None, z0=None, z1=None, lock=True)