Source code for sacc.tracers

import numpy as np
from astropy.table import Table
from .utils import Namespace, hide_null_values, remove_dict_null_values, unique_list

[docs]class BaseTracer: """ A class representing some kind of tracer of astronomical objects. Generically, SACC data points correspond to some combination of tracers for example, tomographic two-point data has two tracers for each data point, indicating the n(z) for the corresponding tomographic bin. All Tracer objects have at least a name attribute. Different subclassses have other requirements. For example, n(z) tracers require z and n(z) arrays. In general you don't need to create tracer objects yourself - the Sacc.add_tracer method will construct them for you. """ _tracer_classes = {} def __init__(self, name, **kwargs): self.name = name self.metadata = kwargs.pop('metadata', {}) def __init_subclass__(cls, tracer_type): cls._tracer_classes[tracer_type] = cls cls.tracer_type = tracer_type
[docs] @classmethod def make(cls, tracer_type, name, *args, **kwargs): """ Select a Tracer subclass based on tracer_type and instantiate in instance of it with the remaining arguments. Parameters ---------- tracer_type: str Must correspond to the tracer_type of a subclass name: str The name for this specific tracer, e.g. a tomographic bin identifier. Returns ------- instance: Tracer object An instance of a Tracer subclass """ subclass = cls._tracer_classes[tracer_type] obj = subclass(name, *args, **kwargs) return obj
[docs] @classmethod def to_tables(cls, instance_list): """Convert a list of tracers to a list of astropy tables This is used when saving data to a file. This class method converts a list of tracers, each of which can instances of any subclass of BaseTracer, and turns them into a list of astropy tables, ready to be saved to disk. Some tracers generate a single table for all of the different instances, and others generate one table per instance. Parameters ---------- instance_list: list List of tracer instances Returns ------- tables: list List of astropy tables """ tables = [] for name, subcls in cls._tracer_classes.items(): tracers = [t for t in instance_list if type(t) == subcls] tables += subcls.to_tables(tracers) return tables
[docs] @classmethod def from_tables(cls, table_list): """Convert a list of astropy tables into a dictionary of tracers This is used when loading data from a file. This class method takes a list of tracers, such as those read from a file, and converts them into a list of instances. It is not quite the inverse of the to_tables method, since it returns a dict instead of a list. Subclasses overrides of this method do the actual work, but should *NOT* call this parent base method. Parameters ---------- table_list: list List of astropy tables Returns ------- tracers: dict Dict mapping string names to tracer objects. """ tracers = {} # Figure out the different subclasses that are present subclass_names = unique_list(table.meta['SACCCLSS'] for table in table_list) subclasses = [cls._tracer_classes[name] for name in subclass_names] # For each subclass find the tables representing that subclass. # We do it like this because we might want to represent one tracer with # multiple tables, or one table can have multiple tracers - it depends on # the tracers class and how complicated it is. for name, subcls in zip(subclass_names, subclasses): subcls_table_list = [table for table in table_list if table.meta['SACCCLSS']==name] # and ask the subclass to read from those tables. tracers.update(subcls.from_tables(subcls_table_list)) return tracers
[docs]class MiscTracer(BaseTracer, tracer_type='misc'): """A Tracer type for miscellaneous other data points. MiscTracers do not have any attributes except for their name, so can be used for tagging external data, for example. Attributes ---------- name: str The name of the tracer """ def __init__(self, name, **kwargs): super().__init__(name, **kwargs)
[docs] @classmethod def to_tables(cls, instance_list): """Convert a list of MiscTracer instances to a astropy tables. This is used when saving data to file. All the instances are converted to a single table, which is returned in a list with one element so that it can be used in combination with the parent. You can use the parent class to_tables class method to convert a mixed list of different tracer types. You shouldn't generally need to call this method directly. Parameters ---------- instance_list: list list of MiscTracer objects Returns ------- tables: list List containing one astropy table """ metadata_cols = set() for obj in instance_list: metadata_cols.update(obj.metadata.keys()) metadata_cols = list(metadata_cols) cols = [[obj.name for obj in instance_list]] for name in metadata_cols: cols.append([obj.metadata.get(name) for obj in instance_list]) table = Table(data=cols, names=['name']+ metadata_cols) table.meta['SACCTYPE'] = 'tracer' table.meta['SACCCLSS'] = cls.tracer_type table.meta['EXTNAME'] = f'tracer:{cls.tracer_type}' hide_null_values(table) return [table]
[docs] @classmethod def from_tables(cls, table_list): """Convert a list of astropy table into a dictionary of MiscTracer instances. In general table_list should have a single element in, since all the MiscTracers are stored in a single table during to_tables Parameters ---------- table_list: List[astropy.table.Table] Returns ------- tracers: Dict[str: MiscTracer] """ tracers = {} for table in table_list: metadata_cols = [col for col in table.colnames if col != 'name'] for row in table: name = row['name'] metadata = {key: row[key] for key in metadata_cols} remove_dict_null_values(metadata) tracers[name] = cls(name, metadata=metadata) return tracers
[docs]class NZTracer(BaseTracer, tracer_type='NZ'): """ A Tracer type for tomographic n(z) data. Takes two arguments arrays of z and n(z) Attributes ---------- z: array Redshift sample values nz: array Number density n(z) at redshift sample points. extra_columns: dict[str: array] or dict[int:array] Additional estimates of the same n(z), by name """ def __init__(self, name, z, nz, extra_columns=None, **kwargs): """ Create a tracer corresponding to a distribution in redshift n(z), for example of galaxies. Parameters ---------- name: str The name for this specific tracer, e.g. a tomographic bin identifier. z: array Redshift sample values nz: array Number density n(z) at redshift sample points. extra_columns: dict[str:array] Optional, default=None. Additional realizations or estimates of the same n(z), by name. Returns ------- instance: NZTracer object An instance of this class """ super().__init__(name, **kwargs) self.z = np.array(z) self.nz = np.array(nz) self.extra_columns = {} if extra_columns is None else extra_columns
[docs] @classmethod def to_tables(cls, instance_list): """Convert a list of NZTracers to a list of astropy tables This is used when saving data to a file. One table is generated per tracer. Parameters ---------- instance_list: list List of tracer instances Returns ------- tables: list List of astropy tables """ tables = [] for tracer in instance_list: names = ['z', 'nz'] cols = [tracer.z, tracer.nz] for nz_id, col in tracer.extra_columns.items(): names.append(str(nz_id)) cols.append(col) table = Table(data=cols, names=names) table.meta['SACCTYPE'] = 'tracer' table.meta['SACCCLSS'] = cls.tracer_type table.meta['SACCNAME'] = tracer.name table.meta['EXTNAME'] = f'tracer:{cls.tracer_type}:{tracer.name}' for key, value in tracer.metadata.items(): table.meta['META_'+key] = value remove_dict_null_values(table.meta) tables.append(table) return tables
[docs] @classmethod def from_tables(cls, table_list): """Convert an astropy table into a dictionary of tracers This is used when loading data from a file. A single tracer object is read from the table. Parameters ---------- table_list: list[astropy.table.Table] Must contain the appropriate data, for example as saved by to_table. Returns ------- tracers: dict Dict mapping string names to tracer objects. Only contains one key/value pair for the one tracer. """ tracers = {} for table in table_list: name = table.meta['SACCNAME'] z = table['z'] nz = table['nz'] extra_columns = {} for col in table.columns.values(): if col.name not in ['z', 'nz']: extra_columns[col.name] = col.data metadata = {} for key, value in table.meta.items(): if key.startswith("META_"): metadata[key[5:]] = value tracers[name] = cls(name, z, nz, extra_columns=extra_columns, metadata=metadata) return tracers