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