File: io.rst

package info (click to toggle)
astropy 5.2.1-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 41,972 kB
  • sloc: python: 219,331; ansic: 147,297; javascript: 13,556; lex: 8,496; sh: 3,319; xml: 1,622; makefile: 185
file content (434 lines) | stat: -rw-r--r-- 16,563 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
.. _cosmology_io:

Read, Write, and Convert Cosmology Objects
******************************************

For *temporary* storage an easy means to serialize and deserialize a Cosmology
object is using the :mod:`pickle` module. This is good for e.g. passing a
|Cosmology| between threads.

.. doctest-skip::

   >>> import pickle
   >>> from astropy.cosmology import Planck18
   >>> with open("planck18.pkl", mode="wb") as file:
   ...     pickle.dump(Planck18, file)
   >>> # and to read back
   >>> with open("planck18.pkl", mode="rb") as file:
   ...     cosmo = pickle.load(file)
   >>> cosmo
   FlatLambdaCDM(name="Planck18", ...

However this method has all the attendant drawbacks of :mod:`pickle` — security
vulnerabilities and non-human-readable files. Pickle files just generally don't
make for good persistent storage.

Solving both these issues, ``astropy`` provides a unified interface for reading
and writing data in different formats.


Getting Started
===============

The |Cosmology| class includes two methods, |Cosmology.read| and
|Cosmology.write|, that make it possible to read from and write to files.

The registered ``read`` / ``write`` formats include "ascii.ecsv" and
"ascii.html", like for Table. Also, custom ``read`` / ``write`` formats may be
registered into the Astropy Cosmology I/O framework.

Writing a cosmology instance requires only the file location and optionally,
if the file format cannot be inferred, a keyword argument "format". Additional
positional arguments and keyword arguments are passed to the reader methods.

.. doctest-skip::

    >>> from astropy.cosmology import Planck18
    >>> Planck18.write("example_cosmology.ecsv", format="ascii.ecsv")

Reading back the cosmology is most safely done from |Cosmology|, the base
class, as it provides no default information and therefore requires the file
to have all necessary information to describe a cosmology.

.. doctest-skip::

    >>> from astropy.cosmology import Cosmology
    >>> cosmo = Cosmology.read("example_cosmology.ecsv", format="ascii.ecsv")
    >>> cosmo == Planck18
    True

To see a list of the available read/write file formats:

.. code-block:: python

    >>> from astropy.cosmology import Cosmology
    >>> Cosmology.read.list_formats()
      Format   Read Write Auto-identify
    ---------- ---- ----- -------------
    ascii.ecsv  Yes   Yes           Yes
    ascii.html  Yes   Yes           Yes

This list will include both built-in and registered 3rd-party formats.

When a subclass of |Cosmology| is used to read a file, the subclass will provide
a keyword argument ``cosmology=<class>`` to the registered read method. The
method uses this cosmology class, regardless of the class indicated in the
file, and sets parameters' default values from the class' signature.

.. doctest-skip::

    >>> from astropy.cosmology import FlatLambdaCDM
    >>> cosmo = FlatLambdaCDM.read('<file name>')
    >>> cosmo == Planck18
    True

Reading and writing |Cosmology| objects go through intermediate
representations, often a dict or |QTable| instance. These intermediate
representations are accessible through the methods |Cosmology.to_format| /
|Cosmology.from_format|.

To see the a list of the available conversion formats:

.. code-block:: python

    >>> from astropy.cosmology import Cosmology
    >>> Cosmology.to_format.list_formats()
          Format      Read Write Auto-identify
    ----------------- ---- ----- -------------
    astropy.cosmology  Yes   Yes           Yes
        astropy.model  Yes   Yes           Yes
          astropy.row  Yes   Yes           Yes
        astropy.table  Yes   Yes           Yes
              mapping  Yes   Yes           Yes
                 yaml  Yes   Yes            No

This list will include both built-in and registered 3rd-party formats.

|Cosmology.to_format| / |Cosmology.from_format| parse a Cosmology to/from
another python object. This can be useful for e.g., iterating through an MCMC
of cosmological parameters or printing out a cosmological model to a journal
format, like latex or HTML. When 3rd party cosmology packages register with
Astropy's Cosmology I/O, ``to/from_format`` can be used to convert cosmology
instances between packages!

.. EXAMPLE START: Planck18 to mapping and back

.. code-block::

    >>> from astropy.cosmology import Planck18
    >>> cm = Planck18.to_format("mapping")
    >>> cm
    {'cosmology': <class 'astropy.cosmology.flrw.lambdacdm.FlatLambdaCDM'>,
     'name': 'Planck18',
     'H0': <Quantity 67.66 km / (Mpc s)>,
     'Om0': 0.30966,
     ...

Now this dict can be used to load a new cosmological instance identical
to the |Planck18| cosmology from which it was created.

.. code-block::

    >>> from astropy.cosmology import Cosmology
    >>> cosmo = Cosmology.from_format(cm, format="mapping")
    >>> cosmo == Planck18
    True

.. EXAMPLE END

.. EXAMPLE START: Planck18 to QTable and back

Another pre-registered format is "table", for converting a |Cosmology| to and
from a |QTable|.

.. code-block::

    >>> ct = Planck18.to_format("astropy.table")
    >>> ct
    <QTable length=1>
      name        H0        Om0    Tcmb0    Neff      m_nu      Ob0
             km / (Mpc s)            K                 eV
      str8     float64    float64 float64 float64  float64[3] float64
    -------- ------------ ------- ------- ------- ----------- -------
    Planck18        67.66 0.30966  2.7255   3.046 0.0 .. 0.06 0.04897

Cosmology supports the astropy Table-like protocol (see
:ref:`Table-like Objects`) to the same effect:

.. code-block::

    >>> from astropy.table import QTable
    >>> ct = QTable(Planck18)
    >>> ct
    <QTable length=1>
      name        H0        Om0    Tcmb0    Neff      m_nu      Ob0
             km / (Mpc s)            K                 eV
      str8     float64    float64 float64 float64  float64[3] float64
    -------- ------------ ------- ------- ------- ----------- -------
    Planck18        67.66 0.30966  2.7255   3.046 0.0 .. 0.06 0.04897

Now this |QTable| can be used to load a new cosmological instance identical to
the |Planck18| cosmology from which it was created.

.. code-block::

    >>> cosmo = Cosmology.from_format(ct, format="astropy.table")
    >>> cosmo
    FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
                  Tcmb0=2.7255 K, Neff=3.046, m_nu=[0. 0. 0.06] eV, Ob0=0.04897)

Perhaps more usefully, |QTable| can be saved to ``latex`` and ``html`` formats,
which can be copied into journal articles and websites, respectively.

.. EXAMPLE END

.. EXAMPLE START: Planck18 to Model and back

Using ``format="astropy.model"`` any redshift(s) method of a cosmology may be
turned into a :class:`astropy.modeling.Model`. Each |Cosmology|
:class:`~astropy.cosmology.Parameter` is converted to a
:class:`astropy.modeling.Model` :class:`~astropy.modeling.Parameter`
and the redshift-method to the model's ``__call__ / evaluate``.
Now you can fit cosmologies with data!

.. code-block::

    >>> model = Planck18.to_format("astropy.model", method="lookback_time")
    >>> model
    <FlatLambdaCDMCosmologyLookbackTimeModel(H0=67.66 km / (Mpc s), Om0=0.30966,
        Tcmb0=2.7255 K, Neff=3.046, m_nu=[0.  , 0.  , 0.06] eV, Ob0=0.04897,
        name='Planck18')>

Like for the other formats, the |Planck18| cosmology can be recovered with
|Cosmology.from_format|.


.. _custom_cosmology_converters:

Custom Cosmology To/From Formats
================================

Custom representation formats may also be registered into the Astropy Cosmology
I/O framework for use by these methods. For details of the framework see
:ref:`io_registry`. Note |Cosmology| ``to/from_format`` uses a custom registry,
available at ``Cosmology.<to/from>_format.registry``.

.. EXAMPLE START : custom to/from format

As an example, the following is an implementation of an |Row| converter. We can
and should use inbuilt parsers, like |QTable|, but to show a more complete
example we limit ourselves to only the "mapping" parser.

We start by defining the function to parse a |Row| into a |Cosmology|. This
function should take 1 positional argument, the row object, and 2 keyword
arguments, for how to handle extra metadata and which Cosmology class to use.
Details about metadata treatment are in
``Cosmology.from_format.help("mapping")``.

.. code-block:: python

    >>> import copy
    >>> from astropy.cosmology import Cosmology

    >>> def from_table_row(row, *, move_to_meta=False, cosmology=None):
    ...     # get name from column
    ...     name = row['name'] if 'name' in row.columns else None
    ...     meta = copy.deepcopy(row.meta)
    ...     # turn row into mapping (dict of the arguments)
    ...     mapping = dict(row)
    ...     mapping['name'] = name
    ...     mapping.setdefault("cosmology", meta.pop("cosmology", None))
    ...     mapping["meta"] = meta
    ...     # build cosmology from map
    ...     return Cosmology.from_format(mapping, move_to_meta=move_to_meta,
    ...                                  cosmology=cosmology)

The next step is a function to perform the reverse operation: parse a
|Cosmology| into a |Row|. This function requires only the cosmology object and
a ``*args`` to absorb unneeded information passed by
:class:`astropy.io.registry.UnifiedReadWrite` (which implements
|Cosmology.to_format|).

.. code-block:: python

    >>> from astropy.table import QTable

    >>> def to_table_row(cosmology, *args):
    ...     p = cosmology.to_format("mapping", cosmology_as_str=True)
    ...     meta = p.pop("meta")
    ...     # package parameters into lists for Table parsing
    ...     params = {k: [v] for k, v in p.items()}
    ...     return QTable(params, meta=meta)[0]  # return row

Last we write a function to help with format auto-identification and then
register everything into `astropy.io.registry`.

.. code-block:: python

    >>> from astropy.cosmology import Cosmology
    >>> from astropy.cosmology.connect import convert_registry
    >>> from astropy.table import Row

    >>> def row_identify(origin, format, *args, **kwargs):
    ...     """Identify if object uses the Table format."""
    ...     if origin == "read":
    ...         return isinstance(args[1], Row) and (format in (None, "astropy.row"))
    ...     return False

    >>> # These exact functions are already registered in astropy
    >>> # convert_registry.register_reader("astropy.row", Cosmology, from_table_row)
    >>> # convert_registry.register_writer("astropy.row", Cosmology, to_table_row)
    >>> # convert_registry.register_identifier("astropy.row", Cosmology, row_identify)

Now the registered functions can be used in |Cosmology.from_format| and
|Cosmology.to_format|.

.. code-block:: python

    >>> from astropy.cosmology import Planck18
    >>> row = Planck18.to_format("astropy.row")
    >>> row
    <Row index=0>
      cosmology     name        H0        Om0    Tcmb0    Neff      m_nu      Ob0
                           km / (Mpc s)            K                 eV
        str13       str8     float64    float64 float64 float64  float64[3] float64
    ------------- -------- ------------ ------- ------- ------- ----------- -------
    FlatLambdaCDM Planck18        67.66 0.30966  2.7255   3.046 0.0 .. 0.06 0.04897

    >>> cosmo = Cosmology.from_format(row)
    >>> cosmo == Planck18  # test it round-trips
    True

.. EXAMPLE END


.. _custom_cosmology_readers_writers:

Custom Cosmology Readers/Writers
================================

Custom ``read`` / ``write`` formats may be registered into the Astropy
Cosmology I/O framework. For details of the framework see :ref:`io_registry`.
Note |Cosmology| ``read/write`` uses a custom registry, available at
``Cosmology.<read/write>.registry``.

.. EXAMPLE START : custom read/write

As an example, in the following we will fully work out a |Cosmology| <-> JSON
(de)serializer. Note that we can use other registered parsers -- here "mapping"
-- to make the implementation much simpler.

We start by defining the function to parse JSON into a |Cosmology|. This
function should take 1 positional argument, the file object or file path. We
will also pass kwargs through to |Cosmology.from_format|, which handles
metadata and which Cosmology class to use. Details of are in
``Cosmology.from_format.help("mapping")``.

.. code-block:: python

    >>> import json, os
    >>> import astropy.units as u
    >>> from astropy.cosmology import Cosmology

    >>> def read_json(filename, **kwargs):
    ...     # read file, from path-like or file-like
    ...     if isinstance(filename, (str, bytes, os.PathLike)):
    ...         with open(filename, "r") as file:
    ...             data = file.read()
    ...     else:  # file-like : this also handles errors in dumping
    ...         data = filename.read()
    ...     mapping = json.loads(data)  # parse json mappable to dict
    ...     # deserialize Quantity
    ...     for k, v in mapping.items():
    ...         if isinstance(v, dict) and "value" in v and "unit" in v:
    ...             mapping[k] = u.Quantity(v["value"], v["unit"])
    ...     for k, v in mapping.get("meta", {}).items():  # also the metadata
    ...         if isinstance(v, dict) and "value" in v and "unit" in v:
    ...             mapping["meta"][k] = u.Quantity(v["value"], v["unit"])
    ...     return Cosmology.from_format(mapping, **kwargs)


The next step is a function to write a |Cosmology| to JSON. This function
requires the cosmology object and a file object/path. We also require the
boolean flag "overwrite" to set behavior for existing files. Note that
|Quantity| is not natively compatible with JSON. In both the ``write`` and
``read`` methods we have to create custom parsers.

.. code-block:: python

    >>> def write_json(cosmology, file, *, overwrite=False, **kwargs):
    ...    data = cosmology.to_format("mapping", cosmology_as_str=True)  # start by turning into dict
    ...    # serialize Quantity
    ...    for k, v in data.items():
    ...        if isinstance(v, u.Quantity):
    ...            data[k] = {"value": v.value.tolist(), "unit": str(v.unit)}
    ...    for k, v in data.get("meta", {}).items():  # also serialize the metadata
    ...        if isinstance(v, u.Quantity):
    ...            data["meta"][k] = {"value": v.value.tolist(), "unit": str(v.unit)}
    ...
    ...    if isinstance(file, (str, bytes, os.PathLike)):
    ...        # check that file exists and whether to overwrite.
    ...        if os.path.exists(file) and not overwrite:
    ...            raise IOError(f"{file} exists. Set 'overwrite' to write over.")
    ...        with open(file, "w") as write_file:
    ...            json.dump(data, write_file)
    ...    else:
    ...        json.dump(data, file)

Last we write a function to help with format auto-identification and then
register everything into :mod:`astropy.io.registry`.

.. code-block:: python

    >>> from astropy.cosmology.connect import readwrite_registry

    >>> def json_identify(origin, filepath, fileobj, *args, **kwargs):
    ...     """Identify if object uses the JSON format."""
    ...     return filepath is not None and filepath.endswith(".json")

    >>> readwrite_registry.register_reader("json", Cosmology, read_json)
    >>> readwrite_registry.register_writer("json", Cosmology, write_json)
    >>> readwrite_registry.register_identifier("json", Cosmology, json_identify)

Now the registered functions can be used in |Cosmology.read| and
|Cosmology.write|.

.. doctest-skip:: win32

    >>> import tempfile
    >>> from astropy.cosmology import Planck18
    >>>
    >>> file = tempfile.NamedTemporaryFile()
    >>> Planck18.write(file.name, format="json", overwrite=True)
    >>> with open(file.name) as f: f.readlines()
    ['{"cosmology": "FlatLambdaCDM", "name": "Planck18",
       "H0": {"value": 67.66, "unit": "km / (Mpc s)"}, "Om0": 0.30966,
       ...
    >>>
    >>> cosmo = Cosmology.read(file.name, format="json")
    >>> file.close()
    >>> cosmo == Planck18  # test it round-trips
    True


.. doctest::
    :hide:

    >>> from astropy.io.registry import IORegistryError
    >>> readwrite_registry.unregister_reader("json", Cosmology)
    >>> readwrite_registry.unregister_writer("json", Cosmology)
    >>> readwrite_registry.unregister_identifier("json", Cosmology)
    >>> try:
    ...     readwrite_registry.get_reader("json", Cosmology)
    ... except IORegistryError:
    ...     pass

.. EXAMPLE END


Reference/API
=============

.. automodapi:: astropy.cosmology.connect

.. automodapi:: astropy.cosmology.io.mapping