PyCosmo 2.3.1__cp313-cp313-macosx_14_0_arm64.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 (94) hide show
  1. PyCosmo/Background.py +464 -0
  2. PyCosmo/BoltzmannSolver/Fields.py +123 -0
  3. PyCosmo/BoltzmannSolver/LinearPerturbationBoltzmann.py +580 -0
  4. PyCosmo/BoltzmannSolver/LsodaSolver.py +130 -0
  5. PyCosmo/BoltzmannSolver/ScipySolver.py +354 -0
  6. PyCosmo/BoltzmannSolver/__init__.py +15 -0
  7. PyCosmo/Cosmo.py +837 -0
  8. PyCosmo/CosmologyCore.py +645 -0
  9. PyCosmo/CosmologyCore_massivenu.py +954 -0
  10. PyCosmo/CosmologyCore_massivenu_rsa.py +341 -0
  11. PyCosmo/CosmologyCore_massivenu_wcdm.py +1010 -0
  12. PyCosmo/CosmologyCore_massivenu_wcdm_rsa.py +367 -0
  13. PyCosmo/CosmologyCore_rsa.py +233 -0
  14. PyCosmo/CosmologyCore_wcdm.py +712 -0
  15. PyCosmo/CosmologyCore_wcdm_rsa.py +261 -0
  16. PyCosmo/LinearPerturbationApprox.py +793 -0
  17. PyCosmo/LinearPerturbationBase.py +132 -0
  18. PyCosmo/NonLinearPerturbationHaloFit.py +513 -0
  19. PyCosmo/NonLinearPerturbationMead.py +857 -0
  20. PyCosmo/NonLinearPerturbation_HI.py +424 -0
  21. PyCosmo/NonLinearPerturbation_HaloModel.py +1266 -0
  22. PyCosmo/Obs.py +934 -0
  23. PyCosmo/PerturbationBase.py +80 -0
  24. PyCosmo/PerturbationTable.py +222 -0
  25. PyCosmo/Projection.py +427 -0
  26. PyCosmo/TheoryPredTables.py +236 -0
  27. PyCosmo/_Cosmics.py +162 -0
  28. PyCosmo/_Recombination.py +171 -0
  29. PyCosmo/_RecombinationClass.py +80 -0
  30. PyCosmo/_RecombinationCosmics.py +79 -0
  31. PyCosmo/_Util.py +54 -0
  32. PyCosmo/__init__.py +39 -0
  33. PyCosmo/_scipy_utils.py +42 -0
  34. PyCosmo/build.py +73 -0
  35. PyCosmo/config/__init__.py +17 -0
  36. PyCosmo/config/default_lcdm.ini +96 -0
  37. PyCosmo/config/default_lcdm_rsa.ini +100 -0
  38. PyCosmo/config/default_mnulcdm.ini +99 -0
  39. PyCosmo/config/default_mnulcdm_rsa.ini +101 -0
  40. PyCosmo/config/default_mnuwcdm.ini +100 -0
  41. PyCosmo/config/default_mnuwcdm_rsa.ini +102 -0
  42. PyCosmo/config/default_wcdm.ini +99 -0
  43. PyCosmo/config/default_wcdm_rsa.ini +101 -0
  44. PyCosmo/config/load_parameters_ini.py +367 -0
  45. PyCosmo/core_file_handling.py +124 -0
  46. PyCosmo/cython/__init__.py +26 -0
  47. PyCosmo/cython/const.c +252 -0
  48. PyCosmo/cython/constf.c +20 -0
  49. PyCosmo/cython/halo_integral.c +32954 -0
  50. PyCosmo/cython/halo_integral.cpython-313-darwin.so +0 -0
  51. PyCosmo/cython/halo_integral.pyx +288 -0
  52. PyCosmo/cython/halo_integral_1h.c +33369 -0
  53. PyCosmo/cython/halo_integral_1h.cpython-313-darwin.so +0 -0
  54. PyCosmo/cython/halo_integral_1h.pyx +298 -0
  55. PyCosmo/cython/halo_integral_2h.c +33530 -0
  56. PyCosmo/cython/halo_integral_2h.cpython-313-darwin.so +0 -0
  57. PyCosmo/cython/halo_integral_2h.pyx +306 -0
  58. PyCosmo/cython/halo_integral_HI_1h.c +33702 -0
  59. PyCosmo/cython/halo_integral_HI_1h.cpython-313-darwin.so +0 -0
  60. PyCosmo/cython/halo_integral_HI_1h.pyx +303 -0
  61. PyCosmo/cython/halo_integral_HI_2h.c +33881 -0
  62. PyCosmo/cython/halo_integral_HI_2h.cpython-313-darwin.so +0 -0
  63. PyCosmo/cython/halo_integral_HI_2h.pyx +314 -0
  64. PyCosmo/cython/logf.c +128 -0
  65. PyCosmo/cython/main.c +11 -0
  66. PyCosmo/cython/mconf.h +175 -0
  67. PyCosmo/cython/mtherr.c +102 -0
  68. PyCosmo/cython/polevl.c +97 -0
  69. PyCosmo/cython/polevlf.c +99 -0
  70. PyCosmo/cython/protos.h +148 -0
  71. PyCosmo/cython/rho_HI_integral.c +33004 -0
  72. PyCosmo/cython/rho_HI_integral.cpython-313-darwin.so +0 -0
  73. PyCosmo/cython/rho_HI_integral.pyx +257 -0
  74. PyCosmo/cython/rho_m_integral.c +32804 -0
  75. PyCosmo/cython/rho_m_integral.cpython-313-darwin.so +0 -0
  76. PyCosmo/cython/rho_m_integral.pyx +246 -0
  77. PyCosmo/cython/sici.c +675 -0
  78. PyCosmo/cython/sici.h +2 -0
  79. PyCosmo/cython/sici32.c +675 -0
  80. PyCosmo/cython/sicif.c +275 -0
  81. PyCosmo/cython/sinf.c +282 -0
  82. PyCosmo/disable_multithreading.py +13 -0
  83. PyCosmo/ini_handling.py +133 -0
  84. PyCosmo/model_config.py +209 -0
  85. PyCosmo/optimize_nq.py +61 -0
  86. PyCosmo/patches.py +24 -0
  87. PyCosmo/predefined_k_grids.py +87 -0
  88. pycosmo-2.3.1.dist-info/METADATA +54 -0
  89. pycosmo-2.3.1.dist-info/RECORD +94 -0
  90. pycosmo-2.3.1.dist-info/WHEEL +5 -0
  91. pycosmo-2.3.1.dist-info/entry_points.txt +2 -0
  92. pycosmo-2.3.1.dist-info/licenses/AUTHORS.rst +21 -0
  93. pycosmo-2.3.1.dist-info/licenses/LICENSE +15 -0
  94. pycosmo-2.3.1.dist-info/top_level.txt +1 -0
