lsst.meas.modelfit g396055baef+4a5624b42d
Loading...
Searching...
No Matches
optimizerDisplay.py
Go to the documentation of this file.
2# LSST Data Management System
3# Copyright 2008-2013 LSST Corporation.
4#
5# This product includes software developed by the
6# LSST Project (http://www.lsst.org/).
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the LSST License Statement and
19# the GNU General Public License along with this program. If not,
20# see <http://www.lsstcorp.org/LegalNotices/>.
21#
22
23import numpy
24import matplotlib
25import matplotlib.colors
26from mpl_toolkits.axes_grid1 import make_axes_locatable
27
28from .densityPlot import hide_xticklabels, hide_yticklabels
29from .. import modelfitLib
30
31__all__ = ("OptimizerDisplay", )
32
33
35
36 def __init__(self, parent, sample):
37 self.parent = parent
38 self.sample = sample
39 # scale unit grid by trust radius
40 self.grid = parent.unitGrid * sample.get(parent.recorder.trust)
41 # offset grid to center it on the current parameter point
42 self.grid += sample.get(parent.recorder.parameters).reshape((1,)*parent.ndim + (parent.ndim,))
43 self._objectiveValues = None
44 self._objectiveModel = None
45 self.rejected = []
46
47 def __getattr__(self, name):
48 # look for keys on the recorder and lookup fields for unknown attributes
49 return self.sample.get(getattr(self.parent.recorder, name))
50
51 @property
52 def objectiveValues(self):
53 if self._objectiveValues is None:
54 self._objectiveValues = numpy.zeros(self.grid.shape[:-1], dtype=float)
55 self.parent.objective.fillObjectiveValueGrid(self.grid.reshape(-1, self.parent.ndim),
56 self._objectiveValues.reshape(-1))
57 good = numpy.isfinite(self._objectiveValues)
58 self._objectiveValues[numpy.logical_not(good)] = self._objectiveValues[good].max()
59 return self._objectiveValues
60
61 @property
62 def objectiveModel(self):
63 if self._objectiveModel is None:
64 self._objectiveModel = numpy.zeros(self.grid.shape[:-1], dtype=float)
65 self.parent.recorder.fillObjectiveModelGrid(self.sample,
66 self.grid.reshape(-1, self.parent.ndim),
67 self._objectiveModel.reshape(-1))
68 return self._objectiveModel
69
70
72
73 def __init__(self, history, model, objective, steps=11):
74 self.recorder = modelfitLib.OptimizerHistoryRecorder(history.schema)
75 # len(dimensions) == N in comments below
76 self.dimensions = list(model.getNonlinearNames()) + list(model.getAmplitudeNames())
77 self.ndim = len(self.dimensions)
78 self.track = []
79 self.objective = objective
80 # This creates a array with shape [steps, ..., steps, N] (a total of N+1 array dimensions):
81 # this is an N-dimensional grid, with the last dimension of the grid array giving the coordinates
82 # of each grid point.
83 # We slice mgrid to generate the basic grid, which is [N, steps, ..., steps]
84 mgridArgs = (slice(-1.0, 1.0, steps*1j),) * self.ndim
85 # We'll index the result of mgrid with these args to make first dimension last
86 transposeArgs = tuple(list(range(1, self.ndim+1)) + [0])
87 self.unitGrid = numpy.mgrid[mgridArgs].transpose(transposeArgs).copy()
88 current = None
89 for sample in history:
90 if sample.get(self.recorder.state) & modelfitLib.Optimizer.STATUS_STEP_REJECTED:
91 assert current is not None
92 current.rejected.append(sample)
93 continue
94 current = OptimizerIterationDisplay(self, sample)
95 self.track.append(current)
96
97 def plot(self, xDim, yDim, n=0):
98 return OptimizerDisplayFigure(self, xDim=xDim, yDim=yDim, n=n)
99
100
102
103 def __init__(self, parent, xDim, yDim, n=0):
104 self.parent = parent
105 self.xDim = xDim
106 self.yDim = yDim
107 self.j = self.parent.dimensions.index(self.xDim)
108 self.i = self.parent.dimensions.index(self.yDim)
109 self.yKey = self.parent.recorder.parameters[self.i]
110 self.xKey = self.parent.recorder.parameters[self.j]
111 self.zKey = self.parent.recorder.objective
112 # grid slice indices corresponding to the dimensions we're plotting
113 self.slice2d = [s//2 for s in self.parent.unitGrid.shape[:-1]]
114 self.slice2d[self.i] = slice(None)
115 self.slice2d[self.j] = slice(None)
116 self.slice2d = tuple(self.slice2d)
117 self.sliceX = [s//2 for s in self.parent.unitGrid.shape[:-1]]
118 self.sliceX[self.j] = slice(None)
119 self.sliceX = tuple(self.sliceX)
120 self.sliceY = [s//2 for s in self.parent.unitGrid.shape[:-1]]
121 self.sliceY[self.i] = slice(None)
122 self.sliceY = tuple(self.sliceY)
123 self.track = dict(
124 x=numpy.array([iteration.sample.get(self.xKey) for iteration in self.parent.track]),
125 y=numpy.array([iteration.sample.get(self.yKey) for iteration in self.parent.track]),
126 z=numpy.array([iteration.sample.get(self.zKey) for iteration in self.parent.track]),
127 )
128 self.n = n
129 self.figure = matplotlib.pyplot.figure(f"{xDim} vs {yDim}", figsize=(16, 8))
130 self.figure.subplots_adjust(left=0.025, right=0.975, bottom=0.08, top=0.95, wspace=0.12)
131 self.axes3d = self.figure.add_subplot(1, 2, 1, projection='3d')
132 self.axes3d.autoscale(False)
133 self.axes3d.set_xlabel(self.xDim)
134 self.axes3d.set_ylabel(self.yDim)
135 self.axes2d = self.figure.add_subplot(1, 2, 2)
136 self.axes2d.set_xlabel(self.xDim)
137 self.axes2d.set_ylabel(self.yDim)
138 self.axes2d.autoscale(False)
139 divider = make_axes_locatable(self.axes2d)
140 self.axesX = divider.append_axes("top", 1.5, pad=0.1, sharex=self.axes2d)
141 self.axesX.autoscale(False, axis='x')
142 hide_xticklabels(self.axesX)
143 self.axesY = divider.append_axes("right", 1.5, pad=0.1, sharey=self.axes2d)
144 self.axesY.autoscale(False, axis='y')
145 hide_yticklabels(self.axesY)
146 self.artists = []
147 self.guessExtent()
148 self.plotTrack()
149 self.plotRejected()
150 self.plotSurfaces()
151
152 @property
153 def xlim(self):
154 return self._extent[:2]
155
156 @property
157 def ylim(self):
158 return self._extent[2:4]
159
160 @property
161 def zlim(self):
162 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()
def setExtent(self, x0=None, x1=None, y0=None, y1=None, z0=None, z1=None, lock=True)
def __init__(self, history, model, objective, steps=11)