py-lfkit 0.1.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. lfkit/__init__.py +19 -0
  2. lfkit/_version.py +24 -0
  3. lfkit/api/__init__.py +0 -0
  4. lfkit/api/corrections.py +308 -0
  5. lfkit/api/lumfunc.py +914 -0
  6. lfkit/corrections/__init__.py +0 -0
  7. lfkit/corrections/color_anchors.py +176 -0
  8. lfkit/corrections/filters.py +185 -0
  9. lfkit/corrections/kcorrect_backend.py +149 -0
  10. lfkit/corrections/kcorrect_from_color.py +111 -0
  11. lfkit/corrections/kcorrect_grids.py +242 -0
  12. lfkit/corrections/poggianti1997.py +386 -0
  13. lfkit/corrections/responses.py +183 -0
  14. lfkit/cosmo/__init__.py +0 -0
  15. lfkit/cosmo/cosmology.py +211 -0
  16. lfkit/data/demo_catalogs/fake_magnitude_limited_catalog.csv +201 -0
  17. lfkit/data/kcorrect/grids/kcorrect__bessell__z0.0000_4.0__Nz801__bsnone.npz +0 -0
  18. lfkit/data/kcorrect/grids/kcorrect__decam__z0.0000_4.0__Nz801__bsnone.npz +0 -0
  19. lfkit/data/kcorrect/grids/kcorrect__sdss__z0.0000_4.0__Nz801__bsnone.npz +0 -0
  20. lfkit/data/kcorrect/grids/kcorrect__subaru_suprimecam__z0.0000_4.0__Nz801__bsnone.npz +0 -0
  21. lfkit/data/poggianti1997/__init__.py +0 -0
  22. lfkit/data/poggianti1997/ecorr.csv +603 -0
  23. lfkit/data/poggianti1997/filters.csv +516 -0
  24. lfkit/data/poggianti1997/kcorr.csv +490 -0
  25. lfkit/data/poggianti1997/kcorrv.csv +37 -0
  26. lfkit/data/poggianti1997/sed.csv +295 -0
  27. lfkit/photometry/__init__.py +0 -0
  28. lfkit/photometry/catalog_completeness.py +381 -0
  29. lfkit/photometry/lf_integrals.py +500 -0
  30. lfkit/photometry/lf_parameter_models.py +386 -0
  31. lfkit/photometry/lf_redshift_density.py +238 -0
  32. lfkit/photometry/luminosities.py +426 -0
  33. lfkit/photometry/luminosity_function.py +707 -0
  34. lfkit/photometry/magnitudes.py +214 -0
  35. lfkit/utils/__init__.py +0 -0
  36. lfkit/utils/download_poggianti97_data.py +70 -0
  37. lfkit/utils/evaluators.py +104 -0
  38. lfkit/utils/interpolation.py +216 -0
  39. lfkit/utils/io.py +240 -0
  40. lfkit/utils/types.py +27 -0
  41. lfkit/utils/units.py +160 -0
  42. lfkit/utils/validators.py +63 -0
  43. py_lfkit-0.1.4.dist-info/METADATA +94 -0
  44. py_lfkit-0.1.4.dist-info/RECORD +47 -0
  45. py_lfkit-0.1.4.dist-info/WHEEL +5 -0
  46. py_lfkit-0.1.4.dist-info/licenses/LICENSE +21 -0
  47. py_lfkit-0.1.4.dist-info/top_level.txt +1 -0
