Coverage for python/lsst/cp/pipe/astierCovFitParameters.py : 22%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of cp_pipe.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22# Code developed at LPNHE ("saunerie", Paris)
24import numpy as np
26"""
27The classes in this file were taken from https://github.com/PierreAstier/bfptc
28by Pierre Astier (Laboratoire de Physique Nucléaire et de Hautes Energies (LPNHE),
29Sorbonne Université, Paris, France).
31File: bfptc/py/fitparameters.py
32Commit hash: d46ba836fd5feb1c0065b61472c5f31b73b8480f
35Notes from original code
36-----------------------
38This module contains utility classes to handle parameters in linear and
39non-linear least square fits implemented in linearmodels and
40nonlinearmodels. It provide 2 main features:
42- `StructArray`: a derivative of numpy class ndarray to manage large
43vectors organized into named subgroups.
45- `FitParameters` : a class to manage large parameter vectors. It
46allows to easily fix/release specific parameters or entire subgroups,
47and remap the remaining free parameters into a contiguous vector.
48"""
51class Structure(object):
52 """ Collection of named slices
54 Slices are specified by a name and a length. If omitted, the length
55 default to one and the name to __0__ for the first unamed slice, __1__
56 for the second and so on.
58 Examples:
59 ---------
60 >>> s = Structure([('a', 7), 3])
61 >>> print len(s)
62 10
63 >>> for name in s: print name
64 a
65 __0__
66 >>> print len(Structure([('a', 3), 'b']))
67 4
68 """
69 def __init__(self, groups):
70 if isinstance(groups, Structure):
71 self.slices = groups.slices.copy()
72 self._len = groups._len
73 else:
74 self.slices = {}
75 i = 0
76 _n_unnamed = 0
77 for group in groups:
78 if isinstance(group, int):
79 name = "__%d__" % _n_unnamed
80 _len = group
81 _n_unnamed += 1
82 elif isinstance(group, str):
83 name = group
84 _len = 1
85 else:
86 try:
87 name, _len = group
88 except TypeError:
89 raise TypeError('Structure specification not understood: %s' % repr(group))
90 self.slices[name] = slice(i, i + _len)
91 i += _len
92 self._len = i
94 def __getitem__(self, arg):
95 if isinstance(arg, str):
96 return self.slices[arg]
97 else:
98 return arg
100 def __iter__(self):
101 return self.slices.__iter__()
103 def __len__(self):
104 return self._len
107class StructArray(np.ndarray):
108 """Decorate numpy arrays with a collection of named slices.
110 Array slices becomes accessible by their name. This is applicable to
111 nd array, although the same `Structure` is shared between all
112 dimensions.
114 Examples:
115 ---------
116 >>> v = StructArray(np.zeros(10), [('a', 3), ('b', 7)])
117 >>> print v['a']
118 [ 0. 0. 0.]
120 >>> C = StructArray(np.zeros((10,10)), [('a', 2), ('b', 8)])
121 >>> print C['a', 'a']
122 [[ 0. 0.]
123 [ 0. 0.]]
124 """
125 def __new__(cls, array, struct=[]):
126 obj = array.view(cls)
127 obj.struct = Structure(struct)
128 return obj
130 def __array_finalize__(self, obj):
131 if obj is None:
132 return
133 self.struct = getattr(obj, 'struct', [])
135 def __getitem__(self, args):
136 if not isinstance(args, tuple):
137 args = args,
138 newargs = tuple([self.struct[arg] for arg in args])
139 return np.asarray(self)[newargs]
141 def __setitem__(self, args, val):
142 if not isinstance(args, tuple):
143 args = args,
144 newargs = tuple([self.struct[arg] for arg in args])
145 np.asarray(self)[newargs] = val
147 def __getslice__(self, start, stop):
148 return self.__getitem__(slice(start, stop))
150 def __reduce__(self):
151 # Get the parent's __reduce__ tuple
152 pickled_state = super(StructArray, self).__reduce__()
153 # Create our own tuple to pass to __setstate__
154 new_state = pickled_state[2] + (self.struct,)
155 # Return a tuple that replaces the parent's __setstate__ tuple with our own
156 return (pickled_state[0], pickled_state[1], new_state)
158 def __setstate__(self, state):
159 self.struct = state[-1] # Set the info attribute
160 # Call the parent's __setstate__ with the other tuple elements.
161 super(StructArray, self).__setstate__(state[0:-1])
164class FitParameters(object):
165 """ Manages a vector of fit parameters with the possibility to mark a subset of
166 them as fixed to a given value.
168 The parameters can be organized in named slices (block of contiguous
169 values) accessible through indexing by their name as in `StructArray`.
171 >>> p_salt = FitParameters(['X0', 'X1', 'Color', 'Redshift'])
172 >>> p_dice = FitParameters([('alpha', 2), ('S', 10), ('dSdT', 10), 'idark'])
174 It is possible to modify the parameters in place. Using the indexing
175 of slices by name simplifies somewhat the operations, as one does
176 not need to care about the position of a slice within the entire
177 parameter vector:
179 >>> p_dice['idark'][0] = -1.0232E-12
180 >>> p_dice['S'][4] = 25.242343E-9
181 >>> p_dice['dSdT'][:] = 42.
182 >>> print p_dice
183 alpha: array([ 0., 0.])
184 S: array([ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
185 0.00000000e+00, 2.52423430e-08, 0.00000000e+00,
186 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
187 0.00000000e+00])
188 dSdT: array([ 42., 42., 42., 42., 42., 42., 42., 42., 42., 42.])
189 idark: array([ -1.02320000e-12])
191 It is also possible to mark parameters as fixed to a value.
192 >>> p_dice.fix(0, 12.)
194 Value is optional. The above is equivalent to:
195 >>> p_dice[0] = 12.
196 >>> p_dice.fix(0)
198 Again named slices simplifies the operations:
199 >>> p_dice['S'].fix([0, -1], 12.)
200 >>> p_dice['dSdT'].fix([0, -1])
202 One can fix entire slices at once:
203 >>> p_dice['idark'].fix()
204 >>> p_salt['Redshift'].fix(val=0.23432)
206 The property ``full'' give access to the vector of parameters. The
207 property "free" gives access to the free parameters:
208 >>> print len(p_dice.free), len(p_dice.full)
209 17 23
211 Note that free relies on fancy indexing. Access thus trigger a
212 copy. As a consequence, the following will not actually alter the
213 data:
214 >>> p_dice.free[0] = 12.
215 >>> print p_dice.free[0]
216 0.0
218 It is still possible to set slices of free parameters as a
219 contiguous vector. For example:
220 >>> p_dice['S'].free = 12.
221 >>> print p_dice['S'].free
222 [ 12. 12. 12. 12. 12. 12. 12. 12.]
224 >>> p_dice[:5].free = 4.
225 >>> print p_dice[:5].free
226 [ 4. 4. 4.]
228 In particular, the typical use case which consists in updating the
229 free parameters with the results of a fit works as expected:
230 >>> p = np.arange(len(p_dice.free))
231 >>> p_dice.free = p
233 Last the class provide a convenience function that return the index
234 of a subset of parameters in the global free parameters vector, and
235 -1 for fixed parameters:
236 >>> print p_dice['dSdT'].indexof()
237 [-1 9 10 11 12 13 14 15 16 -1]
238 >>> print p_dice['dSdT'].indexof([1,2])
239 [ 9 10]
240 """
241 def __init__(self, groups):
242 struct = Structure(groups)
243 self._free = StructArray(np.ones(len(struct), dtype='bool'), struct)
244 self._pars = StructArray(np.zeros(len(struct), dtype='float'), struct)
245 self._index = StructArray(np.zeros(len(struct), dtype='int'), struct)
246 self._struct = struct
247 self._reindex()
248 self._base = self
250 def copy(self):
251 cop = FitParameters(self._struct)
252 cop._free = StructArray(np.copy(self._free), cop._struct)
253 cop._pars = StructArray(np.copy(self._pars), cop._struct)
254 cop._index = StructArray(np.copy(self._index), cop._struct)
255 cop._reindex()
256 cop._base = cop
257 return cop
259 def _reindex(self):
260 self._index[self._free] = np.arange(self._free.sum())
261 self._index[~self._free] = -1
263 def fix(self, keys=slice(None), val=None):
264 self._free[keys] = False
265 if val is not None:
266 self._pars[keys] = val
267 self._base._reindex()
269 def release(self, keys=slice(None)):
270 self._free[keys] = True
271 self._base._reindex()
273 def indexof(self, indices=slice(None)):
274 return self._index[indices]
276 def __getitem__(self, args):
277 # Prevent conversion to scalars
278 if isinstance(args, int):
279 args = slice(args, args + 1)
280 new = FitParameters.__new__(FitParameters)
281 new._free = self._free[args]
282 new._pars = self._pars[args]
283 new._index = self._index[args]
284 new._base = self._base
285 return new
287 def __setitem__(self, args, val):
288 self._pars[args] = val
290 @property
291 def free(self):
292 return self._pars[self._free]
294 @free.setter
295 def free(self, val):
296 self._pars[self._free] = val
298 @property
299 def full(self):
300 return self._pars
302 @full.setter
303 def full(self, val):
304 self._pars = val
306 def __repr__(self):
307 if hasattr(self, '_struct'):
308 s = "\n".join(['%s: %s' % (key, repr(self._pars[key])) for key in self._struct])
309 else:
310 s = repr(self._pars)
311 return s