Getting started =============== The `mojito` package provides tools to download, read, and write Mojito L1 files (also known as "bricks"). This guide will walk you through the basic steps to get started with Mojito L1 files. Installation ------------ To install the `mojito` package, follow the instructions provided in the :doc:`installation` section of the documentation. Pour yourself a drink --------------------- Use the :mod:`mojito.download` module to download Mojito L1 files and source parameter catalogs from the Mojito brick market. Convenience functions are provided to handle authentication, caching (to reduce disk and network usage), integrity checks and versioning. .. note:: Downloading Mojito L1 files and source catalogs from the Mojito brick market requires authentication using a Nextcloud username and token. For more information on how to obtain your credentials and use them with Mojito, see the :ref:`authentication` section. Download brick files ~~~~~~~~~~~~~~~~~~~~ You can use the :func:`mojito.download.download_brick` function to download a Mojito L1 brick file. Simply provide the source type (e.g., "mbhb", "gb", "noise", "combined", etc.) and an optional source identifier. .. code-block:: python from mojito.download import download_brick # Download Mojito L1 brick file for a massive black hole binary source brick_file = download_brick("mbhb", 12) # Download Mojito L1 brick file for noise (no source id needed) noise_file = download_brick("noise", version=1) # Subsequent calls will use the cached file if available (no re-download) brick_file_cached = download_brick("mbhb", 12) # Provide authentication explicitly if needed brick_file = download_brick("mbhb", 12, username="myuser", token="mytoken") # Download reduced (downsampled and stripped off) brick # Warning: reduced bricks are not official L1 files and may be missing some # quantities; use only for testing and development! reduced_brick_file = download_brick("mbhb", 12, reduced=True) If the file is not already cached, it will be downloaded from the Mojito brick market and the path to the downloaded file will be returned. If the file is already cached, the path to the cached file will be returned without re-downloading. .. note:: Authentication is required to download files. You can provide credentials via function arguments (``username`` and ``token``), environment variables (``MOJITO_USERNAME`` and ``MOJITO_TOKEN``), or you will be prompted interactively if credentials are not available. Fetch source parameters ~~~~~~~~~~~~~~~~~~~~~~~ You can also use :func:`mojito.download.get_source_params` to download the relevant source catalog and fetch the source parameters for a specified source. You can also download an entire catalog of source parameters using :func:`mojito.download.download_catalog`. .. code-block:: python from mojito.download import download_catalog, get_source_params # Fetch source parameters for a specified source source_params = get_source_params("mbhb", 12) # Download the entire catalog of galactic binaries galactic_catalog_file = download_catalog("gb") More details on the downloading functions and their options can be found in :doc:`pouring/download`. Caching ~~~~~~~ By default, downloaded files are cached in a directory following the `XDG Base Directory Specification `_ (typically ``~/.cache/mojito``). You can change this location by setting the environment variable ``MOJITO_CACHE_DIR``, or by passing the ``cache_dir`` keyword argument to the download functions. .. note:: Make sure that the cache directory has enough disk space to store the downloaded files. You can clear the cache by using the :func:`mojito.download.clear_cache` function. .. code-block:: python from mojito.download import get_cache_dir, clear_cache # Get the current cache directory cache_dir = get_cache_dir() print(f"Current cache directory: {cache_dir}") # Clear the cache directory clear_cache() .. warning:: Use with caution as this will delete all cached files. All files will need to be re-downloaded if needed again. Versioning ~~~~~~~~~~ By default, the latest version of each brick or parameter catalog is downloaded. You can specify a different version by providing the ``version`` keyword argument. Command-line interface ~~~~~~~~~~~~~~~~~~~~~~ The `mojito` package also provides a command-line interface (CLI) to download Mojito L1 files and source catalogs. You can use .. code-block:: console $ mojito download-brick mbhb 12 $ mojito source-params mbhb 12 $ mojito download-catalog gb $ mojito clear-cache More details on the CLI and its commands can be found in :doc:`pouring/cli`. Have a drink ------------ The :mod:`mojito.reader` module provides the :class:`mojito.reader.MojitoL1File` class to read Mojito L1 brick files. This class provides access to the various datasets stored in the file, including TDI observables, light travel times, spacecraft orbits, and noise estimates. Reading datasets ~~~~~~~~~~~~~~~~ Dataset access is done via attributes of the :class:`mojito.reader.MojitoL1File` object. Convenience properties are provided to stack and rescale datasets (unit conversion) as needed. .. note:: Always use the context manager (``with`` statement) to open Mojito L1 files. This ensures that the file is properly closed after use, preventing resource leaks. .. code-block:: python from mojito import MojitoL1File with MojitoL1File("path/to/file.h5") as f: # TDI observables x2 = f.tdis.x2[:] # TDI X2 observable in Hz y2 = f.tdis.y2[:] # TDI Y2 observable in Hz z2 = f.tdis.z2[:] # TDI Z2 observable in Hz # Complete set of TDI observables in Doppler units xyz = f.tdis.xyz_doppler[:] # TDI XYZ in Doppler units aet = f.tdis.aet_doppler[:] # TDI AET in Doppler units # Associated time sampling tdi_time_sampling = f.tdis.time_sampling tdi_dt = tdi_time_sampling.dt # time step in seconds tdi_times = tdi_time_sampling.t() # array of times in seconds # Light travel times and spacecraft orbits for response function ltts = f.ltts.ltts[:] # Stacked light travel times ltt_times = f.ltts.time_sampling.t() orbits = f.orbits.positions[:] # Stacked spacecraft positions orbits_times = f.orbits.time_sampling.t() # Noise estimates for TDI AET noise_aet = f.noise_estimates.aet[:] # in Hz^2/Hz noise_times = f.noise_estimates.time_sampling.t() # array of times noise_freqs = f.noise_estimates.freq_sampling.f() # array of freqs Multiple bricks can be read together by providing a list of file paths to the constructor. The datasets will be automatically combined across bricks. .. code-block:: python with MojitoL1File(["file1.h5", "file2.h5"]) as f: # Access the first 1000 samples of XYZ TDI observables (Doppler units) # Datasets are automatically combined across bricks xyz = f.tdis.xyz_doppler[:1000] # shape (1000, 3) Note that some datasets will be summed (typically, TDI observables), while others will be OR-ed (typically, quality flags) and some will be returned from one of the bricks (typically, orbits and light travel times). This is transparent to the user. More information on each dataset and its methods can be found in :class:`mojito.reader.MojitoL1File` and its attributes. Time and frequency grids ~~~~~~~~~~~~~~~~~~~~~~~~ The time and frequency grids (sampling) associated with each dataset can be accessed via the ``time_sampling`` and ``freq_sampling`` attributes. These are instances of :class:`mojito.sampling.UniformTimeSampling` and :class:`mojito.sampling.UniformFreqSampling`, and provide methods to reconstruct the time and frequency arrays, as well as properties like the time step ``dt``. .. code-block:: python with MojitoL1File("path/to/file.h5") as f: tdi_time_sampling = f.tdis.time_sampling # Print time sampling information print(tdi_time_sampling) tdi_t0 = tdi_time_sampling.t0 # Start time in seconds tdi_dt = tdi_time_sampling.dt # Time step in seconds tdi_size = tdi_time_sampling.size # Number of samples tdi_duration = tdi_time_sampling.duration # Duration in seconds tdi_fs = tdi_time_sampling.fs # Sampling frequency in Hz # Generate time array in seconds tdi_times = tdi_time_sampling.t() # You can also start at zero (ignore t0) tdi_times_zero = tdi_time_sampling.t(elapsed=True) Initial times ``t0`` are expressed in seconds since the LISA epoch, as defined in :data:`lisaconstants.LISA_EPOCH_TCB`. You can also generate sliced time arrays by providing a ``slice`` object to the ``t()`` method. This allows for better memory management when working with large datasets. .. code-block:: python # Generate time array for samples 1000 to 2000 tdi_times = tdi_time_sampling.t(slice(1000, 2000)) # Generate time array for every other sample tdi_times = tdi_time_sampling.t(slice(None, None, 2)) More details on the time and frequency sampling classes can be found in :doc:`drinking/sampling`. Memory usage and slicing ~~~~~~~~~~~~~~~~~~~~~~~~ All datasets in :class:`mojito.reader.MojitoL1File` support lazy loading to minimize memory usage; therefore, only the requested data (sliced arrays) are loaded into memory. .. warning:: You must slice the datasets to read the data into memory. Accessing a non-sliced dataset returns a lazy-loading object that cannot be used outside of the context manager (that is, outside of the ``with`` block). For example, to read only the first 1000 samples of the TDI X2 observable, you can do: .. code-block:: python with MojitoL1File("path/to/file.h5") as f: x2_first_1000 = f.tdis.x2[:1000] times_first_1000 = f.tdis.time_sampling.t(slice(0, 1000)) Note that some attributes, such as :attr:`mojito.reader.TDI.xyz_doppler` and :attr:`mojito.reader.TDI.aet_doppler`, perform stacking and rescaling of the underlying datasets. They have been designed to provide lazy loading and transparent slicing as well (including along the stacking dimension). For example, to read only the first 1000 samples of the TDI XY observables in Doppler units, you can do: .. code-block:: python with MojitoL1File("path/to/file.h5") as f: xy_doppler_first_1000 = f.tdis.xyz_doppler[:1000, :2] Behind the scenes, Mojito uses `h5py `_ to read HDF5 files, and properties return `h5py.Dataset` objects that support slicing and lazy loading. Stacked datasets are handled by the :class:`mojito.lazy.ScaledStackedDataset` class, which also supports slicing and lazy loading. Stir the drink -------------- Writing Mojito files ~~~~~~~~~~~~~~~~~~~~ Mojito provides a writer to create Mojito L1 files from simulation outputs or by combining existing Mojito L1 files. The writer ensures that the created files are compliant with the Mojito L1 file specification. To combine existing Mojito L1 files, you can use the :func:`mojito.writer.combine_bricks` function. This function can also check for consistency across the input files and raise an error if inconsistencies are found. .. code-block:: python from mojito.writer import combine_bricks combine_bricks( ["file1.h5", "file2.h5"], "combined_file.h5", check_consistent=True, ) The module :mod:`mojito.writer` also provides functions to create empty Mojito L1 files and various groups, and write attributes. The class :class:`mojito.reader.MojitoL1File` can also be used to write data to an existing Mojito L1 file. .. code-block:: python from mojito import MojitoL1File from mojito.sampling import UniformTimeSampling from mojito.writer import write_attrs, create_tdis # Create an empty Mojito L1 file with MojitoL1File("path/to/new_file.h5", mode="a") as f: # Write attributes write_attrs( f, pipeline_name="my_pipeline", lolipops_version="1.0.0", laser_frequency=2.82e14, ) # Create TDI group with time sampling tdi_time_sampling = UniformTimeSampling(t0=0.0, dt=2.0, size=10000) create_tdis(f, time_sampling=tdi_time_sampling) # Write TDI observables f.tdis.x2[:] = my_x2_data # in Hz f.tdis.y2[:] = my_y2_data # in Hz f.tdis.z2[:] = my_z2_data # in Hz # etc. You can copy entire groups from an existing Mojito L1 file to a new one by providign the ``data`` keyword argument to the group creation functions. For example, to copy the orbits and LTT groups from an existing file: .. code-block:: python from mojito import MojitoL1File from mojito.writer import create_orbits, create_ltts with MojitoL1File("path/to/existing_file.h5", mode="r") as f_existing: with MojitoL1File("path/to/new_file.h5", mode="a") as f_new: # Copy orbits group create_orbits( f_new, time_sampling=f_existing.orbits.time_sampling, data=f_existing.orbits, ) # Copy LTT group create_ltts( f_new, time_sampling=f_existing.ltts.time_sampling, data=f_existing.ltts, ) If datasets are missing from the existing file, they will be created as empty datasets in the new file. More details on the writer and its functions can be found in :doc:`stirring/writer`.