lfkit/__init__.py ADDED
@@ -0,0 +1,19 @@
1
+ """Top-level package for LFKit."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from lfkit.api.corrections import Corrections
6
+ from lfkit.api.lumfunc import LuminosityFunction
7
+
8
+ try:
9
+ from lfkit._version import version as __version__
10
+ except ImportError:
11
+ __version__ = "unknown"
12
+
13
+
14
+ __all__ = [
15
+ "Corrections",
16
+ "LuminosityFunction",
17
+ ]
18
+
19
+ __author__ = """Niko Sarcevic and collaborators."""
lfkit/_version.py ADDED
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.1.4'
22
+ __version_tuple__ = version_tuple = (0, 1, 4)
23
+
24
+ __commit_id__ = commit_id = None
lfkit/api/__init__.py ADDED
File without changes
@@ -0,0 +1,308 @@
1
+ """
2
+ Unified k- and e-correction interface.
3
+
4
+ This module is the user-facing entry point for LFKit photometric corrections.
5
+ It provides a small, stable API for evaluating:
6
+
7
+ k(z) bandpass (k-) correction
8
+ e(z) luminosity evolution (e-) correction
9
+ ke(z) combined correction, ke(z) = k(z) + e(z)
10
+
11
+ Design goals
12
+ ------------
13
+ - Keep the runtime API minimal and stable: k(z), e(z), ke(z)
14
+ - Make backend choices explicit and composable:
15
+ - k(z) backend: "poggianti" or "kcorrect"
16
+ - e(z) backend: "none" or "poggianti"
17
+ - Use astronomy-friendly inputs at the public boundary:
18
+ - filterset: "sdss", "hsc", "decam", "bessell", ...
19
+ - band: "r", "i", "V", ...
20
+ - Poggianti uses a typed table key (gal_type)
21
+ - kcorrect uses an SED mixture specified by a color anchor
22
+
23
+ Reality check about kcorrect
24
+ ----------------------------
25
+ kcorrect returns k(z) for a chosen SED mixture and filter response. It does not
26
+ encode physical luminosity evolution by itself. If you want e(z), you supply a
27
+ separate evolution model (for example from Poggianti tabulations), and LFKit
28
+ combines them consistently.
29
+
30
+ Sign convention
31
+ ---------------
32
+ Absolute magnitude is defined as:
33
+
34
+ M = m - DM(z) - k(z) + e(z)
35
+
36
+ With this convention, if galaxies were brighter in the past, then e(z) is
37
+ typically negative for z > z_piv in pivoted evolution models.
38
+
39
+ Notes on filter names and kcorrect responses
40
+ --------------------------------------------
41
+ In LFKit the public choice of a band is expressed as (filterset, band). kcorrect
42
+ internally uses response curve identifiers (file stems). LFKit maps:
43
+
44
+ (filterset, band) -> response_name
45
+
46
+ You can override or extend this mapping via ``response_map`` when working with
47
+ custom surveys or custom filter curves.
48
+ """
49
+
50
+ from __future__ import annotations
51
+
52
+ from pathlib import Path
53
+ from typing import Callable
54
+
55
+ import numpy as np
56
+
57
+ from lfkit.utils.interpolation import build_1d_interpolator, as_1d_finite_grid
58
+ import lfkit.corrections.poggianti1997 as pogg
59
+ from lfkit.corrections.filters import DEFAULT_RESPONSE_MAP
60
+
61
+ ArrayLike = object # anything np.asarray can handle
62
+
63
+ # Default mapping: (filterset, band) -> kcorrect response curve name.
64
+ DEFAULT_KCORRECT_RESPONSE_MAP = DEFAULT_RESPONSE_MAP
65
+
66
+
67
+ class Corrections:
68
+ """Evaluator for k(z), e(z), and ke(z).
69
+
70
+ This object wraps callables representing the k-correction and the
71
+ luminosity evolution correction and provides a consistent interface
72
+ for evaluating:
73
+
74
+ k(z)
75
+ e(z)
76
+ ke(z) = k(z) + e(z)
77
+
78
+ Instances are typically created through the class constructors
79
+ ``Corrections.poggianti`` or ``Corrections.kcorrect``.
80
+
81
+ Args:
82
+ k_func: Callable returning k(z).
83
+ e_func: Optional callable returning e(z). If None, e(z)=0.
84
+ meta: Optional metadata dictionary describing the backend
85
+ configuration.
86
+ """
87
+
88
+ def __init__(
89
+ self,
90
+ k_func: Callable[[ArrayLike], ArrayLike],
91
+ e_func: Callable[[ArrayLike], ArrayLike] | None = None,
92
+ *,
93
+ meta: dict[str, object] | None = None,
94
+ ) -> None:
95
+ self._k = k_func
96
+ self._e = e_func
97
+ self.meta: dict[str, object] = {} if meta is None else dict(meta)
98
+
99
+ def k(self, z: ArrayLike) -> np.ndarray:
100
+ """Evaluate the k-correction.
101
+
102
+ Args:
103
+ z: Scalar or array-like redshift.
104
+
105
+ Returns:
106
+ NumPy array containing k(z).
107
+ """
108
+ return np.asarray(self._k(z), float)
109
+
110
+ def e(self, z: ArrayLike) -> np.ndarray:
111
+ """Evaluate the luminosity evolution correction.
112
+
113
+ Args:
114
+ z: Scalar or array-like redshift.
115
+
116
+ Returns:
117
+ NumPy array containing e(z). If no evolution model is attached,
118
+ zeros are returned.
119
+ """
120
+ if self._e is None:
121
+ return np.zeros_like(np.asarray(z, float))
122
+ return np.asarray(self._e(z), float)
123
+
124
+ def ke(self, z: ArrayLike) -> np.ndarray:
125
+ """Evaluate the combined correction ke(z).
126
+
127
+ Args:
128
+ z: Scalar or array-like redshift.
129
+
130
+ Returns:
131
+ NumPy array containing ke(z) = k(z) + e(z).
132
+ """
133
+ z_arr = np.asarray(z, float)
134
+ return self.k(z_arr) + self.e(z_arr)
135
+
136
+ @classmethod
137
+ def poggianti(
138
+ cls,
139
+ *,
140
+ band: str,
141
+ gal_type: str,
142
+ cosmo=None,
143
+ original_z_for_e: bool = True,
144
+ method: str = "pchip",
145
+ extrapolate: bool = True,
146
+ e_model: str = "poggianti",
147
+ ) -> "Corrections":
148
+ """Construct corrections from Poggianti (1997) tabulated models.
149
+
150
+ Args:
151
+ band: Poggianti band identifier (e.g. "V", "B").
152
+ gal_type: Galaxy spectral type in the Poggianti tables
153
+ (e.g. "E", "Sc").
154
+ cosmo: Optional cosmology object used for lookback-time
155
+ calculations in the evolution correction.
156
+ original_z_for_e: Whether the evolution correction is evaluated
157
+ on the original Poggianti redshift grid.
158
+ method: Interpolation method for the tabulated curves.
159
+ extrapolate: Whether to allow extrapolation outside the
160
+ tabulated redshift range.
161
+ e_model: Evolution backend ("poggianti" or "none").
162
+
163
+ Returns:
164
+ Corrections instance capable of evaluating k(z), e(z), and ke(z).
165
+
166
+ Raises:
167
+ ValueError: If an unsupported evolution model is requested.
168
+ """
169
+ z_k, kcorr, z_e, ecorr = pogg.load_poggianti1997_tables(band=band, sed=gal_type)
170
+
171
+ k_func = pogg.make_kcorr_interpolator(
172
+ z_k, kcorr, method=method, extrapolate=extrapolate
173
+ )
174
+
175
+ e_func: Callable[[ArrayLike], np.ndarray] | None
176
+ if str(e_model).lower() == "none":
177
+ e_func = None
178
+ elif str(e_model).lower() == "poggianti":
179
+ e_func = pogg.make_ecorr_interpolator(
180
+ z_e,
181
+ ecorr,
182
+ original_z=original_z_for_e,
183
+ cosmo=cosmo,
184
+ method=method,
185
+ extrapolate=extrapolate,
186
+ )
187
+ else:
188
+ raise ValueError(
189
+ "e_model must be 'none' or 'poggianti' for Corrections.poggianti()."
190
+ )
191
+
192
+ out = cls(k_func=k_func, e_func=e_func)
193
+ out.meta.update(
194
+ {
195
+ "k_backend": "poggianti1997",
196
+ "e_backend": str(e_model).lower(),
197
+ "band": str(band),
198
+ "gal_type": str(gal_type),
199
+ }
200
+ )
201
+ return out
202
+
203
+ @classmethod
204
+ def kcorrect(
205
+ cls,
206
+ *,
207
+ z_grid: ArrayLike | None = None,
208
+ response_out: str,
209
+ color: tuple[str, str],
210
+ color_value: float,
211
+ z_phot: float = 0.0,
212
+ anchor_band: str | None = None,
213
+ anchor_mag: float = 22.0,
214
+ band_shift: float | None = None,
215
+ response_dir: str | Path | None = None,
216
+ redshift_range: tuple[float, float] = (0.0, 2.0),
217
+ nredshift: int = 4000,
218
+ ivar_level: float = 1e10,
219
+ anchor_z0: bool = True,
220
+ method: str = "pchip",
221
+ extrapolate: bool = True,
222
+ ) -> "Corrections":
223
+ """Construct k(z) using the kcorrect SED template model.
224
+
225
+ The spectral energy distribution (SED) is constrained by a two-band
226
+ color. The color fixes a flux ratio between two bands, which
227
+ determines a mixture of kcorrect templates consistent with
228
+ that constraint. The resulting template mixture is then used
229
+ to evaluate k(z) for the requested output response.
230
+
231
+ Args:
232
+ z_grid: Redshift grid used to compute the k(z) curve. If None,
233
+ a default grid spanning ``redshift_range`` is used.
234
+ response_out: kcorrect response name for which k(z) is evaluated.
235
+ color: Two-band color constraint (band_a, band_b).
236
+ color_value: Target color value in magnitudes.
237
+ z_phot: Redshift at which the color constraint is applied.
238
+ anchor_band: Band used to set the arbitrary flux normalization.
239
+ anchor_mag: Magnitude used to set the arbitrary flux normalization.
240
+ band_shift: Optional kcorrect band shift parameter.
241
+ response_dir: Directory containing custom response curves.
242
+ redshift_range: Internal redshift range used by kcorrect.
243
+ nredshift: Internal redshift grid size used by kcorrect.
244
+ ivar_level: Weight assigned to constrained photometric bands.
245
+ anchor_z0: Whether to normalize so that k(z=0)=0.
246
+ method: Interpolation method used to construct k(z).
247
+ extrapolate: Whether to allow extrapolation outside the
248
+ computed redshift range.
249
+
250
+ Returns:
251
+ Corrections instance evaluating k(z). In this configuration
252
+ e(z) = 0.
253
+ """
254
+ from lfkit.corrections.kcorrect_from_color import kcorrect_from_bandcolor
255
+
256
+ if z_grid is None:
257
+ z_arr = np.linspace(redshift_range[0], redshift_range[1], 4001)
258
+ else:
259
+ z_arr = as_1d_finite_grid(z_grid, name="z_grid")
260
+
261
+ z_ok, k_ok = kcorrect_from_bandcolor(
262
+ z=np.asarray(z_arr, float),
263
+ response_out=str(response_out),
264
+ color=(str(color[0]), str(color[1])),
265
+ color_value=float(color_value),
266
+ z_phot=float(z_phot),
267
+ anchor_band=None if anchor_band is None else str(anchor_band),
268
+ anchor_mag=float(anchor_mag),
269
+ band_shift=band_shift,
270
+ response_dir=response_dir,
271
+ redshift_range=(float(redshift_range[0]), float(redshift_range[1])),
272
+ nredshift=int(nredshift),
273
+ ivar_level=float(ivar_level),
274
+ anchor_z0=bool(anchor_z0),
275
+ )
276
+
277
+ k_func = build_1d_interpolator(
278
+ np.asarray(z_ok, float),
279
+ np.asarray(k_ok, float),
280
+ method=str(method),
281
+ extrapolate=bool(extrapolate),
282
+ extrap_mode="linear_tail",
283
+ )
284
+
285
+ out = cls(k_func=k_func, e_func=None)
286
+ out.meta.update(
287
+ {
288
+ "k_backend": "kcorrect_bandcolor",
289
+ "response_out": str(response_out),
290
+ "color": (str(color[0]), str(color[1])),
291
+ "color_value": float(color_value),
292
+ "z_phot": float(z_phot),
293
+ "anchor_band": None if anchor_band is None else str(anchor_band),
294
+ "anchor_mag": float(anchor_mag),
295
+ "band_shift": band_shift,
296
+ "response_dir": str(response_dir) if response_dir is not None else None,
297
+ "redshift_range": (float(redshift_range[0]), float(redshift_range[1])),
298
+ "nredshift": int(nredshift),
299
+ "ivar_level": float(ivar_level),
300
+ "anchor_z0": bool(anchor_z0),
301
+ "method": str(method),
302
+ "extrapolate": bool(extrapolate),
303
+ "z_valid_min": float(np.asarray(z_ok, float)[0]),
304
+ "z_valid_max": float(np.asarray(z_ok, float)[-1]),
305
+ "n_finite": int(np.asarray(z_ok).size),
306
+ }
307
+ )
308
+ return out