sacc 1.0__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.
sacc/tracers.py ADDED
@@ -0,0 +1,1217 @@
1
+ import numpy as np
2
+ from astropy.table import Table
3
+ from .utils import (Namespace, hide_null_values,
4
+ remove_dict_null_values, unique_list)
5
+
6
+ standard_quantities = Namespace('galaxy_shear',
7
+ 'galaxy_density',
8
+ 'galaxy_convergence',
9
+ 'cluster_density',
10
+ 'cmb_temperature',
11
+ 'cmb_polarization',
12
+ 'cmb_convergence',
13
+ 'cmb_tSZ',
14
+ 'cmb_kSZ',
15
+ 'cluster_mass_count_wl',
16
+ 'cluster_mass_count_xray',
17
+ 'cluster_mass_count_tSZ',
18
+ 'generic')
19
+
20
+
21
+ class BaseTracer:
22
+ """
23
+ A class representing some kind of tracer of astronomical objects.
24
+
25
+ Generically, SACC data points correspond to some combination of tracers
26
+ for example, tomographic two-point data has two tracers for each data
27
+ point, indicating the n(z) for the corresponding tomographic bin.
28
+
29
+ All Tracer objects have at least a name attribute. Different
30
+ subclassses have other requirements. For example, n(z) tracers
31
+ require z and n(z) arrays.
32
+
33
+ In general you don't need to create tracer objects yourself -
34
+ the Sacc.add_tracer method will construct them for you.
35
+ """
36
+ _tracer_classes = {}
37
+
38
+ def __init__(self, name, **kwargs):
39
+ # We encourage people to use existing quantity names, and issue a
40
+ # warning if they do not to prod them in the right direction.
41
+ quantity = kwargs.pop('quantity', 'generic')
42
+ self.name = name
43
+ self.quantity = quantity
44
+ self.metadata = kwargs.pop('metadata', {})
45
+
46
+ def __init_subclass__(cls, tracer_type):
47
+ cls._tracer_classes[tracer_type.lower()] = cls
48
+ cls.tracer_type = tracer_type
49
+
50
+ @classmethod
51
+ def make(cls, tracer_type, name, *args, **kwargs):
52
+ """
53
+ Select a Tracer subclass based on tracer_type
54
+ and instantiate in instance of it with the remaining
55
+ arguments.
56
+
57
+ Parameters
58
+ ----------
59
+ tracer_type: str
60
+ Must correspond to the tracer_type of a subclass
61
+
62
+ name: str
63
+ The name for this specific tracer.
64
+
65
+ Returns
66
+ -------
67
+ instance: Tracer object
68
+ An instance of a Tracer subclass
69
+ """
70
+ subclass = cls._tracer_classes[tracer_type.lower()]
71
+ obj = subclass(name, *args, **kwargs)
72
+ return obj
73
+
74
+ @classmethod
75
+ def to_tables(cls, instance_list):
76
+ """Convert a list of tracers to a list of astropy tables
77
+
78
+ This is used when saving data to a file.
79
+
80
+ This class method converts a list of tracers, each of which
81
+ can instances of any subclass of BaseTracer, and turns them
82
+ into a list of astropy tables, ready to be saved to disk.
83
+
84
+ Some tracers generate a single table for all of the
85
+ different instances, and others generate one table per
86
+ instance.
87
+
88
+ Parameters
89
+ ----------
90
+ instance_list: list
91
+ List of tracer instances
92
+
93
+ Returns
94
+ -------
95
+ tables: list
96
+ List of astropy tables
97
+ """
98
+ tables = []
99
+ for name, subcls in cls._tracer_classes.items():
100
+ tracers = [t for t in instance_list if type(t) == subcls]
101
+ # If the list is empty, we don't want to append any tables.
102
+ if tracers:
103
+ tables += subcls.to_tables(tracers)
104
+ return tables
105
+
106
+ @classmethod
107
+ def from_tables(cls, table_list):
108
+ """Convert a list of astropy tables into a dictionary of tracers
109
+
110
+ This is used when loading data from a file.
111
+
112
+ This class method takes a list of tracers, such as those
113
+ read from a file, and converts them into a list of instances.
114
+
115
+ It is not quite the inverse of the to_tables method, since it
116
+ returns a dict instead of a list.
117
+
118
+ Subclasses overrides of this method do the actual work, but
119
+ should *NOT* call this parent base method.
120
+
121
+ Parameters
122
+ ----------
123
+ table_list: list
124
+ List of astropy tables
125
+
126
+ Returns
127
+ -------
128
+ tracers: dict
129
+ Dict mapping string names to tracer objects.
130
+ """
131
+ tracers = {}
132
+ # Figure out the different subclasses that are present
133
+ subclass_names = unique_list(table.meta['SACCCLSS'].lower()
134
+ for table in table_list)
135
+ subclasses = [cls._tracer_classes[name]
136
+ for name in subclass_names]
137
+
138
+ # For each subclass find the tables representing that subclass.
139
+ # We do it like this because we might want to represent one tracer with
140
+ # multiple tables, or one table can have multiple tracers -
141
+ # it depends on the tracers class and how complicated it is.
142
+ for name, subcls in zip(subclass_names, subclasses):
143
+ subcls_table_list = [table for table in table_list
144
+ if table.meta['SACCCLSS'].lower() == name]
145
+ # and ask the subclass to read from those tables.
146
+ tracers.update(subcls.from_tables(subcls_table_list))
147
+ return tracers
148
+
149
+
150
+ class MiscTracer(BaseTracer, tracer_type='Misc'):
151
+ """A Tracer type for miscellaneous other data points.
152
+
153
+ MiscTracers do not have any attributes except for their
154
+ name, so can be used for tagging external data, for example.
155
+
156
+ Parameters
157
+ ----------
158
+ name: str
159
+ The name of the tracer
160
+ """
161
+
162
+ def __init__(self, name, **kwargs):
163
+ super().__init__(name, **kwargs)
164
+
165
+ @classmethod
166
+ def to_tables(cls, instance_list):
167
+ """Convert a list of MiscTracer instances to a astropy tables.
168
+
169
+ This is used when saving data to file.
170
+
171
+ All the instances are converted to a single table, which is
172
+ returned in a list with one element so that it can be used
173
+ in combination with the parent.
174
+
175
+ You can use the parent class to_tables class method to convert
176
+ a mixed list of different tracer types.
177
+
178
+ You shouldn't generally need to call this method directly.
179
+
180
+ Parameters
181
+ ----------
182
+ instance_list: list
183
+ list of MiscTracer objects
184
+
185
+ Returns
186
+ -------
187
+ tables: list
188
+ List containing one astropy table
189
+ """
190
+ metadata_cols = set()
191
+ for obj in instance_list:
192
+ metadata_cols.update(obj.metadata.keys())
193
+ metadata_cols = list(metadata_cols)
194
+
195
+ cols = [[obj.name for obj in instance_list],
196
+ [obj.quantity for obj in instance_list]]
197
+ for name in metadata_cols:
198
+ cols.append([obj.metadata.get(name) for obj in instance_list])
199
+
200
+ table = Table(data=cols,
201
+ names=['name', 'quantity'] + metadata_cols)
202
+ table.meta['SACCTYPE'] = 'tracer'
203
+ table.meta['SACCCLSS'] = cls.tracer_type
204
+ table.meta['EXTNAME'] = f'tracer:{cls.tracer_type}'
205
+ hide_null_values(table)
206
+ return [table]
207
+
208
+ @classmethod
209
+ def from_tables(cls, table_list):
210
+ """Convert a list of astropy table into a dictionary of MiscTracer instances.
211
+
212
+ In general table_list should have a single element in, since all the
213
+ MiscTracers are stored in a single table during to_tables
214
+
215
+ Parameters
216
+ ----------
217
+ table_list: List[astropy.table.Table]
218
+
219
+ Returns
220
+ -------
221
+ tracers: Dict[str: MiscTracer]
222
+ """
223
+ tracers = {}
224
+
225
+ for table in table_list:
226
+ metadata_cols = [col for col in table.colnames
227
+ if col not in ['name', 'quantity']]
228
+
229
+ for row in table:
230
+ name = row['name']
231
+ quantity = row['quantity']
232
+ metadata = {key: row[key] for key in metadata_cols}
233
+ remove_dict_null_values(metadata)
234
+ tracers[name] = cls(name, quantity=quantity, metadata=metadata)
235
+ return tracers
236
+
237
+
238
+ class MapTracer(BaseTracer, tracer_type='Map'):
239
+ """
240
+ A Tracer type for a sky map.
241
+
242
+ Takes at least two arguments, defining the map beam.
243
+
244
+ Parameters
245
+ ----------
246
+ name: str
247
+ The name for this specific tracer object.
248
+ ell: array
249
+ Array of multipole values at which the beam is defined.
250
+ beam: array
251
+ Beam multipoles at each value of ell.
252
+ beam_extra: array
253
+ Other beam-related arrays
254
+ (e.g. uncertainties, principal components,
255
+ alternative measurements, whatever).
256
+ map_unit: str
257
+ Map units (e.g. 'uK_CMB'). 'none' by default.
258
+ """
259
+
260
+ def __init__(self, name, spin, ell, beam,
261
+ beam_extra=None, map_unit='none', **kwargs):
262
+ super().__init__(name, **kwargs)
263
+ self.spin = spin
264
+ self.map_unit = map_unit
265
+ self.ell = np.array(ell)
266
+ self.beam = np.array(beam)
267
+ self.beam_extra = {} if beam_extra is None else beam_extra
268
+
269
+ @classmethod
270
+ def to_tables(cls, instance_list):
271
+ tables = []
272
+ for tracer in instance_list:
273
+ # Beams
274
+ names = ['ell', 'beam']
275
+ cols = [tracer.ell, tracer.beam]
276
+ for beam_id, col in tracer.beam_extra.items():
277
+ names.append(str(beam_id))
278
+ cols.append(col)
279
+ table = Table(data=cols, names=names)
280
+ table.meta['SACCTYPE'] = 'tracer'
281
+ table.meta['SACCCLSS'] = cls.tracer_type
282
+ table.meta['SACCNAME'] = tracer.name
283
+ table.meta['SACCQTTY'] = tracer.quantity
284
+ extname = f'tracer:{cls.tracer_type}:{tracer.name}:beam'
285
+ table.meta['EXTNAME'] = extname
286
+ table.meta['MAP_UNIT'] = tracer.map_unit
287
+ table.meta['SPIN'] = tracer.spin
288
+ for key, value in tracer.metadata.items():
289
+ table.meta['META_'+key] = value
290
+ remove_dict_null_values(table.meta)
291
+ tables.append(table)
292
+ return tables
293
+
294
+ @classmethod
295
+ def from_tables(cls, table_list):
296
+ tracers = {}
297
+
298
+ # Collect beam and bandpass tables describing the same tracer
299
+ tr_tables = {}
300
+ for table in table_list:
301
+ # Read name and table type
302
+ name = table.meta['SACCNAME']
303
+ tabtyp = table.meta['EXTNAME'].split(':')[-1]
304
+ if tabtyp not in ['beam']:
305
+ raise KeyError("Unknown table type " + table.meta['EXTNAME'])
306
+
307
+ # If not present yet, create new tracer entry
308
+ if name not in tr_tables:
309
+ tr_tables[name] = {}
310
+ # Add table
311
+ tr_tables[name][tabtyp] = table
312
+
313
+ # Now loop through different tracers and build them from their tables
314
+ for dt in tr_tables.values():
315
+ quantity = []
316
+ metadata = {}
317
+ map_unit = 'none'
318
+ ell = []
319
+ beam = []
320
+ beam_extra = {}
321
+ spin = 0
322
+
323
+ if 'beam' in dt:
324
+ table = dt['beam']
325
+ name = table.meta['SACCNAME']
326
+ quantity = table.meta.get('SACCQTTY', 'generic')
327
+ ell = table['ell']
328
+ beam = table['beam']
329
+ for col in table.columns.values():
330
+ if col.name not in ['ell', 'beam']:
331
+ beam_extra[col.name] = col.data
332
+ map_unit = table.meta['MAP_UNIT']
333
+ spin = table.meta['SPIN']
334
+ for key, value in table.meta.items():
335
+ if key.startswith("META_"):
336
+ metadata[key[5:]] = value
337
+
338
+ tracers[name] = cls(name, spin, ell, beam,
339
+ quantity=quantity, beam_extra=beam_extra,
340
+ map_unit=map_unit, metadata=metadata)
341
+ return tracers
342
+
343
+
344
+ class NuMapTracer(BaseTracer, tracer_type='NuMap'):
345
+ """
346
+ A Tracer type for a sky map at a given frequency.
347
+
348
+ Takes at least four arguments, defining the bandpass and beam.
349
+
350
+ Parameters
351
+ ----------
352
+ name: str
353
+ The name for this specific tracer, e.g. a frequency band
354
+ identifier.
355
+ spin: int
356
+ Spin for this observable. Either 0 (e.g. intensity)
357
+ or 2 (e.g. polarization).
358
+ nu: array
359
+ Array of frequencies.
360
+ bandpass: array
361
+ Bandpass transmission.
362
+ bandpass_extra: array
363
+ Other bandpass-related arrays
364
+ (e.g. uncertainties, principal components,
365
+ alternative measurements, whatever).
366
+ ell: array
367
+ Array of multipole values at which the beam is defined.
368
+ beam: array
369
+ Beam.
370
+ beam_extra: array
371
+ Other beam-related arrays
372
+ (e.g. uncertainties, principal components,
373
+ alternative measurements, whatever).
374
+ nu_unit: str
375
+ Frequency units ('GHz' by default).
376
+ map_unit: str
377
+ Map units (e.g. 'uK_CMB'). 'none' by default.
378
+ """
379
+
380
+ def __init__(self, name, spin, nu, bandpass,
381
+ ell, beam, bandpass_extra=None,
382
+ beam_extra=None, nu_unit='GHz',
383
+ map_unit='none', **kwargs):
384
+ super().__init__(name, **kwargs)
385
+ self.spin = spin
386
+ self.nu = np.array(nu)
387
+ self.nu_unit = nu_unit
388
+ self.map_unit = map_unit
389
+ self.bandpass = np.array(bandpass)
390
+ self.bandpass_extra = {} if bandpass_extra is None else bandpass_extra
391
+ self.ell = np.array(ell)
392
+ self.beam = np.array(beam)
393
+ self.beam_extra = {} if beam_extra is None else beam_extra
394
+
395
+ @classmethod
396
+ def to_tables(cls, instance_list):
397
+ tables = []
398
+ for tracer in instance_list:
399
+ # Bandpasses
400
+ names = ['nu', 'bandpass']
401
+ cols = [tracer.nu, tracer.bandpass]
402
+ for bandpass_id, col in tracer.bandpass_extra.items():
403
+ names.append(str(bandpass_id))
404
+ cols.append(col)
405
+ table = Table(data=cols, names=names)
406
+ table.meta['SACCTYPE'] = 'tracer'
407
+ table.meta['SACCCLSS'] = cls.tracer_type
408
+ table.meta['SACCNAME'] = tracer.name
409
+ table.meta['SACCQTTY'] = tracer.quantity
410
+ extname = f'tracer:{cls.tracer_type}:{tracer.name}:bandpass'
411
+ table.meta['EXTNAME'] = extname
412
+ table.meta['NU_UNIT'] = tracer.nu_unit
413
+ table.meta['SPIN'] = tracer.spin
414
+ for key, value in tracer.metadata.items():
415
+ table.meta['META_'+key] = value
416
+ remove_dict_null_values(table.meta)
417
+ tables.append(table)
418
+
419
+ # Beams
420
+ names = ['ell', 'beam']
421
+ cols = [tracer.ell, tracer.beam]
422
+ for beam_id, col in tracer.beam_extra.items():
423
+ names.append(str(beam_id))
424
+ cols.append(col)
425
+ table = Table(data=cols, names=names)
426
+ table.meta['SACCTYPE'] = 'tracer'
427
+ table.meta['SACCCLSS'] = cls.tracer_type
428
+ table.meta['SACCNAME'] = tracer.name
429
+ table.meta['SACCQTTY'] = tracer.quantity
430
+ extname = f'tracer:{cls.tracer_type}:{tracer.name}:beam'
431
+ table.meta['EXTNAME'] = extname
432
+ table.meta['MAP_UNIT'] = tracer.map_unit
433
+ table.meta['SPIN'] = tracer.spin
434
+ for key, value in tracer.metadata.items():
435
+ table.meta['META_'+key] = value
436
+ remove_dict_null_values(table.meta)
437
+ tables.append(table)
438
+ return tables
439
+
440
+ @classmethod
441
+ def from_tables(cls, table_list):
442
+ tracers = {}
443
+
444
+ # Collect beam and bandpass tables describing the same tracer
445
+ tr_tables = {}
446
+ for table in table_list:
447
+ # Read name and table type
448
+ name = table.meta['SACCNAME']
449
+ tabtyp = table.meta['EXTNAME'].split(':')[-1]
450
+ if tabtyp not in ['bandpass', 'beam']:
451
+ raise KeyError("Unknown table type " + table.meta['EXTNAME'])
452
+
453
+ # If not present yet, create new tracer entry
454
+ if name not in tr_tables:
455
+ tr_tables[name] = {}
456
+ # Add table
457
+ tr_tables[name][tabtyp] = table
458
+
459
+ # Now loop through different tracers and build them from their tables
460
+ for dt in tr_tables.values():
461
+ quantity = []
462
+ metadata = {}
463
+ nu = []
464
+ bandpass = []
465
+ bandpass_extra = {}
466
+ nu_unit = 'GHz'
467
+ map_unit = 'none'
468
+ ell = []
469
+ beam = []
470
+ beam_extra = {}
471
+ spin = 0
472
+
473
+ if 'bandpass' in dt:
474
+ table = dt['bandpass']
475
+ name = table.meta['SACCNAME']
476
+ quantity = table.meta.get('SACCQTTY', 'generic')
477
+ nu = table['nu']
478
+ bandpass = table['bandpass']
479
+ for col in table.columns.values():
480
+ if col.name not in ['nu', 'bandpass']:
481
+ bandpass_extra[col.name] = col.data
482
+ nu_unit = table.meta['NU_UNIT']
483
+ spin = table.meta['SPIN']
484
+ for key, value in table.meta.items():
485
+ if key.startswith("META_"):
486
+ metadata[key[5:]] = value
487
+
488
+ if 'beam' in dt:
489
+ table = dt['beam']
490
+ name = table.meta['SACCNAME']
491
+ quantity = table.meta.get('SACCQTTY', 'generic')
492
+ ell = table['ell']
493
+ beam = table['beam']
494
+ for col in table.columns.values():
495
+ if col.name not in ['ell', 'beam']:
496
+ beam_extra[col.name] = col.data
497
+ map_unit = table.meta['MAP_UNIT']
498
+ spin = table.meta['SPIN']
499
+ for key, value in table.meta.items():
500
+ if key.startswith("META_"):
501
+ metadata[key[5:]] = value
502
+
503
+ tracers[name] = cls(name, spin,
504
+ nu, bandpass,
505
+ ell, beam,
506
+ quantity=quantity,
507
+ bandpass_extra=bandpass_extra,
508
+ beam_extra=beam_extra,
509
+ map_unit=map_unit,
510
+ nu_unit=nu_unit,
511
+ metadata=metadata)
512
+ return tracers
513
+
514
+
515
+ class NZTracer(BaseTracer, tracer_type='NZ'):
516
+ """
517
+ A Tracer type for tomographic n(z) data.
518
+
519
+ Takes two arguments arrays of z and n(z)
520
+
521
+ Parameters
522
+ ----------
523
+ name: str
524
+ The name for this specific tracer, e.g. a
525
+ tomographic bin identifier.
526
+
527
+ z: array
528
+ Redshift sample values
529
+
530
+ nz: array
531
+ Number density n(z) at redshift sample points.
532
+
533
+ extra_columns: dict[str: array] or dict[int: array]
534
+ Additional estimates of the same n(z), by name
535
+ """
536
+
537
+ def __init__(self, name, z, nz,
538
+ extra_columns=None, **kwargs):
539
+ """
540
+ Create a tracer corresponding to a distribution in redshift n(z),
541
+ for example of galaxies.
542
+
543
+ Parameters
544
+ ----------
545
+ name: str
546
+ The name for this specific tracer, e.g. a
547
+ tomographic bin identifier.
548
+
549
+ z: array
550
+ Redshift sample values
551
+
552
+ nz: array
553
+ Number density n(z) at redshift sample points.
554
+
555
+ extra_columns: dict[str:array]
556
+ Optional, default=None. Additional realizations or
557
+ estimates of the same n(z), by name.
558
+
559
+ Returns
560
+ -------
561
+ instance: NZTracer object
562
+ An instance of this class
563
+ """
564
+ super().__init__(name, **kwargs)
565
+ self.z = np.array(z)
566
+ self.nz = np.array(nz)
567
+ self.extra_columns = {} if extra_columns is None else extra_columns
568
+
569
+ @classmethod
570
+ def to_tables(cls, instance_list):
571
+ """Convert a list of NZTracers to a list of astropy tables
572
+
573
+ This is used when saving data to a file.
574
+ One table is generated per tracer.
575
+
576
+ Parameters
577
+ ----------
578
+ instance_list: list
579
+ List of tracer instances
580
+
581
+ Returns
582
+ -------
583
+ tables: list
584
+ List of astropy tables
585
+ """
586
+ tables = []
587
+ for tracer in instance_list:
588
+ names = ['z', 'nz']
589
+ cols = [tracer.z, tracer.nz]
590
+ for nz_id, col in tracer.extra_columns.items():
591
+ names.append(str(nz_id))
592
+ cols.append(col)
593
+ table = Table(data=cols, names=names)
594
+ table.meta['SACCTYPE'] = 'tracer'
595
+ table.meta['SACCCLSS'] = cls.tracer_type
596
+ table.meta['SACCNAME'] = tracer.name
597
+ table.meta['SACCQTTY'] = tracer.quantity
598
+ table.meta['EXTNAME'] = f'tracer:{cls.tracer_type}:{tracer.name}'
599
+ for key, value in tracer.metadata.items():
600
+ table.meta['META_'+key] = value
601
+ remove_dict_null_values(table.meta)
602
+ tables.append(table)
603
+ return tables
604
+
605
+ @classmethod
606
+ def from_tables(cls, table_list):
607
+ """Convert an astropy table into a dictionary of tracers
608
+
609
+ This is used when loading data from a file.
610
+ A single tracer object is read from the table.
611
+
612
+ Parameters
613
+ ----------
614
+ table_list: list[astropy.table.Table]
615
+ Must contain the appropriate data, for example as saved
616
+ by to_table.
617
+
618
+ Returns
619
+ -------
620
+ tracers: dict
621
+ Dict mapping string names to tracer objects.
622
+ Only contains one key/value pair for the one tracer.
623
+ """
624
+ tracers = {}
625
+ for table in table_list:
626
+ name = table.meta['SACCNAME']
627
+ quantity = table.meta.get('SACCQTTY', 'generic')
628
+ z = table['z']
629
+ nz = table['nz']
630
+ extra_columns = {}
631
+ for col in table.columns.values():
632
+ if col.name not in ['z', 'nz']:
633
+ extra_columns[col.name] = col.data
634
+
635
+ metadata = {}
636
+ for key, value in table.meta.items():
637
+ if key.startswith("META_"):
638
+ metadata[key[5:]] = value
639
+ tracers[name] = cls(name, z, nz,
640
+ quantity=quantity,
641
+ extra_columns=extra_columns,
642
+ metadata=metadata)
643
+ return tracers
644
+
645
+
646
+ class QPNZTracer(BaseTracer, tracer_type='QPNZ'):
647
+ """
648
+ A Tracer type for tomographic n(z) data represented as a `qp.Ensemble`
649
+
650
+ Takes a `qp.Ensemble` and optionally a redshift array.
651
+
652
+ Requires the `qp` and `tables_io` packages to be installed.
653
+
654
+ Parameters
655
+ ----------
656
+ name: str
657
+ The name for this specific tracer, e.g. a
658
+ tomographic bin identifier.
659
+
660
+ ensemble: qp.Ensemble
661
+ The qp.ensemble in questions
662
+ """
663
+
664
+ def __init__(self, name, ens, z=None, **kwargs):
665
+ """
666
+ Create a tracer corresponding to a distribution in redshift n(z),
667
+ for example of galaxies.
668
+
669
+ Parameters
670
+ ----------
671
+ name: str
672
+ The name for this specific tracer, e.g. a
673
+ tomographic bin identifier.
674
+
675
+ ensemble: qp.Ensemble
676
+ The qp.ensemble in questions
677
+
678
+ z: array
679
+ Optional grid of redshift values at which to evaluate the ensemble.
680
+ If left as None then the ensemble metadata is checked for a grid.
681
+ If that is not present then no redshift grid is saved.
682
+
683
+ Returns
684
+ -------
685
+ instance: NZTracer object
686
+ An instance of this class
687
+ """
688
+ super().__init__(name, **kwargs)
689
+ self.ensemble = ens
690
+ if z is None:
691
+ ens_meta = ens.metadata()
692
+ if 'bins' in list(ens_meta.keys()):
693
+ z = ens_meta['bins'][0]
694
+ self.z = z
695
+ if z is None:
696
+ self.nz = None
697
+ else:
698
+ self.nz = np.mean(ens.pdf(self.z),axis=0)
699
+
700
+ @classmethod
701
+ def to_tables(cls, instance_list):
702
+ """Convert a list of NZTracers to a list of astropy tables
703
+
704
+ This is used when saving data to a file.
705
+ Two or three tables are generated per tracer.
706
+
707
+ Parameters
708
+ ----------
709
+ instance_list: list
710
+ List of tracer instances
711
+
712
+ Returns
713
+ -------
714
+ tables: list
715
+ List of astropy tables
716
+ """
717
+ from tables_io.convUtils import convertToApTables
718
+
719
+ tables = []
720
+
721
+ for tracer in instance_list:
722
+ if tracer.z is not None:
723
+ names = ['z', 'nz']
724
+ cols = [tracer.z, tracer.nz]
725
+ fid_table = Table(data=cols, names=names)
726
+ fid_table.meta['SACCTYPE'] = 'tracer'
727
+ fid_table.meta['SACCCLSS'] = cls.tracer_type
728
+ fid_table.meta['SACCNAME'] = tracer.name
729
+ fid_table.meta['SACCQTTY'] = tracer.quantity
730
+ fid_table.meta['EXTNAME'] = f'tracer:{cls.tracer_type}:{tracer.name}:fid'
731
+
732
+ table_dict = tracer.ensemble.build_tables()
733
+ ap_tables = convertToApTables(table_dict)
734
+ data_table = ap_tables['data']
735
+ meta_table = ap_tables['meta']
736
+ ancil_table = ap_tables.get('ancil', None)
737
+ meta_table.meta['SACCTYPE'] = 'tracer'
738
+ meta_table.meta['SACCCLSS'] = cls.tracer_type
739
+ meta_table.meta['SACCNAME'] = tracer.name
740
+ meta_table.meta['SACCQTTY'] = tracer.quantity
741
+ meta_table.meta['EXTNAME'] = f'tracer:{cls.tracer_type}:{tracer.name}:meta'
742
+
743
+ data_table.meta['SACCTYPE'] = 'tracer'
744
+ data_table.meta['SACCCLSS'] = cls.tracer_type
745
+ data_table.meta['SACCNAME'] = tracer.name
746
+ data_table.meta['SACCQTTY'] = tracer.quantity
747
+ data_table.meta['EXTNAME'] = f'tracer:{cls.tracer_type}:{tracer.name}:data'
748
+
749
+ for kk, vv in tracer.metadata.items():
750
+ meta_table.meta['META_'+kk] = vv
751
+ tables.append(data_table)
752
+ tables.append(meta_table)
753
+ if tracer.z is not None:
754
+ tables.append(fid_table)
755
+ if ancil_table:
756
+ ancil_table.meta['SACCTYPE'] = 'tracer'
757
+ ancil_table.meta['SACCCLSS'] = cls.tracer_type
758
+ ancil_table.meta['SACCNAME'] = tracer.name
759
+ ancil_table.meta['SACCQTTY'] = tracer.quantity
760
+ ancil_table.meta['EXTNAME'] = f'tracer:{cls.tracer_type}:{tracer.name}:ancil'
761
+
762
+ tables.append(ancil_table)
763
+ return tables
764
+
765
+ @classmethod
766
+ def from_tables(cls, table_list):
767
+ """Convert an astropy table into a dictionary of tracers
768
+
769
+ This is used when loading data from a file.
770
+ A single tracer object is read from the table.
771
+
772
+ Parameters
773
+ ----------
774
+ table_list: list[astropy.table.Table]
775
+ Must contain the appropriate data, for example as saved
776
+ by to_table.
777
+
778
+ Returns
779
+ -------
780
+ tracers: dict
781
+ Dict mapping string names to tracer objects.
782
+ Only contains one key/value pair for the one tracer.
783
+ """
784
+ import qp
785
+
786
+ tracers = {}
787
+ sorted_dict = {}
788
+ for table_ in table_list:
789
+ tokens = table_.meta['EXTNAME'].split(':')
790
+ table_key = f'{tokens[0]}:{tokens[1]}:{tokens[2]}'
791
+ table_type = f'{tokens[3]}'
792
+ if table_key not in sorted_dict:
793
+ sorted_dict[table_key] = {table_type: table_}
794
+ else:
795
+ sorted_dict[table_key][table_type] = table_
796
+
797
+ for val in sorted_dict.values():
798
+ meta_table = val['meta']
799
+ if 'fid' in val:
800
+ z = val['fid']['z']
801
+ else:
802
+ z = None
803
+ ensemble = qp.from_tables(val)
804
+ name = meta_table.meta['SACCNAME']
805
+ quantity = meta_table.meta.get('SACCQTTY', 'generic')
806
+ ensemble = qp.from_tables(val)
807
+ metadata = {}
808
+ for key, value in meta_table.meta.items():
809
+ if key.startswith("META_"):
810
+ metadata[key[5:]] = value
811
+ tracers[name] = cls(name, ensemble, z=z,
812
+ quantity=quantity,
813
+ metadata=metadata)
814
+ return tracers
815
+
816
+
817
+ class BinZTracer(BaseTracer, tracer_type="bin_z"): # type: ignore
818
+ """A tracer for a single redshift bin. The tracer shall
819
+ be used for binned data where we want a desired quantity
820
+ per interval of redshift, such that we only need the data
821
+ for a given interval instead of at individual redshifts."""
822
+
823
+ def __init__(self, name: str, lower: float, upper: float, **kwargs):
824
+ """
825
+ Create a tracer corresponding to a single redshift bin.
826
+
827
+ :param name: The name of the tracer
828
+ :param lower: The lower bound of the redshift bin
829
+ :param upper: The upper bound of the redshift bin
830
+ """
831
+ super().__init__(name, **kwargs)
832
+ self.lower = lower
833
+ self.upper = upper
834
+
835
+ def __eq__(self, other) -> bool:
836
+ """Test for equality. If :python:`other` is not a
837
+ :python:`BinZTracer`, then it is not equal to :python:`self`.
838
+ Otherwise, they are equal if names, and the z-range of the bins,
839
+ are equal."""
840
+ if not isinstance(other, BinZTracer):
841
+ return False
842
+ return (
843
+ self.name == other.name
844
+ and self.lower == other.lower
845
+ and self.upper == other.upper
846
+ )
847
+
848
+ @classmethod
849
+ def to_tables(cls, instance_list):
850
+ """Convert a list of BinZTracers to a single astropy table
851
+
852
+ This is used when saving data to a file.
853
+ One table is generated with the information for all the tracers.
854
+
855
+ :param instance_list: List of tracer instances
856
+ :return: List with a single astropy table
857
+ """
858
+
859
+ names = ["name", "quantity", "lower", "upper"]
860
+
861
+ cols = [
862
+ [obj.name for obj in instance_list],
863
+ [obj.quantity for obj in instance_list],
864
+ [obj.lower for obj in instance_list],
865
+ [obj.upper for obj in instance_list],
866
+ ]
867
+
868
+ table = Table(data=cols, names=names)
869
+ table.meta["SACCTYPE"] = "tracer"
870
+ table.meta["SACCCLSS"] = cls.tracer_type
871
+ table.meta["EXTNAME"] = f"tracer:{cls.tracer_type}"
872
+ return [table]
873
+
874
+ @classmethod
875
+ def from_tables(cls, table_list):
876
+ """Convert an astropy table into a dictionary of tracers
877
+
878
+ This is used when loading data from a file.
879
+ One tracer object is created for each "row" in each table.
880
+
881
+ :param table_list: List of astropy tables
882
+ :return: Dictionary of tracers
883
+ """
884
+ tracers = {}
885
+
886
+ for table in table_list:
887
+ for row in table:
888
+ name = row["name"]
889
+ quantity = row["quantity"]
890
+ lower = row["lower"]
891
+ upper = row["upper"]
892
+ tracers[name] = cls(name, quantity=quantity, lower=lower, upper=upper)
893
+ return tracers
894
+
895
+ class BinLogMTracer(BaseTracer, tracer_type="bin_logM"): # type: ignore
896
+ """A tracer for a single log-mass bin. The tracer shall
897
+ be used for binned data where we want a desired quantity
898
+ per interval of log(mass), such that we only need the data
899
+ for a given interval instead of at individual masses."""
900
+
901
+ def __init__(self, name: str, lower: float, upper: float, **kwargs):
902
+ """
903
+ Create a tracer corresponding to a single log-mass bin.
904
+
905
+ :param name: The name of the tracer
906
+ :param lower: The lower bound of the log-mass bin
907
+ :param upper: The upper bound of the log-mass bin
908
+ """
909
+ super().__init__(name, **kwargs)
910
+ self.lower = lower
911
+ self.upper = upper
912
+
913
+ def __eq__(self, other) -> bool:
914
+ """Test for equality. If :python:`other` is not a
915
+ :python:`BinLogMTracer`, then it is not equal to :python:`self`.
916
+ Otherwise, they are equal if names, and the z-range of the bins,
917
+ are equal."""
918
+ if not isinstance(other, BinLogMTracer):
919
+ return False
920
+ return (
921
+ self.name == other.name
922
+ and self.lower == other.lower
923
+ and self.upper == other.upper
924
+ )
925
+
926
+ @classmethod
927
+ def to_tables(cls, instance_list):
928
+ """Convert a list of BinLogMTracers to a single astropy table
929
+
930
+ This is used when saving data to a file.
931
+ One table is generated with the information for all the tracers.
932
+
933
+ :param instance_list: List of tracer instances
934
+ :return: List with a single astropy table
935
+ """
936
+
937
+ names = ["name", "quantity", "lower", "upper"]
938
+
939
+ cols = [
940
+ [obj.name for obj in instance_list],
941
+ [obj.quantity for obj in instance_list],
942
+ [obj.lower for obj in instance_list],
943
+ [obj.upper for obj in instance_list],
944
+ ]
945
+ table = Table(data=cols, names=names)
946
+ table.meta["SACCTYPE"] = "tracer"
947
+ table.meta["SACCCLSS"] = cls.tracer_type
948
+ table.meta["EXTNAME"] = f"tracer:{cls.tracer_type}"
949
+ return [table]
950
+
951
+ @classmethod
952
+ def from_tables(cls, table_list):
953
+ """Convert an astropy table into a dictionary of tracers
954
+
955
+ This is used when loading data from a file.
956
+ One tracer object is created for each "row" in each table.
957
+
958
+ :param table_list: List of astropy tables
959
+ :return: Dictionary of tracers
960
+ """
961
+ tracers = {}
962
+
963
+ for table in table_list:
964
+ for row in table:
965
+ name = row["name"]
966
+ quantity = row["quantity"]
967
+ lower = row["lower"]
968
+ upper = row["upper"]
969
+ tracers[name] = cls(name, quantity=quantity, lower=lower, upper=upper)
970
+ return tracers
971
+
972
+
973
+ class BinRichnessTracer(BaseTracer, tracer_type="bin_richness"): # type: ignore
974
+ """A tracer for a single richness bin. The tracer shall
975
+ be used for binned data where we want a desired quantity
976
+ per interval of log(richness), such that we only need the data
977
+ for a given interval instead of at individual richness."""
978
+
979
+ def __eq__(self, other) -> bool:
980
+ """Test for equality. If :python:`other` is not a
981
+ :python:`BinRichnessTracer`, then it is not equal to :python:`self`.
982
+ Otherwise, they are equal if names and the richness-range of the
983
+ bins, are equal."""
984
+ if not isinstance(other, BinRichnessTracer):
985
+ return False
986
+ return (
987
+ self.name == other.name
988
+ and self.lower == other.lower
989
+ and self.upper == other.upper
990
+ )
991
+
992
+ def __init__(self, name: str, lower: float, upper: float, **kwargs):
993
+ """
994
+ Create a tracer corresponding to a single richness bin.
995
+
996
+ :param name: The name of the tracer
997
+ :param lower: The lower bound of the richness bin in log10.
998
+ :param upper: The upper bound of the richness bin in log10.
999
+ """
1000
+ super().__init__(name, **kwargs)
1001
+ self.lower = lower
1002
+ self.upper = upper
1003
+
1004
+ @classmethod
1005
+ def to_tables(cls, instance_list):
1006
+ """Convert a list of BinZTracers to a list of astropy tables
1007
+
1008
+ This is used when saving data to a file.
1009
+ One table is generated with the information for all the tracers.
1010
+
1011
+ :param instance_list: List of tracer instances
1012
+ :return: List with a single astropy table
1013
+ """
1014
+ names = ["name", "quantity", "lower", "upper"]
1015
+
1016
+ cols = [
1017
+ [obj.name for obj in instance_list],
1018
+ [obj.quantity for obj in instance_list],
1019
+ [obj.lower for obj in instance_list],
1020
+ [obj.upper for obj in instance_list],
1021
+ ]
1022
+
1023
+ table = Table(data=cols, names=names)
1024
+ table.meta["SACCTYPE"] = "tracer"
1025
+ table.meta["SACCCLSS"] = cls.tracer_type
1026
+ table.meta["EXTNAME"] = f"tracer:{cls.tracer_type}"
1027
+ return [table]
1028
+
1029
+ @classmethod
1030
+ def from_tables(cls, table_list):
1031
+ """Convert an astropy table into a dictionary of tracers
1032
+
1033
+ This is used when loading data from a file.
1034
+ One tracer object is created for each "row" in each table.
1035
+
1036
+ :param table_list: List of astropy tables
1037
+ :return: Dictionary of tracers
1038
+ """
1039
+ tracers = {}
1040
+
1041
+ for table in table_list:
1042
+ for row in table:
1043
+ name = row["name"]
1044
+ quantity = row["quantity"]
1045
+ lower = row["lower"]
1046
+ upper = row["upper"]
1047
+ tracers[name] = cls(
1048
+ name,
1049
+ quantity=quantity,
1050
+ lower=lower,
1051
+ upper=upper,
1052
+ )
1053
+ return tracers
1054
+
1055
+
1056
+ class BinRadiusTracer(BaseTracer, tracer_type="bin_radius"): # type: ignore
1057
+ """A tracer for a single radial bin, e.g. when dealing with cluster shear profiles.
1058
+ It gives the bin edges and the value of the bin "center". The latter would typically
1059
+ be returned by CLMM and correspond to the average radius of the galaxies in that
1060
+ radial bin. """
1061
+
1062
+ def __eq__(self, other) -> bool:
1063
+ """Test for equality. If :python:`other` is not a
1064
+ :python:`BinRadiusTracer`, then it is not equal to :python:`self`.
1065
+ Otherwise, they are equal if names and the r-range and centers of the
1066
+ bins, are equal."""
1067
+ if not isinstance(other, BinRadiusTracer):
1068
+ return False
1069
+ return (
1070
+ self.name == other.name
1071
+ and self.lower == other.lower
1072
+ and self.center == other.center
1073
+ and self.upper == other.upper
1074
+ )
1075
+
1076
+ def __init__(self, name: str, lower: float, upper: float, center: float, **kwargs):
1077
+ """
1078
+ Create a tracer corresponding to a single radial bin.
1079
+
1080
+ :param name: The name of the tracer
1081
+ :param lower: The lower bound of the radius bin
1082
+ :param upper: The upper bound of the radius bin
1083
+ :param center: The value to use if a single point-estimate is needed.
1084
+
1085
+ Note that :python:`center` need not be the midpoint between
1086
+ :python:`lower` and :python:`upper`'.
1087
+ """
1088
+ super().__init__(name, **kwargs)
1089
+ self.lower = lower
1090
+ self.upper = upper
1091
+ self.center = center
1092
+
1093
+ @classmethod
1094
+ def to_tables(cls, instance_list):
1095
+ """Convert a list of BinRadiusTracers to a single astropy table
1096
+
1097
+ This is used when saving data to a file.
1098
+ One table is generated with the information for all the tracers.
1099
+
1100
+ :param instance_list: List of tracer instances
1101
+ :return: List with a single astropy table
1102
+ """
1103
+
1104
+ names = ["name", "quantity", "lower", "upper", "center"]
1105
+
1106
+ cols = [
1107
+ [obj.name for obj in instance_list],
1108
+ [obj.quantity for obj in instance_list],
1109
+ [obj.lower for obj in instance_list],
1110
+ [obj.upper for obj in instance_list],
1111
+ [obj.center for obj in instance_list],
1112
+ ]
1113
+
1114
+ table = Table(data=cols, names=names)
1115
+ table.meta["SACCTYPE"] = "tracer"
1116
+ table.meta["SACCCLSS"] = cls.tracer_type
1117
+ table.meta["EXTNAME"] = f"tracer:{cls.tracer_type}"
1118
+ return [table]
1119
+
1120
+ @classmethod
1121
+ def from_tables(cls, table_list):
1122
+ """Convert an astropy table into a dictionary of tracers
1123
+
1124
+ This is used when loading data from a file.
1125
+ One tracer object is created for each "row" in each table.
1126
+
1127
+ :param table_list: List of astropy tables
1128
+ :return: Dictionary of tracers
1129
+ """
1130
+ tracers = {}
1131
+
1132
+ for table in table_list:
1133
+ for row in table:
1134
+ name = row["name"]
1135
+ quantity = row["quantity"]
1136
+ lower = row["lower"]
1137
+ upper = row["upper"]
1138
+ center = row["center"]
1139
+ tracers[name] = cls(
1140
+ name,
1141
+ quantity=quantity,
1142
+ lower=lower,
1143
+ upper=upper,
1144
+ center=center,
1145
+ )
1146
+ return tracers
1147
+
1148
+ class SurveyTracer(BaseTracer, tracer_type="survey"): # type: ignore
1149
+ """A tracer for the survey definition. It shall
1150
+ be used to filter data related to a given survey
1151
+ and to provide the survey sky-area of analysis."""
1152
+
1153
+ def __eq__(self, other) -> bool:
1154
+ """Test for equality. If :python:`other` is not a
1155
+ :python:`SurveyTracer`, then it is not equal to :python:`self`.
1156
+ Otherwise, they are equal if names and the sky-areas are equal."""
1157
+ if not isinstance(other, SurveyTracer):
1158
+ return False
1159
+ return self.name == other.name and self.sky_area == other.sky_area
1160
+
1161
+ def __init__(self, name: str, sky_area: float, **kwargs):
1162
+ """
1163
+ Create a tracer corresponding to the survey definition.
1164
+
1165
+ :param name: The name of the tracer
1166
+ :param sky_area: The survey's sky area in square degrees
1167
+ """
1168
+ super().__init__(name, **kwargs)
1169
+ self.sky_area = sky_area
1170
+
1171
+ @classmethod
1172
+ def to_tables(cls, instance_list):
1173
+ """Convert a list of SurveyTracer to a list of astropy tables
1174
+
1175
+ This is used when saving data to a file.
1176
+ One table is generated with the information for all the tracers.
1177
+
1178
+ :param instance_list: List of tracer instances
1179
+ :return: List of astropy tables with one table
1180
+ """
1181
+ names = ["name", "quantity", "sky_area"]
1182
+
1183
+ cols = [
1184
+ [obj.name for obj in instance_list],
1185
+ [obj.quantity for obj in instance_list],
1186
+ [obj.sky_area for obj in instance_list],
1187
+ ]
1188
+
1189
+ table = Table(data=cols, names=names)
1190
+ table.meta["SACCTYPE"] = "tracer"
1191
+ table.meta["SACCCLSS"] = cls.tracer_type
1192
+ table.meta["EXTNAME"] = f"tracer:{cls.tracer_type}"
1193
+ return [table]
1194
+
1195
+ @classmethod
1196
+ def from_tables(cls, table_list):
1197
+ """Convert an astropy table into a dictionary of tracers
1198
+
1199
+ This is used when loading data from a file.
1200
+ One tracer object is created for each "row" in each table.
1201
+
1202
+ :param table_list: List of astropy tables
1203
+ :return: Dictionary of tracers
1204
+ """
1205
+ tracers = {}
1206
+
1207
+ for table in table_list:
1208
+ for row in table:
1209
+ name = row["name"]
1210
+ quantity = row["quantity"]
1211
+ sky_area = row["sky_area"]
1212
+ tracers[name] = cls(
1213
+ name,
1214
+ quantity=quantity,
1215
+ sky_area=sky_area,
1216
+ )
1217
+ return tracers