LSST Applications  21.0.0-131-g8cabc107+528f53ee53,22.0.0+00495a2688,22.0.0+0ef2527977,22.0.0+11a2aa21cd,22.0.0+269b7e55e3,22.0.0+2c6b6677a3,22.0.0+64c1bc5aa5,22.0.0+7b3a3f865e,22.0.0+e1b6d2281c,22.0.0+ff3c34362c,22.0.1-1-g1b65d06+c95cbdf3df,22.0.1-1-g7058be7+1cf78af69b,22.0.1-1-g7dab645+2a65e40b06,22.0.1-1-g8760c09+64c1bc5aa5,22.0.1-1-g949febb+64c1bc5aa5,22.0.1-1-ga324b9c+269b7e55e3,22.0.1-1-gf9d8b05+ff3c34362c,22.0.1-10-g781e53d+9b51d1cd24,22.0.1-10-gba590ab+b9624b875d,22.0.1-13-g76f9b8d+2c6b6677a3,22.0.1-14-g22236948+57af756299,22.0.1-18-g3db9cf4b+9b7092c56c,22.0.1-18-gb17765a+2264247a6b,22.0.1-2-g8ef0a89+2c6b6677a3,22.0.1-2-gcb770ba+c99495d3c6,22.0.1-24-g2e899d296+4206820b0d,22.0.1-3-g7aa11f2+2c6b6677a3,22.0.1-3-g8c1d971+f253ffa91f,22.0.1-3-g997b569+ff3b2f8649,22.0.1-4-g1930a60+6871d0c7f6,22.0.1-4-g5b7b756+6b209d634c,22.0.1-6-ga02864e+6871d0c7f6,22.0.1-7-g3402376+a1a2182ac4,22.0.1-7-g65f59fa+54b92689ce,master-gcc5351303a+e1b6d2281c,w.2021.32
LSST Data Management Base Package
astierCovFitParameters.py
Go to the documentation of this file.
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/>.
21 
22 # Code developed at LPNHE ("saunerie", Paris)
23 
24 import numpy as np
25 
26 """
27 The classes in this file were taken from https://github.com/PierreAstier/bfptc
28 by Pierre Astier (Laboratoire de Physique NuclĂ©aire et de Hautes Energies (LPNHE),
29 Sorbonne UniversitĂ©, Paris, France).
30 
31 File: bfptc/py/fitparameters.py
32 Commit hash: d46ba836fd5feb1c0065b61472c5f31b73b8480f
33 
34 
35 Notes from original code
36 -----------------------
37 
38 This module contains utility classes to handle parameters in linear and
39 non-linear least square fits implemented in linearmodels and
40 nonlinearmodels. It provide 2 main features:
41 
42 - `StructArray`: a derivative of numpy class ndarray to manage large
43 vectors organized into named subgroups.
44 
45 - `FitParameters` : a class to manage large parameter vectors. It
46 allows to easily fix/release specific parameters or entire subgroups,
47 and remap the remaining free parameters into a contiguous vector.
48 """
49 
50 
52  """ Collection of named slices
53 
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.
57 
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.slicesslices = groups.slices.copy()
72  self._len_len = groups._len
73  else:
74  self.slicesslices = {}
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.slicesslices[name] = slice(i, i + _len)
91  i += _len
92  self._len_len = i
93 
94  def __getitem__(self, arg):
95  if isinstance(arg, str):
96  return self.slicesslices[arg]
97  else:
98  return arg
99 
100  def __iter__(self):
101  return self.slicesslices.__iter__()
102 
103  def __len__(self):
104  return self._len_len
105 
106 
107 class StructArray(np.ndarray):
108  """Decorate numpy arrays with a collection of named slices.
109 
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.
113 
114  Examples:
115  ---------
116  >>> v = StructArray(np.zeros(10), [('a', 3), ('b', 7)])
117  >>> print v['a']
118  [ 0. 0. 0.]
119 
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
129 
130  def __array_finalize__(self, obj):
131  if obj is None:
132  return
133  self.structstruct = getattr(obj, 'struct', [])
134 
135  def __getitem__(self, args):
136  if not isinstance(args, tuple):
137  args = args,
138  newargs = tuple([self.structstruct[arg] for arg in args])
139  return np.asarray(self)[newargs]
140 
141  def __setitem__(self, args, val):
142  if not isinstance(args, tuple):
143  args = args,
144  newargs = tuple([self.structstruct[arg] for arg in args])
145  np.asarray(self)[newargs] = val
146 
147  def __getslice__(self, start, stop):
148  return self.__getitem____getitem__(slice(start, stop))
149 
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.structstruct,)
155  # Return a tuple that replaces the parent's __setstate__ tuple with our own
156  return (pickled_state[0], pickled_state[1], new_state)
157 
158  def __setstate__(self, state):
159  self.structstruct = 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])
162 
163 
165  """ Manages a vector of fit parameters with the possibility to mark a subset of
166  them as fixed to a given value.
167 
168  The parameters can be organized in named slices (block of contiguous
169  values) accessible through indexing by their name as in `StructArray`.
170 
171  >>> p_salt = FitParameters(['X0', 'X1', 'Color', 'Redshift'])
172  >>> p_dice = FitParameters([('alpha', 2), ('S', 10), ('dSdT', 10), 'idark'])
173 
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:
178 
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])
190 
191  It is also possible to mark parameters as fixed to a value.
192  >>> p_dice.fix(0, 12.)
193 
194  Value is optional. The above is equivalent to:
195  >>> p_dice[0] = 12.
196  >>> p_dice.fix(0)
197 
198  Again named slices simplifies the operations:
199  >>> p_dice['S'].fix([0, -1], 12.)
200  >>> p_dice['dSdT'].fix([0, -1])
201 
202  One can fix entire slices at once:
203  >>> p_dice['idark'].fix()
204  >>> p_salt['Redshift'].fix(val=0.23432)
205 
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
210 
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
217 
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.]
223 
224  >>> p_dice[:5].free = 4.
225  >>> print p_dice[:5].free
226  [ 4. 4. 4.]
227 
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
232 
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_free = StructArray(np.ones(len(struct), dtype='bool'), struct)
244  self._pars_pars = StructArray(np.zeros(len(struct), dtype='float'), struct)
245  self._index_index = StructArray(np.zeros(len(struct), dtype='int'), struct)
246  self._struct_struct = struct
247  self._reindex_reindex()
248  self._base_base = self
249 
250  def copy(self):
251  cop = FitParameters(self._struct_struct)
252  cop._free = StructArray(np.copy(self._free_free), cop._struct)
253  cop._pars = StructArray(np.copy(self._pars_pars), cop._struct)
254  cop._index = StructArray(np.copy(self._index_index), cop._struct)
255  cop._reindex()
256  cop._base = cop
257  return cop
258 
259  def _reindex(self):
260  self._index_index[self._free_free] = np.arange(self._free_free.sum())
261  self._index_index[~self._free_free] = -1
262 
263  def fix(self, keys=slice(None), val=None):
264  self._free_free[keys] = False
265  if val is not None:
266  self._pars_pars[keys] = val
267  self._base_base._reindex()
268 
269  def release(self, keys=slice(None)):
270  self._free_free[keys] = True
271  self._base_base._reindex()
272 
273  def indexof(self, indices=slice(None)):
274  return self._index_index[indices]
275 
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_free[args]
282  new._pars = self._pars_pars[args]
283  new._index = self._index_index[args]
284  new._base = self._base_base
285  return new
286 
287  def __setitem__(self, args, val):
288  self._pars_pars[args] = val
289 
290  @property
291  def free(self):
292  return self._pars_pars[self._free_free]
293 
294  @free.setter
295  def free(self, val):
296  self._pars_pars[self._free_free] = val
297 
298  @property
299  def full(self):
300  return self._pars_pars
301 
302  @full.setter
303  def full(self, val):
304  self._pars_pars = val
305 
306  def __repr__(self):
307  if hasattr(self, '_struct'):
308  s = "\n".join(['%s: %s' % (key, repr(self._pars_pars[key])) for key in self._struct_struct])
309  else:
310  s = repr(self._pars_pars)
311  return s