Skip to content
Snippets Groups Projects
report.py 28.75 KiB
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""Produce various plots as a reporting and diagnostic tool.

The 'report' continuous-integration job runs various simulations with
different configurations (mostly individually-enabled noises), and produces
plots. These plots are used as quick human-eye diagnostic tool.
"""

import datetime
import logging
import os
from multiprocessing import Process

import matplotlib.pyplot as plt
import numpy as np
from h5py import File
from matplotlib.backends.backend_pdf import PdfPages
from scipy.signal import welch

from lisainstrument import Instrument, __version__
from lisainstrument.containers import ForEachMOSA, ForEachSC

logging.basicConfig()
logging.getLogger().setLevel(logging.INFO)


PARALLEL = False
"""bool: Whether to run reports in parallel"""

NPERSEG = 2**16 # ~ 65_000
"""int: Number of segments for Welch averaging"""

SIZE = NPERSEG * 3 # ~ 196_000
"""int: Size of simulations [samples]"""


def main():
    """Main function."""

    logging.info("Starting report")

    processes = [
        Process(target=func)
        for func in [
            no_noise,
            laser,
            testmass,
            oms,
            backlink,
            clock,
            ranging,
            modulation,
            jitter,
            dws,
            moc_time_correlation,
        ]
    ]

    for process in processes:
        process.start()
        if not PARALLEL:
            process.join()

    if PARALLEL:
        for process in processes:
            process.join()

    logging.info("Report complete")


def no_noise():
    """No-noise simulation."""

    logging.info("Running no-noise simulation")

    instru = Instrument(
        size=SIZE,
        orbits='tests/esa-trailing-orbits-2-0.h5',
        concurrent=True,
    )
    instru.disable_all_noises()
    instru.write('no-noise.h5', mode='w')

    with PdfPages('no-noise.pdf') as pdf:

        plot_measurements(instru, pdf)
        write_metadata('no-noise', instru.orbit_file, pdf)


def laser():
    """Laser-noise simulation."""

    logging.info("Running laser-noise simulation")

    instru = Instrument(
        size=SIZE,
        orbits='tests/esa-trailing-orbits-2-0.h5',
        concurrent=True,
    )
    instru.disable_all_noises(but='laser')
    instru.write('laser-noise_temp.h5', mode='w', keep_all=True)

    with PdfPages('laser-noise.pdf') as pdf:
        with File('laser-noise_temp.h5', 'r') as hdf5:
            laser_noises = hdf5['laser_noises'][()]

        plt.figure(figsize=(16, 8))
        for mosa in instru.MOSAS:
            plot_tseries(instru.physics_t, laser_noises[mosa], label=mosa)
        plt.ylabel('Frequency [Hz]')
        plt.title('Laser noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plt.figure(figsize=(16, 8))
        for mosa in instru.MOSAS:
            plot_asd(instru.physics_t, laser_noises[mosa], label=mosa)
        plt.ylabel('ASD [Hz/sqrt(Hz)]')
        plt.title('Laser noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plot_measurements(instru, pdf)
        write_metadata('laser-noise', instru.orbit_file, pdf)
    copy_final_measurements('laser-noise.h5', 'laser-noise_temp.h5')


def testmass():
    """Test mass-noise simulation."""

    logging.info("Running test mass-noise simulation")

    instru = Instrument(
        size=SIZE,
        orbits='tests/esa-trailing-orbits-2-0.h5',
        concurrent=True,
    )
    instru.laser_asds = ForEachMOSA(0)
    instru.modulation_asds = ForEachMOSA(0)
    instru.disable_clock_noises()
    instru.disable_ranging_noises()
    instru.disable_jitters()
    instru.backlink_asds = ForEachMOSA(0)
    instru.oms_isi_carrier_asds = ForEachMOSA(0)
    instru.oms_isi_usb_asds = ForEachMOSA(0)
    instru.oms_tmi_carrier_asds = ForEachMOSA(0)
    instru.oms_tmi_usb_asds = ForEachMOSA(0)
    instru.oms_rfi_carrier_asds = ForEachMOSA(0)
    instru.oms_rfi_usb_asds = ForEachMOSA(0)
    instru.dws_asds = ForEachMOSA(0)
    instru.sync_asds = ForEachSC(0)
    instru.write('testmass-noise_temp.h5', mode='w', keep_all=True)

    with PdfPages('testmass-noise.pdf') as pdf:
        with File('testmass-noise_temp.h5', 'r') as hdf5:
            testmass_noises = hdf5['testmass_noises'][()]

        plt.figure(figsize=(16, 8))
        for mosa in instru.MOSAS:
            plot_tseries(instru.physics_t, testmass_noises[mosa], label=mosa)
        plt.ylabel('Velocity [m/s]')
        plt.title('Test-mass noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plt.figure(figsize=(16, 8))
        for mosa in instru.MOSAS:
            plot_asd(instru.physics_t, testmass_noises[mosa], label=mosa)
        plt.ylabel('ASD [m/s/sqrt(Hz)]')
        plt.title('Test-mass noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plot_measurements(instru, pdf)
        write_metadata('testmass-noise', instru.orbit_file, pdf)
    copy_final_measurements('testmass-noise.h5', 'testmass-noise_temp.h5')


def oms():
    """OMS-noise simulation."""

    logging.info("Running OMS-noise simulation")

    instru = Instrument(
        size=SIZE,
        orbits='tests/esa-trailing-orbits-2-0.h5',
        concurrent=True,
    )
    instru.laser_asds = ForEachMOSA(0)
    instru.modulation_asds = ForEachMOSA(0)
    instru.disable_clock_noises()
    instru.disable_ranging_noises()
    instru.disable_jitters()
    instru.backlink_asds = ForEachMOSA(0)
    instru.testmass_asds = ForEachMOSA(0)
    instru.dws_asds = ForEachMOSA(0)
    instru.sync_asds = ForEachSC(0)
    instru.write('oms-noise_temp.h5', mode='w', keep_all=True)

    with PdfPages('oms-noise.pdf') as pdf:
        with File('oms-noise_temp.h5', 'r') as hdf5:
            oms_isi_carrier_noises = hdf5['oms_isi_carrier_noises'][()]
            oms_tmi_carrier_noises = hdf5['oms_tmi_carrier_noises'][()]
            oms_rfi_carrier_noises = hdf5['oms_rfi_carrier_noises'][()]
            oms_isi_usb_noises = hdf5['oms_isi_usb_noises'][()]
            oms_tmi_usb_noises = hdf5['oms_tmi_usb_noises'][()]
            oms_rfi_usb_noises = hdf5['oms_rfi_usb_noises'][()]

        plt.figure(figsize=(16, 8))
        for mosa in instru.MOSAS:
            plot_asd(instru.physics_t, oms_isi_carrier_noises[mosa], label=mosa)
        plt.ylabel('ASD [/sqrt(Hz)]')
        plt.title('OMS ISI carrier noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plt.figure(figsize=(16, 8))
        for mosa in instru.MOSAS:
            plot_asd(instru.physics_t, oms_tmi_carrier_noises[mosa], label=mosa)
        plt.ylabel('ASD [/sqrt(Hz)]')
        plt.title('OMS TMI carrier noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plt.figure(figsize=(16, 8))
        for mosa in instru.MOSAS:
            plot_asd(instru.physics_t, oms_rfi_carrier_noises[mosa], label=mosa)
        plt.ylabel('ASD [/sqrt(Hz)]')
        plt.title('OMS RFI carrier noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plt.figure(figsize=(16, 8))
        for mosa in instru.MOSAS:
            plot_asd(instru.physics_t, oms_isi_usb_noises[mosa], label=mosa)
        plt.ylabel('ASD [/sqrt(Hz)]')
        plt.title('OMS ISI upper-sideband noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plt.figure(figsize=(16, 8))
        for mosa in instru.MOSAS:
            plot_asd(instru.physics_t, oms_tmi_usb_noises[mosa], label=mosa)
        plt.ylabel('ASD [/sqrt(Hz)]')
        plt.title('OMS TMI upper-sideband noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plt.figure(figsize=(16, 8))
        for mosa in instru.MOSAS:
            plot_asd(instru.physics_t, oms_rfi_usb_noises[mosa], label=mosa)
        plt.ylabel('ASD [/sqrt(Hz)]')
        plt.title('OMS RFI upper-sideband noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plot_measurements(instru, pdf)
        write_metadata('oms-noise', instru.orbit_file, pdf)
    copy_final_measurements('oms-noise.h5', 'oms-noise_temp.h5')


def backlink():
    """Backlink-noise simulation."""

    logging.info("Running backlink-noise simulation")

    instru = Instrument(
        size=SIZE,
        orbits='tests/esa-trailing-orbits-2-0.h5',
        concurrent=True,
    )
    instru.laser_asds = ForEachMOSA(0)
    instru.modulation_asds = ForEachMOSA(0)
    instru.disable_clock_noises()
    instru.disable_ranging_noises()
    instru.disable_jitters()
    instru.testmass_asds = ForEachMOSA(0)
    instru.oms_isi_carrier_asds = ForEachMOSA(0)
    instru.oms_isi_usb_asds = ForEachMOSA(0)
    instru.oms_tmi_carrier_asds = ForEachMOSA(0)
    instru.oms_tmi_usb_asds = ForEachMOSA(0)
    instru.oms_rfi_carrier_asds = ForEachMOSA(0)
    instru.oms_rfi_usb_asds = ForEachMOSA(0)
    instru.dws_asds = ForEachMOSA(0)
    instru.sync_asds = ForEachSC(0)
    instru.write('backlink-noise_temp.h5', mode='w', keep_all=True)

    with PdfPages('backlink-noise.pdf') as pdf:
        with File('backlink-noise_temp.h5', 'r') as hdf5:
            backlink_noises = hdf5['backlink_noises'][()]

        plt.figure(figsize=(16, 8))
        for mosa in instru.MOSAS:
            plot_asd(instru.physics_t, backlink_noises[mosa], label=mosa)
        plt.ylabel('ASD [/sqrt(Hz)]')
        plt.title('Backlink noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plot_measurements(instru, pdf)
        write_metadata('backlink-noise', instru.orbit_file, pdf)
    copy_final_measurements('backlink-noise.h5', 'backlink-noise_temp.h5')


def clock():
    """Clock-noise simulation."""

    logging.info("Running clock-noise simulation")

    instru = Instrument(
        size=SIZE,
        orbits='tests/esa-trailing-orbits-2-0.h5',
        concurrent=True,
    )
    instru.laser_asds = ForEachMOSA(0)
    instru.modulation_asds = ForEachMOSA(0)
    instru.disable_ranging_noises()
    instru.disable_jitters()
    instru.disable_pathlength_noises()
    instru.dws_asds = ForEachMOSA(0)
    instru.sync_asds = ForEachSC(0)
    instru.write('clock-noise_temp.h5', mode='w', keep_all=True)

    with PdfPages('clock-noise.pdf') as pdf:
        with File('clock-noise_temp.h5', 'r') as hdf5:
            clock_noise_offsets = hdf5['clock_noise_offsets'][()]
            clock_noise_fluctuations = hdf5['clock_noise_fluctuations'][()]

        plt.figure(figsize=(16, 8))
        for sc in instru.SCS:
            plot_tseries(instru.physics_t, clock_noise_offsets[sc], label=sc)
        plt.ylabel('Fractional frequency deviations')
        plt.title('Clock noise offsets')
        plt.legend()
        pdf.savefig()
        plt.close()

        plt.figure(figsize=(16, 8))
        for sc in instru.SCS:
            plot_tseries(instru.physics_t, clock_noise_fluctuations[sc], label=sc)
        plt.ylabel('Fractional frequency deviations')
        plt.title('Clock noise fluctuations')
        plt.legend()
        pdf.savefig()
        plt.close()

        plt.figure(figsize=(16, 8))
        for sc in instru.SCS:
            plot_asd(instru.physics_t, clock_noise_fluctuations[sc], label=sc)
        plt.ylabel('ASD [/sqrt(Hz)]')
        plt.title('Clock noise fluctuations')
        plt.legend()
        pdf.savefig()
        plt.close()

        plot_measurements(instru, pdf)
        write_metadata('clock-noise', instru.orbit_file, pdf)
    copy_final_measurements('clock-noise.h5', 'clock-noise_temp.h5')


def ranging():
    """Ranging-noise simulation."""

    logging.info("Running ranging-noise simulation")

    instru = Instrument(
        size=SIZE,
        orbits='tests/esa-trailing-orbits-2-0.h5',
        concurrent=True,
    )
    instru.laser_asds = ForEachMOSA(0)
    instru.modulation_asds = ForEachMOSA(0)
    instru.disable_clock_noises()
    instru.disable_jitters()
    instru.disable_pathlength_noises()
    instru.dws_asds = ForEachMOSA(0)
    instru.sync_asds = ForEachSC(0)
    instru.write('ranging-noise_temp.h5', mode='w', keep_all=True)

    with PdfPages('ranging-noise.pdf') as pdf:
        with File('ranging-noise_temp.h5', 'r') as hdf5:
            ranging_noises = hdf5['ranging_noises'][()]

        plt.figure(figsize=(16, 8))
        for mosa in instru.MOSAS:
            plot_tseries(instru.physics_t, instru.ranging_biases[mosa], label=mosa)
        plt.ylabel('Time [s]')
        plt.title('Ranging bias')
        plt.legend()
        pdf.savefig()
        plt.close()

        plt.figure(figsize=(16, 8))
        for mosa in instru.MOSAS:
            plot_tseries(instru.physics_t, ranging_noises[mosa], label=mosa)
        plt.ylabel('Time [s]')
        plt.title('Ranging noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plt.figure(figsize=(16, 8))
        for mosa in instru.MOSAS:
            plot_asd(instru.physics_t, ranging_noises[mosa], label=mosa)
        plt.ylabel('ASD [s/sqrt(Hz)]')
        plt.title('Ranging noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plot_measurements(instru, pdf)
        write_metadata('ranging-noise', instru.orbit_file, pdf)
    copy_final_measurements('ranging-noise.h5', 'ranging-noise_temp.h5')


def modulation():
    """Modulation-noise simulation."""

    logging.info("Running modulation-noise simulation")

    instru = Instrument(
        size=SIZE,
        orbits='tests/esa-trailing-orbits-2-0.h5',
        concurrent=True,
    )
    instru.laser_asds = ForEachMOSA(0)
    instru.disable_clock_noises()
    instru.disable_ranging_noises()
    instru.disable_jitters()
    instru.disable_pathlength_noises()
    instru.dws_asds = ForEachMOSA(0)
    instru.sync_asds = ForEachSC(0)
    instru.write('modulation-noise_temp.h5', mode='w', keep_all=True)

    with PdfPages('modulation-noise.pdf') as pdf:
        with File('modulation-noise_temp.h5', 'r') as hdf5:
            modulation_noises = hdf5['modulation_noises'][()]

        plt.figure(figsize=(16, 8))
        for mosa in instru.MOSAS:
            plot_tseries(instru.physics_t, modulation_noises[mosa], label=mosa)
        plt.ylabel('Fractional frequency deviations')
        plt.title('Modulation noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plt.figure(figsize=(16, 8))
        for mosa in instru.MOSAS:
            plot_asd(instru.physics_t, modulation_noises[mosa], label=mosa)
        plt.ylabel('ASD [/sqrt(Hz)]')
        plt.title('Modulation noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plot_measurements(instru, pdf)
        write_metadata('modulation-noise', instru.orbit_file, pdf)
    copy_final_measurements('modulation-noise.h5', 'modulation-noise_temp.h5')


def jitter():
    """Angular jitter-noise simulation."""

    logging.info("Running angular jitter-noise simulation")

    instru = Instrument(
        size=SIZE,
        orbits='tests/esa-trailing-orbits-2-0.h5',
        concurrent=True,
    )
    instru.laser_asds = ForEachMOSA(0)
    instru.modulation_asds = ForEachMOSA(0)
    instru.disable_clock_noises()
    instru.disable_ranging_noises()
    instru.disable_pathlength_noises()
    instru.dws_asds = ForEachMOSA(0)
    instru.sync_asds = ForEachSC(0)
    instru.write('jitter-noise_temp.h5', mode='w', keep_all=True)

    with PdfPages('jitter-noise.pdf') as pdf:
        with File('jitter-noise_temp.h5', 'r') as hdf5:
            sc_jitter_phis = hdf5['sc_jitter_phis'][()]
            sc_jitter_etas = hdf5['sc_jitter_etas'][()]
            sc_jitter_thetas = hdf5['sc_jitter_thetas'][()]
            mosa_jitter_phis = hdf5['mosa_jitter_phis'][()]

        plt.figure(figsize=(16, 8))
        for sc in instru.SCS:
            plot_asd(instru.physics_t, sc_jitter_phis[sc], label=f'Phi {sc}')
            plot_asd(instru.physics_t, sc_jitter_etas[sc], label=f'Eta {sc}')
            plot_asd(instru.physics_t, sc_jitter_thetas[sc], label=f'Theta {sc}')
        plt.ylabel('ASD [rad/s/sqrt(Hz)]')
        plt.title('Spacecraft angular jitter noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plt.figure(figsize=(16, 8))
        for mosa in instru.MOSAS:
            plot_asd(instru.physics_t, mosa_jitter_phis[mosa], label=f'Phi {mosa}')
            plot_asd(instru.physics_t, instru.mosa_jitter_etas[mosa], label=f'Eta {mosa}')
        plt.ylabel('ASD [rad/s/sqrt(Hz)]')
        plt.title('MOSA angular jitter noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plot_measurements(instru, pdf)
        write_metadata('jitter-noise', instru.orbit_file, pdf)
    copy_final_measurements('jitter-noise.h5', 'jitter-noise_temp.h5')


def dws():
    """DWS-noise simulation."""

    logging.info("Running DWS-noise simulation")

    instru = Instrument(
        size=SIZE,
        orbits='tests/esa-trailing-orbits-2-0.h5',
        concurrent=True,
    )
    instru.laser_asds = ForEachMOSA(0)
    instru.modulation_asds = ForEachMOSA(0)
    instru.disable_clock_noises()
    instru.disable_ranging_noises()
    instru.disable_pathlength_noises()
    instru.disable_jitters()
    instru.sync_asds = ForEachSC(0)
    instru.write('dws-noise.h5', mode='w')

    with PdfPages('dws-noise.pdf') as pdf:

        plt.figure(figsize=(16, 8))
        for mosa in instru.MOSAS:
            plot_tseries(instru.physics_t, instru.dws_phi_noises[mosa], label=f'Phi {mosa}')
            plot_tseries(instru.physics_t, instru.dws_eta_noises[mosa], label=f'Eta {mosa}')
        plt.ylabel('Angular velocity [rad/s]')
        plt.title('DWS measurement noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plt.figure(figsize=(16, 8))
        for mosa in instru.MOSAS:
            plot_asd(instru.physics_t, instru.dws_phi_noises[mosa], label=f'Phi {mosa}')
            plot_asd(instru.physics_t, instru.dws_eta_noises[mosa], label=f'Eta {mosa}')
        plt.ylabel('ASD [rad/s/sqrt(Hz)]')
        plt.title('DWS measurement noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plot_measurements(instru, pdf)
        write_metadata('dws-noise', instru.orbit_file, pdf)


def moc_time_correlation():
    """MOC time correlation-noise simulation."""

    logging.info("Running MOC time correlation-noise simulation")

    instru = Instrument(
        size=SIZE,
        orbits='tests/esa-trailing-orbits-2-0.h5',
        concurrent=True,
    )
    instru.disable_all_noises(but='moc-time-correlation')
    instru.write('moc-time-correlation-noise.h5', mode='w')

    with PdfPages('moc-time-correlation-noise.pdf') as pdf:
        with File('moc-time-correlation-noise.h5', 'r') as hdf5:
            moc_time_correlations = hdf5['moc_time_correlations'][()]

        plt.figure(figsize=(16, 8))
        for sc in instru.SCS:
            plot_tseries(instru.telemetry_t, moc_time_correlations[sc], label=sc)
        plt.ylabel('Time [s]')
        plt.title('MOC time correlation noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plt.figure(figsize=(16, 8))
        for sc in instru.SCS:
            plot_asd(instru.telemetry_t, moc_time_correlations[sc], label=sc)
        plt.ylabel('ASD [s/sqrt(Hz)]')
        plt.title('MOC time correlation noise')
        plt.legend()
        pdf.savefig()
        plt.close()

        plot_measurements(instru, pdf)
        write_metadata('moc-time-correlation-noise', instru.orbit_file, pdf)


def plot_measurements(instru, pdf):
    """Generate plots for all measurements.

    Args:
        instru (Instrument): instrument instance
        pdf (PdfPages): PDF file instance
    """

    # Retrieve data from output file to plot them
    with File(instru.filename, 'r') as hdf5:
        isi_carriers = hdf5['isi_carriers'][()]
        isi_carrier_offsets = hdf5['isi_carrier_offsets'][()]
        tmi_carriers = hdf5['tmi_carriers'][()]
        tmi_carrier_offsets = hdf5['tmi_carrier_offsets'][()]
        rfi_carriers = hdf5['rfi_carriers'][()]
        rfi_carrier_offsets = hdf5['rfi_carrier_offsets'][()]
        isi_usbs = hdf5['isi_usbs'][()]
        isi_usb_offsets = hdf5['isi_usb_offsets'][()]
        tmi_usbs = hdf5['tmi_usbs'][()]
        tmi_usb_offsets = hdf5['tmi_usb_offsets'][()]
        rfi_usbs = hdf5['rfi_usbs'][()]
        rfi_usb_offsets = hdf5['rfi_usb_offsets'][()]
        isi_carrier_fluctuations = hdf5['isi_carrier_fluctuations'][()]
        tmi_carrier_fluctuations = hdf5['tmi_carrier_fluctuations'][()]
        rfi_carrier_fluctuations = hdf5['rfi_carrier_fluctuations'][()]
        isi_usb_fluctuations = hdf5['isi_usb_fluctuations'][()]
        tmi_usb_fluctuations = hdf5['tmi_usb_fluctuations'][()]
        rfi_usb_fluctuations = hdf5['rfi_usb_fluctuations'][()]
        mprs = hdf5['mprs'][()]
        isi_dws_phis = hdf5['isi_dws_phis'][()]
        isi_dws_etas = hdf5['isi_dws_etas'][()]
        moc_time_correlations = hdf5['moc_time_correlations'][()]

    # Time-domain ISI carrier total and offset beatnote frequencies
    plt.figure(figsize=(16, 8))
    for mosa in instru.MOSAS:
        plot_tseries(instru.t, isi_carriers[mosa], label=f'{mosa} (total)')
    for mosa in instru.MOSAS:
        plot_tseries(instru.t, isi_carrier_offsets[mosa], linestyle='--', label=f'{mosa} (offsets)')
    plt.ylabel('Frequency [Hz]')
    plt.title('Total ISI carrier beatnote frequencies')
    plt.legend()
    pdf.savefig()
    plt.close()

    # Time-domain TMI carrier total and offset beatnote frequencies
    plt.figure(figsize=(16, 8))
    for mosa in instru.MOSAS:
        plot_tseries(instru.t, tmi_carriers[mosa], label=f'{mosa} (total)')
    for mosa in instru.MOSAS:
        plot_tseries(instru.t, tmi_carrier_offsets[mosa], linestyle='--', label=f'{mosa} (offsets)')
    plt.ylabel('Frequency [Hz]')
    plt.title('Total TMI carrier beatnote frequencies')
    plt.legend()
    pdf.savefig()
    plt.close()

    # Time-domain RFI carrier total and offset beatnote frequencies
    plt.figure(figsize=(16, 8))
    for mosa in instru.MOSAS:
        plot_tseries(instru.t, rfi_carriers[mosa], label=f'{mosa} (total)')
    for mosa in instru.MOSAS:
        plot_tseries(instru.t, rfi_carrier_offsets[mosa], linestyle='--', label=f'{mosa} (offsets)')
    plt.ylabel('Frequency [Hz]')
    plt.title('Total RFI carrier beatnote frequencies')
    plt.legend()
    pdf.savefig()
    plt.close()

    # Time-domain ISI upper-sideband total and offset beatnote frequencies
    plt.figure(figsize=(16, 8))
    for mosa in instru.MOSAS:
        plot_tseries(instru.t, isi_usbs[mosa], label=f'{mosa} (total)')
    for mosa in instru.MOSAS:
        plot_tseries(instru.t, isi_usb_offsets[mosa], linestyle='--', label=f'{mosa} (offsets)')
    plt.ylabel('Frequency [Hz]')
    plt.title('Total ISI upper-sideband beatnote frequencies')
    plt.legend()
    pdf.savefig()
    plt.close()

    # Time-domain TMI upper-sideband total and offset beatnote frequencies
    plt.figure(figsize=(16, 8))
    for mosa in instru.MOSAS:
        plot_tseries(instru.t, tmi_usbs[mosa], label=f'{mosa} (total)')
    for mosa in instru.MOSAS:
        plot_tseries(instru.t, tmi_usb_offsets[mosa], linestyle='--', label=f'{mosa} (offsets)')
    plt.ylabel('Frequency [Hz]')
    plt.title('Total TMI upper-sideband beatnote frequencies')
    plt.legend()
    pdf.savefig()
    plt.close()

    # Time-domain RFI upper-sideband total and offset beatnote frequencies
    plt.figure(figsize=(16, 8))
    for mosa in instru.MOSAS:
        plot_tseries(instru.t, rfi_usbs[mosa], label=f'{mosa} (total)')
    for mosa in instru.MOSAS:
        plot_tseries(instru.t, rfi_usb_offsets[mosa], linestyle='--', label=f'{mosa} (offsets)')
    plt.ylabel('Frequency [Hz]')
    plt.title('Total RFI upper-sideband beatnote frequencies')
    plt.legend()
    pdf.savefig()
    plt.close()

    # ASD ISI carrier beatnote frequency fluctuations
    plt.figure(figsize=(16, 8))
    for mosa in instru.MOSAS:
        plot_asd(instru.t, isi_carrier_fluctuations[mosa], label=mosa)
    plt.ylabel('ASD [Hz/sqrt(Hz)]')
    plt.title('ISI carrier beatnote frequency fluctuations')
    plt.legend()
    pdf.savefig()
    plt.close()

    # ASD TMI carrier beatnote frequency fluctuations
    plt.figure(figsize=(16, 8))
    for mosa in instru.MOSAS:
        plot_asd(instru.t, tmi_carrier_fluctuations[mosa], label=mosa)
    plt.ylabel('ASD [Hz/sqrt(Hz)]')
    plt.title('TMI carrier beatnote frequency fluctuations')
    plt.legend()
    pdf.savefig()
    plt.close()

    # ASD RFI carrier beatnote frequency fluctuations
    plt.figure(figsize=(16, 8))
    for mosa in instru.MOSAS:
        plot_asd(instru.t, rfi_carrier_fluctuations[mosa], label=mosa)
    plt.ylabel('ASD [Hz/sqrt(Hz)]')
    plt.title('RFI carrier beatnote frequency fluctuations')
    plt.legend()
    pdf.savefig()
    plt.close()

    # ASD ISI upper-sideband beatnote frequency fluctuations
    plt.figure(figsize=(16, 8))
    for mosa in instru.MOSAS:
        plot_asd(instru.t, isi_usb_fluctuations[mosa], label=mosa)
    plt.ylabel('ASD [Hz/sqrt(Hz)]')
    plt.title('ISI upper-sideband beatnote frequency fluctuations')
    plt.legend()
    pdf.savefig()
    plt.close()

    # ASD TMI upper-sideband beatnote frequency fluctuations
    plt.figure(figsize=(16, 8))
    for mosa in instru.MOSAS:
        plot_asd(instru.t, tmi_usb_fluctuations[mosa], label=mosa)
    plt.ylabel('ASD [Hz/sqrt(Hz)]')
    plt.title('TMI upper-sideband beatnote frequency fluctuations')
    plt.legend()
    pdf.savefig()
    plt.close()

    # ASD RFI upper-sideband beatnote frequency fluctuations
    plt.figure(figsize=(16, 8))
    for mosa in instru.MOSAS:
        plot_asd(instru.t, rfi_usb_fluctuations[mosa], label=mosa)
    plt.ylabel('ASD [Hz/sqrt(Hz)]')
    plt.title('RFI upper-sideband beatnote frequency fluctuations')
    plt.legend()
    pdf.savefig()
    plt.close()

    # Time-domain MPR measurements
    plt.figure(figsize=(16, 8))
    for mosa in instru.MOSAS:
        plot_tseries(instru.t, mprs[mosa], label=mosa)
    plt.ylabel('MPR [s]')
    plt.title('Measured pseudo-ranges (MPRs)')
    plt.legend()
    pdf.savefig()
    plt.close()

    # ASD MPR measurements
    plt.figure(figsize=(16, 8))
    for mosa in instru.MOSAS:
        plot_asd(instru.t, mprs[mosa], detrend='linear', label=mosa)
    plt.ylabel('ASD [s/sqrt(Hz)]')
    plt.title('Measured pseudo-ranges (MPRs)')
    plt.legend()
    pdf.savefig()
    plt.close()

    # ASD DWS measurements
    plt.figure(figsize=(16, 8))
    for mosa in instru.MOSAS:
        plot_asd(instru.t, isi_dws_phis[mosa], label=f'DWS Phi {mosa}')
    for mosa in instru.MOSAS:
        plot_asd(instru.t, isi_dws_etas[mosa], label=f'DWS Eta {mosa}')
    plt.ylabel('ASD [rad/s/sqrt(Hz)]')
    plt.title('Differential wavefront sensor (DWS) measurements')
    plt.legend()
    pdf.savefig()
    plt.close()

    # Time-domain MOC time correlations
    plt.figure(figsize=(16, 8))
    for sc in instru.SCS:
        plot_tseries(instru.telemetry_t, moc_time_correlations[sc], skip=0, label=sc)
    plt.ylabel('Deviation [s]')
    plt.title('MOC time correlations')
    plt.legend()
    pdf.savefig()
    plt.close()


def plot_tseries(x, y, skip=100, **kwargs):
    """Plot time series."""

    if 'alpha' not in kwargs:
        kwargs['alpha'] = 0.7

    x, y = np.broadcast_arrays(x, y)
    plt.plot(x[skip:], y[skip:], **kwargs)
    plt.xlabel('Time [s]')
    plt.grid(True)


def plot_asd(x, y, detrend=None, **kwargs):
    """Plot amplitude spectral density."""

    if 'alpha' not in kwargs:
        kwargs['alpha'] = 0.7

    x, y = np.broadcast_arrays(x, y)
    freq, psd = welch(
        y[300:],
        fs=1.0 / (x[1] - x[0]),
        nperseg=NPERSEG,
        window=('kaiser', 40),
        detrend=detrend)

    plt.loglog(freq, np.sqrt(psd), **kwargs)
    plt.xlabel('Frequency [Hz]')
    plt.grid(True)


def write_metadata(config, orbits, pdf):
    """Write metadata.

    Args:
        config (str): description of instrument configuration
        orbits (str): description of orbit file
        pdf(PdfPages): PDF file instance
    """
    info = pdf.infodict()
    info['Title'] = 'LISA Instrument Report'
    info['Author'] = 'LISA Instrument Continuous Integration'
    info['InstrumentConfiguration'] = config
    info['OrbitFile'] = orbits
    info['Version'] = __version__
    info['CreationDate'] = datetime.datetime.now()
    info['ModDate'] = datetime.datetime.now()

def copy_final_measurements(filename, temp_filename):
    """Copy all the final measurements from a HDF5 file to another."""
    final_measurements = ['isi_carriers', 'isi_carrier_offsets', 'tmi_carriers', 'tmi_carrier_offsets',\
                         'rfi_carriers', 'rfi_carrier_offsets', 'isi_usbs', 'isi_usb_offsets',\
                         'tmi_usbs', 'tmi_usb_offsets', 'rfi_usbs', 'rfi_usb_offsets',\
                         'isi_carrier_fluctuations', 'tmi_carrier_fluctuations', 'rfi_carrier_fluctuations',\
                         'isi_usb_fluctuations', 'tmi_usb_fluctuations', 'rfi_usb_fluctuations',\
                         'mprs', 'isi_dws_phis', 'isi_dws_etas', 'moc_time_correlations']
    with File(filename, 'w') as hdf5:
        with File(temp_filename, 'r') as temp_hdf5:
            for name in final_measurements:
                temp_hdf5.copy(name, hdf5)
        os.remove(temp_filename)


if __name__ == "__main__":
    main()