Conditions | 8 |
Total Lines | 137 |
Lines | 0 |
Ratio | 0 % |
Changes | 5 | ||
Bugs | 1 | Features | 1 |
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
1 | #!/usr/bin/env python |
||
42 | def sines_and_cosines(dispersion, flux, ivar, continuum_pixels, L=1400, order=3, |
||
43 | regions=None, fill_value=1.0, **kwargs): |
||
44 | """ |
||
45 | Fit the flux values of pre-defined continuum pixels using a sum of sine and |
||
46 | cosine functions. |
||
47 | |||
48 | :param dispersion: |
||
49 | The dispersion values. |
||
50 | |||
51 | :param flux: |
||
52 | The flux values for all pixels, as they correspond to the `dispersion` |
||
53 | array. |
||
54 | |||
55 | :param ivar: |
||
56 | The inverse variances for all pixels, as they correspond to the |
||
57 | `dispersion` array. |
||
58 | |||
59 | :param continuum_pixels: |
||
60 | A mask that selects pixels that should be considered as 'continuum'. |
||
61 | |||
62 | :param L: [optional] |
||
63 | The length scale for the sines and cosines. |
||
64 | |||
65 | :param order: [optional] |
||
66 | The number of sine/cosine functions to use in the fit. |
||
67 | |||
68 | :param regions: [optional] |
||
69 | Specify sections of the spectra that should be fitted separately in each |
||
70 | star. This may be due to gaps between CCDs, or some other physically- |
||
71 | motivated reason. These values should be specified in the same units as |
||
72 | the `dispersion`, and should be given as a list of `[(start, end), ...]` |
||
73 | values. For example, APOGEE spectra have gaps near the following |
||
74 | wavelengths which could be used as `regions`: |
||
75 | |||
76 | >> regions = ([15090, 15822], [15823, 16451], [16452, 16971]) |
||
77 | |||
78 | :param fill_value: [optional] |
||
79 | The continuum value to use for when no continuum was calculated for that |
||
80 | particular pixel (e.g., the pixel is outside of the `regions`). |
||
81 | |||
82 | :param full_output: [optional] |
||
83 | If set as True, then a metadata dictionary will also be returned. |
||
84 | |||
85 | :returns: |
||
86 | The continuum values for all pixels, and a dictionary that contains |
||
87 | metadata about the fit. |
||
88 | """ |
||
89 | |||
90 | scalar = kwargs.pop("__magic_scalar", 1e-6) # MAGIC |
||
91 | flux, ivar = np.atleast_2d(flux), np.atleast_2d(ivar) |
||
92 | |||
93 | if regions is None: |
||
94 | regions = [(dispersion[0], dispersion[-1])] |
||
95 | |||
96 | region_masks = [] |
||
97 | region_matrices = [] |
||
98 | continuum_masks = [] |
||
99 | continuum_matrices = [] |
||
100 | pixel_included_in_regions = np.zeros_like(flux).astype(int) |
||
101 | for start, end in regions: |
||
102 | |||
103 | # Build the masks for this region. |
||
104 | si, ei = np.searchsorted(dispersion, (start, end)) |
||
105 | region_mask = (end >= dispersion) * (dispersion >= start) |
||
106 | region_masks.append(region_mask) |
||
107 | pixel_included_in_regions[:, region_mask] += 1 |
||
108 | |||
109 | continuum_masks.append(continuum_pixels[ |
||
110 | (ei >= continuum_pixels) * (continuum_pixels >= si)]) |
||
111 | |||
112 | # Build the design matrices for this region. |
||
113 | region_matrices.append( |
||
114 | _continuum_design_matrix(dispersion[region_masks[-1]], L, order)) |
||
115 | continuum_matrices.append( |
||
116 | _continuum_design_matrix(dispersion[continuum_masks[-1]], L, order)) |
||
117 | |||
118 | # TODO: ISSUE: Check for overlapping regions and raise an warning. |
||
119 | |||
120 | # Check for non-zero pixels (e.g. ivar > 0) that are not included in a |
||
121 | # region. We should warn about this very loudly! |
||
122 | warn_on_pixels = (pixel_included_in_regions == 0) * (ivar > 0) |
||
123 | |||
124 | metadata = [] |
||
125 | continuum = np.ones_like(flux) * fill_value |
||
126 | for i in range(flux.shape[0]): |
||
127 | |||
128 | warn_indices = np.where(warn_on_pixels[i])[0] |
||
129 | if any(warn_indices): |
||
130 | # Split by deltas so that we give useful warning messages. |
||
131 | segment_indices = np.where(np.diff(warn_indices) > 1)[0] |
||
132 | segment_indices = np.sort(np.hstack( |
||
133 | [0, segment_indices, segment_indices + 1, len(warn_indices)])) |
||
134 | segment_indices = segment_indices.reshape(-1, 2) |
||
135 | |||
136 | segments = ", ".join(["{:.1f} to {:.1f} ({:d} pixels)".format( |
||
137 | dispersion[s], dispersion[e], e-s) for s, e in segment_indices]) |
||
138 | |||
139 | logging.warn("Some pixels in spectrum index {0} have measured flux " |
||
140 | "values (e.g., ivar > 0) but are not included in any " |
||
141 | "specified continuum region. These pixels won't be " |
||
142 | "continuum-normalised: {1}".format(i, segments)) |
||
143 | |||
144 | # Get the flux and inverse variance for this object. |
||
145 | object_metadata = [] |
||
146 | object_flux, object_ivar = (flux[i], ivar[i]) |
||
147 | |||
148 | # Normalize each region. |
||
149 | for region_mask, region_matrix, continuum_mask, continuum_matrix in \ |
||
150 | zip(region_masks, region_matrices, continuum_masks, continuum_matrices): |
||
151 | if continuum_mask.size == 0: |
||
152 | # Skipping.. |
||
153 | object_metadata.append([order, L, fill_value, scalar, [], None]) |
||
154 | continue |
||
155 | |||
156 | # We will fit to continuum pixels only. |
||
157 | continuum_disp = dispersion[continuum_mask] |
||
158 | continuum_flux, continuum_ivar \ |
||
159 | = (object_flux[continuum_mask], object_ivar[continuum_mask]) |
||
160 | |||
161 | # Solve for the amplitudes. |
||
162 | M = continuum_matrix |
||
163 | MTM = np.dot(M, continuum_ivar[:, None] * M.T) |
||
164 | MTy = np.dot(M, (continuum_ivar * continuum_flux).T) |
||
165 | |||
166 | eigenvalues = np.linalg.eigvalsh(MTM) |
||
167 | MTM[np.diag_indices(len(MTM))] += scalar * np.max(eigenvalues) |
||
168 | eigenvalues = np.linalg.eigvalsh(MTM) |
||
169 | condition_number = max(eigenvalues)/min(eigenvalues) |
||
170 | |||
171 | amplitudes = np.linalg.solve(MTM, MTy) |
||
172 | continuum[i, region_mask] = np.dot(region_matrix.T, amplitudes) |
||
173 | object_metadata.append( |
||
174 | (order, L, fill_value, scalar, amplitudes, condition_number)) |
||
175 | |||
176 | metadata.append(object_metadata) |
||
177 | |||
178 | return (continuum, metadata) |
||
179 | |||
243 |