PyCosmo/Background.py ADDED
@@ -0,0 +1,464 @@
1
+ # This file is part of PyCosmo, a multipurpose cosmology calculation tool in Python.
2
+ #
3
+ # Copyright (C) 2013-2021 ETH Zurich, Institute for Particle and Astrophysics and SIS
4
+ # ID.
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify it under the
7
+ # terms of the GNU General Public License as published by the Free Software Foundation,
8
+ # either version 3 of the License, or (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful, but WITHOUT ANY
11
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12
+ # PARTICULAR PURPOSE. See the GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License along with this
15
+ # program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ import numpy as np
18
+ from scipy import integrate
19
+ from scipy.interpolate import interp1d
20
+
21
+
22
+ def _call(wrapper, function, a=None):
23
+ if a is None:
24
+ return getattr(wrapper, function)()
25
+ elif isinstance(a, (float, int)):
26
+ return getattr(wrapper, function)(a)
27
+ elif isinstance(a, list):
28
+ a = np.array(a)
29
+ assert isinstance(a, np.ndarray)
30
+ return getattr(wrapper, function)(a.astype(float))
31
+
32
+
33
+ class Background(object):
34
+ """
35
+ The background calculations are based on a
36
+ Friedmann-Robertson-Walker model for which the evolution is governed by
37
+ the Friedmann equation.
38
+ """
39
+
40
+ # The first part contains the functions that deal with the elements
41
+ # of the Friedmann equation. In particular the focus in the Hubble
42
+ # function. In the PyCosmo notes, this is section 1.2
43
+
44
+ def __init__(self, cosmo, params, rec, wrapper):
45
+ self._cosmo = cosmo
46
+ self._params = params
47
+ self._rec = rec
48
+ self._wrapper = wrapper
49
+ self._eta_to_a = None
50
+
51
+ def _setup_eta_to_a(self):
52
+ a = np.logspace(-100, 0.0, 1000)
53
+ eta = self.eta(a)
54
+ a = a[eta > 0]
55
+ eta = eta[eta > 0]
56
+ self._eta_to_a = interp1d(
57
+ eta, a, "cubic", bounds_error=False, fill_value="extrapolate"
58
+ )
59
+ self._eta_min = np.min(eta)
60
+ self._eta_max = np.max(eta)
61
+
62
+ def H(self, a):
63
+ """
64
+ Calculates the Hubble parameter for a given scale factor
65
+ by calling the expression from CosmologyCore_model.py.
66
+
67
+ :param a: scale factor [1]
68
+ :return: Hubble parameter :math:`H(a) [km/s/Mpc]`.
69
+
70
+ Example:
71
+
72
+ .. code-block:: python
73
+
74
+ cosmo.background.H(a)
75
+ """
76
+ self._cosmo.reset_wrapper_globals()
77
+ return _call(self._wrapper, "H", a)
78
+
79
+ _hubble = H
80
+
81
+ def _H2_H02_a(self, a=1.0):
82
+ result = self._H2_H02_Omegar_a(a=a)
83
+ result += self._H2_H02_Omegam_a(a=a)
84
+ result += self._H2_H02_Omegak_a(a=a)
85
+ result += self._H2_H02_Omegal_a(a=a)
86
+ return result
87
+
88
+ def _H2_H02_Omegar_a(self, a=1.0):
89
+ return self._params.omega_r / a**4
90
+
91
+ def _H2_H02_Omegam_a(self, a=1.0):
92
+ self._cosmo.reset_wrapper_globals()
93
+ return (
94
+ self._params.omega_m / a**3 + _call(self._wrapper, "omega_nu_m", a) / a**4
95
+ )
96
+
97
+ def _H2_H02_Omegak_a(self, a=1.0):
98
+ return self._params.omega_k / a**2
99
+
100
+ def _H2_H02_Omegal_a(self, a=1.0):
101
+ self._cosmo.reset_wrapper_globals()
102
+ return _call(self._wrapper, "omega_l_a", a)
103
+
104
+ def _H2_H02_Omega_nu_m_a(self, a=1.0):
105
+ self._cosmo.reset_wrapper_globals()
106
+ return _call(self._wrapper, "omega_nu_m", a) / a**4
107
+
108
+ def _w_a(self, a=1.0):
109
+ self._cosmo.reset_wrapper_globals()
110
+ return self._params.w0 + (1.0 - a) * self._params.wa
111
+
112
+ #########################################################
113
+ # A series of functions for calculating #
114
+ # cosmological distances: #
115
+ #########################################################
116
+
117
+ #
118
+ # TODO: have a closer look at this method, can we compile this?
119
+ # math formula should be in notebook
120
+
121
+ def dist_rad_a(self, a=1.0):
122
+ r"""
123
+ Calculates the radial comoving distance, also known as the comoving radius.
124
+
125
+ :param a: scale factor [1]
126
+ :return: Radial comoving distance, :math:`\chi(a)`, in :math:`[Mpc]`.
127
+
128
+ Example:
129
+
130
+ .. code-block:: python
131
+
132
+ cosmo.background.dist_rad_a(a)
133
+
134
+ """
135
+
136
+ return self._chi(a) * self._params.c
137
+
138
+ def _chi(self, a):
139
+ r"""
140
+ Calculates the comoving horizon, by calling the CosmologyCore_model.py
141
+
142
+ :param a: scale factor [1]
143
+ :return: Radial comoving horizon, :math:`\chi(a)`, in :math:`[Mpc s/km]`.
144
+ """
145
+ return _call(self._wrapper, "chi", a)
146
+
147
+ def dist_trans_a(self, a=1.0):
148
+ r"""
149
+ Calculates the transverse comoving distance, also known as comoving
150
+ angular-diameter distance.
151
+
152
+ :param a: scale factor [1]
153
+ :return: Transverse comoving distance, :math:`r(\chi)`, in :math:`[Mpc]`.
154
+
155
+ Example:
156
+
157
+ .. code-block:: python
158
+
159
+ cosmo.background.dist_trans_a(a)
160
+ """
161
+
162
+ Dc = self.dist_rad_a(a)
163
+ if self._params.omega_k == 0.0:
164
+ Dm = Dc
165
+ elif self._params.omega_k > 0.0:
166
+ Dm = (
167
+ self._params.rh
168
+ * np.sinh(self._params.sqrtk * Dc / self._params.rh)
169
+ / self._params.sqrtk
170
+ )
171
+ elif self._params.omega_k < 0.0:
172
+ Dm = (
173
+ self._params.rh
174
+ * np.sin(self._params.sqrtk * Dc / self._params.rh)
175
+ / self._params.sqrtk
176
+ )
177
+ return Dm
178
+
179
+ def dist_ang_a(self, a=1.0):
180
+ """
181
+ Calculates the angular-diameter distance to a given scale factor.
182
+
183
+ :param a: scale factor [1]
184
+ :return: Angular diameter distance, :math:`D_A(a)`, in :math:`[Mpc]`.
185
+
186
+ Example:
187
+
188
+ .. code-block:: python
189
+
190
+ cosmo.background.dist_ang_a(a)
191
+ """
192
+
193
+ return self.dist_trans_a(a) * a
194
+
195
+ def dist_lum_a(self, a=1.0):
196
+ """
197
+ Calculates the luminosity distance to a given scale factor.
198
+
199
+ :param a: scale factor [1]
200
+ :return: Luminosity distance, :math:`D_L(a)`, in :math:`[Mpc]`.
201
+
202
+ Example:
203
+
204
+ .. code-block:: python
205
+
206
+ cosmo.background.dist_lum_a(a)
207
+ """
208
+
209
+ return self.dist_trans_a(a) / a
210
+
211
+ def eta_to_a(self, eta):
212
+ if self._eta_to_a is None:
213
+ self._setup_eta_to_a()
214
+ eta = np.atleast_1d(eta)
215
+ if np.any(eta < 0.95 * self._eta_min) or np.any(eta > self._eta_max * 1.05):
216
+ raise ValueError(
217
+ f"eta value(s) outside of range {self._eta_min:e} .. {self._eta_max:e}"
218
+ )
219
+ return self._eta_to_a(eta)
220
+
221
+ def eta(self, a):
222
+ a = np.atleast_1d(a)
223
+ af = a[a > 1e-100]
224
+ af = np.concatenate(([1e-100], af))
225
+ y, meta = self._wrapper.solve_fast_eta(
226
+ np.array((0.0,)),
227
+ af,
228
+ 1e-8,
229
+ 1e-8,
230
+ )
231
+ eta = np.zeros_like(a)
232
+ eta[a > 1e-100] = y.flatten()[1:]
233
+ return eta
234
+
235
+ #########################################################
236
+ # A series of functions to compute thermodynamical #
237
+ # variables including recombination #
238
+ #########################################################
239
+
240
+ def taudot(self, a=1.0):
241
+ r"""
242
+ Calculates :math:`\dot{\tau} = \frac{d\tau}{d\eta}`,
243
+ where :math:`\tau` is the optical depth and :math:`\eta` is the conformal time.
244
+
245
+ :param a: scale factor [1]
246
+ :return: :math:`\dot{\tau} = \frac{d\tau}{d\eta}` in units of
247
+ [:math:`h \mathrm{Mpc}^{-1}`]
248
+
249
+ Example:
250
+
251
+ .. code-block:: python
252
+
253
+ cosmo.background.taudot(a)
254
+ """
255
+
256
+ a = np.atleast_1d(a)
257
+ return self._rec.taudot_a(a) # h/Mpc
258
+
259
+ def cs(self, a):
260
+ """
261
+ Returns the photon-baryon fluid sound speed [1]
262
+ from the Recombination module
263
+
264
+ :param a: scale factor [1]
265
+ :return: cs: photon-baryon sound speed [1]
266
+
267
+ Example:
268
+
269
+ .. code-block:: python
270
+
271
+ cosmo.background.cs(a)
272
+ """
273
+ a = np.atleast_1d(a)
274
+ return self._rec.cs_a(a)
275
+
276
+ # Flagged: More comments needed, might need to be speed up at some point.
277
+ def tau(self, a=1.0):
278
+ """
279
+ Calculates the optical depth of the photon-baryon fluid,
280
+ by integrating taudot from the Recombination module.
281
+
282
+ :param a: scale factor [1]
283
+ :return: tau: optical depth [1]
284
+
285
+ Example:
286
+
287
+ .. code-block:: python
288
+
289
+ cosmo.background.tau(a)
290
+ """
291
+ a = np.atleast_1d(a)
292
+ return np.array(
293
+ [
294
+ integrate.fixed_quad(self._tau_intgd, np.log(aa), 0.0, n=100)[0]
295
+ for aa in a
296
+ ]
297
+ )
298
+
299
+ def _tau_intgd(self, lna):
300
+ a = np.exp(lna)
301
+ return -self.taudot(a=a) / (a * self.H(a=a) / self._params.H0 / self._params.rh)
302
+
303
+ def tau_b(self, a=1.0):
304
+ """
305
+ Calculates the optical depth of the baryon fluid,
306
+ by integrating taudot from the Recombination module,
307
+ weighted with R, the baryon-photon fraction.
308
+
309
+ :param a: scale factor [1]
310
+ :return: tau: optical depth [1]
311
+
312
+ Example:
313
+
314
+ .. code-block:: python
315
+
316
+ cosmo.background.tau(a)
317
+ """
318
+ a = np.atleast_1d(a)
319
+ return np.array(
320
+ [
321
+ integrate.fixed_quad(self._tau_b_intgd, np.log(aa), 0.0, n=100)[0]
322
+ for aa in a
323
+ ]
324
+ )
325
+
326
+ def _tau_b_intgd(self, lna):
327
+ a = np.exp(lna)
328
+ result = -self.taudot(a=a) / (
329
+ a**2
330
+ * self.H(a=a)
331
+ / self._params.H0
332
+ / self._params.rh
333
+ * 3
334
+ * self._params.omega_b
335
+ / (4 * self._params.omega_gamma)
336
+ )
337
+ return result
338
+
339
+ def g_a(self, a=1.0):
340
+ """
341
+ Calculates the visibility function for a given scale factor.
342
+
343
+ :param a: scale factor [1]
344
+ :return: visibility function [1/Mpc]
345
+
346
+ Example:
347
+
348
+ .. code-block:: python
349
+
350
+ cosmo.background.g_a(a)
351
+ """
352
+ a = np.atleast_1d(a)
353
+ return -self.taudot(a) * np.exp(-self.tau(a)) * self._params.h
354
+
355
+ def r_s(self):
356
+ r"""
357
+ Calculates the sound horizon at drag epoch.
358
+
359
+ :return: sound horizon [:math:`\mathrm{Mpc} / h`]
360
+ """
361
+ a_log = np.logspace(np.log10(2e-4), np.log10(2e-3), 200)
362
+ a_drag = np.where(self.tau_b(a_log) >= 1.0)[0]
363
+
364
+ if not len(a_drag):
365
+ return np.inf
366
+ a_drag = a_drag[-1]
367
+
368
+ return integrate.fixed_quad(self._r_s_intg, np.log(a_drag), 0.0, n=100)[0]
369
+
370
+ def _r_s_intg(self, lna):
371
+ a = np.exp(lna)
372
+ return -1 / (
373
+ a
374
+ * self.H(a=a)
375
+ / self._params.H0
376
+ / self._params.rh
377
+ * np.sqrt(3 * self._params.omega_b / (4 * self._params.omega_gamma) * a)
378
+ )
379
+
380
+ def _r_bph_a(self, a=1.0): # baryon to photon ratio r_bph(a) [1]
381
+ return 3.0 / 4.0 * self._params.omega_b / self._params.omega_gamma * a
382
+
383
+ def _cs_approx(self, a): # photon-baryon fluid sound speed [1]
384
+ # this is a simple expression which is probably a simple approximation - needs
385
+ # refining
386
+ return np.sqrt(1.0 / (3.0 * (1.0 + self._r_bph_a(a))))
387
+
388
+ def _dlnh_dlna(self, a=1.0):
389
+ a = np.atleast_1d(a)
390
+ temp = (
391
+ -0.5
392
+ / self._H2_H02_a(a)
393
+ * (
394
+ 3.0 * self._H2_H02_Omegam_a(a=a)
395
+ + 3.0 * (1.0 + self._w_a(a)) * self._H2_H02_Omegal_a(a=a)
396
+ + 2.0 * self._H2_H02_Omegak_a(a=a)
397
+ )
398
+ )
399
+
400
+ return temp
401
+
402
+ #########################################################
403
+ # The next section lists a set of functions for #
404
+ # calculating the density parameters as a function of #
405
+ # a. This is also consistent with the notes in #
406
+ # section 1.2 of the PyCosmo notes. #
407
+ #########################################################
408
+
409
+ def _omega_m_a(self, a=1.0):
410
+ """
411
+ Calculates the matter density for a given scale factor
412
+ (includes massive neutrinos as matter)
413
+ input: a - scale factor [1]
414
+ output: [1]
415
+ """
416
+ return self._H2_H02_Omegam_a(a=a) / self._H2_H02_a(a=a)
417
+
418
+ def _omega_r_a(self, a=1.0):
419
+ """
420
+ Calculates the radiation density for a given scale factor
421
+ includes photons and massless neutrinos
422
+ input: a - scale factor [1]
423
+ output: [1]
424
+ """
425
+ return self._H2_H02_Omegar_a(a=a) / self._H2_H02_a(a=a)
426
+
427
+ def _omega_l_a(self, a=1.0):
428
+ """
429
+ Calculates the dark energy density for a given scale factor
430
+ input: a - scale factor [1]
431
+ output: [1]
432
+ """
433
+ result = self._H2_H02_Omegal_a(a=a)
434
+ result /= self._H2_H02_a(a=a)
435
+ return result
436
+
437
+ def _omega_nu_m_a(self, a=1.0):
438
+ """
439
+ Calculates the massive neutrinos density for a given scale factor
440
+ input: a - scale factor [1]
441
+ output: [1]
442
+ """
443
+ return self._H2_H02_Omega_nu_m_a(a=a) / self._H2_H02_a(a=a)
444
+
445
+ def _omega_a(self, a=1.0):
446
+ """
447
+ Calculates the total density for a given scale factor
448
+ input: a - scale factor [1]
449
+ output: [1]
450
+ """
451
+ return (
452
+ self._omega_m_a(a=a)
453
+ + self._omega_r_a(a=a)
454
+ + self._omega_l_a(a=a)
455
+ + self._omega_nu_m_a(a=a)
456
+ )
457
+
458
+ def _omega_k_a(self, a=1.0):
459
+ """
460
+ Calculates the curvature for a given scale factor
461
+ input: a - scale factor [1]
462
+ output: [1]
463
+ """
464
+ return 1.0 - self._omega_a(a=a)
@@ -0,0 +1,123 @@
1
+ # This file is part of PyCosmo, a multipurpose cosmology calculation tool in Python.
2
+ #
3
+ # Copyright (C) 2013-2021 ETH Zurich, Institute for Particle and Astrophysics and SIS
4
+ # ID.
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify it under the
7
+ # terms of the GNU General Public License as published by the Free Software Foundation,
8
+ # either version 3 of the License, or (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful, but WITHOUT ANY
11
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12
+ # PARTICULAR PURPOSE. See the GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License along with this
15
+ # program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ from collections import defaultdict
18
+
19
+ import numpy as np
20
+
21
+
22
+ class Fields(object):
23
+ """
24
+ Class for linear perturbations field objects.
25
+ Usage example: fields.delta(5) returns the dark matter density perturbation at the
26
+ 5th time step
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ cosmology,
32
+ ):
33
+ self._cosmology = cosmology
34
+ self._wrapper = cosmology._wrapper
35
+
36
+ self._y = None
37
+ self.lna = None
38
+ self._offsets = defaultdict(list)
39
+
40
+ def set_results(self, lna, solver_result):
41
+ self.lna = lna
42
+
43
+ fields_map = self._wrapper.fields(lna, solver_result, self._cosmology)
44
+
45
+ offsets = defaultdict(list)
46
+ self._symbols = []
47
+ _y = []
48
+ for i, (symbol, yvec) in enumerate(fields_map.items()):
49
+ self._symbols.append(symbol)
50
+ _y.append(yvec)
51
+
52
+ if "[" not in symbol:
53
+ offsets[symbol] = i
54
+ continue
55
+
56
+ index = int(symbol.split("[")[1].split("]")[0])
57
+ stem = symbol.split("[")[0]
58
+ offsets[stem].append((index, i))
59
+
60
+ self._y = np.array(_y)
61
+ assert self._y.shape[1] == len(lna), "fields and lna vector mismatch"
62
+ self._offsets = offsets
63
+
64
+ def __dir__(self):
65
+ return super().__dir__() + list(map(str, self._symbols))
66
+
67
+ def __getattr__(self, symbol):
68
+ # needed to support pickling:
69
+ if "_offsets" not in vars(self):
70
+ raise AttributeError()
71
+
72
+ if symbol not in self._offsets:
73
+ return self.__getattribute__(symbol)
74
+
75
+ index = self._offsets[symbol]
76
+ if isinstance(index, int):
77
+ return self._y[index, :]
78
+
79
+ assert isinstance(index, list)
80
+ index = [j for (i, j) in sorted(index)]
81
+ return VectorWrapper(self._y, index)
82
+
83
+ def __getstate__(self):
84
+ return self.__dict__
85
+
86
+ def __setstate__(self, dd):
87
+ self.__dict__.update(dd)
88
+
89
+ @property
90
+ def a(self):
91
+ """
92
+ a(a_index): scale factor [1]
93
+ """
94
+ return np.exp(self.lna)
95
+
96
+ @property
97
+ def d_t(self):
98
+ """
99
+ d_t: time step in ln(a) units (one fewer value than ln(a) [1]
100
+ """
101
+ a = self.a
102
+ return a[1:] - a[:-1]
103
+
104
+
105
+ class VectorWrapper:
106
+ def __init__(self, y, indices):
107
+ self.y = y
108
+ self.indices = indices
109
+
110
+ # supports conversion to np.ndarray using np.array(...):
111
+ # (inspired from https://stackoverflow.com/questions/39376892/)
112
+ data = y[indices, :]
113
+ self.__array_interface__ = dict(
114
+ shape=data.shape,
115
+ typestr=data.dtype.kind,
116
+ data=data.__array_interface__["data"],
117
+ )
118
+
119
+ def __len__(self):
120
+ return len(self.indices)
121
+
122
+ def __getitem__(self, index):
123
+ return self.y[self.indices[index], :]