diff --git a/docs/build/doctrees/appendix.doctree b/docs/build/doctrees/appendix.doctree new file mode 100644 index 00000000..5411ce90 Binary files /dev/null and b/docs/build/doctrees/appendix.doctree differ diff --git a/docs/build/doctrees/bibliography.doctree b/docs/build/doctrees/bibliography.doctree new file mode 100644 index 00000000..296e429b Binary files /dev/null and b/docs/build/doctrees/bibliography.doctree differ diff --git a/docs/build/doctrees/data_layout.doctree b/docs/build/doctrees/data_layout.doctree new file mode 100644 index 00000000..c81d47e3 Binary files /dev/null and b/docs/build/doctrees/data_layout.doctree differ diff --git a/docs/build/doctrees/detectors.doctree b/docs/build/doctrees/detectors.doctree new file mode 100644 index 00000000..b7b730f4 Binary files /dev/null and b/docs/build/doctrees/detectors.doctree differ diff --git a/docs/build/doctrees/dipole.doctree b/docs/build/doctrees/dipole.doctree new file mode 100644 index 00000000..ceee75f5 Binary files /dev/null and b/docs/build/doctrees/dipole.doctree differ diff --git a/docs/build/doctrees/environment.pickle b/docs/build/doctrees/environment.pickle new file mode 100644 index 00000000..01d14d14 Binary files /dev/null and b/docs/build/doctrees/environment.pickle differ diff --git a/docs/build/doctrees/gaindrifts.doctree b/docs/build/doctrees/gaindrifts.doctree new file mode 100644 index 00000000..8702575a Binary files /dev/null and b/docs/build/doctrees/gaindrifts.doctree differ diff --git a/docs/build/doctrees/hwp.doctree b/docs/build/doctrees/hwp.doctree new file mode 100644 index 00000000..96616f01 Binary files /dev/null and b/docs/build/doctrees/hwp.doctree differ diff --git a/docs/build/doctrees/hwp_sys.doctree b/docs/build/doctrees/hwp_sys.doctree new file mode 100644 index 00000000..809819a9 Binary files /dev/null and b/docs/build/doctrees/hwp_sys.doctree differ diff --git a/docs/build/doctrees/imo.doctree b/docs/build/doctrees/imo.doctree new file mode 100644 index 00000000..ade7f802 Binary files /dev/null and b/docs/build/doctrees/imo.doctree differ diff --git a/docs/build/doctrees/index.doctree b/docs/build/doctrees/index.doctree new file mode 100644 index 00000000..7308ae51 Binary files /dev/null and b/docs/build/doctrees/index.doctree differ diff --git a/docs/build/doctrees/installation.doctree b/docs/build/doctrees/installation.doctree new file mode 100644 index 00000000..3034a68b Binary files /dev/null and b/docs/build/doctrees/installation.doctree differ diff --git a/docs/build/doctrees/integrating.doctree b/docs/build/doctrees/integrating.doctree new file mode 100644 index 00000000..4b54fb60 Binary files /dev/null and b/docs/build/doctrees/integrating.doctree differ diff --git a/docs/build/doctrees/map_scanning.doctree b/docs/build/doctrees/map_scanning.doctree new file mode 100644 index 00000000..5e93bd2b Binary files /dev/null and b/docs/build/doctrees/map_scanning.doctree differ diff --git a/docs/build/doctrees/mapmaking.doctree b/docs/build/doctrees/mapmaking.doctree new file mode 100644 index 00000000..643ebfb1 Binary files /dev/null and b/docs/build/doctrees/mapmaking.doctree differ diff --git a/docs/build/doctrees/mpi.doctree b/docs/build/doctrees/mpi.doctree new file mode 100644 index 00000000..131595d2 Binary files /dev/null and b/docs/build/doctrees/mpi.doctree differ diff --git a/docs/build/doctrees/noise.doctree b/docs/build/doctrees/noise.doctree new file mode 100644 index 00000000..dd00ed20 Binary files /dev/null and b/docs/build/doctrees/noise.doctree differ diff --git a/docs/build/doctrees/non_linearity.doctree b/docs/build/doctrees/non_linearity.doctree new file mode 100644 index 00000000..b6c0393d Binary files /dev/null and b/docs/build/doctrees/non_linearity.doctree differ diff --git a/docs/build/doctrees/observations.doctree b/docs/build/doctrees/observations.doctree new file mode 100644 index 00000000..387d2d8a Binary files /dev/null and b/docs/build/doctrees/observations.doctree differ diff --git a/docs/build/doctrees/part1.doctree b/docs/build/doctrees/part1.doctree new file mode 100644 index 00000000..18ea4bac Binary files /dev/null and b/docs/build/doctrees/part1.doctree differ diff --git a/docs/build/doctrees/part2.doctree b/docs/build/doctrees/part2.doctree new file mode 100644 index 00000000..b0f1031c Binary files /dev/null and b/docs/build/doctrees/part2.doctree differ diff --git a/docs/build/doctrees/part3.doctree b/docs/build/doctrees/part3.doctree new file mode 100644 index 00000000..58ed24f0 Binary files /dev/null and b/docs/build/doctrees/part3.doctree differ diff --git a/docs/build/doctrees/part4.doctree b/docs/build/doctrees/part4.doctree new file mode 100644 index 00000000..11036305 Binary files /dev/null and b/docs/build/doctrees/part4.doctree differ diff --git a/docs/build/doctrees/part5.doctree b/docs/build/doctrees/part5.doctree new file mode 100644 index 00000000..2c9a517e Binary files /dev/null and b/docs/build/doctrees/part5.doctree differ diff --git a/docs/build/doctrees/part6.doctree b/docs/build/doctrees/part6.doctree new file mode 100644 index 00000000..c84442a6 Binary files /dev/null and b/docs/build/doctrees/part6.doctree differ diff --git a/docs/build/doctrees/plot_fp.doctree b/docs/build/doctrees/plot_fp.doctree new file mode 100644 index 00000000..de1e1fab Binary files /dev/null and b/docs/build/doctrees/plot_fp.doctree differ diff --git a/docs/build/doctrees/pointing_sys.doctree b/docs/build/doctrees/pointing_sys.doctree new file mode 100644 index 00000000..3bf0556b Binary files /dev/null and b/docs/build/doctrees/pointing_sys.doctree differ diff --git a/docs/build/doctrees/profiling.doctree b/docs/build/doctrees/profiling.doctree new file mode 100644 index 00000000..4d03aa57 Binary files /dev/null and b/docs/build/doctrees/profiling.doctree differ diff --git a/docs/build/doctrees/quaternions.doctree b/docs/build/doctrees/quaternions.doctree new file mode 100644 index 00000000..7532c9e6 Binary files /dev/null and b/docs/build/doctrees/quaternions.doctree differ diff --git a/docs/build/doctrees/random_numbers.doctree b/docs/build/doctrees/random_numbers.doctree new file mode 100644 index 00000000..559ab9a1 Binary files /dev/null and b/docs/build/doctrees/random_numbers.doctree differ diff --git a/docs/build/doctrees/reports.doctree b/docs/build/doctrees/reports.doctree new file mode 100644 index 00000000..794c0d3d Binary files /dev/null and b/docs/build/doctrees/reports.doctree differ diff --git a/docs/build/doctrees/scanning.doctree b/docs/build/doctrees/scanning.doctree new file mode 100644 index 00000000..22622b2d Binary files /dev/null and b/docs/build/doctrees/scanning.doctree differ diff --git a/docs/build/doctrees/simulations.doctree b/docs/build/doctrees/simulations.doctree new file mode 100644 index 00000000..7a27c4c3 Binary files /dev/null and b/docs/build/doctrees/simulations.doctree differ diff --git a/docs/build/doctrees/singularity.doctree b/docs/build/doctrees/singularity.doctree new file mode 100644 index 00000000..0289cf6c Binary files /dev/null and b/docs/build/doctrees/singularity.doctree differ diff --git a/docs/build/doctrees/sky_maps.doctree b/docs/build/doctrees/sky_maps.doctree new file mode 100644 index 00000000..0da952f0 Binary files /dev/null and b/docs/build/doctrees/sky_maps.doctree differ diff --git a/docs/build/doctrees/tutorial.doctree b/docs/build/doctrees/tutorial.doctree new file mode 100644 index 00000000..e56189af Binary files /dev/null and b/docs/build/doctrees/tutorial.doctree differ diff --git a/docs/build/html/.buildinfo b/docs/build/html/.buildinfo new file mode 100644 index 00000000..675599f1 --- /dev/null +++ b/docs/build/html/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 4c494d747a2a3309a821e12e38277420 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/build/html/_downloads/0c513417754328995f29c097b59265ce/thermalgain_demo.png b/docs/build/html/_downloads/0c513417754328995f29c097b59265ce/thermalgain_demo.png new file mode 100644 index 00000000..53b38998 Binary files /dev/null and b/docs/build/html/_downloads/0c513417754328995f29c097b59265ce/thermalgain_demo.png differ diff --git a/docs/build/html/_downloads/10c1d16889c045ddb83df88beba2aa51/thermalgain_demo.hires.png b/docs/build/html/_downloads/10c1d16889c045ddb83df88beba2aa51/thermalgain_demo.hires.png new file mode 100644 index 00000000..d2a239d1 Binary files /dev/null and b/docs/build/html/_downloads/10c1d16889c045ddb83df88beba2aa51/thermalgain_demo.hires.png differ diff --git a/docs/build/html/_downloads/1a23db2426a59e7579e865ba811a62e2/spacecraft_demo.py b/docs/build/html/_downloads/1a23db2426a59e7579e865ba811a62e2/spacecraft_demo.py new file mode 100644 index 00000000..85488f18 --- /dev/null +++ b/docs/build/html/_downloads/1a23db2426a59e7579e865ba811a62e2/spacecraft_demo.py @@ -0,0 +1,33 @@ +import numpy as np +import litebird_sim as lbs +from astropy.time import Time +import matplotlib.pylab as plt + +orbit = lbs.SpacecraftOrbit(start_time=Time("2023-01-01")) + +posvel = lbs.spacecraft_pos_and_vel( + orbit, + start_time=orbit.start_time, + time_span_s=3.15e7, # One year + delta_time_s=86_400.0, # One day +) + +# posvel.positions_km is a N×3 array containing the XYZ position +# of the spacecraft calculated every day for one year. We compute +# the modulus of the position, which is of course the +# Sun-LiteBIRD distance. +sun_distance_km = np.linalg.norm(posvel.positions_km, axis=1) + +# We do the same with the velocities +speed_km_s = np.linalg.norm(posvel.velocities_km_s, axis=1) + +# Plot distance and speed as functions of time +_, ax = plt.subplots(nrows=2, ncols=1, figsize=(7, 7)) + +ax[0].plot(sun_distance_km) +ax[0].set_xlabel("Day") +ax[0].set_ylabel("Distance [km]") + +ax[1].plot(speed_km_s) +ax[1].set_xlabel("Day") +ax[1].set_ylabel("Speed [km/s]") diff --git a/docs/build/html/_downloads/1d737c7888c940f957b5e5b7f9b6c376/dipole_demo.hires.png b/docs/build/html/_downloads/1d737c7888c940f957b5e5b7f9b6c376/dipole_demo.hires.png new file mode 100644 index 00000000..74ac8e57 Binary files /dev/null and b/docs/build/html/_downloads/1d737c7888c940f957b5e5b7f9b6c376/dipole_demo.hires.png differ diff --git a/docs/build/html/_downloads/24922bc47ca45a6c04ef342c5a856d8b/dipole_demo.pdf b/docs/build/html/_downloads/24922bc47ca45a6c04ef342c5a856d8b/dipole_demo.pdf new file mode 100644 index 00000000..ad131330 Binary files /dev/null and b/docs/build/html/_downloads/24922bc47ca45a6c04ef342c5a856d8b/dipole_demo.pdf differ diff --git a/docs/build/html/_downloads/2c527c73c72e71d690b18bccd1971129/thermalgain_demo.py b/docs/build/html/_downloads/2c527c73c72e71d690b18bccd1971129/thermalgain_demo.py new file mode 100644 index 00000000..1c6f4fd8 --- /dev/null +++ b/docs/build/html/_downloads/2c527c73c72e71d690b18bccd1971129/thermalgain_demo.py @@ -0,0 +1,96 @@ +import numpy as np +import litebird_sim as lbs +from astropy.time import Time +import matplotlib.pyplot as plt + +start_time = Time("2034-05-02") +duration_s = 100 +sampling_freq_Hz = 1 + +# Creating a list of detectors. The three detectors belong to two +# different wafer groups. +dets = [ + lbs.DetectorInfo( + name="det_A_wafer_1", sampling_rate_hz=sampling_freq_Hz, wafer="wafer_1" + ), + lbs.DetectorInfo( + name="det_B_wafer_1", sampling_rate_hz=sampling_freq_Hz, wafer="wafer_1" + ), + lbs.DetectorInfo( + name="det_C_wafer_2", sampling_rate_hz=sampling_freq_Hz, wafer="wafer_2" + ), +] + +# Defining the gain drift simulation parameters with no detector mismatch +drift_params_no_mismatch = lbs.GainDriftParams( + drift_type=lbs.GainDriftType.THERMAL_GAIN, + focalplane_group="wafer", + detector_mismatch=0.0, +) + +# Defining the gain drift simulation parameters with detector mismatch +drift_params_with_mismatch = lbs.GainDriftParams( + drift_type=lbs.GainDriftType.THERMAL_GAIN, + focalplane_group="wafer", + detector_mismatch=1.0, +) + +sim1 = lbs.Simulation( + start_time=start_time, + duration_s=duration_s, + random_seed=12345, +) + +sim1.create_observations( + detectors=dets, + split_list_over_processes=False, + num_of_obs_per_detector=1, +) + +sim1.observations[0].thermalgain_tod_no_mismatch = np.ones_like( + sim1.observations[0].tod +) +sim1.observations[0].thermalgain_tod_with_mismatch = np.ones_like( + sim1.observations[0].tod +) + +# Generating the gain drift factors with no detector mismatch +sim1.apply_gaindrift( + drift_params=drift_params_no_mismatch, + component="thermalgain_tod_no_mismatch", + user_seed=12345, +) + +# Generating the gain drift factors with detector mismatch +sim1.apply_gaindrift( + drift_params=drift_params_with_mismatch, + component="thermalgain_tod_with_mismatch", + user_seed=12345, +) + +plt.figure(figsize=(8, 10)) + +plt.subplot(211) +for idx in range(sim1.observations[0].tod.shape[0]): + plt.plot( + sim1.observations[0].thermalgain_tod_no_mismatch[idx], + label=sim1.observations[0].name[idx], + ) + +plt.xlabel("Time (in seconds)") +plt.ylabel("Linear gain factor amplitude") +plt.title("Thermal gain drift factor with no detector mismatch") +plt.legend() + +plt.subplot(212) +for idx in range(sim1.observations[0].tod.shape[0]): + plt.plot( + sim1.observations[0].thermalgain_tod_with_mismatch[idx], + label=sim1.observations[0].name[idx], + ) + +plt.xlabel("Time (in seconds)") +plt.ylabel("Linear gain factor amplitude") +plt.title("Thermal gain drift factor with detector mismatch") +plt.legend() +plt.tight_layout() diff --git a/docs/build/html/_downloads/3c7f580fe11f6ae3b6b2537ebb1e5707/bandpass_demo.png b/docs/build/html/_downloads/3c7f580fe11f6ae3b6b2537ebb1e5707/bandpass_demo.png new file mode 100644 index 00000000..98ec82db Binary files /dev/null and b/docs/build/html/_downloads/3c7f580fe11f6ae3b6b2537ebb1e5707/bandpass_demo.png differ diff --git a/docs/build/html/_downloads/4cd1b70cc4ca892052d924dadee24f11/bandpass_demo.pdf b/docs/build/html/_downloads/4cd1b70cc4ca892052d924dadee24f11/bandpass_demo.pdf new file mode 100644 index 00000000..8e8b7205 Binary files /dev/null and b/docs/build/html/_downloads/4cd1b70cc4ca892052d924dadee24f11/bandpass_demo.pdf differ diff --git a/docs/build/html/_downloads/57cb5d5e5a03f83dcbbc60b5819d0a7a/bandpass_demo.py b/docs/build/html/_downloads/57cb5d5e5a03f83dcbbc60b5819d0a7a/bandpass_demo.py new file mode 100644 index 00000000..e00e126a --- /dev/null +++ b/docs/build/html/_downloads/57cb5d5e5a03f83dcbbc60b5819d0a7a/bandpass_demo.py @@ -0,0 +1,24 @@ +import litebird_sim as lbs +import matplotlib.pylab as plt + +# Generate the «model» (i.e., ideal band, with no wiggles) +band = lbs.BandPassInfo( + bandcenter_ghz=43.0, + bandwidth_ghz=10.0, + bandtype="top-hat-cosine", + normalize=True, + nsamples_inband=100, +) +plt.plot(band.freqs_ghz, band.weights, label="Ideal band") + +# Now generate a more realistic band with random wiggles +new_weights = band.bandpass_resampling() +plt.plot( + band.freqs_ghz, + new_weights, + label="Random realization", +) + +plt.xlabel("Frequency [GHz]") +plt.ylabel("Weight") +plt.legend() diff --git a/docs/build/html/_downloads/7c01310eac947c15d269f4ed7100811c/lingain_demo.pdf b/docs/build/html/_downloads/7c01310eac947c15d269f4ed7100811c/lingain_demo.pdf new file mode 100644 index 00000000..c4be721d Binary files /dev/null and b/docs/build/html/_downloads/7c01310eac947c15d269f4ed7100811c/lingain_demo.pdf differ diff --git a/docs/build/html/_downloads/8f658199a3d3cac5ea13995d60bf5bf5/lingain_demo.hires.png b/docs/build/html/_downloads/8f658199a3d3cac5ea13995d60bf5bf5/lingain_demo.hires.png new file mode 100644 index 00000000..3e21325c Binary files /dev/null and b/docs/build/html/_downloads/8f658199a3d3cac5ea13995d60bf5bf5/lingain_demo.hires.png differ diff --git a/docs/build/html/_downloads/96d1d92116c317651ba8d0c45fe2859c/lingain_demo.py b/docs/build/html/_downloads/96d1d92116c317651ba8d0c45fe2859c/lingain_demo.py new file mode 100644 index 00000000..0f6b63d8 --- /dev/null +++ b/docs/build/html/_downloads/96d1d92116c317651ba8d0c45fe2859c/lingain_demo.py @@ -0,0 +1,61 @@ +import numpy as np +import litebird_sim as lbs +from astropy.time import Time +import matplotlib.pyplot as plt + +start_time = Time("2034-05-02") +duration_s = 4 * 24 * 3600 +sampling_freq_Hz = 1 + +# Creating a list of detectors. +dets = [ + lbs.DetectorInfo( + name="det_A_wafer_1", sampling_rate_hz=sampling_freq_Hz, wafer="wafer_1" + ), + lbs.DetectorInfo( + name="det_B_wafer_1", sampling_rate_hz=sampling_freq_Hz, wafer="wafer_1" + ), +] + +# Defining the gain drift simulation parameters +drift_params = lbs.GainDriftParams( + drift_type=lbs.GainDriftType.LINEAR_GAIN, + calibration_period_sec=24 * 3600, +) + +sim1 = lbs.Simulation( + start_time=start_time, + duration_s=duration_s, + random_seed=12345, +) + +sim1.create_observations( + detectors=dets, + split_list_over_processes=False, + num_of_obs_per_detector=1, +) + +sim1.observations[0].lingain_tod = np.ones_like(sim1.observations[0].tod) + +# Applying gain drift using the `Simulation` class method +sim1.apply_gaindrift( + drift_params=drift_params, + component="lingain_tod", +) + +plt.figure(figsize=(8, 5)) + +time_domain = ( + np.arange(sim1.observations[0].tod.shape[1]) / sampling_freq_Hz / 24 / 3600 +) + +for idx in range(sim1.observations[0].tod.shape[0]): + plt.plot( + time_domain, + sim1.observations[0].lingain_tod[idx], + label=sim1.observations[0].name[idx], + ) + +plt.xlabel("Time (in days)") +plt.ylabel("Linear gain factor amplitude") +plt.legend() diff --git a/docs/build/html/_downloads/97eedd704b5c69d06e0c8099efc36685/bandpass_demo.hires.png b/docs/build/html/_downloads/97eedd704b5c69d06e0c8099efc36685/bandpass_demo.hires.png new file mode 100644 index 00000000..a490ee7f Binary files /dev/null and b/docs/build/html/_downloads/97eedd704b5c69d06e0c8099efc36685/bandpass_demo.hires.png differ diff --git a/docs/build/html/_downloads/b003556039a3163cc1458edbb410683a/thermalgain_demo.pdf b/docs/build/html/_downloads/b003556039a3163cc1458edbb410683a/thermalgain_demo.pdf new file mode 100644 index 00000000..9db02451 Binary files /dev/null and b/docs/build/html/_downloads/b003556039a3163cc1458edbb410683a/thermalgain_demo.pdf differ diff --git a/docs/build/html/_downloads/b1ff4303533b13878146528f9ad738d1/lingain_demo.png b/docs/build/html/_downloads/b1ff4303533b13878146528f9ad738d1/lingain_demo.png new file mode 100644 index 00000000..880fa516 Binary files /dev/null and b/docs/build/html/_downloads/b1ff4303533b13878146528f9ad738d1/lingain_demo.png differ diff --git a/docs/build/html/_downloads/bdc082ba2af4303a964695b790258f9c/spacecraft_demo.pdf b/docs/build/html/_downloads/bdc082ba2af4303a964695b790258f9c/spacecraft_demo.pdf new file mode 100644 index 00000000..adabcd2d Binary files /dev/null and b/docs/build/html/_downloads/bdc082ba2af4303a964695b790258f9c/spacecraft_demo.pdf differ diff --git a/docs/build/html/_downloads/c801375eacf27ac75efaedee3f62e2c2/spacecraft_demo.png b/docs/build/html/_downloads/c801375eacf27ac75efaedee3f62e2c2/spacecraft_demo.png new file mode 100644 index 00000000..ec97692b Binary files /dev/null and b/docs/build/html/_downloads/c801375eacf27ac75efaedee3f62e2c2/spacecraft_demo.png differ diff --git a/docs/build/html/_downloads/db9d2aed004b9cdb4001394d56adf8c2/dipole_demo.py b/docs/build/html/_downloads/db9d2aed004b9cdb4001394d56adf8c2/dipole_demo.py new file mode 100644 index 00000000..c9392613 --- /dev/null +++ b/docs/build/html/_downloads/db9d2aed004b9cdb4001394d56adf8c2/dipole_demo.py @@ -0,0 +1,69 @@ +from astropy.time import Time +import numpy as np +import litebird_sim as lbs +import matplotlib.pylab as plt + +start_time = Time("2022-01-01") +time_span_s = 120.0 # Two minutes +sampling_hz = 20 + +sim = lbs.Simulation(start_time=start_time, duration_s=time_span_s, random_seed=12345) + +# We pick a simple scanning strategy where the spin axis is aligned +# with the Sun-Earth axis, and the spacecraft spins once every minute +sim.set_scanning_strategy( + lbs.SpinningScanningStrategy( + spin_sun_angle_rad=np.deg2rad(0), + precession_rate_hz=0, + spin_rate_hz=1 / 60, + start_time=start_time, + ), + delta_time_s=5.0, +) + +# We simulate an instrument whose boresight is perpendicular to +# the spin axis. +sim.set_instrument( + lbs.InstrumentInfo( + boresight_rotangle_rad=0.0, + spin_boresight_angle_rad=np.deg2rad(90), + spin_rotangle_rad=np.deg2rad(75), + ) +) + +# A simple detector looking along the boresight direction +det = lbs.DetectorInfo( + name="Boresight_detector", + sampling_rate_hz=sampling_hz, + bandcenter_ghz=100.0, +) + +(obs,) = sim.create_observations(detectors=[det]) + +sim.prepare_pointings() + +# Simulate the orbit of the spacecraft and compute positions and +# velocities +orbit = lbs.SpacecraftOrbit(obs.start_time) +pos_vel = lbs.spacecraft_pos_and_vel(orbit, obs, delta_time_s=60.0) + +t = obs.get_times() +t -= t[0] # Make `t` start from zero + +# Create two plots: the first shows the full two-minute time span, and +# the second one shows a zoom over the very first points of the TOD. +_, axes = plt.subplots(nrows=2, ncols=1, figsize=(10, 12)) + +# Make a plot of the TOD using all the dipole types +for type_idx, dipole_type in enumerate(lbs.DipoleType): + obs.tod[0][:] = 0 # Reset the TOD + lbs.add_dipole_to_observations(obs, pos_vel, dipole_type=dipole_type) + + axes[0].plot(t, obs.tod[0], label=str(dipole_type)) + axes[1].plot(t[0:20], obs.tod[0][0:20], label=str(dipole_type)) + +axes[0].set_xlabel("Time [s]") +axes[0].set_ylabel("Signal [K]") +axes[1].set_xlabel("Time [s]") +axes[1].set_ylabel("Signal [K]") +axes[1].legend() diff --git a/docs/build/html/_downloads/f8704f44ef47aebc8d1644b811851eb1/spacecraft_demo.hires.png b/docs/build/html/_downloads/f8704f44ef47aebc8d1644b811851eb1/spacecraft_demo.hires.png new file mode 100644 index 00000000..fdf4662f Binary files /dev/null and b/docs/build/html/_downloads/f8704f44ef47aebc8d1644b811851eb1/spacecraft_demo.hires.png differ diff --git a/docs/build/html/_downloads/fd61824b95cfd15e67ffbfd2efd491a6/dipole_demo.png b/docs/build/html/_downloads/fd61824b95cfd15e67ffbfd2efd491a6/dipole_demo.png new file mode 100644 index 00000000..8b49f23a Binary files /dev/null and b/docs/build/html/_downloads/fd61824b95cfd15e67ffbfd2efd491a6/dipole_demo.png differ diff --git a/docs/build/html/_images/bandpass_demo.png b/docs/build/html/_images/bandpass_demo.png new file mode 100644 index 00000000..98ec82db Binary files /dev/null and b/docs/build/html/_images/bandpass_demo.png differ diff --git a/docs/build/html/_images/coordinate-systems.svg b/docs/build/html/_images/coordinate-systems.svg new file mode 100644 index 00000000..f60580a4 --- /dev/null +++ b/docs/build/html/_images/coordinate-systems.svg @@ -0,0 +1 @@ +xyzpolarizationdirectionxyzboresightdirectionxyzEarth's orbit on theEcliptic planexyzspinaxisDetectorFocal planeSpacecraftEcliptic plane \ No newline at end of file diff --git a/docs/build/html/_images/destriper-baselines-memory-layout.svg b/docs/build/html/_images/destriper-baselines-memory-layout.svg new file mode 100644 index 00000000..b8908f8e --- /dev/null +++ b/docs/build/html/_images/destriper-baselines-memory-layout.svg @@ -0,0 +1,607 @@ + + + + + + + + + + + + + + + + + + + + + a11 + a12 + a13 + a14 + a15 + + + + + + a21 + a22 + a23 + a24 + a25 + + + + a11 + a12 + a13 + + + + a21 + a22 + a23 + , + [ + ] + Observation #1 + Observation #2 + Baseline values + + + + + + + + + + + + + + + + + + + Time + Observation #1 + Observation #2 + Det. A + Det. B + + diff --git a/docs/build/html/_images/destriper-baselines.svg b/docs/build/html/_images/destriper-baselines.svg new file mode 100644 index 00000000..a688bb0e --- /dev/null +++ b/docs/build/html/_images/destriper-baselines.svg @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + First baseline(7 samples) + Second baseline(6 samples) + Sample value + Time + + diff --git a/docs/build/html/_images/destriper-pixel1.svg b/docs/build/html/_images/destriper-pixel1.svg new file mode 100644 index 00000000..8de1c54b --- /dev/null +++ b/docs/build/html/_images/destriper-pixel1.svg @@ -0,0 +1,51 @@ + + + + + + + + + + diff --git a/docs/build/html/_images/destriper-pixel2.svg b/docs/build/html/_images/destriper-pixel2.svg new file mode 100644 index 00000000..14a07db0 --- /dev/null +++ b/docs/build/html/_images/destriper-pixel2.svg @@ -0,0 +1,51 @@ + + + + + + + + + + diff --git a/docs/build/html/_images/destriper-tod-angles.svg b/docs/build/html/_images/destriper-tod-angles.svg new file mode 100644 index 00000000..02c363e3 --- /dev/null +++ b/docs/build/html/_images/destriper-tod-angles.svg @@ -0,0 +1,199 @@ + + + + + + + + First pixel + + + + + + + #1 + #2 + #7 + Second pixel + + + + + + + + #3 + #4 + #5 + #6 + + diff --git a/docs/build/html/_images/dipole_demo.png b/docs/build/html/_images/dipole_demo.png new file mode 100644 index 00000000..8b49f23a Binary files /dev/null and b/docs/build/html/_images/dipole_demo.png differ diff --git a/docs/build/html/_images/hitmap_with_vibration.png b/docs/build/html/_images/hitmap_with_vibration.png new file mode 100644 index 00000000..ba772ecf Binary files /dev/null and b/docs/build/html/_images/hitmap_with_vibration.png differ diff --git a/docs/build/html/_images/jupiter-angular-distance.svg b/docs/build/html/_images/jupiter-angular-distance.svg new file mode 100644 index 00000000..9b604b87 --- /dev/null +++ b/docs/build/html/_images/jupiter-angular-distance.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/build/html/_images/lingain_demo.png b/docs/build/html/_images/lingain_demo.png new file mode 100644 index 00000000..880fa516 Binary files /dev/null and b/docs/build/html/_images/lingain_demo.png differ diff --git a/docs/build/html/_images/litebird-scanning-strategy.svg b/docs/build/html/_images/litebird-scanning-strategy.svg new file mode 100644 index 00000000..8eb6f19c --- /dev/null +++ b/docs/build/html/_images/litebird-scanning-strategy.svg @@ -0,0 +1 @@ +Boresight (LFT)Spin axisSun-Earth axisβαBoresight (MHFT) \ No newline at end of file diff --git a/docs/build/html/_images/litebird_sim_sample_report_docx.png b/docs/build/html/_images/litebird_sim_sample_report_docx.png new file mode 100644 index 00000000..a906a2e4 Binary files /dev/null and b/docs/build/html/_images/litebird_sim_sample_report_docx.png differ diff --git a/docs/build/html/_images/litebird_sim_sample_report_latex.png b/docs/build/html/_images/litebird_sim_sample_report_latex.png new file mode 100644 index 00000000..b87c974a Binary files /dev/null and b/docs/build/html/_images/litebird_sim_sample_report_latex.png differ diff --git a/docs/build/html/_images/mbs_i.png b/docs/build/html/_images/mbs_i.png new file mode 100644 index 00000000..20ffb586 Binary files /dev/null and b/docs/build/html/_images/mbs_i.png differ diff --git a/docs/build/html/_images/observation_data_distribution.png b/docs/build/html/_images/observation_data_distribution.png new file mode 100644 index 00000000..60a0b345 Binary files /dev/null and b/docs/build/html/_images/observation_data_distribution.png differ diff --git a/docs/build/html/_images/orientation-direction.svg b/docs/build/html/_images/orientation-direction.svg new file mode 100644 index 00000000..18b16fce --- /dev/null +++ b/docs/build/html/_images/orientation-direction.svg @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + North + P + + + θ + d + + ex + ey + ez + + ex + + + d + p + + + + φ + ψ + diff --git a/docs/build/html/_images/quaternion-sampling.svg b/docs/build/html/_images/quaternion-sampling.svg new file mode 100644 index 00000000..e5392c92 --- /dev/null +++ b/docs/build/html/_images/quaternion-sampling.svg @@ -0,0 +1 @@ +Samples acquired by the detectorQuaternions representing the spacecraft's orientationTime \ No newline at end of file diff --git a/docs/build/html/_images/report_example.png b/docs/build/html/_images/report_example.png new file mode 100644 index 00000000..e780b8c0 Binary files /dev/null and b/docs/build/html/_images/report_example.png differ diff --git a/docs/build/html/_images/right-handed-coordinates.svg b/docs/build/html/_images/right-handed-coordinates.svg new file mode 100644 index 00000000..9e548591 --- /dev/null +++ b/docs/build/html/_images/right-handed-coordinates.svg @@ -0,0 +1 @@ +xyz \ No newline at end of file diff --git a/docs/build/html/_images/scanning-strategy-example.png b/docs/build/html/_images/scanning-strategy-example.png new file mode 100644 index 00000000..57a01a2f Binary files /dev/null and b/docs/build/html/_images/scanning-strategy-example.png differ diff --git a/docs/build/html/_images/simple-scanning-strategy.png b/docs/build/html/_images/simple-scanning-strategy.png new file mode 100644 index 00000000..e2c24564 Binary files /dev/null and b/docs/build/html/_images/simple-scanning-strategy.png differ diff --git a/docs/build/html/_images/spacecraft_demo.png b/docs/build/html/_images/spacecraft_demo.png new file mode 100644 index 00000000..ec97692b Binary files /dev/null and b/docs/build/html/_images/spacecraft_demo.png differ diff --git a/docs/build/html/_images/speedscope-example.png b/docs/build/html/_images/speedscope-example.png new file mode 100644 index 00000000..79882523 Binary files /dev/null and b/docs/build/html/_images/speedscope-example.png differ diff --git a/docs/build/html/_images/thermalgain_demo.png b/docs/build/html/_images/thermalgain_demo.png new file mode 100644 index 00000000..53b38998 Binary files /dev/null and b/docs/build/html/_images/thermalgain_demo.png differ diff --git a/docs/build/html/_images/tutorial-bare-report.png b/docs/build/html/_images/tutorial-bare-report.png new file mode 100644 index 00000000..fc50ef49 Binary files /dev/null and b/docs/build/html/_images/tutorial-bare-report.png differ diff --git a/docs/build/html/_images/tutorial-coverage-map.png b/docs/build/html/_images/tutorial-coverage-map.png new file mode 100644 index 00000000..4d3ef799 Binary files /dev/null and b/docs/build/html/_images/tutorial-coverage-map.png differ diff --git a/docs/build/html/_images/tutorial-maps.png b/docs/build/html/_images/tutorial-maps.png new file mode 100644 index 00000000..40b69b80 Binary files /dev/null and b/docs/build/html/_images/tutorial-maps.png differ diff --git a/docs/build/html/_images/tutorial-timeline.png b/docs/build/html/_images/tutorial-timeline.png new file mode 100644 index 00000000..09df5ec8 Binary files /dev/null and b/docs/build/html/_images/tutorial-timeline.png differ diff --git a/docs/build/html/_sources/appendix.rst.txt b/docs/build/html/_sources/appendix.rst.txt new file mode 100644 index 00000000..b8e2f2aa --- /dev/null +++ b/docs/build/html/_sources/appendix.rst.txt @@ -0,0 +1,7 @@ +Appendices +========== + +.. toctree:: + :maxdepth: 1 + + bibliography.rst diff --git a/docs/build/html/_sources/bibliography.rst.txt b/docs/build/html/_sources/bibliography.rst.txt new file mode 100644 index 00000000..6cd24c82 --- /dev/null +++ b/docs/build/html/_sources/bibliography.rst.txt @@ -0,0 +1,5 @@ +Bibliography +============ + +.. bibliography:: + :style: plain diff --git a/docs/build/html/_sources/data_layout.rst.txt b/docs/build/html/_sources/data_layout.rst.txt new file mode 100644 index 00000000..d61aabfe --- /dev/null +++ b/docs/build/html/_sources/data_layout.rst.txt @@ -0,0 +1,147 @@ +Data layout +=========== + +This page discusses how data is laid down in memory, and it provides +some hints about how to use NumPy efficiently with the memory layout +used by the LiteBIRD Simulation Framework. We focus mostly on the +detectors time-ordered-data (TOD), as they are the largest object we +expect to handle frequently. + +The LiteBIRD Simulation Framework stores TOD as a matrix with one row +per detector and the other detector attributes as arrays with one +entry per detector. This means that, given an observation ``obs``, you +access quantities with patterns like ``obs.fknee[12]`` (as opposed to +``obs.detectors[12].fknee``). Note you can easily write single lines +that operate on all the detectors:: + + # Apply to each detector its own calibration factor + obs.tod *= obs.calibration_factors[:, None] + # Generate white noise at different level for each detector + obs.tod = (np.random.normal(size=obs.tod.shape) + * obs.wn_level[:, None]) + + +TOD as two-dimensional arrays +----------------------------- + +Organizing the TOD in a matrix is ideal because it enables fast access +to (1) many samples for one detector and (2) many detectors for one +time sample. In fact, TOD are a large set of samples. Each sample is +identified by the time at which it was taken and by the detector that +took it. If all the detectors are synchronously sampled, these samples +can be organized in a matrix D-by-T, where D is the number of +detectors and T is the number of time samples. Following +``numpy.ndarray`` notation, we denote it as ``tod[row_index, +col_index]``. Accessing the :math:`i`-th row can be done using the +notation ``tod[i]``: for instance, ``tod[3]`` gets the fourth row (the +TOD of the fourth detector); accessing the fourth column, i.e., the +fourth sample of each detector, can be done with ``tod[:, 3]`` (remember that in Python we start counting from 0). So, the +way the framework keeps TODs in memory makes easy to operate on the +«detector dimension» as well as on the «time dimension». + +You can take advantage of NumPy functions to easily propagate +operations along one dimension or another. For instance, taking the +time stream of the mean signal across the focal plane is done with +``tod.mean(axis=0)``. In general, an extremely large number of +operations can be expressed in terms of ``numpy`` functions and they +can operate easily and efficiently over axes of multi-dimensional +arrays. Similarly, if ``C`` is a cross-talk matrix, the operation ``C +@ tod`` mixes the TODs. + +The previous examples are not only easy to write, they are also (very likely) +as fast as they can be (compared to different data layouts). This is +particularly true for the last example. The matrix multiplication ``C @ tod`` +calls internally some highly optimized dense linear algebra routines. + +Even when there is not really communications between detectors +involved, having data in a 2-dimensional array produces (arguably) +cleaner code and sometimes faster code (never slower). Suppose you +have a *list* of ``calibration_factors`` and a *list* of time streams +``tod``. You can apply the calibration factors with + +.. code-block:: python + + for calibration_factor, det_tod in zip(calibration_factors, tod): + det_tod *= calibration_factor + +but if ``calibration_factors`` is an *array* and ``tod`` a *matrix* you can +achieve the same result with the easier and (typically) faster + +.. code-block:: python + + tod *= calibration_factors[:, None] + +Splitting the TOD matrix +------------------------ + +When working on a laptop (or a single compute node) we can live in the +ideal case discussed above. We can benefit both from the API +simplifications and the performance of the compiled codes that the +numerical libraries typically call. Moreover, these libraries are +often threaded (or easily threadable) and therefore we can in +principle leverage on all the processors available. + +However, when working on clusters, we have to split this matrix into +pieces. We resort to supercomputers either when we want more CPU power +than a laptop or because we need more memory to store our data. +Both motivations apply to full-scale LiteBIRD simulations (4000 +detectors, sampled at 20 GHz for 3 years take approximately 10 TB). +Therefore, we have to distribute the matrix and a compute node has +access only to the portion of the matrix that is in its memory. + +To do this, we split the matrix into blocks that are stored as +(sub-)matrices. At the present stage, there is no constraint on the +shape of these blocks, they can also be a single row or a single +column. The important thing is that, if the block spans more than a +row, it is stored as a matrix (a 2-dimensional array). This means +that, for the time- and detector-chunk assigned to a compute node, all +the benefits discussed in the previous section apply. + + +Choosing the shape of the blocks +-------------------------------- + +The most convenient block shape depends on the application: + +- Some operations prefer to store an entire row. For example, + :math:`4\pi` beam convolution has a memory overhead of the order of + the GB for each beam, which is in principle different for each + detector. For this operation is therefore more convenient to hold a + detector for the entire length of the mission. + +- However, other operations prefer to have an entire column. For + example, computing the common mode across the focal plane requires + information from all the detectors at every time sample. This is + trivial if the full detector column is in the memory, but it is very + complicated if it is scattered across many compute nodes. + + Another example is cross-talks. Mixing the TOD of different + detectors is trivial when everything is in memory, but otherwise it + requires sending large messages across the network. Sending messages + is not only a performance issue, it means that probably a custom + algorithm has to be written, and this algorithm will probably + require MPI communications, which are notoriously hard to develop + and debug. + +Since there is no one-size-fit-all solution, the LiteBIRD Simulation +Framework keeps the most generic approach. The shape of the blocks +depends on the application, and it is possible to change it during the +execution. (However, this takes time and should thus be done only when +strictly necessary.) + + +Caveats +------- + +An important thing to remember is that, if the TOD matrix is chunked +along the detector dimension, only the corresponding portion of the +property array is detained in memory. + +This has a few important implications: + +1. Regardless if and how the TOD is distributed, both ``obs.tod[i]`` + and ``obs.wn_level[i]`` refer to the same detector; + +2. ``obs.tod`` and ``obs.wn_level`` have the same length; + +3. Operations like ``obs.tod * obs.wn_level[:, None]`` are correct. diff --git a/docs/build/html/_sources/detectors.rst.txt b/docs/build/html/_sources/detectors.rst.txt new file mode 100644 index 00000000..39916f6c --- /dev/null +++ b/docs/build/html/_sources/detectors.rst.txt @@ -0,0 +1,365 @@ +.. _detectors: + +Detectors, channels, and instruments +==================================== + +The core of the LiteBIRD instruments is the set of *detectors* that +perform the measurement of the electromagnetic power entering through +the telescope. Several simulation modules revolve around the concept +of «detector», «frequency channel», and «instrument», and the LiteBIRD +Simulation Framework implements a few classes and functions to handle +the information associated with them. + +Consider this example:: + + import litebird_sim as lbs + + detectors = [ + lbs.DetectorInfo( + name="Dummy detector #1", + net_ukrts=100.0, + bandcenter_ghz=123.4, + bandwidth_ghz=5.6, + ), + lbs.DetectorInfo( + name="Dummy detector #2", + net_ukrts=110.0, + fwhm_arcmin=65.8, + ), + ] + + # Now simulate the behavior of the two detectors + # … + +Here you see that the :class:`.DetectorInfo` class can be used to +store information related to single detectors, and that you can choose +which information provide for each detector: the example specifies +bandshape information only for the first detector (``Dummy detector +#1``), but not for the other. Missing information is usually +initialized with zero or a sensible default. The simulation modules +provided by the framework typically expect one or more +:class:`.DetectorInfo` objects to be passed as input. + +Detectors are grouped according to their nominal central frequency in +*frequency channels*; there are several frequency channels in the +three LiteBIRD instruments (LFT, MFT, HFT), and sometimes one does not +need to simulate each detector within a frequency channel, but only +the channel as a whole. In this case there is the +:class:`.FreqChannelInfo` class, which can produce a mock +:class:`.DetectorInfo` object by taking out the «average» information +of the frequency channel: + +.. testcode:: + + import litebird_sim as lbs + + chinfo = lbs.FreqChannelInfo( + bandcenter_ghz=40.0, + net_channel_ukrts=40.0, # Taken from Sugai et al. 2020 (JLTP) + number_of_detectors=64, # 32 pairs + ) + + # Return a "mock" detector that is representative of the + # frequency channel + mock_det = chinfo.get_boresight_detector(name="mydet") + + # Look, ma, a detector! + assert isinstance(mock_det, lbs.DetectorInfo) + assert mock_det.name == "mydet" + + print("The NET of one detector at 40 GHz is {0:.1f} µK·√s" + .format(mock_det.net_ukrts)) + +.. testoutput:: + + The NET of one detector at 40 GHz is 320.0 µK·√s + +Finally, the :class:`.Instrument` class holds information about one of +the three instruments onboard the LiteBIRD spacecraft (LFT, MFT, and +HFT). + + +Reading from the IMO +-------------------- + +The way information about detectors and frequency channels is stored +in the IMO (see :ref:`imo`) closely match the :class:`.DetectorInfo` and +:class:`.FreqChannelInfo` classes. In fact, they can be retrieved +easily from the IMO using the static methods +:meth:`.DetectorInfo.from_imo` and :meth:`.FreqChannelInfo.from_imo`. + +The following example uses the PTEP IMO to show how to use the API: + +.. testcode:: + + import litebird_sim as lbs + + # The location of the PTEP IMO is stored in the constant + # PTEP_IMO_LOCATION + imo = lbs.Imo(flatfile_location=lbs.PTEP_IMO_LOCATION) + + det = lbs.DetectorInfo.from_imo( + imo=imo, + url="/releases/vPTEP/satellite/LFT/L1-040/" + "000_000_003_QA_040_T/detector_info", + ) + + print(f"The bandcenter for {det.name} is {det.bandcenter_ghz} GHz") + + freqch = lbs.FreqChannelInfo.from_imo( + imo=imo, + url="/releases/vPTEP/satellite/LFT/L1-040/channel_info", + ) + + print( + f"The average bandcenter for {freqch.channel} " + f"is {freqch.bandcenter_ghz} GHz" + ) + +.. testoutput:: + + The bandcenter for 000_000_003_QA_040_T is 40.0 GHz + The average bandcenter for L1-040 is 40.0 GHz + + +Detectors in parameter files +---------------------------- + +It is often the case that the list of detectors to simulate must be +read from a parameter file. There are several situations that +typically need to be accomodated: + +1. In some simulations, you just need to simulate one detector (whose + parameters can be taken either from the IMO or from the parameter + file itself); +2. In other cases, you want to simulate all the detectors in one or + more frequency channels: in this case, you would like to avoid + specifying them one by one! +3. In other cases, you might want to specify just a subset +4. Finally, you might base your simulation on the IMO definition of a + detector/channel but twiddle a bit with some parameters. + +The LiteBIRD simulation framework provides a way to read a set of +detectors/channels from a dictionary, which can be used to build a +list of :class:`.DetectorInfo`/:class:`.FreqChannelInfo` objects. + +In the following examples, we will refer to a «mock» IMO, which +contains information about a few fake detectors. It's included in the +source distribution of the framework, under the directory +``litebird_sim/test/mock_imo``, and it defines one frequency channel +with four detectors. Here is a summary of its contents: + +.. testcode:: + + from pathlib import Path + import litebird_sim as lbs + + # This ensures that the mock IMO can be found when generating this + # HTML page + imo = lbs.Imo(flatfile_location=lbs.PTEP_IMO_LOCATION) + + # This UUID refers to a 140 GHz "channel_info" object. + ch = imo.query("/data_files/463e9ea9-c1f0-484d-9bfd-05092851d8f4") + metadata = ch.metadata + + print("Here are the contents of the mock IMO:") + print(f'Channel: {metadata["channel"]}') + + detector_names = metadata["detector_names"] + print(f'There are {len(detector_names)} detectors') + print("Here are the first 5 of them:") + for name, obj in list(zip(metadata["detector_names"], + metadata["detector_objs"]))[0:5]: + det_obj = imo.query(obj) + det_metadata = det_obj.metadata + bandcenter_ghz = det_metadata["bandcenter_ghz"] + print(f' {name}: band center at {bandcenter_ghz:.1f} GHz') + +.. testoutput:: + + Here are the contents of the mock IMO: + Channel: M1-140 + There are 366 detectors + Here are the first 5 of them: + 001_002_030_00A_140_T: band center at 140.0 GHz + 001_002_030_00A_140_B: band center at 140.0 GHz + 001_002_031_15B_140_T: band center at 140.0 GHz + 001_002_031_00B_140_B: band center at 140.0 GHz + 001_002_022_15A_140_T: band center at 140.0 GHz + + +Now, let's turn back to the problem of specifying a set of detectors +in a parameter file. The following TOML file shows some of the +possibilities granted by the framework. The parameter `random_seed` +is mandatory for the :class:`.Simulation` constructor. + +.. literalinclude:: ../det_list1.toml + :language: toml + +If the TOML file you are using in your simulation follows this +structure, you can use the function +:func:`.detector_list_from_parameters`, which parses the parameters +and uses an :class:`.Imo` object to build a list of +:class:`.DetectorInfo` objects. + +The following code will read the TOML file above and produce a list of +6 detectors: + +.. testcode:: + + from pathlib import Path + import litebird_sim as lbs + + imo = lbs.Imo(flatfile_location=lbs.PTEP_IMO_LOCATION) + + # Tell Simulation to use the PTEP IMO and to + # load the TOML file shown above + sim = lbs.Simulation(imo=imo, parameter_file="det_list1.toml") + + det_list = lbs.detector_list_from_parameters( + imo=sim.imo, + definition_list=sim.parameters["detectors"], + ) + + for idx, det in enumerate(det_list): + print(f"{idx + 1}. {det.name}: band center at {det.bandcenter_ghz} GHz") + +.. testoutput:: + + 1. 000_000_003_QA_040_T: band center at 40.0 GHz + 2. 001_002_030_00A_140_T: band center at 140.0 GHz + 3. 001_002_030_00A_140_B: band center at 140.0 GHz + 4. foo_boresight: band center at 140.0 GHz + 5. planck30GHz: band center at 28.4 GHz + 6. 000_000_003_QA_040_T: band center at 40.0 GHz + +You are not forced to use ``detectors`` as the name of the parameter +in the TOML file, as :func:`.detector_list_from_parameters` accepts a +generic list. As an example, consider the following TOML file. Note +again the mandatory parameter `random_seed`. + +.. literalinclude:: ../det_list2.toml + :language: toml + +Its purpose is to provide the parameters for a two-staged simulation, +and each of them requires its own list of detectors. For this purpose, +it uses the TOML syntax ``[[simulation1.detectors]]`` to specify that +the ``detectors`` list belongs to the section ``simulation1``, and +similarly for ``[[simulation2.detectors]]``. Here is a Python script +that interprets the TOML file and prints the detectors to be used at +each stage of the simulation: + +.. testcode:: + + from pathlib import Path + import litebird_sim as lbs + + imo = lbs.Imo(flatfile_location=lbs.PTEP_IMO_LOCATION) + + # Tell the Simulation object that we want to use the PTEP IMO + sim = lbs.Simulation(imo=imo, parameter_file="det_list2.toml") + + det_list1 = lbs.detector_list_from_parameters( + imo=sim.imo, + definition_list=sim.parameters["simulation1"]["detectors"], + ) + + det_list2 = lbs.detector_list_from_parameters( + imo=sim.imo, + definition_list=sim.parameters["simulation2"]["detectors"], + ) + + print("Detectors to be used in the first simulation:") + for det in det_list1: + print(f"- {det.name}") + + print("Detectors to be used in the second simulation:") + for det in det_list2: + print(f"- {det.name}") + + +.. testoutput:: + + Detectors to be used in the first simulation: + - 000_000_004_QB_040_B + Detectors to be used in the second simulation: + - 000_000_004_QB_040_T + + +Bandpasses +---------- + +Any electromagnetic detector is sensitive only to some frequencies of +the spectrum, and it is vital to know what they are. We refer to these +as the «bandpass», i.e., the list of frequencies that can «pass +through the detector» and be detected. A bandpass is a function +:math:`B(\nu)` that associates the frequency :math:`\nu` with a pure +number in the range 0…1, representing the fraction of power that is +actually measured by the instrument. The functional form of +:math:`B(\nu)` is usually called the *bandshape*, and it is usually +nonzero in a limited subset of the real space. + +A widely used analytical model of the bandshape is the top-hat bandpass: + +.. math:: + :name: eq:tophatbandpass + + B(\nu) = \begin{cases} + 1\,\text{ if } \nu_0 - \Delta\nu/2 < \nu < \nu_0 + \Delta\nu/2,\\ + 0\,\text{ otherwise.} + \end{cases} + +Given a bandshape :math:`B(\nu)`, two quantities are usually employed +to synthetically represent it: + +- The *central frequency* :math:`\left<\nu\right>`, which is just the + average value of :math:`\nu` when weighted with :math:`B(\nu)`: + + .. math:: + \left<\nu\right> = \frac{\int_0^\infty \nu B(\nu)\,\mathrm{d}\nu}{\int_0^\infty B(\nu)\,\mathrm{d}\nu} + + which is of course the formula of the weighted average. For the + top-hat band, the central frequency is :math:`\nu_0`. + +- The *bandwidth* :math:`\Delta\nu`, which is the *width* of the band, + i.e., a measurement of how wide is the subrange of the + electromagnetic spectrum sampled by the detector: + + .. math:: + + \Delta\nu^2 = \frac{\left(\int_0^\infty B(\nu)\,\mathrm{d}\nu\right)^2}{\int_0^\infty B^2(\nu)\,\mathrm{d}\nu}, + + which is again inspired by the formula of the standard square + deviation. For the top-hat bandshape, the value of :math:`\Delta\nu` + in :math:numref:`eq:tophatbandpass` is exactly the bandwidth. + + +Modeling bandshapes +~~~~~~~~~~~~~~~~~~~ + +In the LiteBIRD Simulation Framework, bandpasses are encoded in the +class :class:`.BandPassInfo`, which can be used either to load +bandpasses from the IMO or to generate them on scratch. In the latter +case, you must specify the central frequency and the bandwidth, +alongside with other parameter that introduce a random component meant +to make the band more realistic. Here is a simple example, which shows +how to create a bandpass and then how to add «noise» to make it more +realistic: + +.. plot:: pyplots/bandpass_demo.py + :include-source: + + + +API reference +------------- + +.. automodule:: litebird_sim.detectors + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: litebird_sim.bandpasses + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/dipole.rst.txt b/docs/build/html/_sources/dipole.rst.txt new file mode 100644 index 00000000..5c1f2bfa --- /dev/null +++ b/docs/build/html/_sources/dipole.rst.txt @@ -0,0 +1,273 @@ +.. _dipole-anisotropy: + +Dipole anisotropy +================= + +The LiteBIRD Simulation Framework provides tools to simulate the +signal associated with the relative velocity between the rest frame of +the spacecraft with respect to the CMB. The motion of the spacecraft +in the rest frame of the CMB is the composition of several components: + +1. The motion of the spacecraft around L2; + +2. The motion of the L2 point in the Ecliptic plane; + +3. The motion of the Solar System around the Galactic Centre; + +4. The motion of the Milky Way. + +Components 1 and 2 are simulated by the LiteBIRD Simulation Framework +using appropriate motion models, while components 3 and 4 are included +using the Sun velocity derived by the solar dipole measured by the +Planck satellite. + +The motion of the spacecraft around L2 is modelled using a Lissajous +orbit similar to what was used for the WMAP experiment +:cite:`2008:wmap:cavaluzzi`, and it is encoded using the +:class:`.SpacecraftOrbit` class. + +Position and velocity of the spacecraft +--------------------------------------- + +The class :class:`.SpacecraftOrbit` describes the orbit of the +LiteBIRD spacecraft with respect to the Barycentric Ecliptic Reference +Frame and the motion of the Barycentric Ecliptic Reference Frame with +respect to the CMB; this class is necessary because the class +:class:`.ScanningStrategy` (see the chapter :ref:`scanning-strategy`) +only models the *direction* each instrument is looking at but knows +nothing about the velocity of the spacecraft itself. + +The class :class:`.SpacecraftOrbit` is a dataclass that can initialize +its members to sensible default values taken from the literature. As +the LiteBIRD orbit around L2 is not fixed yet, the code assumes a +WMAP-like Lissajous orbit, :cite:`2008:wmap:cavaluzzi`. For the Sun +velocity it assumes Planck 2018 solar dipole +:cite:`2020:planck:hfi_data_processing`. + +To compute the position/velocity of the spacecraft, you call +:func:`.spacecraft_pos_and_vel`; it requires either a period or an +:class:`.Observation` object, and it returns an instance of the class +:class:`.SpacecraftPositionAndVelocity`: + +.. testcode:: + + import litebird_sim as lbs + from astropy.time import Time + + orbit = lbs.SpacecraftOrbit(start_time=Time("2023-01-01")) + posvel = lbs.spacecraft_pos_and_vel( + orbit, + start_time=orbit.start_time, + time_span_s=86_400.0, # One day + delta_time_s=3600.0 # One hour + ) + + print(posvel) + +.. testoutput:: + + SpacecraftPositionAndVelocity(start_time=2023-01-01 00:00:00.000, time_span_s=86400.0, nsamples=25) + +The output of the script shows that 25 «samples» have been computed; +this means that the ``posvel`` variable holds information about 25 +position/velocity pairs evenly spaced between 2023-01-01 and +2023-01-02: one at midnight, one at 1:00, etc., till midnight +2023-01-02. The :class:`.SpacecraftPositionAndVelocity` class keeps +the table with the positions and the velocities in the fields +``positions_km`` and ``velocities_km_s``, respectively, which are +arrays of shape ``(nsamples, 3)``. + +Here is a slightly more complex example that shows how to plot the +distance between the spacecraft and the Sun as a function of time and +speed. The latter quantity is, of course, most relevant when computing +the CMB dipole. + +.. plot:: pyplots/spacecraft_demo.py + :include-source: + + +Computing the dipole +-------------------- + +The CMB dipole is caused by a Doppler shift of the frequencies +observed while looking at the CMB blackbody spectrum, according to the formula + +.. math:: + :label: dipole + + T(\vec\beta, \hat n) = \frac{T_0}{\gamma \bigl(1 - \vec\beta \cdot \hat n\bigr)}, + +where :math:`T_0` is the temperature in the rest frame of the CMB, +:math:`\vec \beta = \vec v / c` is the dimensionless velocity vector, +:math:`\hat n` is the direction of the line of sight, and +:math:`\gamma = \bigl(1 - \vec\beta \cdot \vec\beta\bigr)^2`. + +However, CMB experiments usually employ the linear thermodynamic +temperature definition, where temperature differences :math:`\Delta_1 T` +are related to the actual temperature difference :math:`\Delta T` by +the relation + +.. math:: + :label: linearized-dipole + + \Delta_1 T = \frac{T_0}{f(x)} \left(\frac{\mathrm{BB}(T_0 + \Delta T)}{\mathrm{BB}(T_0)} - 1\right) = + \frac{T_0}{f(x)} \left(\frac{\exp x - 1}{\exp\left(x\frac{T_0}{T_0 + \Delta T}\right) - 1} - 1\right), + +where :math:`x = h \nu / k_B T`, + +.. math:: f(x) = \frac{x e^x}{e^x - 1}, + +and :math:`\mathrm{BB}(\nu, T)` is the spectral radiance of a +black-body according to Planck's law: + +.. math:: \mathrm{BB}(\nu, T) = \frac{2h\nu^3}{c^2} \frac1{e^{h\nu/k_B T} - 1} = \frac{2h\nu^3}{c^2} \frac1{e^x - 1}. + +There is no numerical issue in computing the complete formula, but +often, models use some simplifications to make the math more +manageable to work on the blackboard. The LiteBIRD Simulation +Framework implements several simplifications of the formula, which are +based on a series expansion of :eq:`dipole`; the caller must pass an +object of type :class:`.DipoleType` (an `enum class +`_), whose value signals +which kind of approximation to use: + +1. The most simple formula uses a series expansion of :eq:`dipole` at + the first order: + + .. math:: \Delta T(\vec\beta, \hat n) = T_0 \vec\beta\cdot\hat n, + + which is associated to the constant ``DipoleType.LINEAR``. + +2. The same series expansion for :eq:`dipole`, but stopped at the + second order (``DipoleType.QUADRATIC_EXACT``): + + .. math:: \Delta T(\vec\beta, \hat n) = T_0\left(\vec\beta\cdot\hat n + \bigl(\vec\beta\cdot\hat n\bigr)^2\right), + + which discards a :math:`-T_0\,\beta^2/2` term (monopole). + +3. The exact formula as in :eq:`dipole` (``DipoleType.TOTAL_EXACT``). + +4. Using a series expansion to the second order of + :eq:`linearized-dipole` instead of :eq:`dipole` and neglecting + monopoles (``DipoleTotal.QUADRATIC_FROM_LIN_T``): + + .. math:: \Delta_2 T(\nu) = T_0 \left(\vec\beta\cdot\hat n + q(x) \bigl(\vec\beta\cdot\hat n\bigr)^2\right), + + where the dependence on the frequency ν is due to the presence of + the term :math:`x = h\nu / k_B T` in the equation. This is the + formula to use if you want the leading frequency-dependent term + (second order) without the boosting induced monopoles. + +5. Finally, linearizing :eq:`dipole` through :eq:`linearized-dipole` + (``DipoleTotal.TOTAL_FROM_LIN_T``): + + .. math:: + + \Delta T = \frac{T_0}{f(x)} \left(\frac{\mathrm{BB}\left(T_0 / \gamma\bigl(1 - \vec\beta\cdot\hat n\bigr)\right)}{\mathrm{BB}(T_0)} - 1\right) = + \frac{T_0}{f(x)} \left(\frac{\mathrm{BB}\bigl(\nu\gamma(1-\vec\beta\cdot\hat n), T_0\bigr)}{\bigl(\gamma(1-\vec\beta\cdot\hat n)\bigr)^3\mathrm{BB}(T_0)}\right). + + In this case too, the temperature variation depends on the + frequency because of :eq:`linearized-dipole`. This is the formula + that is typically used by CMB experiments. + +You can *add* the dipole signal to an existing TOD through the +function :func:`.add_dipole_to_observations`, as the following example +shows: + +.. plot:: pyplots/dipole_demo.py + :include-source: + +The example plots two minutes of a simulated timeline for a very +simple instrument, and it zooms over the very first points to show +that there is indeed some difference in the estimate provided by each +method. + + +Methods of class simulation +--------------------------- + +The class :class:`.Simulation` provides two simple functions that compute +poisition and velocity of the spacescraft :func:`.Simulation.compute_pos_and_vel`, +and add the solar and orbital dipole to all the observations of a given +simulation :func:`.Simulation.add_dipole`. + +.. testcode:: + + import litebird_sim as lbs + from astropy.time import Time + import numpy as np + + start_time = Time("2025-01-01") + time_span_s = 1000.0 + sampling_hz = 10.0 + + sim = lbs.Simulation( + start_time=start_time, + duration_s=time_span_s, + random_seed=12345, + ) + + # We pick a simple scanning strategy where the spin axis is aligned + # with the Sun-Earth axis, and the spacecraft spins once every minute + sim.set_scanning_strategy( + lbs.SpinningScanningStrategy( + spin_sun_angle_rad=np.deg2rad(0), + precession_rate_hz=0, + spin_rate_hz=1 / 60, + start_time=start_time, + ), + delta_time_s=5.0, + ) + + # We simulate an instrument whose boresight is perpendicular to + # the spin axis. + sim.set_instrument( + lbs.InstrumentInfo( + boresight_rotangle_rad=0.0, + spin_boresight_angle_rad=np.deg2rad(90), + spin_rotangle_rad=np.deg2rad(75), + ) + ) + + # A simple detector looking along the boresight direction + det = lbs.DetectorInfo( + name="Boresight_detector", + sampling_rate_hz=sampling_hz, + bandcenter_ghz=100.0, + ) + + sim.create_observations(detectors=det) + + sim.prepare_pointings() + + sim.compute_pos_and_vel() + + sim.add_dipole() + + for i in range(5): + print(f"{sim.observations[0].tod[0][i]:.5e}") + +.. testoutput:: + + 3.44963e-03 + 3.45207e-03 + 3.45413e-03 + 3.45582e-03 + 3.45712e-03 + +Note that even if :func:`Simulation.compute_pos_and_vel` is not explicitly +invoked, :func:`Simulation.add_dipole` takes care of that internally initializing +:class:`SpacecraftOrbit` and computing positions and velocities. + +API reference +------------- + +.. automodule:: litebird_sim.spacecraft + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: litebird_sim.dipole + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/gaindrifts.rst.txt b/docs/build/html/_sources/gaindrifts.rst.txt new file mode 100644 index 00000000..fe51bf93 --- /dev/null +++ b/docs/build/html/_sources/gaindrifts.rst.txt @@ -0,0 +1,253 @@ +Gain drift injection +==================== + +Gain drift is the systematic that is multiplicative to time-ordered +data. The LiteBIRD Simulation Framework provides a gain drift +simulation module based on the same module in ``toast3``. Though the +exact nature of the gain drift depends on the specifics of the +electronics, the gain drift module provides the functions to simulate +two kinds of gain drifts: + +1. Linear gain drifts; + +2. Thermal gain drifts. + +For any gain drift, one can use either the method of +:class:`.Simulation` class :meth:`.Simulation.apply_gaindrift()`, or +any of the low-level functions: +:func:`.apply_gaindrift_to_observations()`, +:func:`.apply_gaindrift_to_tod()`, +:func:`.apply_gaindrift_for_one_detector()`. The following example +shows the typical usage of the method and low-level functions: + +.. code-block:: python + + import numpy as np + import litebird_sim as lbs + from astropy.time import Time + + start_time = Time("2034-05-02") + duration_s = 2*24*3600 + sampling_freq_Hz = 1 + + # Creating a list of detectors. The detector name is used as one of + # two seeds to introduce unique and reproducible randomness to the + # gain drift for each detector. + dets = [ + lbs.DetectorInfo( + name="det_A_wafer_1", sampling_rate_hz=sampling_freq_Hz, wafer="wafer_1" + ), + lbs.DetectorInfo( + name="det_B_wafer_1", sampling_rate_hz=sampling_freq_Hz, wafer="wafer_1" + ), + lbs.DetectorInfo( + name="det_C_wafer_2", sampling_rate_hz=sampling_freq_Hz, wafer="wafer_2" + ), + ] + + # Defining the gain drift simulation parameters + drift_params = lbs.GainDriftParams( + drift_type=lbs.GainDriftType.LINEAR_GAIN, + sampling_uniform_low=0.2, + sampling_uniform_high=0.7, + ) + + sim1 = lbs.Simulation( + base_path="linear_gd_example", + start_time=start_time, + duration_s=duration_s, + random_seed=12345, + ) + + sim1.create_observations( + detectors=dets, + split_list_over_processes=False, + num_of_obs_per_detector=1, + ) + + # Creating fiducial TODs + sim1.observations[0].gain_2_self = np.ones_like(sim1.observations[0].tod) + sim1.observations[0].gain_2_obs = np.ones_like(sim1.observations[0].tod) + sim1.observations[0].gain_2_tod = np.ones_like(sim1.observations[0].tod) + sim1.observations[0].gain_2_det = np.ones_like(sim1.observations[0].tod) + + # Applying gain drift using the `Simulation` class method + sim1.apply_gaindrift( + drift_params=drift_params, + component="gain_2_self", + ) + + # Applying gain drift on the given TOD component of an `Observation` object + lbs.apply_gaindrift_to_observations( + observations=sim1.observations, + drift_params=drift_params, + component="gain_2_obs", + ) + + # Applying gain drift on the TOD array. One must pass the name of the + # associated detectors (or just an array of string objects) to seed + # the reproducible randomness + lbs.apply_gaindrift_to_tod( + tod=sim1.observations[0].gain_2_tod, + sampling_freq_hz=sampling_freq_Hz, + det_name=sim1.observations[0].name, + drift_params=drift_params, + ) + + # Applying gain drift on the TOD arrays of the individual detectors. + # One must pass the name of the detector (or a string object) to seed + # the reproducible randomness. + for idx, tod in enumerate(sim1.observations[0].gain_2_det): + lbs.apply_gaindrift_for_one_detector( + det_tod=tod, + sampling_freq_hz=sampling_freq_Hz, + det_name=sim1.observations[0].name[idx], + drift_params=drift_params, + ) + + # The four TODs we obtain this way are equal to each other. + +One has to specify the gain drift simulation parameters as an instance +of the :class:`.GainDriftParams` class. The type of the gain drift can +be selected using the enum class :class:`.GainDriftType`. The +:class:`.GainDriftParams` class also offers the facility to specify +the distribution of the slope for the linear gain and the distribution +of the detector mismatch for the thermal gain, which can be specified +with the help of the enum class :class:`.SamplingDist`. + +Following is an example of linear gain drift simulation parameters +where the slope of gain for different detectors follows Gaussian +distribution with a mean of 0.8 and a standard deviation of 0.2: + +.. code-block:: python + + import litebird_sim as lbs + + drift_params = lbs.GainDriftParams( + drift_type = lbs.GainDriftType.LINEAR_GAIN, + sampling_dist = lbs.SamplingDist.GAUSSIAN, + sampling_gaussian_loc = 0.8, + sampling_gaussian_scale = 0.2, + ) + +The following example shows the thermal gain drift simulation +parameters where the detector mismatch within a detector group has a +uniform distribution varying between the factors 0.2 to 0.8: + +.. code-block:: python + + import litebird_sim as lbs + + drift_params = lbs.GainDriftParams( + drift_type = lbs.GainDriftType.THERMAL_GAIN, + sampling_dist = lbs.SamplingDist.UNIFORM, + sampling_uniform_low = 0.2, + sampling_uniform_high = 0.8, + ) + +Refer to the :ref:`gd-api-reference` for the full list of gain drift simulation parameters. + +Linear gain drift +----------------- + +Linear gain drift is the linearly increasing factor for TODs. The +:mod:`.gaindrifts` module provides methods and functions to simulate +the linear gain drift with the possibility of periodic calibration. +The calibration event resets the gain factor to one periodically after +every calibration period interval. You can specify the calibration +period with the attribute +:attr:`.GainDriftParams.calibration_period_sec`. The following example +shows the time evolution of the linear gain drift factor over four +days with a calibration period of 24 hours: + +.. plot:: pyplots/lingain_demo.py + :include-source: + +Note that the figure above shows only the nature of the linear gain +drift factor to be multiplied by the sky TOD. + +The module generates different gain slopes for different detectors. +The factor :math:`\sigma_{drift}\times\delta` determines the slope (or +the peak amplitude) of the linear gain, where :math:`\sigma_{drift}` +is a dimensionless parameter specified by +:attr:`.GainDriftParams.sigma_drift` and :math:`\delta` is the random +factor generated uniquely for each detector. The distribution of +:math:`\delta` or, conversely, the distribution of the gain slopes +over all the detectors can be specified with attributes of +:class:`.SamplingDist` enum class and the associated parameters listed +in :class:`.GainDriftParams`. + +Thermal gain drift +------------------ + +The thermal gain drift is modelled as the gain drift due to +:math:`1/f` fluctuation in focalplane temperature. In the first step, +the :math:`1/f` noise timestream is generated from oversampled power +spectral density given by + +.. math:: + S(f) = \sigma_{drift}^2\left(\frac{f_{knee}}{f}\right)^{\alpha_{drift}} + +The noise timestream is considered to be the same for all the +detectors belonging to a given detector group. One may specify which +detector parameter to use to make a detector group, using the +attribute :attr:`.GainDriftParams.focalplane_group`. Valid values are +`"wafer"`, `"pixtype"`, or `"channel"`. For example, if +:attr:`.GainDriftParams.focalplane_group = "wafer"`, all the detectors +with the same wafer name will be considered in one group and have the +same noise timestream. + +Once the noise timestreams are obtained for all the groups in the +focal plane, the code inserts a mismatch for the detectors within a +group by a random factor and the detector mismatch factor. We can +express the noise timestream with detector mismatch using the +following expression: + +.. math:: + t^{(mis)}_{stream} = (1 + \delta\times\alpha_{mis})t_{stream} + +where :math:`\alpha_{mis}` is the detector mismatch factor specified +using the attribute :attr:`.GainDriftParams.detector_mismatch` and +:math:`\delta` is the random factor generated uniquely for each +detector. The distribution of :math:`\delta` or, conversely, the +distribution of noise timestream mismatch can be specified with +attributes of :class:`.SamplingDist` enum class and the associated +parameters listed in :class:`.GainDriftParams`. + +The mismatched timestream is then scaled and passed through a +responsivity function to obtain the thermal gain factor +(:math:`\sigma`): + +.. math:: + \sigma = \text{responsivity_function}\left(1+\frac{ t^{(mis)}_{stream} \times \delta_T }{T_{bath}}\right) + +where :math:`\delta_T` is the amplitude of the thermal gain +fluctuation in Kelvin unit, specified with attribute +:attr:`.GainDriftParams.thermal_fluctuation_amplitude_K`, and +:math:`T_{bath}` is the temperature of the focalplane in Kelvin unit +specified with the attribute +:attr:`.GainDriftParams.focalplane_Tbath_K`. + +The following example shows the comparison of thermal gain drift +factor with or without detector mismatch over 100 seconds. + +.. plot:: pyplots/thermalgain_demo.py + :include-source: + +In the plots above, when there is no detector mismatch, +``det_A_wafer_1`` and ``det_B_wafer_1`` have the same gain drift +factor as they belong to the same focal plane group (grouped by wafer +name). However, when the detector mismatch is enabled, the two gain +drift factors have the same shape due to the same underlying noise +timestream but differ slightly in amplitude due to an additional +random mismatch factor. + +.. _gd-api-reference: + +API reference +------------- + +.. automodule:: litebird_sim.gaindrifts + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/hwp.rst.txt b/docs/build/html/_sources/hwp.rst.txt new file mode 100644 index 00000000..1f1a4256 --- /dev/null +++ b/docs/build/html/_sources/hwp.rst.txt @@ -0,0 +1,71 @@ +Ideal Half Wave Plate +===================== + +The rotation of the polarization angle induced by a HWP can be +included in the returned pointing information by passing an instance +of a descendant of the class :class:`.HWP` to the method +:meth:`.Simulation.set_hwp`. Here is an example:: + + import litebird_sim as lbs + + sim = lbs.Simulation( + start_time=0, + duration_s=100.0, + random_seed=12345, + ) + + sim.set_scanning_strategy( + lbs.SpinningScanningStrategy( + spin_sun_angle_rad=0.785_398_163_397_448_3, + precession_rate_hz=8.664_850_513_998_931e-05, + spin_rate_hz=0.000_833_333_333_333_333_4, + start_time=sim.start_time, + ), + delta_time_s=60, + ) + + sim.set_instrument( + instrument = lbs.InstrumentInfo( + boresight_rotangle_rad=0.0, + spin_boresight_angle_rad=0.872_664_625_997_164_8, + spin_rotangle_rad=3.141_592_653_589_793, + ) + ) + + sim.set_hwp( + lbs.IdealHWP(ang_speed_radpsec=4.084_070_449_666_731), + ) + + det = lbs.DetectorInfo( + name="Boresight_detector", + sampling_rate_hz=1.0, + quat=[0.0, 0.0, 0.0, 1.0], + ) + obs, = sim.create_observations(detectors=[det]) + + sim.prepare_pointings() + + pointing, hwp_angle = sim.observations[0].get_pointings() + +This example uses the :class:`.IdealHWP`, which represents an ideal +spinning HWP. The method :func:`.get_pointings` returns both pointing +and hwp angle on the fly. As shown before a similar syntax allow to +precompute the pointing and the hwp angle:: + + sim.prepare_pointings() + sim.precompute_pointings() + + sim.observations[0].pointing_matrix.shape + sim.observations[0].hwp_angle.shape + +This fills the fields `pointing_matrix` and `hwp_angle` for all the +observations in the current simulation. + + +API reference +------------- + +.. automodule:: litebird_sim.hwp + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/hwp_sys.rst.txt b/docs/build/html/_sources/hwp_sys.rst.txt new file mode 100644 index 00000000..09745d00 --- /dev/null +++ b/docs/build/html/_sources/hwp_sys.rst.txt @@ -0,0 +1,189 @@ +.. _hwp_sys: + +HWP systematics +=============== + +This module implements HWP non-idealities both using Jones’ formalism +(as described in `Giardiello et al. 2021 +`_) and Mueller’s. In Jones’ +formalism, a non-ideal HWP is described by + +.. math:: + :label: non_ideal_hwp_jones + + J_{\text{HWP}} = \begin{pmatrix} 1+h_{1} & \zeta_{1} e^{i \chi_1}\\ \zeta_{2} e^{i \chi_2}& -(1+h_{2}) e^{i \beta} \\ \end{pmatrix} + +where: + +* :math:`h_1` and :math:`h_2` are the efficiencies, describing the deviation from the unitary + transmission of light components :math:`E_x`, :math:`E_y`. In the ideal case, + :math:`h_1 = h_2 = 0`; + +* :math:`\beta=\phi-\pi`, where :math:`\phi` is the phase shift between the two + directions. It accounts for variations of the phase difference between :math:`E_x` + and :math:`E_y` with respect to the nominal value of :math:`\pi` for an ideal HWP. + In the ideal case, :math:`\beta=0`; + +* :math:`\zeta_{1,2}` and :math:`\chi_{1,2}` are amplitudes and phases of the + off-diagonal terms, coupling :math:`E_x` and :math:`E_y`. In practice, if the + incoming wave is fully polarized along x(y), a spurious y(x) component would + show up in the outgoing wave. In the ideal case, :math:`\zeta_{1,2}=\chi_{1,2}=0`. + +In the Mueller formalism, we have a general matrix + +.. math:: + :label: non_ideal_hwp_mueller + + J_{\text{HWP}} = \begin{pmatrix} M^{TT} & M^{TQ} & M^{TU} \\ M^{QT} & M^{QQ} & M^{QU} \\ M^{UT} & M^{UQ} & M^{UU} \\ \end{pmatrix} + +which, in the ideal case, would be + +.. math:: + :label: ideal_hwp_mueller + + J_{\text{HWP}} = \begin{pmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & -1 \\ \end{pmatrix} + +In the most general case, the Jones non-ideal parameters and the Mueller matrix elements can vary inside a bandpass. + +The class :class:`.hwp_sys.HwpSys` is a container for the parameters +of the HWP systematics. It defines three methods: + +* :meth:`.hwp_sys.HwpSys.set_parameters`, which sets the defaults and + handles the interface with the parameter file of the simulation. The + relevant section is tagged by ``[hwp_sys]``. The HWP parameters can + be passed both in the Jones' and Mueller's formalism. This choice is + regulated by the flag ``mueller_or_jones``, which can have the + values ``"mueller"`` or ``"jones"``. In case the Jones formalism is + chosen, it is converted automatically into the Mueller one through + the function ``.hwp_sys.JonesToMueller``. There is also the + possibility of passing precomputed input maps (as a NumPy array) + through the ``maps`` argument. Otherwise, the code computes input + maps through the module Mbs (see :ref:`Mbs`). The argument + ``integrate_in_band`` sets whether to perform band integration in + the TOD computation; if ``built_map_on_the_fly = True``, the + map-making can be performed internally (instead of using the + litebird_sim binner); ``correct_in_solver`` sets whether non-ideal + parameters can be used in the map-making (map-making assuming a + non-ideal HWP, generally using different HWP non-ideal parameters + than the one used in the TOD, representing our estimate of their + true value); ``integrate_in_band_solver`` regulates whether band + integration is performed in the map-making (to compute the + :math:`B^T B` and :math:`B^T d` terms, see below). + +* :meth:`.hwp_sys.HwpSys.fill_tod` which fills the tod in a given Observation. The ``pointings`` + angles passed have to include no rotating HWP, since the effect of the rotating HWP to the + polarization angle is included in the TOD computation. + The TOD is computed performing this operation: + + .. math:: + + d_{\text{obs}}\left(t_{i}\right)\,=\,\frac{\int d\nu\,\frac{\partial BB(\nu,T)}{\partial T_{\text{CMB}}}\,\tau\left(\nu\right)\,M_{i}^{TX}(\nu)\left(m_{\text{CMB}}+m_{\text{FG}}\left(\nu\right)\right)}{\int d\nu \frac{\partial BB(\nu,T)}{\partial T_{\text{CMB}}}\,\tau \left(\nu\right)}, + + where :math:`\tau(\nu)` is the bandpass, + :math:`\frac{\partial BB(\nu,T)}{\partial T_{\text{CMB}}}` converts from CMB thermodynamic temperature + to differential source intensity (see eq.8 of https://arxiv.org/abs/1303.5070) and + :math:`M_{i}^{TX}(\nu)` is the Mueller matrix element including the non-ideal HWP. + + If ``built_map_on_the_fly = True``, the code computes also + + .. math:: + + m_{\text{out}} = {\,\left(\sum_{i} B_{i}^{T} B_{i} \right)^{-1} \left( \sum_{i} B_{i}^{T} d_{\text{obs}}(t_{i}) \right)}, + + where the map-making matrix is + + .. math:: + + B^X = \left(\frac{\int d\nu \,\frac{\partial BB(\nu,T)}{\partial T_{\text{CMB}}}\,\tau_{s}\left(\nu\right)\,M_{i,s}^{TX}(\nu)}{\int d\nu \frac{\partial BB(\nu,T)}{\partial T_{\text{CMB}}}\,\tau_{s}\left(\nu\right)}\,\right). + + :math:`\tau_s(\nu)` and :math:`M_{i,s}^{TX}(\nu)` are the estimate of + the bandpass and Mueller matrix elements used in the map-making. + +* :meth:`.hwp_sys.HwpSys.make_map` which can bin the observations in a map. This is available only + if ``built_map_on_the_fly`` variable is set to ``True``. With this method, it is possible to + include non-ideal HWP knowledge in the map-making procedure, so use that instead of the general + ``litebird_sim`` binner if you want to do so. + +Defining a bandpass profile in ``hwp_sys`` +------------------------------------------ + +It is possible to define more complex bandpass profiles than a top-hat when using ``hwp_sys``. +This can be done both for the TOD computation (:math:`\tau`) and the map-making procedure +(:math:`\tau_s`). All you have to do is create a dictionary with key "hwp_sys" in the parameter +file (a toml file) assigned to the simulation: + +.. code-block:: python + + sim = lbs.Simulation( + parameter_file=toml_filename, + random_seed=0, + ) + +The dictionary under the key "hwp_sys" will also contain the paths to the files from which the HWP +parameters are read in the multifrequency case (under the keys "band_filename/band_filename_solver"), or their values in the single frequency one. See the notebook ``hwp_sys/examples/simple_scan`` +for more details. +To define the bandpasses to use, you need to have a dictionary with key "bandpass" +(for :math:`\tau`) or "bandpass_solver" (for :math:`\tau_s`) under "hwp_sys": + +.. code-block:: python + + paramdict = {... + "hwp_sys": {... + "band_filename": path_to_HWP_param_file, + "band_filename_solver": path_to_HWP_solver_param_file, + "bandpass": {"band_type": "cheby", + "band_low_edge": band_low_edge, + "band_high_edge": band_high_edge, + "bandcenter_ghz": bandcenter_ghz, + "band_ripple_dB": ripple_dB_tod, + "band_order": args.order_tod}, + "bandpass_solver": {...}, + ...}} + +The above example is for a bandpass with Chebyshev filter, but there are other parameters to define +different bandpass profile. It is important to define the "band_type", which can be "top-hat", +"top-hat-exp", "top-hat-cosine" and "cheby" (see the ``bandpass`` module for more details) +and the band edges, which define the frequency range over which the bandpass transmission +is close or equal to 1. If not assigned, the "band_type" is automatically set to "top-hat" +and the band edges will correspond to the limits of the frequency array used (which, in the +``hwp_sys`` module, is read from the HWP parameter files). There are default values also for +the parameters defining the specific bandpass profiles (see the +``hwp_sys/hwp_sys/bandpass_template_module`` code). + +There is also the possibility to read the bandpass profile from an external file, which has to be +a .txt file with two columns, the frequency and the bandpass transmission. It is important that the +frequency array used for "bandpass/bandpass_solver" coincides with the ones passed in the +"band_filename/band_filename_solver" file. Here is how to pass the bandpass file: + +.. code-block:: python + + paramdict = {... + "hwp_sys": {... + "band_filename": path_to_HWP_param_file, + "band_filename_solver": path_to_HWP_solver_param_file, + "bandpass": {"bandpass_file": path_to_bandpass_file }, + "bandpass_solver": {"bandpass_file": path_to_bandpass_solver_file}, + ...}} + +You can find more examples for the bandpass construction in the ``hwp_sys/examples/simple_scan`` notebook. + +API reference +------------- + +HWP_sys +~~~~~~~ + +.. automodule:: litebird_sim.hwp_sys.hwp_sys + :members: + :show-inheritance: + :private-members: + :member-order: bysource + +Bandpass template +~~~~~~~~~~~~~~~~~ + +.. automodule:: litebird_sim.hwp_sys.bandpass_template_module + :members: + :show-inheritance: + :private-members: + :member-order: bysource diff --git a/docs/build/html/_sources/imo.rst.txt b/docs/build/html/_sources/imo.rst.txt new file mode 100644 index 00000000..3c01e2e7 --- /dev/null +++ b/docs/build/html/_sources/imo.rst.txt @@ -0,0 +1,293 @@ +.. _imo: + +The IMo +======= + +To run a realistic simulation of an instrument, one needs to known its +details: the noise level of the detectors, the angular resolution of +the beams, etc. This kind of information is stored in an «Instrument +Model database», called IMo, and the LiteBIRD Simulation Framework +provides a few facilities to access it. The code makes use of the +library `Libinsdb `_; please +refer to its `User's manual `_ +for further information. + +.. note:: + + The type of information stored in the LiteBIRD Instrument Model + Database is extremely diverse: it goes from CAD/optical/thermal + models to high-level parameters representing some general + characteristics of the instrument. + + The LiteBIRD Simulation Framework enables to access any information + stored in the IMO, but it only provides full support for those + parameters that are actually used by the framework itself. (As an + example, you can use this interface to download a CAD file, but the + framework does not implement any facility to render/analyze the + file.) + +Let's start from a simple example, which will guide us in the +following paragraphs:: + + from litebird_sim import Imo + + imo = Imo(flatfile_location="/storage/litebird/my_imo") + scan_params = imo.query( + "/releases/v1.3/satellite/scanning_parameters" + ) + metadata = scan_params.metadata + print(metadata["spin_sun_angle_deg"]) + + # Output: the angle between the sun and the spin axis, in degrees + + +This example shows how to retrieve the parameters of the scanning +strategy followed by the LiteBIRD spacecraft. Everything revolves +around the class :class:`.Imo`, which reads the IMO from the files +saved in the folder ``/storage/litebird/my_imo``. + +The call to ``imo.query`` retrieves a specific bit of information; +note that the access to the parameters is done using a file-like path, +``/releases/v1.3/satellite/scanning_parameters``. This is not a real +file, but a way to tell the IMO which kind of information is +requested: the ``/releases/v1.3`` specifies the IMO version to use, +and the remaining path ``/satellite/scanning_parameters`` points to +the information you're looking for. + + +.. _imo-configuration: + +Configuring the IMO +------------------- + +The LiteBIRD Simulation Framework comes with a bundled IMO, which contains +only public information about the mission and the instruments. It was built +using the numbers reported in the paper +`Probing cosmic inflation with the LiteBIRD cosmic microwave background +polarization survey `_ +(PTEP, 2022). You can use this database by passing the path +``lbs.PTEP_IMO_LOCATION`` to the constructor of the :class:`.Imo` class:: + + imo = lbs.Imo(flatfile_location=lbs.PTEP_IMO_LOCATION) + +However, to run serious simulations you should grab a copy of the +official IMO database released by the IMO team and install it on your +computer. If you just need basic information, it is enough to download +the JSON file associated with any data release from the site +https://litebirdimo.ssdc.asi.it (authentication is required). For +extensive simulations that use data files like beams and bandpasses, +you should ask the ASI SSDC for a tarball bundle containing the whole +database and decompress it in a folder on your computer. + +Assuming that you put the JSON file or the decompressed tarball on a +local folder like ``/storage/litebird_imo``, run the following +command: + +.. code-block:: text + + python -m litebird_sim.install_imo + +This is an interactive program that lets you to configure the IMO. +Choose the item “local copy” and specify the folder. Save the changes +by pressing ``s``, and you will have your IMO configured. + +To sum up, there are three possibilities to access an IMO: + +1. Use the bundled PTEP IMO by passing + ``flatfile_location=lbs.PTEP_IMO_LOCATION`` to the constructor of + the :class:`.Imo` class. In this case, the only IMO release tag + that you will see is ``vPTEP``. + +2. Download a JSON file from the ASI SSDC website, save it in a folder + and run ``python -m litebird_sim.install_imo`` to make it visible. + In this case, only basic information will be available. + +3. Ask the ASI SSDC for a bundled tarball containing one or more IMO + versions. Decompress the tarball in a folder and run ``python -m + litebird_sim.install_imo`` to make it visible. + + +Local/remote access to the IMO +---------------------------------- + +The IMO can be accessed either through an Internet connection or by +reading it directly from a file. Each approach has its own advantages +and disadvantages: + +1. Having the IMO saved in a local file (like in the example above) is + the fastest way to access its contents. However, it might not + contain the latest version of the data you're looking for. Note + that the framework does not require that a *full* database be + available, as only the data that are actually needed in a + simulation are retrieved: for this reason, you might opt to + download a reduced version of the IMO containing only those + high-level parameters that you want to use in your simulation. + +2. Using a remote IMO through an Internet connection ensures that you + have access to the most updated version of the instrument; however, + accessing it can be slow, and you're out of luck if your internet + connection is unstable. Here is an example:: + + from litebird_sim import Imo + + imo = Imo( + url="https://dummy-litebird-imo.org", + user="username", + password="12345", + ) + scan_params = imo.query( + "/releases/v1.3/satellite/scanning_parameters" + ) + +Once the :class:`.Imo` object has been created, accessing information +follows the same rules and uses the same syntax for paths. + + +How objects are stored +---------------------- + +Information in the IMO is stored using a hierarchical format, and +every datum is versioned. There are three fundamental concepts that +you need to grasp: + +1. The IMO can store data files and Python-like dictionaries, called + «metadata». +2. Different versions of the same data file can be kept at the same + time in the IMO; we use the term **quantity** to refer to a data + file but we don't care about its version. +3. Quantities can be stored in hierarchical structures, using the + concept of **entity**, which enable to structure entities in a + tree-like shape. + +Here is an example: + +.. code-block:: text + + satellite + | + +--- spacecraft + | | + | +--- *scanning_parameters* + | + +--- LFT + | | + | +--- 40_ghz + | | | + | | +--- *noise_characteristics* + | | | + | | +--- *band_response* + | | + | +--- 50_ghz + | | | + | | +--- *noise_characteristics* + | | | + | | +--- *band_response* + | ... + | + +--- MFT + | | + | ... + | + +--- HFT + | + ... + +The diagram above shows how different quantities (marked using +asterisks in the diagram: ``scanning_parameters``, +``noise_characteristics``, ``band_response``) can be structured in a +tree-like structure using entities (the branches in the tree, e.g., +``40_ghz``, ``LFT``). Different data files can be associated with +quantities like ``BAND_RESPONSE``. + +In the code example at the top of this page, we accessed the scanning +strategy parameters using the string +``/releases/v1.3/satellite/scanning_parameters``. The meaning of the +string in terms of entities, quantities, and data files is the +following: + +1. ``Satellite`` is an entity; +2. ``scanning_parameters`` is a quantity, because it's the last part + of the path; +3. Of all the possible versions of the data file that have been saved + in the quantity ``scanning_parameters``, we're asking for the one + that is part of IMO 1.3 (the string ``v1.3`` in the path). + +Apart from paths like +``/releases/v1.3/satellite/scanning_parameters``, there is a more +low-level method to access data files, using UUIDs. Each quantity and +each datafile is identified by a unique UUID, an hexadecimal string +that it's granted to be unique. This string is assigned automatically +when information is added to the IMO, and it can be used to retrieve +the information later. In Python, you can use the ``UUID`` type from +the `uuid library `_ to +encode this information from a string:: + + from uuid import UUID + + # The string below is usually read from a parameter file + my_uuid = UUID("5b9e3155-72f2-4e18-95d4-9881bc3e592d") + + # Use "my_uuid" to access data in the IMO + scan_params = imo.query(my_uuid) + +The advantage of the latter method is that you can access data files +that have not been formally included in a versioned IMO. + +Browsing the IMO database +------------------------- + +The LiteBIRD Simulation Framework provides a text-mode program to +navigate the contents of the IMO. You can start it using the following +command: + +.. code-block:: text + + python3 -m litebird_sim.imobrowser + +Here is a short demo of its capabilities: + +.. asciinema:: imobrowser.cast + :preload: 1 + +When «opening» a data file, you can copy either the full path of the +data file or its UUID (the hexadecimal string uniquely identifying it) +in the clipboard: this can be handy when you are developing codes that +need to access specific objects. On Ubuntu, clipboard copying only +works if you have ``xclip`` or ``xsel`` installed; on +Ubuntu/Mint/Debian Linux, you can install ``xclip`` with the following +command: + +.. code-block:: text + + sudo apt-get install xclip + +If ``xclip`` is not installed, clipboard functions are automatically +disabled. + + +IMO and reports +--------------- + +When constructing a :class:`.Simulation` object, you should pass an +instance of an :class:`.Imo` class. In this way, simulation modules can +take advantage of an existing connection to the IMO. + +This has the additional advantage that the report produced at the end +of the simulation will include a list of all the data files in the IMO +that were accessed during the simulation. + +There are cases when you want to query some information from the IMO, +but you do not want it to be tracked. (For instance, you are just +navigating through the tree of entities but are not going to *use* the +quantities you are querying.) In this case, you can pass +``track=False`` to the :meth:`.Imo.query` method: the object you have +queried will not be included in the final report produced by the +:class:`.Simulation` object. + +API reference +------------- + +.. automodule:: litebird_sim.imo + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/index.rst.txt b/docs/build/html/_sources/index.rst.txt new file mode 100644 index 00000000..eecfc6f6 --- /dev/null +++ b/docs/build/html/_sources/index.rst.txt @@ -0,0 +1,29 @@ +.. litebird_sim documentation master file, created by + sphinx-quickstart on Tue Jan 14 10:10:16 2020. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +LBS User’s Manual +======================================== + +This is the User’s Manual of the LiteBIRD Simulation Framework. + +.. toctree:: + :maxdepth: 2 + :caption: Parts + + part1.rst + part2.rst + part3.rst + part4.rst + part5.rst + part6.rst + appendix.rst + installation + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/build/html/_sources/installation.rst.txt b/docs/build/html/_sources/installation.rst.txt new file mode 100644 index 00000000..4c3139f9 --- /dev/null +++ b/docs/build/html/_sources/installation.rst.txt @@ -0,0 +1,104 @@ +.. _installation_procedure: + +Installation +============ + +The framework is `registered on PyPI `_, +it can be installed with the following procedure: + +.. code-block:: text + + # Create a directory where you're going to write your scripts, + # notebooks, etc. + mkdir -p ~/litebird && cd ~/litebird + + # Create a virtual environment with + virtualenv lbs_env + # or with + python3 -m venv lbs_env + + # Activate the environment + . lbs_env/bin/activate + + # Finally install litebird_sim with pip + pip install litebird_sim + +When the command is completed, check that everything works by issuing +the following command at the terminal prompt: + +.. code-block:: text + + python -c "import litebird_sim" + +A similar procedure can be used with conda: + +.. code-block:: text + + # Create a conda environment + conda create -n lbs_env python=3.9 + + # Activate the environment + conda activate lbs_env + + # Finally install litebird_sim with pip + pip install litebird_sim + + +Hacking litebird_sim +-------------------- + +To develop ``litebird_sim``, you can create an enviroment, as described +above, then checkout and install a local copy of the framework. + +.. code-block:: text + + # Create a virtual environment and activate it + virtualenv my_venv && . my_venv/bin/activate + + # First clone the code + git clone https://github.com/litebird/litebird_sim litebird_sim + + # Then install it with pip + cd litebird_sim && pip install . + + +Run code validators +~~~~~~~~~~~~~~~~~~~ + +As every commit and pull request is validated through `ruff +`_, you might want to run them +before pushing modifications to the GitHub repository. In this case +enter the ``litebird_sim`` directory and run the following command: + +.. code-block:: text + + # Always remember to activate your virtual environment! + . my_venv/bin/activate + + # Install some useful hooks for git + pre-commit install + +What this command does is to install a few «pre-commit» hooks: they +are programs that are run whenever you run ``git commit`` and do some +basic checks on your code before actually committing it. These checks +are the same that are run by GitHub once you push your changes in a +pull request, so they can save you several back-and-forth iterations. + + +Development with MPI +~~~~~~~~~~~~~~~~~~~~ + +As explained in the chapter :ref:`using_mpi`, the LiteBIRD Simulation +Framework supports MPI. To use it, you must ensure that `mpi4py +`_ is installed. + +If you have created a virtual environment to work with +``litebird_sim`` (as you should have), just install it using ``pip``: + +.. code-block:: text + + pip install mpi4py + +That's it: the next time you run a script that uses ``litebird_sim``, +MPI functions will be automatically enabled in the framework. See the +chapter :ref:`using_mpi` for more details. diff --git a/docs/build/html/_sources/integrating.rst.txt b/docs/build/html/_sources/integrating.rst.txt new file mode 100644 index 00000000..1e776bad --- /dev/null +++ b/docs/build/html/_sources/integrating.rst.txt @@ -0,0 +1,243 @@ +Integrating existing codes +========================== + +In this section, we provide details about how to integrate existing +codes in the LiteBIRD Simulation Framework, be they general-purpose +libraries or modules specifically developed for LiteBIRD. + +What kind of codes can be integrated +------------------------------------ + +The LiteBIRD simulation framework provides a set of tools to simulate +the stages of data acquisition of the instrument onboard the +spacecraft. Therefore, any code that helps in producing synthetic +sample output can be integrated in the framework, in principle. +Examples of codes are: + +1. Simulation of ADC non-linearities; +2. Injection of gain drifts; +3. Convolution of the sky signal with beam functions; +4. Generation of noise; +5. Et cetera. + +Data *analysis* codes (in opposition to *simulation* codes producing +synthetic data) can be integrated, provided that they help in +producing larger simulations for the kind of analysis done by the +Joint Study Groups (JSGs). This means that the following codes can be +integrated, even if the are analysis codes rather than simulation +codes: + +1. Map-making; +2. Component separation; +3. Etc. + +If you plan to import a general-purpose library, the code should be +available as Python modules that can be installed using ``pip``; +therefore, they should be available on the `Python Package Index +`_. On the other hand, if the code you want to +integrate is specific to LiteBIRD, it can probably be integrated +directly into the `litebird_sim` codebase. + +How to integrate codes +---------------------- + +General-purpose libraries that are installable using ``pip`` should +not be copied in the `litebird_sim` repository, unless there is some +strong reason to do so; instead, the library should be added to the +list of dependencies using the command ``poetry add LIBRARY_NAME``. If +the library is specific to LiteBIRD, it is probably better to import +it into the repository https://github.com/litebird/litebird_sim. + +Once the code is available, either as a dependency or as some code in +the repository, the author must implement a class that wraps the +functionality of the library and accesses input/output parameters +through the :class:`.Simulation` class. Writing this wrapper ensures +the following properties: + +1. Input parameters can be easily read from the IMO; +2. Output files are saved in the directory specified by the user for + the simulation; +3. The wrapper can provide functions to write automatic reports. + +If you are writing some code from scratch for `litebird_sim`, it is +advisable to implement this approach and split it into a low-level +part that performs all the calculations, and a high-level part that +takes its data using the :class:`.Simulation` class. In this way, the +low-level part can be called directly from the terminal without the +hassle to create a :class:`.Simulation` object, which is easier for +debugging. + +Finally, you should add some automatic tests and put them in the +folder ``litebird_sim/test`` in files whose name matches the pattern +``test_*.py``: in this way, they will be called automatically after +every commit and will ensure that your code works as expected on the +target machine. + + +A practical example +------------------- + +Imagine you have developed a robust noise generation module, which has +the following structure:: + + # File mynoisegen.py + + class NoiseGenerator: + def __init__(self, seed): + self.seed = seed + + def generate_noise(self): + # Generate some noise + return ... + +To integrate this module in the LiteBIRD Simulation Framework, you +might want to write a wrapper class to ``NoiseGenerator`` that has an +interface of this kind:: + + import litebird_sim as lbs + import numpy as np + import mynoisegen # This is the library we're integrating + + class MyWrapper: + def __init__(self, sim: lbs.Simulation): + self.sim = sim + + # To decide which seed to pass to "NoiseGenerator", + # take advantage of the "sim" object. Here we + # assume to load it using the "seed" key in a + # section named "noise" + self.seed = self.sim.parameters["noise"].get("seed", 1234) + + # Initialize your code + self.generator = mynoisegen.NoiseGenerator(seed) + + def run(self): + # Call the function doing the *real* work + self.noise = self.generator.generate_noise() + + self.sim.append_to_report(""" + # Noise generation + + The noise generator produced {num_of_points} points, + with an average {avg} and a standard deviation {sd}. + """, + num_of_points=len(self.noise), + avg=np.mean(self.noise), + sd=np.std(self.noise), + ) + + # Now use "self.noise" somehow! + ... + +The interface implements the following features, which were missing in +the class ``NoiseGenerator``: + +- It loads the seed of the generator from the parameter file passed by + the user; the noise generator is likely to be used in a wider + pipeline, and this ensures that parameters to ``NoiseGenerator`` can + be kept with any other input parameter. The TOML parameter file + could be the following: + + .. code-block:: text + + [noise] + seed = 6343 + + [scanning_strategy] + parameters = "/releases/v1.3/Satellite/scanning_parameters/" + + [map_maker] + nside = 512 + + The code above accesses the field ``sim.parameters``, which is a + Python dictionary containing the parsed content of the TOML file; + the call to the standard `get` method ensures that a default + value (1234) is used if parameter ``seed`` is not found in the TOML + file, but in the example above it would retrieve the number + ``6343``. Note that the wrapper class does not need to deal with the + other sections in the file (``scanning_strategy``, ``map_maker``): + they are handled by other modules in the pipeline. See + :ref:`parameter_files`. + +- It produces a section in the report output by the framework, which + contains some statistics about the generated noise (number of + samples, average, standard deviation). See :ref:`report-generation`. + +Finally, we must add some tests. If the ``NoiseGenerator`` class is +expected to produce zero-mean output, then you might check that this +is indeed the case:: + + # Save this in file litebird_sim/test/test_noise_generator.py + + import numpy as np + import litebird_sim as lbs + + def test_noise_generator(): + sim = lbs.Simulation(random_seed=12345) + noisegen = MyWrapper(sim) + noisegen.run() + + # Of course, in real-life codes you would implement a + # much more robust check here… + assert np.abs(np.mean(noisegen.noise)) < 1e-5 + + +Checklist +--------- + +Here we list what any developer should check before integrating their +codes in the LiteBIRD Simulation Framework: + +1. You must not leave sensitive information in the code (e.g., + hardcoded noise levels): anything related to a quantitative + description of the instrument should be loaded from parameter files + or from the Instrument Model database. The best way to do this is + to delegate the loading of input parameters in a wrapper class that + uses a :class:`.Simulation` object (see above). + +2. All the *public* functions should be documented, either using + docstrings or other tools. You can put most of your effort in + documenting the wrapper class (in the example above, + ``MyWrapper``), as this is the public interface most of the people + will use. Prefer the + `numpy sphinx syntax + `_. + +3. All the measurement units should be stated clearly, possibly in + parameter/variable/function names. Consider the following + function:: + + def calc_sensitivity(t_ant): + # Some very complex calculation comes here + return f(t_ant, whatever...) + + The prototype does not help the user to understand what kind of + measurement units should be used for ``t_ant``, nor what is the + measurement unit of the value returned by the function. The + following is much better:: + + def calc_sensitivity_k_sqr_s(t_ant_k): + # The same calculations as above + return f(t_ant_k, whatever...) + + The second definition clarifies that the antenna temperature must + be specified in Kelvin, and that the result is in K⋅√s. + +4. If you want to produce logging message, rely on the `logging + library `_ in the + Python standard library. + +5. You **must** format your code using `ruff + `_. If you fail to do so, + your code cannot be merged in the framework, as we automatically + check its conformance every time a new pull request is opened. + +6. Similarly, your code must pass all the tests run by `ruff check`. + +7. Always implement some tests! + +8. If you are unsure about your python coding practices, the `Google + style guide + `_ + is a good resource. See also our `CONTRIBUTING file + `_ diff --git a/docs/build/html/_sources/map_scanning.rst.txt b/docs/build/html/_sources/map_scanning.rst.txt new file mode 100644 index 00000000..d82cdb58 --- /dev/null +++ b/docs/build/html/_sources/map_scanning.rst.txt @@ -0,0 +1,197 @@ +.. _mapscanning: + +Scanning a map to fill a TOD +============================ + +The framework provides :func:`.scan_map`, a routine which scans an +input map accordingly to the scanning strategy and fills the detector +timestreams. You can fill with signal an existing TOD by using the +function :func:`.scan_map_in_observations`, as the following example +shows: + +.. testcode:: + + import litebird_sim as lbs + import numpy as np + + hwp_radpsec = np.pi / 8 + start_time_s = 0 + time_span_s = 1 + + nside = 256 + npix = 12 * nside * nside + + # Create a simulation + sim = lbs.Simulation( + base_path="./output", + start_time=start_time_s, + duration_s=time_span_s, + random_seed=12345, + ) + + # Define the scanning strategy + sim.set_scanning_strategy( + lbs.SpinningScanningStrategy( + spin_sun_angle_rad=0.785_398_163_397_448_3, + precession_rate_hz=8.664_850_513_998_931e-05, + spin_rate_hz=0.000_833_333_333_333_333_4, + start_time=start_time_s, + ), + delta_time_s=7200, + ) + + sim.set_instrument( + lbs.InstrumentInfo( + boresight_rotangle_rad=0.0, + spin_boresight_angle_rad=0.872_664_625_997_164_8, + spin_rotangle_rad=3.141_592_653_589_793, + ), + ) + + # Create a detector object + det = lbs.DetectorInfo( + name="Detector", + sampling_rate_hz=10, + quat=[0.0, 0.0, 0.0, 1.0], + ) + + # Initialize the observation + (obs,) = sim.create_observations(detectors=[det]) + + # Prepare the quaternions used to compute the pointings + sim.prepare_pointings() + + # Create a map to scan (in realistic simulations, + # use the MBS module provided by litebird_sim) + maps = np.ones((3, npix)) + in_map = {"Detector": maps, "Coordinates": lbs.CoordinateSystem.Ecliptic} + + # Here scan the map and fill tod + lbs.scan_map_in_observations( + obs, + in_map, + input_map_in_galactic = False, + ) + + for i in range(obs.n_samples): + # + 0. removes leading minus from negative zero + value = np.round(obs.tod[0][i], 5) + 0. + print(f"{value:.5f}") + +.. testoutput:: + + 0.00000 + -0.00075 + -0.00151 + -0.00226 + -0.00301 + -0.00376 + -0.00451 + -0.00526 + -0.00601 + -0.00676 + + +The input maps to scan can be either included in a dictionary with the name of +the channel or the name of the dectector as keyword (the routines described in +:ref:`Mbs` already provied the inputs in the correct format), or a numpy array +with shape (3, n_pixels). + +The pointing information can be included in the observation or passed through +`pointings`. If both `observations` and `pointings` are provided, they must be +coherent, so either a single Observation and a single numpy array, or same +lenght list of Observations and numpy arrays. +If the input map is ecliptic coordinates set `input_map_in_galactic` to `False`. +The effect of a possible HWP is included in the pointing information, see +:ref:`scanning-strategy`. + +The routine provides an on-the-fly interpolation of the input maps. This option +is available through the argument `interpolation` which specifies the type of TOD +interpolation ("" for no interpolation, "linear" for linear interpolation). +Default: no interpolation. + + +Methods of the Simulation class +------------------------------- + +The class :class:`.Simulation` provides the function +:func:`.Simulation.fill_tods`, which takes a map and scans it. Using this with +:func:`.Simulation.add_noise`, the generation of a simulation becomes quite +transparent: + +.. testcode:: + + import litebird_sim as lbs + from astropy.time import Time + import numpy as np + + start_time = 0 + time_span_s = 1000.0 + sampling_hz = 10.0 + nside = 128 + + sim = lbs.Simulation( + start_time=start_time, + duration_s=time_span_s, + random_seed=12345, + ) + + # We pick a simple scanning strategy where the spin axis is aligned + # with the Sun-Earth axis, and the spacecraft spins once every minute + sim.set_scanning_strategy( + lbs.SpinningScanningStrategy( + spin_sun_angle_rad=np.deg2rad(0), + precession_rate_hz=0, + spin_rate_hz=1 / 60, + start_time=start_time, + ), + delta_time_s=5.0, + ) + + # We simulate an instrument whose boresight is perpendicular to + # the spin axis. + sim.set_instrument( + lbs.InstrumentInfo( + boresight_rotangle_rad=0.0, + spin_boresight_angle_rad=np.deg2rad(90), + spin_rotangle_rad=np.deg2rad(75), + ) + ) + + # A simple detector looking along the boresight direction + det = lbs.DetectorInfo( + name="Boresight_detector", + sampling_rate_hz=sampling_hz, + bandcenter_ghz=100.0, + net_ukrts=50.0, + ) + + sim.create_observations(detectors=det) + + sim.prepare_pointings() + + sky_signal = np.ones((3,12*nside*nside))*1e-4 + + sim.fill_tods(sky_signal) + + sim.add_noise(noise_type='white') + + for i in range(5): + print(f"{sim.observations[0].tod[0][i]:.5e}") + +.. testoutput:: + + 4.14241e-04 + 5.46700e-05 + 3.03378e-04 + 6.13975e-05 + 4.72613e-05 + + +API reference +------------- + +.. automodule:: litebird_sim.scan_map + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/mapmaking.rst.txt b/docs/build/html/_sources/mapmaking.rst.txt new file mode 100644 index 00000000..e234d3a2 --- /dev/null +++ b/docs/build/html/_sources/mapmaking.rst.txt @@ -0,0 +1,992 @@ +.. _mapmaking: + +Map-making +========== + +The primary aim of the LiteBIRD Simulation Framework is to create +synthetic timestreams as if the real LiteBIRD acquired them. +The process of creating maps out of these timelines is +called *map-making* and it is strictly considered a data-analysis +task, not a simulation task. However, since most of the assessments on +the quality of a timestream can only be done on maps, the LiteBIRD +Simulation Framework provides some facilities to produce maps out of +timestreams. These maps are created using the `Healpix +`_ pixelization scheme and +saved in FITS files. + +The framework provides the following solutions: + +1. A *binner*, i.e., a simple map-maker that assumes that only + uncorrelated noise is present in the timelines. + +2. A *destriper*, i.e., a more advanced map-maker that can remove the + effect of correlated instrumental noise from the timelines before + producing a map. We usually refer to correlated noise as 1/f + noise, and the purpose of the destriper is to estimate its + contribution and remove it from the timelines; then, a classical + *binner* is run over the cleaned timelines. + +3. A wrapper that enables you to use the + `TOAST2 `_ destriper. (To use + this, you must ensure that the package + `toast `_ is installed.) + +4. You can also use :func:`.save_simulation_for_madam` to save TODs + and pointing information to disk and then manually call the `Madam + map-maker `_. + +In this chapter, we assume you have already created the timelines +to be used as input to the destriper. Here is a sample code +that creates a simple timeline containing white noise for two +detectors:: + + import numpy as np + import astropy.units as u + from numpy.random import MT19937, RandomState, SeedSequence + + import litebird_sim as lbs + + sim = lbs.Simulation( + base_path="destriper_output", + start_time=0, + duration_s=86400.0, + random_seed=12345, + ) + + sim.set_scanning_strategy( + scanning_strategy=lbs.SpinningScanningStrategy( + spin_sun_angle_rad=np.deg2rad(30), # CORE-specific parameter + spin_rate_hz=0.5 / 60, # Ditto + # We use astropy to convert the period (4 days) in + # seconds + precession_rate_hz=1.0 / (4 * u.day).to("s").value, + ) + ) + instr = lbs.InstrumentInfo( + name="core", + spin_boresight_angle_rad=np.deg2rad(65), + ) + + # We create two detectors, whose polarization angles are separated by π/2 + sim.create_observations( + detectors=[ + lbs.DetectorInfo(name="0A", sampling_rate_hz=10), + lbs.DetectorInfo( + name="0B", sampling_rate_hz=10, quat=lbs.quat_rotation_z(np.pi / 2) + ), + ], + tod_dtype=np.float64, # Needed if you use the TOAST destriper + n_blocks_time=lbs.MPI_COMM_WORLD.size, + split_list_over_processes=False, + ) + + # Generate some white noise + rs = RandomState(MT19937(SeedSequence(123456789))) + for curobs in sim.observations: + curobs.tod *= 0.0 + curobs.tod += rs.randn(*curobs.tod.shape) + + +The paper :cite:`2009:kurkisuonio:destriping` explains +the theory of binners and destripers. Our implementation +closely follows the terminology used in the paper. In +this chapter, we will refer to the paper as KS2009. + +.. _mapmaking-binner: + +Binner +------ + +Once you have generated a set of observations, either on a single +process or distributed over several mpi processes, you can create a +simple binned map with the function :func:`.make_binned_map`. This function +takes one or more :class:`.Observation` objects and the Healpix +resolution of the output map (``nside``), and it produces a coadded map. +The algorithm assumes white noise and each detector gets weighted by +:math:`1 / \text{NET}^2`. If the pointing information is not provided in the +observation, it can be passed through the optional argument `pointings`, +with a syntax similar to :func:`.scan_map_in_observations`. +The output map is in Galactic coordinates, but you can specify the +coordinate system you want via the parameter `output_coordinates`. +This is how it should be called:: + + result = lbs.make_binned_map(nside=128, observations=observations) + healpy.mollview(result.binned_map[0]) + +The function obtains pointing information from the :class:`.Observation` +(either using the precomputed pointing or computed on the fly). As an +alternative, you can provide pointings as a list of numpy arrays. +The result is an instance of the class :class:`.BinnerResult` +and contains both the I/Q/U maps and the covariance matrix. + +Function :func:`.make_binned_map` has a high-level interface in the class +:class:`.Simulation` that bins the content of the observations into maps. +The syntax is identical to :func:`.make_binned_map`:: + + result = sim.make_binned_map(nside=nside) + healpy.mollview(result.binned_map[0]) + + +The :class:`.BinnerResult` class contains the field ``binned_map``, +which is a Healpix map containing the binned values of the samples. + +Using a simple example, we now explain how the function calculates +the binned map. Suppose we measured just two pixels in the map with +only seven measurements. (These numbers are ridiculous, but in this +way, the matrices we will write will be more manageable!) + +Each sample is associated with a direction in the sky (the *pointing +information*), but from the point of view of the binner, the only +quantity that matters is the pixel index in the map associated with +the pointing direction. In our example, the seven samples will measure +just two pixels in the sky with different attack (polarization) angles. +The following figure and table show how the seven TOD samples observe +the two pixels. The bars refer to the polarization angle of the detector. + +.. |pixel1| image:: images/destriper-pixel1.svg + +.. |pixel2| image:: images/destriper-pixel2.svg + +.. _todsamples-in-mapmaking-figure: +.. figure:: images/destriper-tod-angles.svg + + Toy-example of a TOD containing 7 samples. + + The figure shows how the seven samples observe each of the two pixels + in the map. The thick bars represent the direction of the attack angle + ψ, which coincides with the polarization angle of the detector projected + on the center of the pixel. + + +.. _todsamples-in-mapmaking-table: +.. list-table:: Samples in the TOD, their polarization angle ψ, and the pixel they hit + :header-rows: 1 + + * - # + - ψ + - Pixel + * - 1 + - 90° + - |pixel1| (1) + * - 2 + - 30° + - |pixel1| (1) + * - 3 + - 90° + - |pixel2| (2) + * - 4 + - 15° + - |pixel2| (2) + * - 5 + - 30° + - |pixel2| (2) + * - 6 + - 45° + - |pixel2| (2) + * - 7 + - 60° + - |pixel1| (1) + +A fundamental quantity in KS2009 is the *pointing matrix* matrix +:math:`P`: it is a :math:`(N_t, 3N_p)` matrix where :math:`N_t` +is the number of samples in the TOD (7 in our case) and +:math:`N_p` is the number of pixels observed by the TOD (2 in +our case). Thus, for our simple example :math:`P` is + +.. math:: + + P = \begin{pmatrix} + \textcolor{#002683}{1}& \textcolor{#002683}{\cos90^\circ}& \textcolor{#002683}{\sin90^\circ}& 0& 0& 0\\ + \textcolor{#002683}{1}& \textcolor{#002683}{\cos30^\circ}& \textcolor{#002683}{\sin30^\circ}& 0& 0& 0\\ + 0& 0& 0& \textcolor{#268300}{1}& \textcolor{#268300}{\cos90^\circ}& \textcolor{#268300}{\sin90^\circ}\\ + 0& 0& 0& \textcolor{#268300}{1}& \textcolor{#268300}{\cos15^\circ}& \textcolor{#268300}{\sin15^\circ}\\ + 0& 0& 0& \textcolor{#268300}{1}& \textcolor{#268300}{\cos30^\circ}& \textcolor{#268300}{\sin30^\circ}\\ + 0& 0& 0& \textcolor{#268300}{1}& \textcolor{#268300}{\cos45^\circ}& \textcolor{#268300}{\sin45^\circ}\\ + \textcolor{#002683}{1}& \textcolor{#002683}{\cos60^\circ}& \textcolor{#002683}{\sin60^\circ}& 0& 0& 0 + \end{pmatrix}, + +where the number of rows matches the number of samples in the TOD (7), and +for each pixel there are three columns corresponding to I, Q, and U. + +Enough samples must cover each pixel: there must be at least three +non-degenerate polarization angles to recover the three Stokes +parameters associated with it (I, Q, and U). KS2009 describes +how to compute them through the matrix +:math:`M = P^T\cdot C_w^{-1}\cdot P`, where :math:`C_w` is a diagonal +matrix with shape :math:`(N_t, N_t)`, where each element along the diagonal +is :math:`\sigma^2`, the white-noise variance of the sample: + +.. math:: + + C_w = \begin{pmatrix} + \sigma^2& 0& 0& 0& 0& 0& 0\\ + 0& \sigma^2& 0& 0& 0& 0& 0\\ + 0& 0& \sigma^2& 0& 0& 0& 0\\ + 0& 0& 0& \sigma^2& 0& 0& 0\\ + 0& 0& 0& 0& \sigma^2& 0& 0\\ + 0& 0& 0& 0& 0& \sigma^2& 0\\ + 0& 0& 0& 0& 0& 0& \sigma^2 + \end{pmatrix} + +The square matrix :math:`M = P^T\cdot C_w^{-1}\cdot P` has shape +:math:`(3 N_p, 3 N_p)`: each pixel in the map is associated to a +3×3 block along the diagonal: + +.. math:: + + M = \begin{pmatrix} + \textcolor{#002683}{\frac3{\sigma^2}}& + \textcolor{#002683}{-\frac1{\sigma^2}}& + \textcolor{#002683}{\frac{\sqrt3}{\sigma^2}}& + 0& 0& 0\\ + \textcolor{#002683}{-\frac1{\sigma^2}}& + \textcolor{#002683}{\frac3{2\sigma^2}}& + \textcolor{#002683}{0}& + 0& 0& 0\\ + \textcolor{#002683}{\frac{\sqrt3}{\sigma^2}}& + \textcolor{#002683}{0}& + \textcolor{#002683}{\frac{3}{2\sigma^2}}& + 0& 0& 0\\ + 0& 0& 0& + \textcolor{#268300}{\frac4{\sigma^2}}& + \textcolor{#268300}{\frac{\sqrt3 - 1}{2\sigma^2}}& + \textcolor{#268300}{\frac{\sqrt3 + 3}{2\sigma^2}}\\ + 0& 0& 0& + \textcolor{#268300}{\frac{\sqrt3 - 1}{2\sigma^2}}& + \textcolor{#268300}{\frac2{\sigma^2}}& + \textcolor{#268300}{\frac{\sqrt3}{2\sigma^2}}\\ + 0& 0& 0& + \textcolor{#268300}{\frac{\sqrt3 + 3}{2\sigma^2}}& + \textcolor{#268300}{\frac{\sqrt 3}{2\sigma^2}}& + \textcolor{#268300}{\frac2{\sigma^2}} + \end{pmatrix} + +If we assigned different values for :math:`\sigma` to the +seven elements along the diagonal of :math:`C_w`, then +the 3×3 subblocks of the :math:`M` matrix would have changed +to reflect the fact that those samples with smaller +:math:`\sigma` have a greater weight in determining the +value of the Stokes parameter. + +Once we have the matrices :math:`P`, :math:`C_w`, and :math:`M`, +determining the value of the three Stokes parameters I, Q, U for +each pixel is trivial. The signal :math:`s` measured by a detector +is a function of :math:`I`, :math:`Q`, :math:`U`, and the +polarization angle :math:`\psi`: + +.. math:: + + s = I + Q \cos2\psi + U\sin2\psi + +and thus we can exploit the redundancy of the measurements per +each pixel (remember: at least three measurements per pixel with +non-degenerate angles!) by running a simple :math:`\chi^2` minimization +assuming Gaussian noise. The result is the following: + +.. math:: + + m = M^{-1}\cdot P^T\cdot C_w^{-1}\cdot y, + +where :math:`y` is a 7-element vector containing the TOD samples +:math:`s_i` and :math:`m` is a 6-element vector containing the +estimates of the Stokes parameters I, Q, and U for the two pixels +in the map according to the following order: + +.. math:: + + m = \begin{pmatrix}I_1& Q_1& U_1& I_2& Q_2& U_2\end{pmatrix} + +The presence of the matrix :math:`C_w^{-1}` scales +the value of each sample so that the ones affected by larger noise +will be made smaller; the :math:`P^T` factor sums all the samples +in the TOD that fall within the same pixel, and finally :math:`M^{-1}` +“solves” the linear system for the three parameters I, Q, and U per +each pixel. + +Once the call to :func:`.make_binned_map` ends, the field ``invnpp`` +of the :class:`.BinnerResult` object returned by the function +contains an array with shape :math:`(N_p, 3, 3)`, where each +3×3 block is the inverse of the sub-matrix :math:`M_i` for the +*i*-th pixel. + + +Data splits +^^^^^^^^^^^ + + +The function :func:`.make_binned_map` is also able to provide data +splits both in time and in detector space. The time split is passed +through a string in the parameter `time_split`; the same applies for +`detector_split`. Currently, the supported time splits are: + +- ``"full"`` (default): no split. +- ``"first_half"``: use the first half of the TOD. +- ``"second_half"``: use the second half of the TOD. +- ``"odd"``: use the odd samples of the TOD. +- ``"even"``: use the even samples of the TOD. +- ``"yearX"``: use the TOD samples relative to the X-th year of observation. + The X-th part must be an integer between 1 and 3. +- ``"surveyX"``: assuming that a complete survey is performed in 6 months, + use the X-th survey. X must be an integer between 1 and 6. + +There are three supported options for the detector split: + +- ``"full"`` (default): no split is performed. +- ``"waferXXX"``: use the detectors in the wafer XXX. The XXX part + must specify the wafer (e.g. "L00", "M01", or "H02"). + +The final data split will correspond to the combination of the two splits. + +The function :func:`.check_valid_splits` will check whether the requested +split is part of the list above. If the split is not valid, the function +will raise a ``ValueError``. In addition, it will check whether the requested +split is compatible with the observation duration and the detector +list. Thus, for example, if the observation lasts one year, the split ``year2`` +will raise an ``AssertionError``. Similarly, if some detector in the L00 wafer +does the observation, ``waferL03`` will also raise an ``AssertionError``. + +.. _mapmaking-destriper: + +Destriper +--------- + +If you know that your simulation contains 1/f noise, you should avoid using +the binner and instead employ a better map-making algorithm. With 1/f noise, +the hypothesis that the noise be white drops and the binning equation is no +longer valid. + +The destriping algorithm is implemented by the :func:`.make_destriped_map`, +functionally equivalent to :func:`.make_binned_map`. However, as +the algorithm it implements is more complex, you must provide more +information when calling it. Specifically, you should instantiate an +instance of the class :class:`.DestriperParameters`:: + + params = DestriperParameters( + ... + ) + + result = lbs.make_destriped_map( + nside=nside, + observations=observations, + params=params, + ) + healpy.mollview(result.destriped_map) + +Pointing information is handled identically to :func:`.make_binned_map`. +The result is an instance of the class :class:`.DestriperResult`, which +is similar to :class:`.BinnerResult` but it contains much more information. + +Function :func:`.make_destriped_map` has a high level interface in the class +:class:`.Simulation` that applies the destriper algorithm to all the +observations in the simulation. +The syntax is identical to :func:`.make_destriped_map`:: + + result = sim.make_destriped_map(nside=nside) + healpy.mollview(result.destriped_map) + +We will now explain how a destriper works and what the meaning of each +parameter in the classes :class:`.DestriperParameters` and +:class:`.DestriperResult` is. Apart from KS2009, another source of information +is the file +`test/test_destriper.py `_: +it compares the results of the destriper with the analytical solution of the +simple 7-sample model we discuss here. + +The idea of a destriper is to group consecutive samples in the same TOD into +different *baselines*. Each baseline must contain a number of samples such that +the noise within it will be roughly white; thus, the baseline should not contain +too many samples! A good rule of thumb is to make the time span covered by +one baseline shorter than the inverse of the knee frequency for that detector. +The destriper works by assuming that the effect of 1/f noise on all the samples +within the baselines is a purely additive factor; this is an approximation, +but for experiments like Planck, it has worked well. + +.. figure:: images/destriper-baselines.svg + + Baselines in a destriper + + A destriper seeing the 13 samples in the figure could decide to group them + into two baselines: 7 within the first baseline, and 6 within the second + baseline. The level :math:`a_i` of each baseline is represented by a dashed + horizontal line. + +The purpose of the destriper is to model 1/f noise using the baselines and produce +a cleaned map. It does so by doing the following steps: + +1. It estimates the value of the baselines by minimizing a :math:`\chi^2` + function (more on this later); +2. It removes the value of the baselines from each sample in the TOD; +3. It applies a binning operation on the cleaned TOD, using the same equations + explained in the section :ref:`mapmaking-binner`. + +To provide a concrete example of how the destriper works, we will resume the toy +TOD containing seven samples that we discussed in the section :ref:`mapmaking-binner`. +We will group these samples into two baselines of 4 and 3 samples, respectively. + +It's easier to understand what the algorithm does if we play following the rules +of the destriper: instead of thinking of proper 1/f noise, let's pretend that what +we have in the TOD are the *baselines*: constant values that last for +some time and are added to the samples in the TOD. How can the destriper understand +the level of each baseline in the figure above? The trick is easy to understand +if we recall Table :ref:`todsamples-in-mapmaking-table`. Let's consider the first +pixel: it was measured by the first two samples, then the detector moved to another +position in the sky (the second pixel), but it moved back to the first pixel just +while measuring the last sample in the TOD (#7). The point is that the first two samples +#1 and #2 belong to the first baseline, since it lasts 4 pixels, while sample #7 belongs +to the second one. Yet, once we take 1/f noise into account, the estimated I/Q/U +parameter for this pixel must agree. We can rewrite this problem as a :math:`\chi^2` +minimization problem: the destriper looks for the values of the two baselines that +minimize the discrepancy in the values of the pixels as estimated by every sample in the TOD. + +Apart from the standard matrices :math:`C_w`, :math:`P`, and :math:`M`, the +destriper requires a new matrix :math:`F,` whose shape is :math:`(N_t, N_b)`: + +.. math:: + + F = \begin{pmatrix} + 1& 0\\ + 1& 0\\ + 1& 0\\ + 1& 0\\ + 0& 1\\ + 0& 1\\ + 0& 1 + \end{pmatrix} + +Don’t be fooled by the fact that the number of columns is the same as the number +of pixels in the map: in this case, the number of columns corresponds to the +number of *baselines* in the TOD! The operator :math:`F` tells which baseline +“owns” the samples in the TOD, and, as you can see, we are assigning +the first four samples to the first baseline and the last three samples to +the last baseline. Applying :math:`F` to a set of :math:`N_t` samples +(the TOD) produces a vector containing :math:`N_b` samples (the baselines). As +it is always the case that :math:`N_b \ll N_t,` this means that :math:`F` is +a very tall and narrow matrix! + +The purpose of :math:`F` is to “project” the values of the two baselines +into a 7-element TOD: + +.. math:: + + F \cdot \begin{pmatrix}a_1\\a_2\end{pmatrix} = + \begin{pmatrix} + a_1\\ a_1\\ a_1\\ a_1\\ a_2\\ a_2\\ a_2 + \end{pmatrix}. + +The transpose :math:`F^T` represents a linear operator that takes a 7-element +TOD and sums up all the elements belonging to the same baseline: + +.. math:: + + F^T \cdot \begin{pmatrix} + y_1\\ y_2\\ y_3\\ y_4\\ y_5\\ y_6\\ y_7 + \end{pmatrix} = + \begin{pmatrix}y_1 + y_2 + y_3 + y_4\\ y_5 + y_6 + y_7\end{pmatrix}. + +At the basis of the destriper there is the operator :math:`Z,` which is defined as +follows: + +.. math:: + + Z = I - P\cdot M^{-1}\cdot P^T\cdot C_w^{-1} + +(:math:`I` is the identity operator), and it is applied to a TOD: :math:`Z\cdot y`. +The purpose of the operator is to “clean up” the TOD from all the components that +are not white noise. It does so by creating a map (note the presence on the right +of the map-binning operator :math:`M^{-1}\cdot P^T\cdot C_w^{-1}` we discussed +before) and then scanning the map back into a TOD (the last operator :math:`P` +on the left of the map-binning operator). Because of the difference between +the identity operator :math:`I` and this matrix, the result of this +“map-and-scan” operation is subtracted from the TOD sample. + +The destriper estimates the vector of baselines :math:`a` by solving iteratively +the following equation, which is the solution of a minimization problem on the +:math:`\chi^2` we qualitatively discussed before: + +.. math:: + + \left(F^T\cdot C_w^{-1}\cdot Z\cdot F\right) a = F^T\cdot C_w^{-1}\cdot Z\cdot y + +This equation is of the form :math:`Ax = b,` where :math:`A` and :math:`b` are +known and :math:`x` is the quantity to determine. (The symbol :math:`A` is commonly +found in linear algebra textbooks, so we will switch to this notation even if +KS2009 uses :math:`D`.) The solution would just be :math:`x = A^{-1}b`, but +there are two problems with this formula: + +1. Matrix :math:`A` is too large to be inverted; +2. Matrix :math:`A` is not invertible! + +The second point is alarming, and it might sound like an irrecoverable +problem. The fact that :math:`\det A = 0` stems from the fact that the solution to +the destriping equation is not unique, as if :math:`a` is a solution, then +:math:`a + K` is still a solution for any scalar constant :math:`K.` (Remember, the +purpose of the destriper is to make the many measurements for I/Q/U within each +pixel *consistent* once it subtracts the baselines and the consistency is the same +if it shifts all the baselines by the same amount!) We are looking for +a solution :math:`a` that is orthogonal to the null space of :math:`A`. + +We can solve both problems by employing the fact that :math:`A` is a symmetric +semi-definite matrix and thus employing the so-called +`Conjugate Gradient (CG) method `_, +which is able to produce a solution for the problem :math:`Ax = b` even +if :math:`A` is singular, because it simply minimizes the quantity :math:`Ax - b` +without trying to invert :math:`A`. If you are curious about the details of +the algorithm, a good pedagogical reference is :cite:`1994:shewchuk:conjugategradient`. + +The way the algorithm works is to start from a guess for :math:`x` (the set of +baselines), which must be orthogonal to the null-space of :math:`A`; in our +case, it is enough to require that the mean value of the first guess of the baselines +is zero, i.e., :math:`\sum_i x_i = 0`. Starting from guess :math:`x^{(0)},` the +algorithm produces a new guess :math:`x^{(1)}` such that :math:`r^{(1)} = Ax^{(1)} - b` is +closer to zero than :math:`r^{(0)} = Ax^{(0)} - b`. The procedure keeps going until the +residual :math:`r^{(n)} = Ax^{(n)} - b` is small enough, always satisfying the +fact that :math:`\sum_i x_i^{(n)} = 0` for any :math:`n.` Thus, the final solution +will have the property that the average value of the baselines will be zero. + +The CG algorithm requires iterations to continue until the residuals :math:`r^{(n)}` +are “small enough”. But how can we tell this? Vector :math:`r^{(n)}` contains +:math:`N_b` elements, corresponding to the residual for each baseline, and it might +be that some of the residuals are small and some are not. The most common way to +deal with this is to compute some norm over :math:`r^{(n)}` to produce a +single scalar and then to check whether this value is below some threshold. +There are several possible definitions for the norm: + +1. The standard :math:`L_2` norm (Euclidean): :math:`\left\|r^{(n)}\right\|^2`; +2. The :math:`L_\infty` norm: :math:`\max_i\left|r_i^{(n)}\right|`. + +Function :func:`.make_destriped_map` adopts :math:`L_\infty`: it is +stricter than :math:`L_2`, because it does not wash out the +presence of a few large residuals even if all the others are negligible. + +The convergence of the Conjugated Gradient can be controlled by the +following fields in the :class:`.DestriperParameters` class: + +- ``iter_max``: the maximum number of iterations, i.e., the upper limit for :math:`n`. +- ``threshold``: the value to be reached by the :math:`L_\infty` norm when + applied to the residuals :math:`r^{(n)}`: if the norm is smaller than ``threshold``, + then the CG algorithm stops. +- ``samples_per_baseline``: this can either be an integer, in which case it will + be used for *all* the baselines, or a list of 1D arrays, each containing the + length of each baseline for each observation passed through the parameter + ``observations``. Note that if you provide an integer, it might be that not all + baselines will have exactly that length: it depends on whether the number + :math:`N_t` of samples in the TOD is evenly divisible by ``samples_per_baseline`` + or not. The algorithm tries to make the number of elements per baseline as evenly + as possible. +- ``use_preconditioner``: if this flag is ``True``, the preconditioning matrix + :math:`F^T\cdot C_w^{-1}\cdot F` will be used with the CG algorithm. This + might speed up the convergence. + +Once the destriper has completed the process, the following fields in the +:class:`.DestriperResult` object can be inspected to check how the CG iterations +went: + +- ``converged``: a Boolean flag telling whether the ``threshold`` was reached + (``True``) or not (``False``) +- ``history_of_stopping_factors``: a list of values of the :math:`L_\infty` + norm applied to the residuals :math:`r^{(n)},` one per each iteration of + the CG algorithm. Inspecting those values might help in understanding if + the destriper was not able to converge because of a too small value of + ``iter_max``. + +The baselines are saved in the field ``baselines`` of the :class:`.DestriperResult` +class; this is a list of 2D arrays, where each element in the list is +associated with one of the observations passed in the parameter ``observations``. The +shape of each 2D arrays is :math:`(N_d, N_b),` where +:math:`N_d` is the number of detectors for the observation and :math:`N_b` is +the number of baselines. A visual representation of the memory layout of +the field ``baselines`` is shown in the following figure. + +.. figure:: images/destriper-baselines-memory-layout.svg + + Memory layout for baselines. + +The image assumes the baselines contain data for two detectors, A and B. +In the **upper image**, the baselines are shown as horizontal lines that span +the time range covered by the two (consecutive) observations. We use continuous +lines for the baselines of detector A and dashed lines for B. +Note that the number of baselines in the first :class:`.Observation` object +(5) differs from the number in the second object (3). The **lower image** offers +a visual representation of the layout of the ``baselines`` field in the class +:class:`.DestriperResult`: a Python list of two elements, each containing 2D +arrays with shape :math:`(N_d, N_b)`. The *i*-th row of each 2D array contains +the baselines for the *i*-th detector. + +The field ``baseline_errors`` has the same memory layout as ``baselines`` +and contains a rough estimate of the error per each baseline, assuming that +the noise in the baselines is not correlated. (Unrealistic!) Finally, +the field ``baseline_lengths`` is a list of 1D integer arrays of :math:`N_b` +elements containing the number of samples in each baseline; it should match +the value provided in the field ``samples_per_baseline`` in the class +:class:`.DestriperParameters`. + +Once the destriper has computed a solution for the destriping equation, you +can ask to remove the baselines from the TOD using one of the functions +:func:`.remove_baselines_from_tod` or :func:`.remove_destriper_baselines_from_tod`. + +You can save the results of the destriper using the function +:func:`.save_destriper_results` and load them back again with the +:func:`.load_destriper_results`. Note that if you are running your code +using MPI, you should call both functions on *all* the MPI processes, +and the number of processes should be the same between the two calls. + +In addition, func:`.make_destriped_map` can accept a custom set of baselines +as input. The destriper will skip the CG iterations and proceed directly to +the map-making step if these have the correct dimensions. + +How the N_obs matrix is stored +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The destriper uses a different method to store the matrix :math:`M` in +memory. As the 3×3 sub-blocks of this matrix need to be inverted often +during the CG process, the function :func:`.make_destriped_map` decomposes +each block using `Cholesky decomposition +`_: each 3×3 +matrix :math:`M_i` associated with the pixels in the map is decomposed +into + +.. math:: + + M_i = L_i L_i^T, + +where :math:`L` is a lower-triangular matrix, and only the nonzero +coefficients of the matrix are saved. The advantages are twofold: + +1. Less memory is required; +2. Calculating the inverse :math:`M^{-1}` is faster and more accurate. + +The fields are saved in an instance of the class :class:`.NobsMatrix`, +which contains the following fields: + +- ``nobs_matrix`` is a 2D array of shape :math:`(N_p, 6)`, containing + the nonzero components of the :math:`N_p` matrices; +- ``valid_pixel`` is a 1D Boolean array containing :math:`N_p` elements: + each of them is ``True`` if the corresponding matrix :math:`M_i` + was invertible (i.e., it was observed with at least three non-degenerate + attack angles), ``False`` otherwise; +- ``is_cholesky`` is a flag that tells whether ``nobs_matrix`` contains + the nonzero coefficients of the many :math:`L_i` matrices or the + lower triangular part of :math:`M_i`. After a successful call to + :func:`.make_destriped_map`, this should always be ``True``: it is + set to ``False`` during the execution of the destriper but updated + to ``True`` before the CG iteration starts. + +Given that it is often useful to have access to matrix :math:`M^{-1}`, +the method :meth:`.NobsMatrix.get_invnpp` computes this inverse as +a 3D array with shape :math:`(N_pix, 3, 3)`, where the first index +runs over all the pixels and the last two dimensions are used to +store the value of :math:`M_i^{-1}`. + + +Data splits +^^^^^^^^^^^ + +Similarly to the function :func:`.make_binned_map`, +:func:`.make_destriped_map` too can provide data splits both in time +and detector space. Given that the splits are implemented in the same +way, refer to the documentation of :func:`.make_binned_map` for more +details. + +The difference is that each split is applied to the TOD before the +destriper runs. Therefore, the destriper will only use the samples +within the split to compute the baselines. + +TOAST2 Destriper +---------------- + +If you install the `toast `_ using ``pip``, +you can use the `TOAST2 `_ destriper within +the LiteBIRD Simulation Framework. As TOAST is an optional dependency, you +should check if the Framework was able to detect its presence:: + + import litebird_sim as lbs + + if lbs.TOAST_ENABLED: + # It's ok to use TOAST + ... + else: + # TOAST is not present, do something else + ... + +The procedure to use the TOAST2 destriper is similar to the steps required to +call the internal destriper: you must create a +:class:`.ExternalDestriperParameters` object that specifies which input +parameters (apart from the timelines) should be used:: + + params = lbs.ExternalDestriperParameters( + nside=16, + return_hit_map=True, + return_binned_map=True, + return_destriped_map=True, + ) + +The parameters we use here are the resolution of the output map +(``nside=16``), and the kind of results that must be returned: +specifically, we are looking here for the *hit map* (i.e., a map that +specifies how many samples were observed while the detector was +looking at a specific pixel), the *binned map* (the same map that +would be produced by the *binner*, see above), and the *destriped map*. + +.. note:: + + The TOAST destriper only works with timelines containing 64-bit + floating point numbers. As the default data type for timelines + created by ``sim.create_observations`` is a 32-bit float, if you + plan to run the destriper you should pass the flag + ``tod_dtype=np.float64`` to ``sim.create_observations`` (see the + code above), otherwise ``destripe`` will create an internal copy of + the TOD converted in 64-bit floating-point numbers, which is + usually a waste of space. + +To run the TOAST2 destriper, you simply call +:func:`.destripe_with_toast2`:: + + result = lbs.destripe_with_toast2(sim, params) + +The result is an instance of the class :class:`.Toast2DestriperResult` and +contains the three maps we have asked above (hit map, binned map, +destriped map). + +.. note:: + + Unlike the internal destriper, TOAST2 does not return information + about the convergence of the CG algorithm, and it is *not* granted + that the norm used to estimate the stopping factor is the same + as the one calculated by the internal destriper. Therefore, please + avoid comparing the stopping factors of the two destripers! + + +Saving files for Madam +---------------------- + +The function :func:`.save_simulation_for_madam` takes a :class:`.Simulation` +object, a list of detectors and a output path (the default is a subfolder of +the output path of the simulation) and saves a set of files in it: + +1. Pointing information, saved as FITS files; +2. TOD samples, saved as FITS files; +3. A so-called «simulation file», named ``madam.sim``; +4. A so-called «parameter file», named ``madam.par``. + +These files are ready to be used with the Madam map-maker; you just need +to pass the parameter file ``madam.par`` to one of the executables provided +by Madam (the other ones are referenced by the parameter file). For instance, +the following command will compute the amount of memory needed to run Madam: + +.. code-block:: text + + $ inputcheck madam.par + +The following command will run Madam: + +.. code-block:: text + + $ madam madam.par + +Of course, in a realistic situation you want to run ``madam`` using MPI, +so you should call ``mpiexec``, ``mpirun``, or something similar. + + +Creating several maps with Madam +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +There are cases where you want to create several maps out of +one simulation. A common case is when you simulate several +components to be put in the TOD and store them in different +fields within each :class:`.Observation` object: + +.. code-block:: python + + sim.create_observations(params) + + for cur_obs in sim.observations: + # We'll include several components in the signal: + # CMB, white noise, 1/f noise, and dipole + cur_obs.wn_tod = np.zeros_like(cur_obs.tod) + cur_obs.oof_tod = np.zeros_like(cur_obs.tod) + cur_obs.cmb_tod = np.zeros_like(cur_obs.tod) + cur_obs.dip_tod = np.zeros_like(cur_obs.tod) + + # Now fill each of the *_tod fields appropriately + +In cases like this, it is often useful to generate several +sets of maps that include different subsets of the components: + +1. A map including just the CMB and white noise; +2. A map including CMB, white noise and 1/f, but not the dipole; +3. A map including all the components. + +You could of course call :func:`.save_simulation_for_madam` three +times, but this would be a waste of space because you would end +up with three identical copies of the FITS file containing the +pointings, and the last set of FITS files would contain the same +components that were saved for the first two maps. +(Remember, :func:`.save_simulation_for_madam` saves both the +pointings and the TODs!) + +A trick to avoid wasting so much space is to save the FITS files +only once, including *all* the TOD components, and then call +:func:`.save_simulation_for_madam` again for each other map using +``save_pointings=False`` and ``save_tods=False``: + +.. code-block:: python + + # This code will generate *three* sets of Madam files: + # - One including the CMB and white noise + # - One including 1/f as well + # - The last one will include the dipole too + save_files = True + + # Iterate over all the maps we want to produce. For each of + # them we specify the name of the subfolder where the Madam + # files will be saved and the list of components to include + for (subfolder, components_to_bin) in [ + ("cmb+wn", ["wn_tod", "cmb_tod"]), + ("cmb+wn+1f", ["wn_tod", "oof_tod", "cmb_tod"]), + ("cmb+wn+1f+dip", ["wn_tod", "oof_tod", "cmb_tod", "dip_tod"]), + ]: + save_simulation_for_madam( + sim=sim, + params=params, + madam_subfolder_name=subfolder, + components=["cmb_tod", "wn_tod", "oof_tod", "dipole_tod"], + components_to_bin=components_to_bin, + save_pointings=save_files, + save_tods=save_files, + ) + + # Set this to False for all the following iterations (maps) + save_files = False + + +It is important that the `components` parameter in the call to +:func:`.save_simulation_for_madam` list *all* the components, even +if you ask Madam not to use them in the first and second maps. The reason +is that the function uses this parameter to create a «map» of the +components as they are supposed to appear in the FITS files; for +example, the ``cmb_tod`` field is the *third* in each TOD file, but this +would not be apparent while producing the first map, where it is the +*second* in the list of components that must be used. The ``.par`` +file will list the components that need to be used to +create the map, so it will not be confused if the TOD FITS files +contain more components than needed. (This is a neat feature +of Madam.) + +.. note:: + + To understand how this kind of stuff works, it is useful to + recap how Madam works, as the possibility to reuse TODs for + different maps is linked to the fact that Madam requires + *two* files to be run: the *parameter file* and the *simulation + file*. + + The *simulation file* represents a «map» of the content of a + set of FITS files. A simulation file includes no information + about the map-making process; it just tells how many FITS + files to read and what is inside each. + + The *parameter file* tells Madam how to create the maps. You + can ask Madam to skip parts of the TODs in the parameter file. + For example, you do not want to include the dipole in the output map. + + When you call :func:`.save_simulation_for_madam`, the + `components` parameter is used to build the *simulation file*: + thus, if you plan to build more than one map out of the same + set of components, you want to have the very same simulation + files because they «describe» what’s in the FITS files. This + is the reason why we passed the same value to `components` + every time we called :func:`.save_simulation_for_madam`. + + However, when we create the three *parameter files*, each of them + differs in the list of components that need to be included. + If you inspect the three files ``cmb+wn/madam.par``, + ``cmb+wn+1f/madam.par``, and ``cmb+wn+1f+dip/madam.par``, you + will see that they only differ for the following lines:: + + # cmb+wn/madam.par + tod_1 = wn_tod + tod_2 = cmb_tod + + # cmb+wn+1f/madam.par + tod_1 = wn_tod + tod_2 = oof_tod + tod_3 = cmb_tod + + # cmb+wn+1f+dip/madam.par + tod_1 = wn_tod + tod_2 = oof_tod + tod_3 = cmb_tod + tod_4 = dip_tod + + + That's it. The lines with ``tod_*`` are enough to + do all the magic to build the three maps. + + +Of course, once the three directories ``cmb+wn``, ``cmb+wn+1f``, and +``cmb+wn+1f+dip`` are created, Madam will run successfully only in +the first one, ``cmb+wn``. The reason is that only that directory +includes the pointing and TOD FITS files! But if you are saving data +on a filesystem that supports `symbolic links +`_, you can use them to +make the files appear in the other directories too. For instance, the +following commands will create them directly from a Unix shell (Bash, +Sh, Zsh): + +.. code-block:: sh + + ln -srv cmb+wn/*.fits cmb+wn+1f + ln -srv cmb+wn/*.fits cmb+wn+1f+dip + +(The ``-s`` flag asks to create *soft* links, the ``-r`` flag requires +paths to be relative, and ``-v`` makes ``ln`` be more verbose.) + +If you want a fully authomatic procedure, you can create the symbolic +links in Python, taking advantage of the fact that +:func:`.save_simulation_for_madam` returns a dictionary containing the +information needed to do this programmatically: + +.. code-block:: python + + # First map + params1 = save_simulation_for_madam(sim, params) + + # Second map + params2 = save_simulation_for_madam( + sim, + params, + madam_subfolder_name="madam2", + save_pointings=False, + save_tods=False, + ) + + # Caution: you want to do this only within the first MPI process! + if litebird_sim.MPI_COMM_WORLD.rank == 0: + for source_file, dest_file in zip( + params1["tod_files"] + params1["pointing_files"], + params2["tod_files"] + params2["pointing_files"], + ): + # This is a Path object associated with the symlink that we + # want to create + source_file_path = source_file["file_name"] + dest_file_path = dest_file["file_name"] + + dest_file_path.symlink_to(source_file_path) + +You can include this snippet of code in the script that calls +:func:`.save_simulation_for_madam`, so that the procedure will +be 100% automated. + + +API reference +------------- + +.. automodule:: litebird_sim.mapmaking + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: litebird_sim.madam + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/mpi.rst.txt b/docs/build/html/_sources/mpi.rst.txt new file mode 100644 index 00000000..ec64e75a --- /dev/null +++ b/docs/build/html/_sources/mpi.rst.txt @@ -0,0 +1,179 @@ +.. _using_mpi: + +Multithreading and MPI +====================== + +As typical operations on time-ordered data can be quite consuming, +the LiteBIRD Simulation Framework provides a number of tools to +exploit the presence of multiple CPU cores and even multiple +computing nodes. This section details how to take advantage +of these facilities and is split in two parts: + +- We will first present the ability of the framework to use multiple + CPU threads; in this context, the data samples are kept in a chunk + of memory that is shared between several processes. The framework + uses Numba, which can take advantage either of the `Intel + Threading Building Blocks `_ + library or of `OpenMP `_. + +- Then, we will discuss the possibility to run the code on + multiple *computing nodes*, where the memory of each node is + **not** shared with the others. The framework is able to + use any MPI library, through the Python package + `mpi4py `_. + + +Multithreading +~~~~~~~~~~~~~~ + +Some parts of the LiteBIRD Simulation Framework are able to +exploit multiple cores because several of its modules rely on +the `Numba `_ library. + +If you are running your code on your multi-core laptop, you do not +have to do anything fancy in order to use all the CPUs on your machine: +in its default configuration, the Framework should be able to take +advantage all the available CPU cores. + +However, if you want to tune the way the Framework uses the CPUs, +you can either set the environment variable ``OMP_NUM_THREADS`` +to the number of CPUs to use, or use two parameters in +the constructor of the class :class:`.Simulation`: + +- `numba_num_of_threads`: this is the number of CPUs that Numba will + use for parallel calculations. The parameter defaults to ``None``, + which means that Numba will check how many CPUs are available and will + use all of them. + +- `numba_threading_layer`: this parameter is a string that specifies + which threading library should be used by Numba. The value depends + both on the version of Numba you are running and on the availability + of these libraries, as they are not installed together with the + LiteBIRD Simulation Framework. Numba 0.53 provides the following choices: + + - ``tbb``: `Intel Threading Building Blocks + `_. + You should pick this if you are running your code on Intel machines and the + Tbb library is available. + + - ``omp``: `OpenMP `_. If you pick this one, + be sure that the OpenPM library is available. + + - ``workqueue``: this is an internal threading library provided by Numba. + It's probably the least efficient of the three; its main advantage is + that it is always available. + +These parameters can be passed through a TOML parameter file (see +:ref:`parameter_files`) as well: + +.. code-block:: toml + + # This is file "my_conf.toml" + [simulation] + random_seed = 12345 + numba_num_of_threads = 32 + numba_threading_layer = "tbb" + +Both ``tbb`` and ``omp`` require that the relevant library be available on +your system, as the command ``pip install litebird_sim`` does **not** install +them. If you are running your code on a HPC cluster, it is probably a matter +of running a command like the following: + +.. code-block:: sh + + # This might change depending on how the environment on your cluster + # is configured; the following commands are just examples. + $ module load tbb # Intel Threading Building Blocks + $ module load openmp # OpenMP + + +MPI +~~~ + +The LiteBIRD Simulation Framework lists mpi4py as an *optional* +dependency. This means that simulation codes should be able to cope +with the lack of MPI. + +The framework can be forced to use MPI or not using the variable +``LITEBIRD_SIM_MPI``: + +- Set the variable to 1 or an empty value to *force* importing `mpi4py`; +- Set the variable to 0 to avoid importing `mpi4py`; +- If the variable is not set, the code will try to import `mpi4py`, + but in case of error it will not complain and will silently shift to + serial execution. + +The framework provides a global variable, :data:`.MPI_COMM_WORLD`, +which is the same as ``mpi4py.MPI.COMM_WORLD`` if MPI is being used. +Otherwise, if MPI is **not** being used, it still contains the +following members: + +- `rank` (set to ``0``); +- `size` (set to ``1``). + +Thus, the following code works regardless whether MPI is present or +not:: + + import litebird_sim as lbs + + if lbs.MPI_COMM_WORLD.rank == 0: + print("Hello, world!") + +However, you can use :data:`.MPI_COMM_WORLD` to call MPI functions +only if MPI was actually enabled. You can check this using the Boolean +variable :data:`.MPI_ENABLED`:: + + import litebird_sim as lbs + + comm = lbs.MPI_COMM_WORLD + if lbs.MPI_ENABLED: + comm.barrier() + + +To ensure that your code uses MPI in the proper way, you should always +use :data:`.MPI_COMM_WORLD` instead of importing ``mpi4py`` directly. + + +Enabling/disabling MPI +---------------------- + +The user can control whether MPI must be used or not in a script, +through the environment variable ``LITEBIRD_SIM_MPI`` (``ENABLE_MPI`` +is accepted as well): + +- If the variable is set to the empty string or to ``1``, ``true``, + ``on``, ``yes``, then ``mpi4py`` is imported, and an exception is + raised if this cannot be done (e.g., because it was not installed + using the flag ``--extra=mpi`` when ``poetry install`` was called). + +- If the variable is set to ``0``, ``false``, ``off`` or ``no``, then + ``mpi4py`` is *not* imported, even if it is installed. + +- If the variable is not set, then ``mpi4py`` will be imported, but + any failure will be accepted and the framework will silently switch + to serial mode. + + +Grasping how MPI is being used +------------------------------ + +You will typically use MPI to spread TODs among many MPI processes, so +that the simulation can span several detectors and a longer time scale. +Unfortunately, this means that it's often complicated to understand how +data is being kept in memory. + +If you use the :class:`.Simulation` object (and you should, you *really* +should!), you can call :meth:`.Simulation.describe_mpi_distribution` after +you have allocated the TODs via :meth:`.Simulation.create_observations`; it +will return an instance of the class :class:`.MpiDistributionDescr`, which +can be inspected and printed to the terminal. See Section :ref:`simulations` +for more information about this. + + +API reference +------------- + +.. automodule:: litebird_sim.mpi + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/noise.rst.txt b/docs/build/html/_sources/noise.rst.txt new file mode 100644 index 00000000..0b7c376f --- /dev/null +++ b/docs/build/html/_sources/noise.rst.txt @@ -0,0 +1,263 @@ +.. _noise: + +Instrumental noise +================== + +The ability to add noise to your detector timestreams is supported through the +function :func:`.add_noise_to_observations` and the low-level versions +:func:`.add_noise`, :func:`.add_white_noise`, and +:func:`.add_one_over_f_noise`. + +Here is a short example that shows how to add noise: + +.. testcode:: + + import litebird_sim as lbs + import numpy as np + + # Create a simulation lasting 100 seconds + sim = lbs.Simulation( + base_path='./output', + start_time=0, + duration_s=100, + random_seed=12345, + ) + + # Create a detector object + det = lbs.DetectorInfo( + net_ukrts=100, + sampling_rate_hz=10 + ) + + obs = sim.create_observations(detectors=[det]) + + # Here we add white noise using the detector + # noise parameters from the `det` object. + # We use the random number generator provided + # by `sim`, which is initialized with the + # seed we passed to the Simulation constructor + # to ensure repeatability. + lbs.noise.add_noise_to_observations(obs, 'white', random=sim.random) + + for i in range(10): + print(f"{obs[0].tod[0][i]:.5e}") + +.. testoutput:: + + 5.65263e-04 + -1.54522e-04 + 3.42276e-04 + -1.42274e-04 + -1.71110e-04 + 8.72188e-05 + -1.23400e-04 + -6.99311e-05 + 6.58389e-05 + 5.51306e-04 + + +Note that we pass ``sim.random`` as the number generator to use. +This is a member variable that is initialized by the constructor +of the class :class:`.Simulation`, and it is safe to be used with +multiple MPI processes as it ensures that each process has its +own random number generator with a different seed. You can also +pass another random number generator, as long as it has the +``normal`` method. More information on the generation of random +numbers can be found in :ref:`random-numbers`. + +To add white noise using a custom white noise sigma, in µK, we can +call the low level function directly: + +.. testcode:: + + import litebird_sim as lbs + + sim = lbs.Simulation( + base_path='./output', + start_time=0, + duration_s=100, + random_seed=12345, + ) + + det = lbs.DetectorInfo( + net_ukrts=100, + sampling_rate_hz=10, + ) + + obs = sim.create_observations(detectors=[det]) + + custom_sigma_uk = 1234 + lbs.noise.add_white_noise(obs[0].tod[0], custom_sigma_uk, random=sim.random) + +We can also add 1/f noise using a very similar call to the above: + +.. testcode:: + + import litebird_sim as lbs + + sim = lbs.Simulation( + base_path='./output', + start_time=0, + duration_s=100, + random_seed=12345, + ) + + det = lbs.DetectorInfo( + net_ukrts=100, + sampling_rate_hz=10, + alpha=1, + fknee_mhz=10 + ) + + obs = sim.create_observations(detectors=[det]) + + # Here we add 1/f noise using the detector noise + # parameters from the detector object + lbs.noise.add_noise_to_observations(obs, 'one_over_f', random=sim.random) + +Again, to generate noise with custom parameters, we can either use the low-level function or edit the :class:`.Observation` object to contain the desired noise parameters. + +.. testcode:: + + import litebird_sim as lbs + import numpy as np + + sim = lbs.Simulation( + base_path='./output', + start_time=0, + duration_s=100, + random_seed=12345, + ) + + det = lbs.DetectorInfo( + net_ukrts=100, + sampling_rate_hz=10, + alpha=1, + fknee_mhz=10, + fmin_hz=0.001, + ) + + obs = sim.create_observations(detectors=[det]) + + custom_sigma_uk = 1234 + custom_fknee_mhz = 12.34 + custom_alpha = 1.234 + custom_fmin_hz = 0.0123 + + # Option 1: we call the low-level function directly + lbs.noise.add_one_over_f_noise( + obs[0].tod[0], + custom_fknee_mhz, + custom_fmin_hz, + custom_alpha, + custom_sigma_uk, + obs[0].sampling_rate_hz, + sim.random, + ) + + # Option 2: we change the values in `obs` + obs[0].fknee_mhz[0] = custom_fknee_mhz + obs[0].fmin_hz[0] = custom_fmin_hz + obs[0].alpha[0] = custom_alpha + obs[0].net_ukrts[0] = ( + custom_sigma_uk / np.sqrt(obs[0].sampling_rate_hz) + ) + + lbs.noise.add_noise_to_observations(obs, 'one_over_f', random=sim.random) + + +.. warning:: + + It's crucial to grasp the distinction between the noise level in a + timestream and the noise level in a map. While the latter is + dependent on the former, the conversion is influenced by several + factors. This understanding will empower you in your data analysis + tasks. + + A common mistake is to use the mission time divided by the number + of pixels in the map in a call to func:`.add_white_noise`. This is + **wrong**, as the noise level per pixel depends on the overall + integration time, which is always less than the mission time + because of cosmic ray loss, repointing maneuvers, etc. These + effects reduce the number of samples in the timeline that can be + used to estimate the map, but they do not affect the noise of the + timeline. + + +Methods of the Simulation class +------------------------------- + +The class :class:`.Simulation` provides the function +:func:`.Simulation.add_noise` which adds noise to the timelines. +All the details of the noise are provided in the class observation and +the interface is simplified. + +.. testcode:: + + import litebird_sim as lbs + from astropy.time import Time + import numpy as np + + start_time = 0 + time_span_s = 1000.0 + sampling_hz = 10.0 + nside = 128 + + sim = lbs.Simulation( + start_time=start_time, + duration_s=time_span_s, + random_seed=12345, + ) + + # We pick a simple scanning strategy where the spin axis is aligned + # with the Sun-Earth axis, and the spacecraft spins once every minute + sim.set_scanning_strategy( + lbs.SpinningScanningStrategy( + spin_sun_angle_rad=np.deg2rad(0), + precession_rate_hz=0, + spin_rate_hz=1 / 60, + start_time=start_time, + ), + delta_time_s=5.0, + ) + + # We simulate an instrument whose boresight is perpendicular to + # the spin axis. + sim.set_instrument( + lbs.InstrumentInfo( + boresight_rotangle_rad=0.0, + spin_boresight_angle_rad=np.deg2rad(90), + spin_rotangle_rad=np.deg2rad(75), + ) + ) + + # A simple detector looking along the boresight direction + det = lbs.DetectorInfo( + name="Boresight_detector", + sampling_rate_hz=sampling_hz, + bandcenter_ghz=100.0, + net_ukrts=50.0, + ) + + sim.create_observations(detectors=det) + + sim.add_noise(noise_type='one_over_f') + + for i in range(5): + print(f"{sim.observations[0].tod[0][i]:.5e}") + +.. testoutput:: + + 2.83998e-04 + -7.58942e-05 + 1.72505e-04 + -6.97704e-05 + -8.41885e-05 + +API reference +------------- + +.. automodule:: litebird_sim.noise + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/non_linearity.rst.txt b/docs/build/html/_sources/non_linearity.rst.txt new file mode 100644 index 00000000..e78bf2bb --- /dev/null +++ b/docs/build/html/_sources/non_linearity.rst.txt @@ -0,0 +1,202 @@ +Non-linearity injection +======================= + +Non-linearity is the effect of a non-ideal TES detector response, +where the responsivity :math:`S` is not constant as the optical power +varies. The LiteBIRD Simulation Framework provides a non-linearity +simulation module to simulate the effect of non-linearity on TODs. + +The framework provides the simplest case, which is quadratic +non-linearity. This case is described in `Micheli+2024 +`_, where the effect of +non-linearity is propagated to the estimation of the tensor-to-scalar +ratio. + +Considering a first order correction of the usual linear gain, a TOD +:math:`d(t)` is modified according to: + +.. math:: + d(t) = [1+g_1 d(t)] d(t) + +where :math:`g_1` is the detector non-linearity factor in units of +:math:`K^{-1}`. + +To simulate a quadratic non-linearity, one can use the method of +:class:`.Simulation` class +:meth:`.Simulation.apply_quadratic_nonlin()`, or any of the low-level +functions: :func:`.apply_quadratic_nonlin_to_observations()`, +:func:`.apply_quadratic_nonlin_for_one_detector()`. The following +example shows the typical usage of the method and low-level functions: + +.. code-block:: python + + import numpy as np + import litebird_sim as lbs + from astropy.time import Time + + start_time = Time("2025-02-02T00:00:00") + mission_time_days = 1 + sampling_hz = 19 + + dets = [ + lbs.DetectorInfo( + name="det_A", sampling_rate_hz=sampling_hz + ), + lbs.DetectorInfo( + name="det_B", sampling_rate_hz=sampling_hz + ), + ] + + sim = lbs.Simulation( + base_path="nonlin_example", + start_time=start_time, + duration_s=mission_time_days * 24 * 3600.0, + random_seed=12345, + ) + + sim.create_observations( + detectors=dets, + split_list_over_processes=False, + ) + + # Creating fiducial TODs + sim.observations[0].nl_2_self = np.ones_like(sim.observations[0].tod) + sim.observations[0].nl_2_obs = np.ones_like(sim.observations[0].tod) + sim.observations[0].nl_2_det = np.ones_like(sim.observations[0].tod) + +If the non-linearity parameter is not read from the IMo, one has to +specify :math:`g_1` using the ``g_one_over_k`` argument as in the +following example: + +.. code-block:: python + + # Define non-linear parameters for the detectors. We choose the same + # value for both detectors in this example, but it is not necessary. + sim.observations[0].g_one_over_k = np.ones(len(dets)) * 1e-3 + + # Applying non-linearity using the `Simulation` class method + sim.apply_quadratic_nonlin(component = "nl_2_self",) + + # Applying non-linearity on the given TOD component of an `Observation` + # object + lbs.non_linearity.apply_quadratic_nonlin_to_observations( + observations=sim.observations, + component="nl_2_obs", + ) + + # Applying non-linearity on the TOD arrays of the individual detectors. + for idx, tod in enumerate(sim.observations[0].nl_2_det): + lbs.non_linearity.apply_quadratic_nonlin_for_one_detector( + tod_det=tod, + g_one_over_k=sim.observations[0].g_one_over_k[idx], + ) + +In particular, the effect of detector non-linearity must be included +to assess its impact when coupled with other systematic effects. As +described in `Micheli+2024 `_, a +typical case is the coupling with HWP synchronous signal (HWPSS) +appearing at twice the rotation frequency of the HWP. This kind of +signal can be produced by non-idealities of the HWP, such as its +differential transmission and emission. + +In that case, the usual TOD :math:`d(t)` will contain an additional +term, and can be written as: + +.. math:: + d(t) = d(t) = I + \mathrm{Re}[\epsilon_{\mathrm{pol}}e^{4i\chi}(Q+iU)]+A_2 \cos(2 \omega_{HWP} t) + N + +where :math:`A_2` is the amplitude of the HWPSS and +:math:`\omega_{HWP}` is the rotation speed of the HWP. In presence of +detector non-linearity, the 2f signal is up-modulated to 4f, affecting +the science band. + +The framework provides an independent module to introduce this signal +in the simulation, adding it to the TODs. To simulate the 2f signal +from a rotating, non-ideal HWP, one can use the method of +:class:`.Simulation` class :meth:`.Simulation.add_2f()`, or any of the +low-level functions: :func:`.add_2f_to_observations()`, +:func:`.add_2f_for_one_detector()`. + +If the 2f amplitude is not read from the IMo, one has to specify +:math:`A_2` using the ``amplitude_2f_k`` argument. The argument +``optical_power_k`` enables the inclusion of the integrated nominal +optical power expected for each channel. See the following example: + +.. code-block:: python + + import numpy as np + import litebird_sim as lbs + from astropy.time import Time + from litebird_sim.pointings import get_hwp_angle + + + telescope = "LFT" + channel = "L4-140" + detlist = ["000_001_017_QB_140_T", "000_001_017_QB_140_B"] + imo_version = "vPTEP" + start_time = Time("2025-02-02T00:00:00") + mission_time_days = 1 + + imo = lbs.Imo(flatfile_location=lbs.PTEP_IMO_LOCATION) + + + sim = lbs.Simulation( + base_path="nonlin_example", + start_time=start_time, + imo=imo, + duration_s=mission_time_days * 24 * 3600.0, + random_seed=12345, + ) + + # Load the definition of the instrument + sim.set_instrument( + lbs.InstrumentInfo.from_imo( + imo, + f"/releases/{imo_version}/satellite/{telescope}/instrument_info", + ) + ) + + dets = [] + for n_det in detlist: + det = lbs.DetectorInfo.from_imo( + url=f"/releases/{imo_version}/satellite/{telescope}/{channel}/{n_det}/detector_info", + imo=imo,) + det.sampling_rate_hz = 1 + dets.append(det) + + sim.create_observations( + detectors=dets, + split_list_over_processes=False, + ) + + sim.set_scanning_strategy(imo_url=f"/releases/{imo_version}/satellite/scanning_parameters/") + + sim.set_hwp( + lbs.IdealHWP(sim.instrument.hwp_rpm * 2 * np.pi / 60,), + ) + + sim.prepare_pointings() + sim.precompute_pointings() + + # Creating fiducial TODs + sim.observations[0].tod_2f = np.zeros_like(sim.observations[0].tod) + + # Define differential emission parameters for the detectors. + sim.observations[0].amplitude_2f_k = np.array([0.1, 0.1]) + sim.observations[0].optical_power_k = np.array([1.0, 1.0]) + + # Adding 2f signal from HWP differential emission using the `Simulation` class method + sim.add_2f(component="tod_2f") + + +Refer to the :ref:`nl-api-reference` for the full list of non-linearity simulation parameters. + +.. _nl-api-reference: + +API reference +------------- + +.. automodule:: litebird_sim.non_linearity + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/observations.rst.txt b/docs/build/html/_sources/observations.rst.txt new file mode 100644 index 00000000..5489f78c --- /dev/null +++ b/docs/build/html/_sources/observations.rst.txt @@ -0,0 +1,316 @@ +.. _observations: + +Observations +============ + +The :class:`.Observation` class is the container for the data acquired by the +telescope during a scanning period (and the relevant information about it). + +Serial applications +------------------- + +In a serial code :class:`.Observation` is equivalent to an empty class in which you +can put anything:: + + import litebird_sim as lbs + import numpy as np + + obs = lbs.Observation( + detectors=2, + start_time_global=0.0, + sampling_rate_hz=5.0, + n_samples_global=5, + ) + + obs.my_new_attr = 'value' + setattr(obs, 'another_new_attr', 'another value') + +Across the framework, the coherence in the names and content of the +attributes is guaranteed **by convention** (no check is done by the +:class:`.Observation` class). At the moment, the most common field +that you can find in the class are the following, assuming that it is +meant to collect :math:`N` samples for :math:`n_d` detectors: + +1. ``Observation.tod`` is initialized when you call + :meth:`.Simulation.create_observations`. It's a 2D array + of shape :math:`(n_d, N)`. This means that ``Observation.tod[0]`` is + the time stream of the first detector, and ``obs.tod[:, 0]`` are the + first time samples of all the detectors. + + You can create other TOD-like arrays through the parameter ``tods``; + it accepts a list of :class:`.TodDescription` objects that specify + the name of the field used to store the 2D array, a textual + description, and the value for ``dtype``. (By default, the ``tod`` + field uses 32-bit floating-point numbers.) Here is an example:: + + sim.create_observations( + detectors=[det1, det2, det3], + tods=[ + lbs.TodDescription( + name="tod", description="TOD", dtype=np.float64, + ), + lbs.TodDescription( + name="noise", description="1/f+white noise", dtype=np.float32 + ), + ], + ) + + for cur_obs in sim.observations: + print("Shape of 'tod': ", cur_obs.tod.shape) + print("Shape of 'noise': ", cur_obs.noise.shape) + +2. If you called :func:`.prepare_pointings()` and then + :func:`.precompute_pointings()`, the field ``Observation.pointing_matrix`` + is a :math:`(n_d, N, 3)` matrix containing the pointing information in + Ecliptic coordinates for each detector: colatitude θ, longitude φ, + orientation ψ. If you specified a HWP in the call to + :func:`.prepare_pointings()`, the field ``Observation.hwp_angle`` will + be a :math:`(N,)` vector containing the angle of the HWP in radians. + +3. ``Observation.local_flags`` is a :math:`(n_d, N)` matrix containing + flags for the :math:`n_d` detectors. These flags are typically + associated to peculiarities in the single detectors, like + saturations or mis-calibrations. + +4. ``Observation.global_flags`` is a vector of :math:`N` elements + containing flags that must be associated with *all* the detectors + in the observation. + +Keep in mind that the general rule is that detector-specific +attributes are collected in arrays. Thus, ``obs.calibration_factors`` +should be a 1-D array of :math:`n_d` elements (one per each detector). + +With this memory layout, typical operations look like this:: + + # Collect detector properties in arrays + obs.calibration_factors = np.array([1.1, 1.2]) + obs.wn_levels = np.array([2.1, 2.2]) + + # Apply to each detector its own calibration factor + obs.tod *= obs.calibration_factors[:, None] + + # Add white noise at a different level for each detector + obs.tod += (np.random.normal(size=obs.tod.shape) + * obs.wn_levels[:, None]) + + +Parallel applications +--------------------- + +The only work that the :class:`.Observation` class actually does is handling +parallelism. ``obs.tod`` can be distributed over a +``n_blocks_det`` by ``n_blocks_time`` grid of MPI ranks. The blocks can be +changed at run-time. + +The coherence between the serial and parallel operations is achieved by +distributing also the arrays of detector properties: +each rank keeps in memory only an interval of ``calibration_factors`` or +``wn_levels``, the same detector interval of ``tod``. + +The main advantage is that the example operations in the Serial section are +achieved with the same lines of code. +The price to pay is that you have to set detector properties with special methods. + +:: + + import litebird_sim as lbs + from mpi4py import MPI + + comm = MPI.COMM_WORLD + + obs = lbs.Observation( + detectors=2, + start_time_global=0.0, + sampling_rate_hz=5.0, + n_samples_global=5, + n_blocks_det=2, # Split the detector axis in 2 + comm=comm # across the processes of this communicator + ) + + # Add detector properties either with a global array that contains + # all the detectors (which will be scattered across the processor grid) + obs.setattr_det_global('calibration_factors', np.array([1.1, 1.2])) + + # Or with the local array, if somehow you have it already + if comm.rank == 0: + wn_level_local = np.array([2.1]) + elif comm.rank == 1: + wn_level_local = np.array([2.2]) + else: + wn_level_local = np.array([]) + obs.setattr_det('wn_levels', wn_level_local) + + # Operate on the local portion of the data just like in serial code + + # Apply to each detector its own calibration factor + obs.tod *= obs.calibration_factors[:, None] + + # Add white noise at a different level for each detector + obs.tod += (np.random.normal(size=obs.tod.shape) + * obs.wn_levels[:, None]) + + # Change the data distribution + obs.set_blocks(n_blocks_det=1, n_blocks_time=1) + # Now the rank 0 has exactly the data of the serial obs object + +For clarity, here is a visualization of how data (a detector attribute and the +TOD) gets distributed. + +.. image:: ./images/observation_data_distribution.png + +When ``n_blocks_det != 1``, keep in mind that ``obs.tod[0]`` or +``obs.wn_levels[0]`` are quantities of the first *local* detector, not global. +This should not be a problem as the only thing that matters is that the two +quantities refer to the same detector. If you need the global detector index, +you can get it with ``obs.det_idx[0]``, which is created +at construction time. + +To get a better understanding of how observations are being used in a +MPI simulation, use the method :meth:`.Simulation.describe_mpi_distribution`. +This method must be called *after* the observations have been allocated using +:meth:`.Simulation.create_observations`; it will return an instance of the +class :class:`.MpiDistributionDescr`, which can be inspected to determine +which detectors and time spans are covered by each observation in all the +MPI processes that are being used. For more information, refer to the Section +:ref:`simulations`. + +Other notable functionalities +----------------------------- + +The starting time can be represented either as floating-point values +(appropriate in 99% of the cases) or MJD; in the latter case, it +is handled through the `AstroPy `_ library. + +Instead of adding detector attributes after construction, you can pass a list of +dictionaries (one entry for each detectors). One attribute is created for every +key. + +:: + + import litebird_sim as lbs + from astropy.time import Time + + # Second case: use MJD to track the time + obs_mjd = lbs.Observation( + detectors=[{"name": "A"}, {"name": "B"}] + start_time_global=Time("2020-02-20", format="iso"), + sampling_rate_hz=5.0, + nsamples_global=5, + ) + + obs.name == np.array(["A", "B"]) # True + + +Reading/writing observations to disk +------------------------------------ + +The framework implements a couple of functions to write/read +:class:`.Observation` objects to disk, using the HDF5 file format. By +default, each observation is saved in a separate HDF5 file; the +following information are saved and restored: + +- Whether times are tracked as floating-point numbers or proper + AstroPy dates; +- The TOD matrix (in ``.tod``); +- The quaternions used to create the pointings. +- Optionally, full pointings can be computed on the fly and stored + in the files; this is useful if the TOD is supposed to be read by + some other program. +- Global and local flags saved in ``.global_flags`` and + ``.local_flags`` (see below). + +The function used to save observations is :func:`.Simulation.write_observations`, +which acts on a :class:`.Simulation` object; if you prefer to +operate without a :class:`.Simulation` object, you can call +:func:`.write_list_of_observations`. + +To read observations, you can use :func:`.Simulation.read_observations` and +:func:`.read_list_of_observations`. + +The framework writes one HDF5 file for each :class:`.Observation`; each +file contains the following datasets: + +- One dataset per each TOD; each dataset has the same name as the ones passed to + ``tods=`` in the call to ``create_observations``. It has the following attributes: + + - ``use_mjd`` (Boolean): ``True`` if ``start_time`` is a MJD, ``False`` if it is + a plain floating-point value + + - ``start_time`` (Float): the time of the first sample in the TOD, see also + ``use_mjd`` to correctly interpret this + + - ``sampling_rate_hz`` (Float) + + - ``detectors`` (string): a JSON record containing basic information about the + detectors + + - ``description`` (string): a human-readable string describing what's in this TOD + +- ``global_flags``: the matrix of the global flags for the observation + +- ``flags_NNNN`: the local flags for detector ``NNNN`` (starting from ``0000``). + There are as many datasets of this kind as the number of detectors in this + :class:`.Observation` object. + +- ``pointing_provider_rot_quaternion``: the rotation quaternion that + converts the boresight direction of the focal plane of the instrument + into ecliptic coordinates. It is a matrix with shape ``(N, 4)``, and it + has the attributes ``start_time`` (either a floating-point value or a string, + the latter being used for ``astropy.time.Time`` types) and ``sampling_rate_hz``. + +- ``pointing_provider_hwp``: a dataset containing the details of the Half-Wave + Plate. Its interprentation depends on the kind of HWP; for instances of the + class :class:`.IdealHWP`, the dataset is empty and the only fields are the + attributes ``class_name`` (always equal to ``IdealHWP``), ``ang_speed_radpsec``, + and ``start_angle_rad`` (two floating-point numbers). + +- ``rot_quaternion_NNNN``: the rotation quaternion for detector + ``NNNN`` (starting from ``0000``). It has the same structure as + ``pointing_provider_rot_quaternion`` (see above), and there are as many datasets of + this kind as the number of detectors in this :class:`.Observation` object. + + +Flags +----- + +The LiteBIRD Simulation Framework permits to associate flags with the +scientific samples in a TOD. These flags are usually unsigned integer +numbers that are treated like bitmasks that signal peculiarities in +the data. They are especially useful when dealing with data that have +been acquired by some real instrument to signal if the hardware was +malfunctioning or if some disturbance was recorded by the detectors. +Other possible applications are possible: + +1. Marking samples that should be left out of map-making (e.g., + because a planet or some other transient source was being observed + by the detector). + +2. Flagging potentially interesting observations that should be used + in data analysis (e.g., observations of the Crab nebula that are + considered good enough for polarization angle calibration). + +Similarly to other frameworks like TOAST, the LiteBIRD Simulation +Framework lets to store both «global» and «local» flags associated +with the scientific samples in TODs. Global flags are associated with +all the samples in an Observation, and they must be a vector of ``M`` +elements, where ``M`` is the number of samples in the TOD. Local +samples are stored in a matrix of shape ``N × M``, where ``N`` is the +number of detectors in the observation. + +The framework encourages to store these flags in the fields +``local_flags`` and ``global_flags``: in this way, they can be saved +correctly in HDF5 files by functions like :func:`.write_observations`. + + +API reference +------------- + +.. automodule:: litebird_sim.observations + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: litebird_sim.io + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/part1.rst.txt b/docs/build/html/_sources/part1.rst.txt new file mode 100644 index 00000000..2dee4393 --- /dev/null +++ b/docs/build/html/_sources/part1.rst.txt @@ -0,0 +1,8 @@ +Introduction +============ + +.. toctree:: + :maxdepth: 1 + + installation.rst + singularity.rst diff --git a/docs/build/html/_sources/part2.rst.txt b/docs/build/html/_sources/part2.rst.txt new file mode 100644 index 00000000..cb897652 --- /dev/null +++ b/docs/build/html/_sources/part2.rst.txt @@ -0,0 +1,17 @@ +Structure of the framework +========================== + +.. toctree:: + :maxdepth: 1 + + simulations.rst + imo.rst + detectors.rst + plot_fp.rst + observations.rst + data_layout.rst + profiling.rst + reports.rst + random_numbers.rst + mpi.rst + integrating.rst diff --git a/docs/build/html/_sources/part3.rst.txt b/docs/build/html/_sources/part3.rst.txt new file mode 100644 index 00000000..7ef244f2 --- /dev/null +++ b/docs/build/html/_sources/part3.rst.txt @@ -0,0 +1,13 @@ +Basic simulation modules +======================== + +.. toctree:: + :maxdepth: 1 + + scanning.rst + quaternions.rst + hwp.rst + noise.rst + sky_maps.rst + dipole.rst + map_scanning.rst diff --git a/docs/build/html/_sources/part4.rst.txt b/docs/build/html/_sources/part4.rst.txt new file mode 100644 index 00000000..35f1baa0 --- /dev/null +++ b/docs/build/html/_sources/part4.rst.txt @@ -0,0 +1,10 @@ +Systematic effects +================== + +.. toctree:: + :maxdepth: 1 + + gaindrifts.rst + non_linearity.rst + hwp_sys.rst + pointing_sys.rst diff --git a/docs/build/html/_sources/part5.rst.txt b/docs/build/html/_sources/part5.rst.txt new file mode 100644 index 00000000..d55fc66d --- /dev/null +++ b/docs/build/html/_sources/part5.rst.txt @@ -0,0 +1,7 @@ +Data-reduction modules +====================== + +.. toctree:: + :maxdepth: 1 + + mapmaking.rst diff --git a/docs/build/html/_sources/part6.rst.txt b/docs/build/html/_sources/part6.rst.txt new file mode 100644 index 00000000..f8526797 --- /dev/null +++ b/docs/build/html/_sources/part6.rst.txt @@ -0,0 +1,7 @@ +Tutorials +========= + +.. toctree:: + :maxdepth: 1 + + tutorial.rst diff --git a/docs/build/html/_sources/plot_fp.rst.txt b/docs/build/html/_sources/plot_fp.rst.txt new file mode 100644 index 00000000..368376b6 --- /dev/null +++ b/docs/build/html/_sources/plot_fp.rst.txt @@ -0,0 +1,44 @@ +.. _plot_fp: + +Focal plane visualization +========================= + +We can visualize detectors in the focal plane by: + +.. code-block:: text + + python -m litebird_sim.plot_fp + +This software loads the IMo, which is installed on the machine you are using. +As the conversation unfolds, an interactive Matplotlib window will appear. + +Detectors corresponding to the specified channels are represented as blue dots. +Clicking on a dot reveals the `DetectorInfo` for that detector in real time, highlighted with a red star. + +Additionally, if you agree during the conversation to generate a detector file, +a list of starred detectors will be saved into a text file at the designated location after you close the plot. + +The format of the detector file is as follows: + ++------------+---------+---------+------------+------------+-----------------------+ +| Telescope | Channel | IMO_NET | Number_det | Scaled_NET | Detector_name | ++------------+---------+---------+------------+------------+-----------------------+ +| LFT | L1-040 | 114.63 | 2/48 | 13.51 | `000_003_003_UB_040_T`| ++------------+---------+---------+------------+------------+-----------------------+ +| LFT | L1-040 | 114.63 | 2/48 | 13.51 | `000_003_003_UB_040_B`| ++------------+---------+---------+------------+------------+-----------------------+ + +The description of each column is as follows: + +- `Telescope`: The telescope name. +- `Channel`: The channel name. +- `IMO_NET`: The NET of the detector in IMo. +- `Number_det`: :math:`N_{\text{selected}}/N_{\text{total}}` where :math:`N_{\text{selected}}` is the number of selected detectors by clicking and :math:`N_{\text{total}}` is the total number of detectors in the channel. +- `Scaled_NET`: The scaled NET of the detectors is given by the following equation: + + .. math:: + + \text{Scaled NET} = \text{NET}_{\text{IMO}} \sqrt{\frac{t_{\text{obs}}}{3} \frac{N_{\text{selected}}}{N_{\text{total}}}} + + where :math:`t_{\text{obs}}` is the observation time in years that you can specify in the conversation. The factor of 3 is the nominal observation time in years. +- `Detector_name`: The detector name. diff --git a/docs/build/html/_sources/pointing_sys.rst.txt b/docs/build/html/_sources/pointing_sys.rst.txt new file mode 100644 index 00000000..fbca891a --- /dev/null +++ b/docs/build/html/_sources/pointing_sys.rst.txt @@ -0,0 +1,243 @@ +.. _pointing_sys: + +Pointing systematics +==================== +The pointing systematics causes some disturbances in the pointing direction of the detectors. Due to the systematics, we will have a signal from the sky where we are not pointing, and the obtained TOD will be perturbed by the systematics. + +These TODs with systematics will be used for map-making, though the pointing matrix in the map-maker will be created with the expected pointings, i.e., pointings without systematics. + +In order to simulate the pointing systematics, we multiply the systematic quaternions, which describe perturbations, by the expected quaternions. +For example, to add a 5-degree offset around the :math:`x`-axis to the positional quaternion :math:`q_z=(0,0,1,0)` indicating the :math:`z`-axis, the following operation is available: + +.. code-block:: python + + import litebird_sim as lbs + import numpy as np + + def print_quaternion(q): + print("{:.3f} {:.3f} {:.3f} {:.3f}".format(*q)) + + det = lbs.DetectorInfo( + name="000_000_003_QA_040_T", + sampling_rate_hz=1.0, + ) + + # Positional quaternion indicating z-axis + q_z = lbs.RotQuaternion( + quats=np.array([0.0, 0.0, 1.0, 0.0]), + ) + + # Systematic quaternion indicating 5-degree rotation around x-axis + q_sys = lbs.RotQuaternion( + quats=np.array( + lbs.quat_rotation_x(np.deg2rad(5.0)) + ), + ) + + # The systematic quaternions are multiplied by the expected quaternions in-place. + lbs.left_multiply_syst_quats( + q_z, + q_sys, + det, + start_time=0.0, + sampling_rate_hz=det.sampling_rate_hz, + ) + + print("Rotation by 5 deg. around x-axis:") + print_quaternion(q_z.quats[0]) + +.. testoutput:: + + Rotation by 5 deg. around x-axis: + -0.000 -0.044 0.999 -0.000 + +The function :func:`.left_multiply_syst_quats` multiplies the systematic quaternions by the expected quaternions in-place. + +By using this function, the :class:`.PointingSys` class is implemented to inject the systematic quaternions into the quaternions in a specific coordinate. + +:class:`.PointingSys` class +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The :class:`.PointingSys` class is used to inject the systematic quaternions into the quaternions in a specific coordinate. + +**Note that every coordinate is defined by a left-handed coordinate system, and projected pointings by HEALPix are defined as a view from the spacecraft into the sky.** + +The supported coordinates are: + +* :class:`.FocalplaneCoord`: The coordinate of the focal plane with the :math:`z`-axis along the boresight. + * :meth:`.FocalplaneCoord.add_offset`: Add the offset to the detectors in the focal plane. + * :meth:`.FocalplaneCoord.add_disturb`: Add the time-dependent disturbance to the detectors in the focal plane. +* :class:`.SpacecraftCoord`: The coordinate of the spacecraft with the :math:`z`-axis along its spin axis. + * :meth:`.SpacecraftCoord.add_offset`: Add the offset to the entire spacecraft. + * :meth:`.SpacecraftCoord.add_disturb`: Add the time-dependent disturbance to the entire spacecraft. +* :class:`.HWPCoord`: Same coordinates as the focal plane but treats the pointing systematics due to the HWP rotation. + * :meth:`.HWPCoord.add_hwp_rot_disturb`: Add the rotational disturbance synchronized with the HWP rotation. + +These classes are accessible through the :attr:`.PointingSys.focalplane`, :attr:`.PointingSys.spacecraft`, and :attr:`.PointingSys.hwp` attributes. + +How to inject the pointing systematics +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +It is possible to operate :class:`.FocalplaneCoord`, :class:`.SpacecraftCoord`, and :class:`.HWPCoord` directly, but it is recommended to use the :class:`.PointingSys` class to inject the pointing systematics. The following is an example of injecting vibrations into the focal plane. + +.. code-block:: python + + import numpy as np + import litebird_sim as lbs + from pathlib import Path + import tomlkit + import healpy as hp + + def get_hitmap(nside, pointings): + npix = hp.nside2npix(nside) + ipix = hp.ang2pix(nside, pointings[:, :, 0], pointings[:, :, 1]) + hitmap, _ = np.histogram(ipix, bins=np.arange(npix + 1)) + return hitmap + + start_time = 0.0 + sampling_hz = 1.0 + telescope = "LFT" + dets = [] + + # Load the mock focal plane configuration + path_of_toml = ( + Path(lbs.__file__).resolve().parent.parent + / "test" + / "pointing_sys_reference" + / "mock_focalplane.toml" + ) + with open(path_of_toml, "r", encoding="utf-8") as toml_file: + toml_data = tomlkit.parse(toml_file.read()) + for i in range(len(toml_data[telescope])): + dets.append(lbs.DetectorInfo.from_dict(toml_data[telescope][f"det_{i:03}"])) + + # Create a simulation object + sim = lbs.Simulation( + base_path="pntsys_example", + start_time=start_time, + duration_s=1000.0, + random_seed=12345, + ) + + # Set the scanning strategy + sim.set_scanning_strategy( + scanning_strategy=lbs.SpinningScanningStrategy( + spin_sun_angle_rad=np.deg2rad(45.0), + spin_rate_hz=0.05 / 60.0, + precession_rate_hz=1.0 / (3.2 * 60 * 60), + ), + delta_time_s=1.0 / sampling_hz, + ) + + # Set the instrument + sim.set_instrument( + lbs.InstrumentInfo( + name="mock_LiteBIRD", + spin_boresight_angle_rad=np.deg2rad(50.0), + ), + ) + + # Set the HWP + sim.set_hwp(lbs.IdealHWP(sim.instrument.hwp_rpm * 2 * np.pi / 60)) + + # Create the observations + (obs,) = sim.create_observations( + detectors=dets, + split_list_over_processes=False, + ) + + # Initialize the pointing systematics object + pntsys = lbs.PointingSys(sim, obs, dets) + nquats = obs.n_samples + 1 + axes = ["x", "y"] + noise_sigma_deg = 1.0 + + for ax in axes: + # Prepare the noise to add vibration to the focal plane + noise_rad_array = np.zeros(nquats) + lbs.add_white_noise( + noise_rad_array, sigma=np.deg2rad(noise_sigma_deg), random=sim.random + ) + # Add the vibration to the pointings + pntsys.focalplane.add_disturb(noise_rad_array, ax) + + lbs.prepare_pointings( + obs, + sim.instrument, + sim.spin2ecliptic_quats, + sim.hwp, + ) + + pointings, hwp_angle = obs.get_pointings("all") + nside = 64 + hitmap = get_hitmap(nside, pointings) + hp.mollview(hitmap, title="Hit-map with focal plane vibration") + +.. image:: images/hitmap_with_vibration.png + +In this code snippet, the pointing systematics are injected into the focal plane by adding white noise with a standard deviation of 1 degree to the focal plane. The hit-map is created with the injected pointing systematics. + +Tips for MPI distribution +~~~~~~~~~~~~~~~~~~~~~~~~~ +In the case we consider time-dependent pointing systematics, we may have to increase the number of quaternions by changing ``delta_time_s`` in the :meth:`.Simulation.set_scanning_strategy` method. For example, it is changed by considering the nominal sampling rate of LiteBIRD, i.e., 19 Hz: + +.. code-block:: python + + sim.set_scanning_strategy( + scanning_strategy=lbs.SpinningScanningStrategy( + spin_sun_angle_rad=np.deg2rad(45.0), + spin_rate_hz=0.05 / 60.0, + precession_rate_hz=1.0 / (3.2 * 60 * 60), + ), + delta_time_s=1.0 / 19.0, + ) + +However, after this execution, all of :attr:`.Simulation.spin2ecliptic_quats` from start to end in the observation are calculated and stored in memory. And this is not distributed by MPI, so that even when we use MPI, every process will calculate :attr:`.Simulation.spin2ecliptic_quats` from start to end of the observation. This causes a memory issue. + +To avoid this issue, a simple trick to distribute the quaternion calculations is: + +.. code-block:: python + + # After preparing: + # `Simulation` with MPI_COMM_WORLD, + # list of `DetectorInfo`, + # `Imo` object... + + # Create observation + (obs,) = sim.create_observations( + detectors=dets, + n_blocks_det=1, + n_blocks_time=size, + split_list_over_processes=False + ) + + pntsys = lbs.PointingSys(sim, obs, dets) + + # Define scanning strategy parameters from IMO + scanning_strategy = lbs.SpinningScanningStrategy.from_imo( + url= f"/releases/vPTEP/satellite/scanning_parameters/", + imo=imo, + ) + + # Calculate quaternions for each process + sim.spin2ecliptic_quats = scanning_strategy.generate_spin2ecl_quaternions( + start_time=obs.start_time, + time_span_s=obs.n_samples/obs.sampling_rate_hz, + delta_time_s=1.0/19.0, + ) + + # After injecting the pointing systematics... + + lbs.prepare_pointings( + obs, + sim.instrument, + sim.spin2ecliptic_quats, + sim.hwp, + ) + +Especially, this trick is needed when the time-dependent pointing systematics' spectrum has higher frequency than the quaternion's sampling rate given by ``delta_time_s``. + +API Reference +------------- +.. automodule:: litebird_sim.pointing_sys + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/profiling.rst.txt b/docs/build/html/_sources/profiling.rst.txt new file mode 100644 index 00000000..f444b73c --- /dev/null +++ b/docs/build/html/_sources/profiling.rst.txt @@ -0,0 +1,95 @@ +.. _profiling: + +Profiling +========= + +The LiteBIRD Simulation Framework implements a few tools to measure the performance of the code. + +Basic profiling +--------------- + +The class :class:`.TimeProfiler` is a context manager that measures the amount of time spent running the code within a ``with`` block. + +Here is an example: + +.. testcode:: + + from litebird_sim import TimeProfiler + from time import sleep + + with TimeProfiler(name="my_code") as perf: + sleep(1.0) + + print(f"The elapsed time is {perf.elapsed_time_s():.0f} s") + +.. testoutput:: + + The elapsed time is 1 s + + +Custom parameters +----------------- + +The constructor for :class:`.TimeProfiler` accepts keyword arguments; they are saved in the `parameters` field of the object: + +.. testcode:: + + from litebird_sim import TimeProfiler + from time import sleep + + with TimeProfiler(name="my_code", my_param="hello") as perf: + sleep(0.1) + + print("my_param =", perf.parameters["my_param"]) + +.. testoutput:: + + my_param = hello + + +Export to Speedscope +-------------------- + +You can collect several :class:`TimeProfiler` objects in a list and save it in a JSON file that can be imported into `Speedscope `_, an online viewer for performance profiles. +This can be done using the function :func:`.profile_list_to_speedscope`, which outputs a dictionary that can be saved to a JSON file. + +Here is an example:: + + import json + from litebird_sim import TimeProfiler, profile_list_to_speedscope + from time import sleep + + perf_list = [] # type: list[TimeProfiler] + + # First code block to profile + with TimeProfiler(name="function_A") as perf: + sleep(1.0) + perf_list.append(perf) + + # Second code block to profile + with TimeProfiler(name="function_B") as perf: + sleep(2.0) + perf_list.append(perf) + + # Save the profile measurements into "profile.json" + with open("profile.json", "wt") as out_f: + json.dump(profile_list_to_speedscope(perf_list), out_f) + + # Now go to https://www.speedscope.app/ and open "profile.json" + + +When the file ``profile.json`` is opened in https://www.speedscope.app/, you will see a time graph with two horizontal bars representing the two ``with`` blocks: + +.. image:: images/speedscope-example.png + :class: with-border + +When using the :class:`.Simulation` class, a number of functions are automatically profiled and JSON files are saved automatically in the output directory. +See Sect. :ref:`simulation-profiling` for more information. + +API reference +------------- + +.. automodule:: litebird_sim.profiler + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/quaternions.rst.txt b/docs/build/html/_sources/quaternions.rst.txt new file mode 100644 index 00000000..21cf5142 --- /dev/null +++ b/docs/build/html/_sources/quaternions.rst.txt @@ -0,0 +1,142 @@ +.. _quaternions-chapter: + +Quaternions +=========== + +Rotations are important in defining a scanning strategy. Here, we +present a short tutorial on quaternions and explain the facilities +provided by LBS. + +There are several choices to describe a rotation in 3D space: `Euler +angles `_, `rotation +matrices `_, +`quaternions `_, etc. Each +of these systems has its share of advantages and disadvantages. For +instance, rotation matrices are handy when you have a vector and want +to rotate it, as it is just a matter of doing a matrix-vector +multiplication. Quaternions are more complicated in this regard, but +they offer a mathematical operation called *slerp* (shorthand for +*spherical linear interpolation*) that is not available with other +representations, like rotation matrices. We assume that the reader +knows what quaternions are and their mathematical properties; if you +are not, be sure to read the book *Visualizing quaternions*, by +Andrew J. Hanson (Elsevier, 2006, ISBN-0080474772) and the provocative +essay by Marc ten Bosch, `Let’s remove Quaternions from every 3D +engine `_. + +The LiteBIRD simulation framework models quaternions using the +convention :math:`(v_x, v_y, v_z, w)`; be aware that some textbooks +use the order :math:`(w, v_x, v_y, v_z)`. As the framework uses +quaternions only to model rotations, they all must obey the relation +:math:`v_x^2 + v_y^2 + v_z^2 + w^2 = 1` (*normalized* quaternions), +which is a property satisfied by rotation quaternions. + +The class :class:`.RotQuaternion` can model time-varying quaternions. +It is enough to provide a list of quaternions, a starting time, and a +sampling frequency, which is assumed to be constant:: + + import litebird_sim as lbs + + time_varying_quaternion = lbs.RotQuaternion( + # Three rotation quaternions + quats=np.array( + [ + [0.5, 0.0, 0.0, 0.8660254], + [0.0, -0.38268343, 0.0, 0.92387953], + [0.0, 0.0, 0.30901699, 0.95105652], + ] + ), + start_time=0.0, + sampling_rate_hz=1.0, + ) + + +This example assumes that ``time_varying_quaternion`` describes a +rotation that evolves with time, starting from ``t = 0`` and lasting +3 seconds, as the sampling frequency is 1 Hz. + +Rotation quaternions can be multiplied together; however, they must refer +to the same starting time and have the same sampling frequency. + + +Python functions for quaternions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The LiteBIRD Simulation Framework provides three functions, +:func:`.quat_rotation_x`, :func:`.quat_rotation_y`, :func:`.quat_rotation_z` to +compute simple rotation quaternions; they return plain the normalized +quaternion representing a rotation by an angle :math:`\theta` around +one of the three axis `x`, `y`, and `z`. These quaternions are plain +NumPy arrays and can be passed to the parameter ``quats`` of the +constructor for :class:`.RotQuaternion`: + +.. testcode:: + + import litebird_sim as lbs + import numpy as np + + def print_quaternion(q): + print("{:.3f} {:.3f} {:.3f} {:.3f}".format(*q)) + + print("Rotation by π/3 around x:") + print_quaternion(lbs.quat_rotation_x(theta_rad=np.pi/3)) + print("Rotation by π/3 around y:") + print_quaternion(lbs.quat_rotation_y(theta_rad=np.pi/3)) + print("Rotation by π/3 around z:") + print_quaternion(lbs.quat_rotation_z(theta_rad=np.pi/3)) + +.. testoutput:: + + Rotation by π/3 around x: + 0.500 0.000 0.000 0.866 + Rotation by π/3 around y: + 0.000 0.500 0.000 0.866 + Rotation by π/3 around z: + 0.000 0.000 0.500 0.866 + + +There are two functions that implement in-place multiplication of +quaternions: :func:`.quat_right_multiply` performs the calculation +:math:`r \leftarrow r \times q`, and :func:`.quat_left_multiply` +performs the calculation :math:`r \leftarrow q \times r` (where +:math:`\leftarrow` indicates the assignment operation): + +.. testcode:: + + quat = np.array(lbs.quat_rotation_x(np.pi / 3)) + lbs.quat_left_multiply(quat, *lbs.quat_rotation_z(np.pi / 2)) + print("Rotation by π/3 around x and then by π/2 around z:") + print_quaternion(quat) + +.. testoutput:: + + Rotation by π/3 around x and then by π/2 around z: + 0.354 0.354 0.612 0.612 + +Note the syntax for :func:`.quat_left_multiply`: you are supposed to +pass the four components of the quaternion :math:`q` as separate +arguments, and thus we need to prepend the call to +``lbs.quat_rotation_z`` with ``*`` to expand the result (a 4-element +tuple) into the four parameters required by +:func:`.quat_left_multiply`. The reason for this weird syntax is +efficiency, as Numba (which is used extensively in the code) can +easily optimize this kind of function call. + +Finally, the framework provides the function :func:`.rotate_vector`, +which applies the rotation described by a normalized quaternion to a +vector. There are faster versions in :func:`.rotate_x_vector`, +:func:`.rotate_y_vector`, and :func:`.rotate_z_vector` that rotate the +three basis vectors ``(1, 0, 0)``, ``(0, 1, 0)``, and ``(0, 0, 1)``. +The functions :func:`.all_rotate_vectors`, +:func:`.all_rotate_x_vectors`, :func:`.all_rotate_y_vectors`, and +:func:`.all_rotate_z_vectors` can be applied to arrays of quaternions +and vectors. + + +API reference +------------- + +.. automodule:: litebird_sim.quaternions + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/random_numbers.rst.txt b/docs/build/html/_sources/random_numbers.rst.txt new file mode 100644 index 00000000..f5df839e --- /dev/null +++ b/docs/build/html/_sources/random_numbers.rst.txt @@ -0,0 +1,102 @@ +.. _random-numbers: + +Random numbers +============== + +The LiteBIRD Simulation Framework can be used to generate random numbers, +used for example for producing noise timelines. In order to do so, +a seed and a Random Number Generator (RNG) are used. + +Seed +---- + +The ``random_seed`` is used to control the behavior of the RNG. The +seed can be ``None`` or an integer number. If you are **not** +interested in the reproducibility of your results, you can set +``random_seed`` to ``None``. However, this is not recommended, as if +you run many times a function or method that uses an RNG, e.g., for +producing noise timelines, then the outputs will be **different**, and +you will not be able to re-obtain these results again. If, instead, +you are interested in the reproducibility of your results, you can set +``random_seed`` to an integer number. With this choice, if you run a +function or method multiple times using an RNG, then the outputs will +be **the same**, and you will obtain these results again by re-running +your script, as the random numbers produced by the generator will be +the same. + +How should you set the ``random_seed``? This parameter **must** be +passed to the constructor of a :class:`.Simulation` class. If you +create a :class:`.Simulation` instance without passing the seed, the +constructor will raise an error, and your script will fail. We +implemented this feature to avoid automatic settings of the seed and +unclear behavior of the generation of random numbers. If you run a +parallel script using MPI, you do not have to worry about setting a +different seed for different MPI ranks. The random number generator +ensures that the random numbers generated by the ranks will produce +orthogonal sequences. We want this behavior as we do not want +repetitions of, e.g., the same noise TOD if we split their computation +on different MPI ranks. For example, in this way, if you split the TOD +matrix of an :class:`.Observation` class by the time, you will not +encounter the same noise after the samples generated by a certain +rank; if you split the TOD matrix of an :class:`.Observation` class by +the detectors, each one will have a different noise timestream. + +Regarding the reproducibility of the results in a parallel code, there +is an **important thing** to bear in mind. If you set the seed to an +integer number but run your script with a different number of MPI +ranks, the outputs will be **different**! Think about a noise time +stream of 4 samples. If you use 2 MPI ranks, then the first two +samples will be generated by one RNG (rank 0), while the last two +samples will be generated by a different RNG (rank 1). If you then run +the same script with the same seed but with 4 MPI ranks, each of the +samples will be generated by a different RNG, and only the first +sample will be the same for the two runs, as it is always the first +one generated by rank 0’s RNG. + +The setting of the ``random_seed`` is as simple as this:: + + sim = lbs.Simulation( + base_path="/storage/output", + start_time=astropy.time.Time("2020-02-01T10:30:00"), + duration_s=3600.0, + name="My noise simulation", + description="I want to generate some noise and be able to reproduce my results", + random_seed=12345, # Here the seed for the random number generator is set + ) + +The :class:`.Simulation` constructor runs the +:meth:`.Simulation.init_random` method, which builds the RNG and seeds +it with ``random_seed``. You can then use this RNG by calling +``sim.random``. There is also the possibility to change the random +seed after creating a :class:`.Simulation` instance. You can achieve +this by calling :meth:`.Simulation.init_random`:: + + sim = lbs.Simulation(random_seed=12345) + [...] + sim.init_random(random_seed=42) + +Random number generators +------------------------ + +The random number generator used by the :meth:`.Simulation.init_random` +method of the :class:`.Simulation` class is +`PCG64 `_. +After creating this RNG by calling :meth:`.Simulation.init_random` +(directly or from the :class:`.Simulation` constructor), you can use it +via `sim.random`:: + + sim = lbs.Simulation(random_seed=12345) + [...] + sim.add_noise(noise_type='white', random=sim.random) + +You can also use your own RNG with the functions and methods of +``litebird_sim``:: + + sim = lbs.Simulation(random_seed=12345) + [...] + my_rng = ... # New RNG definition + sim.add_noise(noise_type='white', random=my_rng) + +You should just make sure that your custom RNG implements the +``normal`` method, so it can be used for white noise generation. + diff --git a/docs/build/html/_sources/reports.rst.txt b/docs/build/html/_sources/reports.rst.txt new file mode 100644 index 00000000..fc2ba5b7 --- /dev/null +++ b/docs/build/html/_sources/reports.rst.txt @@ -0,0 +1,257 @@ +.. _reporting: + +Creating reports +================ + +The LiteBIRD simulation framework has the ability to automatically +produce reports in the output directory specified during the creation +of a :class:`.Simulation` object. + +The framework uses the `Jinja2 +`_ templating library to +render the text, and the `Python-Markdown +`_ library to convert the text to +HTML. + +The usage of the facility is easy:: + + import litebird_sim as lbs + import matplotlib.pylab as plt + + # Create some plot + data_points = [0, 1, 2, 3] + plt.plot(data_points) + fig = plt.gcf() + + sim = lbs.Simulation(base_dir="./report_example", random_seed=12345) + sim.append_to_text(""" + + ## Section name + + Here is some Markdown text written in *italic* and in **bold**. + + Here is a list: + + - One + - Two + - Three + + The value of the variable `foo` is {{ foo }}. + + And here is a figure: + + ![](myfigure.png) + """, + figures=[(fig, "myfigure.png")], + foo=123, + ) + +The method :meth:`.Simulation.append_to_report` accepts arbitrary +keywords, which are substituted in the string whenever the form ``{{ … +}}`` is found. Thus, the string ``The value of the variable `foo` is +{{ foo }}`` is converted into ``The value of the variable `foo` is +123``. The keyword ``figures`` is however interpreted directly by +:meth:`.Simulation.append_to_report`: it must be a list of 2-tuples of +the form ``(figure, filename)``, and it triggers the save of the +Matplotlib figure into a file with the specified name; you can refer +to the file using Markdown's usual syntax ``![](filename)``, as shown +in the example above. + + +Including tables +---------------- + +Reports can include tables as well. They are formatted using the ASCII +characters ``|`` and ``-``, as in the following example:: + + sim.append_to_report(""" + + Here is a table: + + Column A | Column B + ---------- | ------------ + Item 1 | Value 1 + Item 2 | Value 2 + Item 3 | Value 3 + """) + +If you are going to write the rows of the table using Jinja2's ``for`` +loops, be sure to end the loop with ``{% endfor -%}`` instead of ``{% +endfor %}``, in order not to leave empty lines between consecutive rows. + + +Converting the report to other formats +-------------------------------------- + +It might be the case that the report produced by your script contains +some complex table/paragraphs that you would like to include in an +article written in LaTeX or Microsoft Word. You can easily convert the +report into one of these formats using `Pandoc `_. + +The Simulation Framework saves the Markdown source text of the report +in the same directory as ``report.html``, and you can ask Markdown to +convert to some other format than HTML. The following shell commands +will produce files ``report.tex`` (LaTeX source), ``report.docx`` +(Microsoft Word file), and ``report.pdf`` (PDF file produced using +LaTeX): + +.. code-block:: text + + $ pandoc -f markdown -t latex -o report.tex report.md + $ pandoc -f markdown -t docx -o report.docx report.md + $ pandoc -f markdown -t latex -o report.pdf report.md + +Any image included in the report is saved in the same directory as +``report.md`` and ``report.html``, so you can take and reuse them. To +ease the possibility to include images in the documents, you should +stick to the PNG image format, as it is easily embeddable in HTML, +pdfLaTeX/xeLaTeX/luaLaTeX, and Microsoft Word files. The alternative +is to manually convert the image files to other formats. + +If the report contains mathematical equations, things get a little +trickier. You must create a text file with your favourite editor and +copy the following code in it (the file is available in the Python +distribution as ``litebird_sim/misc/pandoc-filter.lua``): + +.. code-block:: lua + + -- mode: lua + + -- Pass the following option to pandoc: + -- + -- pandoc --lua-filter gitlab-math.lua ... + -- + -- to use this filter + + function Math(el) + if el.mathtype == "InlineMath" then + if el.text:sub(1,1) == '`' and el.text:sub(#el.text) == '`' then + local text = el.text:sub(2,#el.text-1) + return pandoc.Math(el.mathtype, text) + else + local cont = pandoc.read(el.text) + return { pandoc.Str("$") } .. cont.blocks[1].content .. { pandoc.Str("$") } + end + end + end + + function CodeBlock(el) + if el.classes[1] == "math" then + return pandoc.Para({ pandoc.Math("DisplayMath", el.text) }) + end + end + + +and save it somewhere in your computer; let's suppose that the path is +``/my/scripts/litebird-sim-pandoc.lua`` (better to use the extension +``.lua``, as this is a `Lua `_ script). Then, +run pandoc with the option ``--lua-filter +/my/scripts/litebird-sim-pandoc.lua`` and the equations will be +converted properly. The following image shows the result of running +the filter on a report generated by an analysis script to convert the +report in a ``docx`` file: + +.. image:: images/litebird_sim_sample_report_docx.png + +And here is a screenshot of one page of the PDF file that has been +produced by Pandoc by converting the same document to a LaTeX file: + +.. image:: images/litebird_sim_sample_report_latex.png + +Converting reports to LaTeX is particularly handy when you want to +re-use tables written in the report, as these are notoriously a pain +to write manually in LaTeX. + + +A few stylistical tips +---------------------- + +Template files +~~~~~~~~~~~~~~ + +If the report is getting larger and larger, it is advisable to move +the string passed to :meth:`.Simulation.append_to_report` in a +separate file and load it at runtime. (This is what we do in the +source code of the framework, see the folder +``litebird_sim/templates``.) For instance, +you could put the following text in a file ``my_template_report.md``: + +.. code-block:: text + + Here is some number: {{ "0.2f" | format(val) }} + Blah blah blah + + Here follows a few very long paragraphs... + + And here some more text... + +and then you would load it in your script in the following way:: + + with open("my_template_report.md", "rt") as inpf: + template = "".join(inpf.readlines()) + + sim.append_to_report(template, val=value) + +In this way, you can remove much clutter from the Python file, and it +is easier for people who want to improve the report to contribute, as +they do not need to understand Python to do it. + + +Where to put the formatting logic +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You should always try to put the formatting logic of the report within +the string passed to :meth:`.Simulation.append_to_report`, possibly +using `Jinja2's filters +`_, +instead of formatting the arguments in your Python script. Thus, +instead of doing this:: + + sim.append_to_report( + "Here is some number: {{ val }}", + val="%0.2f" % value, # Don't do this! + ) + +you should do this:: + + # Ok, put the formatting logic into the string and rely + # on Jinja2's filter named "format" + sim.append_to_report( + 'Here is some number: {{ "0.2f" | format(val) }}', + val=value, + ) + +The reason is that if you keep the template in a separate Markdown +file, as explained above, it is better to put whatever concerns the +*representation* of the values in the template instead of the code. +For instance, at some point you might revise the layout of your report +by adding space, and this might enable you to write some date field as +``February, 8th 2022`` instead of ``22-02-08``; this kind of change +should affect only the Markdown template and not the Python code, +because it's the template that was modified to enable more space. + +Try to follow this tip even for short snippets: in the authors' +experience, it is often the case that short reports grow more and more +as time passes, until at some point they are moved to separate +Markdown files! + +You can refer to `Jinja2's documentation +`_ for +a complete list of filters, but here is a selection of the most useful +ones: + +- ``{{ str | format(value) }}`` formats ``value`` like the C function + ``printf(str, value)`` + +- ``{{ var | round(n) }}`` rounds ``val`` to ``n`` digits + +- ``{{ var | filesizeformat }}`` interprets ``var`` as a size in bytes + and formats it in a human-readable format. Examples: ``13 kB``, ``4.1 + MB``, etc. + +- ``{{ var | upper }}`` converts ``var`` (a string) to uppercase + +- ``{{ var | lower }}`` converts ``var`` (a string) to lowercase + +- ``{{ var | capitalize }}`` capitalizes ``var`` (a string), so that + ``jupiter`` is turned into ``Jupiter``. diff --git a/docs/build/html/_sources/scanning.rst.txt b/docs/build/html/_sources/scanning.rst.txt new file mode 100644 index 00000000..ad0a0ef4 --- /dev/null +++ b/docs/build/html/_sources/scanning.rst.txt @@ -0,0 +1,842 @@ +.. _scanning-strategy: + +Scanning strategy +================= + +The LiteBIRD Simulation Framework provides a set of tools to simulate +the orbit of the spacecraft and compute the directions where each +detector is looking at the sky as a function of time. The time stream +of directions and orientations of each detector is usually called +*pointing information*, and we will consistently use this jargon in +the documentation. + +Note that this chapter only deals with the *direction* along which +some detector is looking, but the actual position/velocity of the +spacecraft is optional when doing this calculation. The framework +provides other facilities to compute this information, and they are +described in :ref:`dipole-anisotropy`. + +This chapter provides an in-depth explanation of how to use the +facilities provided by the framework to compute the pointing +information for any detector in one of the focal planes. + +The motion of the spacecraft +---------------------------- + +In the case of a space mission like LiteBIRD, the motion of the +spacecraft and its structure decide where each detector is looking at +each time. The following video depicts the kind of motion simulated by +our framework: + +.. raw:: html + + + +(The animation was created using `POV-Ray `_, and +the files used to create it are available on `GitHub +`_ .) + +You can see that there are *two* rotations in the animation: the +spacecraft spins quickly around its spin axis (grey axis), but this +axis does not stand still: it performs a precession around the blue +axis, which represents the Sun-Earth direction. (You should imagine +the Earth on the left and the Sun on the far left.) + +Note that the detectors are not necessarily aligned with the spin +axis; in fact, the animation shows the actual direction of observation +for two different detectors as two red and green lines: you can see +that they are looking at two opposite sides of the spin axis. Every +detector looks along its direction, but detectors belonging to the +same instrument (e.g., LFT) look not far away from each other; it is +customary to express their pointing directions relative to an +«average» direction, called the *boresight direction*, which is the +main optical axis of the instrument. In LiteBIRD, there are *three* +instruments (LFT, MFT, HFT), so there should be *three* boresight +directions; however, MFT and HFT share the same telescope, and thus it +is customary to show only one boresight for both. This is the true +meaning of the red and green axes in the video above: the red axis +represents the «average» direction where LFT detectors are looking, +and the green axis is the same for MFT/HFT. + +The animation does not show a *third* rotation happening, which is the +revolution of the spacecraft around the Sun, taking one year to +complete. (Including it in the video would have been useless, as it is +*really* slow when compared with the spin and precession!). Thus, the +motion of the spacecraft is the composition of *three* rotations: + +1. Rotation of the spacecraft around the spin axis (grey line); + +2. Rotation (precession) of the spin axis around the Sun-Earth axis + (blue line); + +3. Yearly rotation of the Sun-Earth axis around the Sun. + +If you think about it, you will realize that the following quantities +can fully describe the kinematics of this motion: + +- The angle between the spin axis and the boresight direction(s), + usually called β; + +- The angle between the spin and Sun-Earth axes is usually called α. + +- The speed of the rotation of the boresight direction around the spin + axis; + +- The speed of the precession around the Sun-Earth axis, which is + usually slower than the rotation speed; + + +They are sketched in the following diagram: + +.. image:: images/litebird-scanning-strategy.svg + +These parameters define the so-called *scanning strategy*, i.e., how +the instruments observe the sky during the mission lifetime. The +LiteBIRD Simulation Framework provides all the tools necessary to +simulate the composition of these rotations, and it can produce +pointing information from the synthetic description of the scanning +strategy. Here is a complete example using the scanning strategy +proposed for CORE (:cite:`2018:core:delabrouille`), which is +qualitatively similar to what is going to be used for LiteBIRD: + +.. testcode:: + + import litebird_sim as lbs + import astropy.units as u + import numpy as np + + sim = lbs.Simulation( + start_time=0, + duration_s=60.0, + description="Simple simulation", + random_seed=12345, + ) + + # We now simulate the motion of the spacecraft over a time span + # of one minute (specified in the `duration_s` parameter above). + sim.set_scanning_strategy( + scanning_strategy=lbs.SpinningScanningStrategy( + spin_sun_angle_rad=np.deg2rad(30), # CORE-specific parameter + spin_rate_hz=0.5 / 60.0, # Ditto + # We use astropy to convert the period (4 days) in + # seconds + precession_rate_hz=1.0 / (4 * u.day).to("s").value, + ) + ) + + # Here we specify the β angle of the focal plane of + # the instrument + sim.set_instrument( + lbs.InstrumentInfo( + name="core", + spin_boresight_angle_rad=np.deg2rad(65), + ), + ) + + # We include a fake boresight detector `det`, belonging + # to the instrument `core` (unlike LiteBIRD, CORE had only one focal + # plane and one instrument) + det = lbs.DetectorInfo(name="foo", sampling_rate_hz=10) + + # By default, `create_observations` creates just *one* observation + obs, = sim.create_observations(detectors=[det]) + + # Prepare the quaternions needed to compute the pointings, i.e., + # the direction in the sky where the detector is looking at as + # a function of time + sim.prepare_pointings() + + # `get_pointings()` returns both the pointing matrix and the + # HWP angle; we ignore the latter with `_`, as we do not have + # a HWP here. The pointing matrix contains the angles (θ, φ, ψ) + # for each sample in the TOD and each detector. + pointings, _ = obs.get_pointings(0) + + print("Shape:", pointings.shape) + print("Pointings:") + print(np.array_str(pointings, precision=3)) + +.. testoutput:: + + Shape: (600, 3) + Pointings: + [[ 2.182 -0. -1.571] + [ 2.182 -0.006 -1.576] + [ 2.182 -0.012 -1.582] + ... + [ 0.089 -2.967 -1.738] + [ 0.088 -3.021 -1.687] + [ 0.087 -3.075 -1.635]] + +We explain all the details of this code in the following sections, so +for now, keep in mind the overall shape of the code: + +1. Once we set the duration of the simulation (one minute in the + example above), we call the method + :meth:`.Simulation.set_scanning_strategy`, which forces the + framework to compute how the orientation of the spacecraft with + respect to the sky sphere evolves with time. This method produces a + set of quaternions, which encode the result of the composition of + all the rotations (spin, precession, revolution around the Sun) + described above; LBS saves these quaternions in the + ``spin2ecliptic_quats`` field of the ``sim`` class. For more + information about quaternions, see :ref:`quaternions-chapter` . + +2. When the simulation code needs to determine where a detector is + pointing to (the detector ``det`` in our example), it uses the + quaternions to retrieve (1) the coordinates on the Sky sphere, + and (2) the orientation angle (ψ). LBS computes both quantities in + the Ecliptic reference frame using the sampling rate of the + detector, which in our example is 10 Hz (i.e., ten samples per + second). In the example above, this is done by the function + :func:`.get_pointings`. + +3. The method :meth:`.Observation.get_pointings` returns an array with + either 2 or 3 fields depending on the argument passed: + + - if the caller passes an integer, LBS interprets this as the index + of the detector in the observation and returns a ``(N, 3)`` + matrix where the first column contains the colatitude + :math:`\theta`, the second column the longitude :math:`\phi`, and + the third column the orientation angle :math:`\psi`. All the + angles are expressed in radians. + + - if the caller passes a list of indices, LBS interprets it as a + list of detectors in the observation for which the caller wants + to compute the pointing. It returns a ``(D, N, 3)`` matrix where + D represents the detector index, N the index of the sample, and + the three final columns are the same described in the first case. + + - if the caller passes the string "all", LBS returns a ``(D, N, + 3)`` matrix containing the pointing information for all the + detectors in the observation. + + These angles are expressed in the Ecliptic Coordinate System, where + the Equator is aligned with the Ecliptic Plane of the Solar System. + + +Computing the orientation of the spacecraft +------------------------------------------- + +To compute where a detector is looking at the sky sphere, there is a +number of transformations that need to be carried out: + +.. image:: images/coordinate-systems.svg + +We start from the detector reference frame, where the main beam of the +radiation pattern is aligned with the `z` axis and is oriented using +the `x` axis as the reference axis. (In other words, the `x` axis +provides a reference frame for asymmetric beams.) + +The next reference frame is the *boresight*, and to convert from the +detector reference frame to the boresight there is a rotation, which +is encoded in a rotation quaternion that is saved in the IMO. The +framework implements the class :class:`.RotQuaternion` to encode a +rotation quaternion; this class can also model time-varying rotations, +which can be useful to simulate vibrations and wobbles in the optical +structure of the instruments. + +Next, we move from the reference frame of the boresight to that of the +spacecraft. The class :class:`.InstrumentInfo` encodes the information +about the placement of the boresight with respect to the spin axis. +After this transformation, the spin axis is aligned with the `z` axis. + +The next transformation goes from the spacecraft to the Ecliptic +reference frame; the Ecliptic is on the `xy` plane, and the `z` axis +points towards the Ecliptic North Pole. In this case, the framework +provides two ways to compute the transformation: + +1. The revolution of the Earth around the Sun is modeled using a plain + circular motion, and the starting position is arbitrary; this mode + is triggered whenever the time is tracked using floating-point + numbers (i.e., the parameter `start_time` in the constructor of + :class:`.Simulation` is a ``float``). + +2. The proper motion of the Earth around the Sun is computed using + ephemeridis tables; the calculation is much slower, but the result + enables to properly simulate the observation of moving objects in + the Solar System, like planets or asteroids. In this case, the + parameter `start_time` must be an instance of the class + ``astropy.time.Time``. In the example above, we would enable the + computation of the proper motion of the Earth with the following + minor change:: + + import astropy.time + + sim = lbs.Simulation( + # Turn on full computation of the Earth orbit around the Sun + start_time=astropy.time.Time("2020-01-01"), + duration_s=60.0, + description="Simple simulation", + random_seed=12345, + ) + +You should compute the proper motion of the Earth around the Sun only +if you need to, as it makes the computation of the pointing +information 10÷100 times slower. + + +From quaternions to detector pointings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To compute the pointing information for a detector, the quaternions +computed through the call to :meth:`.Simulation.set_scanning_strategy` +are not enough, as they only tell how to convert a vector from the +*spin axis* reference frame to the Ecliptic reference frame. We need +two more quaternions that tell how to convert from the reference frame +of the detector to that of the spin axis: + +1. The first quaternion describes how the detector reference frame + (with the `z` axis aligned with the main axis of the radiation + pattern) can be converted to the reference frame of the focal plane + (with the `z` axis aligned with the boresight). This information is + included in the IMO and is properly initialized if you call + :meth:`.DetectorInfo.from_imo`. If you do not specify any + quaternion, the constructor for :class:`.DetectorInfo` will assume + that the detector is looking at the boresight. It will thus create + a default :class:`.RotQuaternion` object, which corresponds to the + identity quaternion :math:`(0 0 0 1)`; this is the case of the + simple example we presented above. + +2. The second quaternion describes how to convert the reference frame + of the focal plane (with the `z` axis aligned with the boresight) + to the reference frame of the spacecraft (where the `z` axis is + aligned with its spin axis). This quaternion is stored in the field + ``bore2spin_quat`` of the class :class:`.InstrumentInfo`, and it is + initialized when you call the method + :meth:`.Simulation.set_scanning_strategy`. + +The LiteBIRD Simulation Framework recomputes the orientation of the +spacecraft with a regular spacing in time (the default is one minute). +However, pointings need to be known at the same sampling frequency +used by the detector, which is usually much higher than the frequency +used to compute the quaternions (in our example above, the sampling +frequency of detector ``det`` was 10 Hz, but the sampling frequency of +the quaternions was 1/60 Hz). Since the framework uses quaternions to +encode the orientation of the spacecraft, oversampling them to the +sampling frequency of the detector is just a matter of applying a +spherical linear interpolation (abbr. `slerp +`_), according to the following +figure: + +.. image:: images/quaternion-sampling.svg + +To be sure to include an additional quaternion *after* the last +sample, like in the figure above, the framework provides the static +method :meth:`.ScanningStrategy.optimal_num_of_quaternions`, which +computes how many quaternions are needed to cover some period with a +given interval between quaternions. For instance, if our simulation +lasts 100 s and we want one quaternion every minute, then the number +of quaternions needed is 3: one at :math:`t = 0`, one at :math:`t = +60\,\mathrm{s}`, and one at :math:`t = 120\,\mathrm{s}`, so that the +latter two can be interpolated for the samples in the range +:math:`60\,\mathrm{s} \leq t \leq 100\,\mathrm{s}`. Here is how the +function works: + +.. testcode:: + + print(lbs.ScanningStrategy.optimal_num_of_quaternions( + time_span_s=100, + delta_time_s=60, + )) + +.. testoutput:: + + 3 + +When using MPI, the relatively small size in memory of the quaternions +(the thick black lines in the figure) enables the framework to keep +a duplicate of the list in all the MPI processes. This is unlike +what happens with the data in TODs (the thin gray lines), which are +split in several blocks inside the :class:`.Observation` class. + +.. note:: + + Slerp assumes a rotation with constant angular speed and axis + between consecutive quaternions. Thus, it only approximates the + proper composition of all the rotations (spin, precession, + revolution around the Sun) that we discussed above. However, + remember that the *actual* spacecraft will follow a scanning + strategy that will be more complex than the one described by our + geometrical model because of many unavoidable non-idealities in a + spacecraft. The approximation of the «slerp» operation is thus + unlikely to be relevant. + +Once LBS has all the quaternions sampled at the proper sampling rate, +it can compute the direction of the detector on the sky and its +orientation angle through :meth:`.Observation.get_pointings`. The +calculation works as follows: + +- The direction is the vector :math:`\vec d = R \hat e_z`, where + :math:`R` is the overall rotation from the detector reference frame + to the Ecliptic reference frame, and :math:`\hat e_z = (0, 0, 1)` is + the one-length vector aligned with the `z` axis. + +- The orientation angle is given by the angle between the North + direction passing through the vector :math:`\vec d` (i.e., along the + meridian of :math:`\vec d`) and the vector :math:`\vec p = R \hat + e_x`, where :math:`R` is the same as above and :math:`\hat e_x = (1, + 0, 0)`, as shown in the following figure (note that :math:`\hat e_x` + has been drawn twice because the one in the upper side represents + the orientation direction in the detector reference frame): + + .. image:: images/orientation-direction.svg + +The purpose of the method :meth:`.Simulation.prepare_pointings`, used +in the example at the beginning of this chapter, is to combine the +quaternions that model the transformations between the many reference +frames used in the framework. The method +:meth:`.Observation.get_pointings` uses these quaternions to compute +the actual pointing directions and the HWP angle on the fly. + +To save memory,:meth:`.Observation.get_pointings` does *not* save the +pointings in a variable once it has calculated their value, and so +they must be recomputed every time you need them. However, in some +applications, pointings need to be accessed several times during a +simulation, and these repeated computations can introduce a noticeable +slowdown in the code. + +If you want to trade speed with memory occupation, you can use the +function :func:`.precompute_pointings` to compute all the pointings at +once and save them into every :class:`.Observation` objects. This +function fills the fields `pointing_matrix` and `hwp_angle`. The +datatype for the pointings is specified by ``pointings_dtype``. This +can be done either with the low level functions :: + + obs = sim.create_observations(detectors=[det]) + lbs.prepare_pointings(obs,sim.instrument,sim.spin2ecliptic_quats) + lbs.precompute_pointings(obs, pointings_dtype=np.float64) + +or with the methods of the :class:`.Simulation`:: + + sim.create_observations(detectors=[det]) + sim.prepare_pointings() + sim.precompute_pointings(pointings_dtype=np.float64) + + +How the boresight is specified +------------------------------ + +As LiteBIRD includes three focal planes and two telescopes, the +specification of the boresight requires some care. In +:cite:`2018:core:delabrouille` and :cite:`2019:pico:hanany`, the +boresight direction is encoded using just one number, the angle +between the boresight and the spin axis. However, both papers deal +with spacecrafts hosting only *one* focal plane. + +The orientation of the boresight direction is specified using three +angles: + +1. The ψ angle encodes the rotation of the focal plane with respect to + the boresight direction itself, and it is ideally 0°; + +2. The angle between the boresight direction and the spin axis is + usually notated with the symbol β (among the three, this is the + most crucial number: it is 65° for CORE, 69° for PICO); + +3. Finally, the boresight can be rotated by an angle φ around the spin + axis; this is important only when you have more than one focal + plane. For LiteBIRD, :math:`\phi_\text{LFT} - \phi_\text{MHFT} + \approx 180^\circ`. + +.. raw:: html + + + +Interpretation of pointings +--------------------------- + +With «pointing», we refer to two different concepts: + +1. The direction where the detector is looking at; + +2. The orientation of the detector while looking at the sky. + +Theoretically, one can encode the direction as a one-length vector +``(x, y, z)`` or as a couple of angles. LBS adopts the second option +to save memory, and it encodes directions using the colatitude (i.e., +90° minus the latitude) and the longitude, commonly indicated with the +letters θ (colatitude) and φ (longitude). + +The orientation of the detector (second point above) can be expressed +either as a vector tangent to the sky sphere or as an angle calculated +with respect to the meridian/parallel going through the point the +detector is looking at. Again, to reduce memory usage, our framework +only encodes the angle. + +The method :meth:`.Observation.get_pointings` returns two matrices: a +“pointing matrix”, laid in memory as a :math:`(N, 3)` matrix, where +:math:`N` is the number of samples in the timeline, and the last +dimension holds the colatitude, longitude, and orientation (in +radians). The second matrix contains the angle of the HWP. Let's +visualize the position of these pointings on a Healpix map:: + + import healpy, numpy as np + import matplotlib.pylab as plt + + nside = 64 + pixidx = healpy.ang2pix(nside, pointings[:, 0], pointings[:, 1]) + m = np.zeros(healpy.nside2npix(nside)) + m[pixidx] = 1 + healpy.mollview(m) + +.. image:: images/scanning-strategy-example.png + + +Custom scanning strategies +-------------------------- + +This section explains how LBS can model scanning strategies other than +the nominal «spinning» one. You will need to understand the functions +provided by the framework to deal with quaternions. + +The framework uses a right-handed coordinate system, like the one +shown in figure: + +.. image:: images/right-handed-coordinates.svg + +where the grey arrows indicate the verse of *positive* rotations. +(They follow the usual right-hand rule: point your thumb along the +axis and the other fingers will point towards the positive direction +of the rotation.) + + +A simple scanning strategy +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We are now ready to discuss implementing other types of scanning +strategies. There are plenty of reasons why one would like to go +beyond the class :class:`.SpinningScanningStrategy`: + +1. You want to study the effect of non-idealities, like second-order + effects caused by contractions/dilations in the mechanical + structure of the telescope that make the angle between the spin + axis and the boresight vary with time. + +2. You are thinking about how to make dedicated observations of some + celestial source (e.g., the Crab Nebula) to calibrate the + instruments. + +To define a new scanning strategy, we define a descendant of the +:class:`.ScanningStrategy` class, an `Abstract Base Class (ABC) +`_; the only mandatory +method is :meth:`.ScanningStrategy.generate_spin2ecl_quaternions`, +which takes as inputs the start time, the length of the simulation, +and the time interval to be used between consecutive quaternions. The +method must return an instance of the :class:`.RotQuaternion`, +containing the computed sequence of quaternions. + +We will code here a straightforward scanning strategy, which does not +involve anything fancy: the spacecraft will spin around the Sun-Earth +axis, and the boresight direction will be along the same spin axis. +Thus, the boresight detector will see only the points along the +Ecliptic plane. This scanning strategy is scientifically useless, but +it is simple enough to be implemented in a few lines of code: + +1. The transformation from boresight to the spin axis reference frame + is the identity; + +2. There is no precession of the spin axis; therefore, the latter + stays on the Ecliptic axis; + +3. The only rotation is caused by the revolution of the Sun-Earth axis + around the Sun, which is implemented as a rotation on the `xy` + plane, i.e., around the `z` axis. + +The following code implements our mock scanning strategy:: + + import litebird_sim as lbs + from litebird_sim import RotQuaternion + import astropy + from typing import Union + + class SimpleScanningStrategy(lbs.ScanningStrategy): + def generate_spin2ecl_quaternions( + self, + start_time: Union[float, astropy.time.Time], + time_span_s: float, + delta_time_s: float, + ) -> RotQuaternion: + # Compute how many quaternions are needed to cover + # the time interval specified by "time_span_s" + num_of_quaternions = ( + lbs.ScanningStrategy.optimal_num_of_quaternions( + time_span_s=time_span_s, + delta_time_s=delta_time_s, + ) + ) + + # Make room for the quaternions + spin2ecliptic_quats = np.empty((num_of_quaternions, 4)) + + # We compute the times when the quaternions need to be + # calculated. Note that ScanningStrategy returns two + # arrays ("time" and "time_s"), but we neglect the second + # because we don't need it in this very simple case + (time, _) = lbs.ScanningStrategy.get_times( + start_time=start_time, + delta_time_s=delta_time_s, + num_of_quaternions=num_of_quaternions, + ) + + # Compute the angle on the Ecliptic plane between the x + # axis and the Sun-Earth axis, possibly using AstroPy + sun_earth_angles_rad = ( + lbs.calculate_sun_earth_angles_rad(time) + ) + + # This code is *not* optimized: in a real-world case, + # you'll probably want to use Numba instead of the + # following "for" loop + for i in range(num_of_quaternions): + # Rotate by 90° around the y axis (move the boresight + # to the Ecliptic xy plane) + spin2ecliptic_quats[i, :] = lbs.quat_rotation_y(np.pi / 2) + + # Simulate the revolution of the spacecraft around + # the Sun using the angles computed above + lbs.quat_left_multiply( + spin2ecliptic_quats[i, :], + *lbs.quat_rotation_z(sun_earth_angles_rad[i]), + ) + + # Return the quaternions wrapped in an instance of + # "RotQuaternion" + return lbs.RotQuaternion( + start_time=start_time, + sampling_rate_hz=1.0 / delta_time_s, + quats=spin2ecliptic_quats, + ) + +To test the class ``SimpleScanningStrategy``, we write some code +similar to the example presented at the beginning of this section. +However, we cannot simulate for just one hour, as it would not be +enough to see any change in the pointing direction: the only thing +that changes as time passes is the position of the Earth on the +Ecliptic plane, and it takes 365 days to make one revolution. +Therefore, we extend the length of the simulation to 365 days. Of +course, there is no need to use a high sampling frequency in our +example, so we use here just one sample per day; for the same reason, +instead of computing one quaternion every minute, we compute one +quaternion every 30 days:: + + import astropy.units as u + import healpy + import numpy as np + + sim = lbs.Simulation( + start_time=0, + duration_s=(365 * u.day).to("s").value, + description="Simple simulation", + random_seed=12345, + ) + + sim.set_scanning_strategy( + scanning_strategy=SimpleScanningStrategy(), + delta_time_s=(30 * u.day).to("s").value + ) + + det = lbs.DetectorInfo( + name="foo", + sampling_rate_hz=1.0 / ((1.0 * u.day).to("s").value), + ) + + (obs,) = sim.create_observations(detectors=[det]) + lbs.prepare_pointings(obs, lbs.InstrumentInfo(), sim.spin2ecliptic_quats) + pointings, _ = obs.get_pointings("all") + + m = np.zeros(healpy.nside2npix(64)) + pixidx = healpy.ang2pix(64, pointings[0, :, 0], pointings[0, :, 1]) + m[pixidx] = 1 + healpy.mollview(m) + +Here is the result: we are indeed scanning the Ecliptic plane! + +.. image:: images/simple-scanning-strategy.png + + +Observing point sources in the sky +---------------------------------- + +It is helpful to simulate the observation of point sources in the sky, +both for a scientific purpose and for instrument calibration. For +instance, an essential task in the calibration of a CMB space +experiment is the estimation of the radiation pattern +:math:`\gamma(\theta, \phi)` for each detector (sometimes +:math:`\gamma` is called the *beam function*). One can do this task +through the observation of a bright point source, like one of the +outer planets (Mars, Jupiter, Saturn, etc.): assuming that the source +is pointlike and neglecting every other emission from the sky, the +response measured by a detector is proportional to the radiation +pattern :math:`\gamma(\theta, \phi)`, where the angles :math:`\theta, +\phi` identify the position of the planet *in the reference frame of +the detector*, i.e., where :math:`\theta = 0` is the direction of the +main beam axis. + +You can use the functions described in this chapter to analyze how +detectors will observe point sources in the sky, properly taking into +account proper motions of the sources (this applies to Solar System +objects, like planets and comets). The library provides the functions +:func:`.get_ecl2det_quaternions`, which has the same syntax as +:func:`.get_pointings` but returns a matrix with shape ``(N, 4)`` +containing the ``N`` quaternions that transform from the Ecliptic +reference frame to the detector. Thus, you can use this method to +estimate how far from the main beam axis a celestial object is and its +orientation with respect to the orientation of the detector. + +Here we show a simple example; the first part is identical to the +examples shown above (using the same scanning strategy as for CORE), +but here we employ AstroPy to compute the Ecliptic coordinates of +Jupiter during the simulation and convert them in the reference frame +of the boresight detector using :func:`.get_ecl2det_quaternions`: + +.. testcode:: + + import numpy as np + import litebird_sim as lbs + import astropy.time, astropy.units as u + from astropy.coordinates import ( + ICRS, + get_body_barycentric, + BarycentricMeanEcliptic, + solar_system_ephemeris, + ) + + sim = lbs.Simulation( + # We use AstroPy times here! + start_time=astropy.time.Time("2020-01-01T00:00:00"), + duration_s=60.0, + description="Simple simulation", + random_seed=12345, + ) + + sim.set_scanning_strategy( + scanning_strategy=lbs.SpinningScanningStrategy( + spin_sun_angle_rad=np.deg2rad(30), + spin_rate_hz=0.5 / 60.0, + precession_rate_hz=1.0 / (4 * u.day).to("s").value, + ) + ) + + sim.set_instrument( + lbs.InstrumentInfo( + name="core", + spin_boresight_angle_rad=np.deg2rad(65), + ), + ) + + det = lbs.DetectorInfo(name="foo", sampling_rate_hz=10) + obs, = sim.create_observations(detectors=[det]) + + ################################################################# + # Here begins the juicy part + + solar_system_ephemeris.set("builtin") + + # The variable "icrs_pos" contains the x,y,z coordinates of Jupiter + # in the ICRS reference frame for each sample time in the + # observation. + icrs_pos = get_body_barycentric( + "jupiter", + obs.get_times(astropy_times=True), + ) + # Convert the ICRS r.f. into the barycentric mean Ecliptic r.f., + # which is the reference frame used by the LiteBIRD simulation + # framework + ecl_vec = (ICRS(icrs_pos) + .transform_to(BarycentricMeanEcliptic()) + .cartesian + .get_xyz() + .value + ) + + # The variable ecl_vec is a 3×N matrix containing the vectors. + # We normalize them so that each has length one (using the L_2 + # norm, hence ord=2) + ecl_vec /= np.linalg.norm(ecl_vec, axis=0, ord=2) + + # Convert the matrix to a N×3 shape + ecl_vec = ecl_vec.transpose() + + # Calculate the quaternions that convert the Ecliptic + # reference system into the detector reference system + quats = lbs.get_ecl2det_quaternions( + obs, + sim.spin2ecliptic_quats, + bore2spin_quat=sim.instrument.bore2spin_quat, + detector_quats=[det.quat], + ) + + # Make room for the xyz vectors in the detector reference frame + det_vec = np.empty_like(ecl_vec) + + # Do the rotation! + lbs.all_rotate_vectors(det_vec, quats[0], ecl_vec) + + print(det_vec) + +.. testoutput:: + + [[ 0.57053937 0.07219102 -0.81809124] + [ 0.57038372 0.06957116 -0.8184267 ] + [ 0.57023386 0.0669494 -0.81874973] + ... + [ 0.99293109 -0.0800506 0.08763421] + [ 0.99310516 -0.07743726 0.08800916] + [ 0.99327345 -0.07482179 0.08837171]] + + +Again, the vectors printed by this script are in the *reference frame +of the detector*, where the vector ``[0 0 1]`` indicates the main axis +of the detector. We can inspect how close Jupiter moves to the main +beam axis during the simulation if we convert the set of `(x, y, z)` +vectors into the angles :math:`\theta` (colatitude) and :math:`\phi` +(longitude), as the colatitude is simply the angular distance from the +main beam axis (:math:`\theta = 0`):: + + import healpy + theta, phi = healpy.vec2ang(det_vec) + + import matplotlib.pylab as plt + + times = obs.get_times() + plt.plot(times - times[0], np.rad2deg(theta)) + plt.xlabel("Time [s]") + plt.ylabel("Angular separation [deg]") + +.. image:: images/jupiter-angular-distance.svg + +We see that Jupiter is ~10° away from the beam axis after ~30 seconds +since the start of the simulation. + +API reference +------------- + +.. automodule:: litebird_sim.scanning + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: litebird_sim.pointings + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: litebird_sim.pointings_in_obs + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/simulations.rst.txt b/docs/build/html/_sources/simulations.rst.txt new file mode 100644 index 00000000..3af70cbe --- /dev/null +++ b/docs/build/html/_sources/simulations.rst.txt @@ -0,0 +1,596 @@ +.. _simulations: + +Simulations +=========== + +The LiteBIRD Simulation Framework is built on the :class:`.Simulation` +class, which should be instantiated in any pipeline built using this +framework. The class acts as a container for the many analysis modules +available to the user, and it offers the following features: + +1. Provenance model; +2. Interface with the instrument database; +3. System abstractions; +4. Generation of reports; +5. Printing status messages on the terminal (logging). + +Provenance model +---------------- + +A «provenance model» is, generally speaking, a way to track the +history and origin of a data set by recording the following +information: + +1. Who or what created the dataset? +2. Which algorithm or instrumentation was used to produce it? +3. Which steps were undertaken to process the raw data? +4. How can one get access to the raw samples used to produce the + dataset? + +The LiteBIRD Simulation Framework tracks these information using +parameter files (in TOML format), accessing the LiteBIRD Instrument +Model (IMO) through the unique identifiers provided by the Data Access +Layer (DAL), and generating reports after the simulation completes. + +.. _parameter_files: + +Parameter files +--------------- + +When you run a simulation, there are typically plenty of parameters +that need to be passed to the code: the resolution of an output map, +the names of the detectors to simulate, whether to include synchrotron +emission in the sky model, etc. + +The :class:`.Simulation` class eases this task by accepting the path to +a TOML file as a parameter (``parameter_file``). Specifying this +parameter triggers two actions: + +1. The file is copied to the output directory where the simulation + output files are going to be written (i.e., the same that contains + ``report.html``); +2. The file is read and made available in the field + ``Simulation.parameters`` (a Python dictionary). + +Using a parameter file is optional; if you do not specify +``parameter_file`` while creating a :class:`.Simulation` object, the +`parameters` field will be set to an empty dictionary. (You can even +directly pass a dictionary to a :class:`.Simulation` object: this can +be handy if you already constructed a parameter object somewhere +else.) + +Take this example of a simple TOML file: + +.. code-block:: toml + + # This is file "my_conf.toml" + [simulation] + random_seed = 12345 + + [general] + nside = 512 + imo_version = "v1.3" + + [sky_model] + components = ["synchrotron", "dust", "cmb"] + +The following example loads the TOML file and prints its contents to +the terminal:: + + import litebird_sim as lbs + + sim = lbs.Simulation(parameter_file="my_conf.toml") + + print("Seed for the random number generator:", + sim.parameters["simulation"]["random_seed"]) + print("NSIDE =", sim.parameters["general"]["nside"]) + print("The IMO I'm going to use is", + sim.parameters["general"]["imo_version"]) + + print("Here are the sky components I'm going to simulate:") + for component in sim.parameters["sky_model"]["components"]: + print("-", component) + +The output of the script is the following: + +.. code-block:: text + + Seed for the random number generator: 12345 + NSIDE = 512 + The IMO I'm going to use is v1.3 + Here are the sky components I'm going to simulate: + - synchrotron + - dust + - cmb + + +A :class:`.Simulation` object only interprets the section +``simulation`` and leaves everything else unevaluated: it's up to the +simulation modules to make sense of any other section. The recognized +parameters in the section named ``simulation`` are the following: + +- ``base_path``: a string containing the path where to save the + results of the simulation. +- ``start_time``: the start time of the simulation. If it is a string + or a `TOML datetime + `_, + it will be passed to the constructor for ``astropy.time.Time``, + otherwise it must be a floating-point value. +- ``duration_s``: a floating-point number specifying how many seconds + the simulation should last. You can pass a string, which can contain + a measurement unit as well: in this case, you are not forced to + specify the duration in seconds. Valid units are: ``days`` (or + ``day``), ``hours`` (or ``hour``), ``min``, and ``sec`` (or ``s``). +- ``name``: a string containing the name of the simulation. +- ``description``: a string containing a (possibly long) description + of what the simulation does. +- ``random_seed``: the seed for the random number generator. An integer + value ensures the reproducibility of the results obtained with random + numbers; by passing ``None`` there will not be the possibility to + re-obtain the same outputs. You can find more details in :ref:`random-numbers`. +- ``numba_num_of_threads``: number of threads used by Numba when running + a parallel calculation. +- ``numba_threading_layer``: the multi-threading library to be used by + Numba for parallel computations. See the `Numba user's manual + `_ to + check which choices are available. + +These parameters can be used instead of the keywords in the +constructor of the :class:`.Simulation` class. Consider the following +code:: + + sim = lbs.Simulation( + base_path="/storage/output", + start_time=astropy.time.Time("2020-02-01T10:30:00"), + duration_s=3600.0, + name="My simulation", + description="A long description should be put here", + random_seed=12345, + numba_num_of_threads=4, + numba_threading_layer="workqueue", + ) + +You can achieve the same if you create a TOML file named ``foo.toml`` +that contains the following lines: + +.. code-block:: toml + + [simulation] + base_path = "/storage/output" + start_time = 2020-02-01T10:30:00 + duration_s = 3600.0 + name = "My simulation" + description = "A long description should be put here" + random_seed = 12345 + numba_num_of_threads = 4 + numba_threading_layer = "workqueue" + +and then you initialize the `sim` variable in your Python code as +follows:: + + sim = lbs.Simulation(parameter_file="foo.toml") + +A nice feature of the framework is that the duration of the mission +must not be specified in seconds. You would achieve identical results +if you specify the duration in one of the following ways: + +.. code-block:: toml + + # All of these are the same + duration_s = "1 hour" + duration_s = "60 min" + duration_s = "3600 s" + +.. _imo-interface: + +Interface with the instrument database +-------------------------------------- + +To simulation LiteBIRD's data acquisition, the simulation code must be +aware of the characteristics of the instrument. These are specified in +the LiteBIRD Instrument Model (IMO) database, which can be accessed by +people with sufficient rights. This Simulation Framework has the +ability to access the database and take the input parameters necessary +for its analysis modules to produce the expected output. + + +System abstractions +------------------- + +In some cases, simulations must be ran on HPC computers, distributing +the job on many processing units; in other cases, a simple laptop +might be enough. The LiteBIRD Simulation Framework uses MPI to +parallelize its codes, which is however an optional dependency: the +code can be ran serially. + +If you want to use MPI in your scripts, assuming that you have created +a virtual environment as explained in the :ref:`tutorial`, you have +just to install `mpi4py `_: + +.. code-block:: sh + + $ pip install mpi4py + + # Always do this after "pip install", it will record the + # version number of mpi4py and all the other libraries + # you have installed in your virtual environment + $ pip freeze > requirements.txt + + # Run the program using 4 processes + $ mpiexec -n 4 python3 my_script.py + +The framework will detect the presence of ``mpi4py``, and it will +automatically use distributed algorithms where applicable; otherwise, +serial algorithms will be used. You can configure this while creating +a :class:`.Simulation` object:: + + import litebird_sim as lbs + import mpi4py + + # This simulation *must* be ran using MPI + sim = lbs.Simulation(mpi_comm=mpi4py.MPI.COMM_WORLD, random_seed=12345) + +The framework sets a number of variables related to MPI; these +variables are *always* defined, even if MPI is not available, and they +can be used to make the code work in serial environments too. If your +code must be able to run both with and without MPI, you should +initialize a :class:`.Simulation` object using the variable +:data:`.MPI_COMM_WORLD`, which is either ``mpi4py.MPI.COMM_WORLD`` +(the default MPI communicator) or a dummy class if MPI is disabled:: + + import litebird_sim as lbs + + # This simulation can take advantage of MPI if present, + # otherwise it will stick to serial execution + sim = lbs.Simulation(mpi_comm=lbs.MPI_COMM_WORLD, random_seed=12345) + +See the page :ref:`using_mpi` for more information. + + +.. _report-generation: + +Generation of reports +--------------------- + +This section should explain how reports can be generated, first from +the perspective of a library user, and then describing how developers +can generate plots for their own modules. + +Here is an example, showing several advanced topics like mathematical +formulae, plots, and value substitution:: + + import litebird_sim as lbs + import matplotlib.pylab as plt + + sim = lbs.Simulation( + name="My simulation", + base_path="output", + random_seed=12345, + ) + data_points = [0, 1, 2, 3] + + plt.plot(data_points) + fig = plt.gcf() + + sim.append_to_report(''' + Here is a formula for $`f(x)`$: + + ```math + f(x) = \sin x + ``` + + And here is a completely unrelated plot: + + ![](myplot.png) + + The data points have the following values: + {% for sample in data_points %} + - {{ sample }} + {% endfor %} + ''', figures=[(fig, "myplot.png")], + data_points=data_points) + + sim.flush() + +And here is the output, which is saved in ``output/report.html``: + +.. image:: images/report_example.png + :align: center + + +Logging +------- + +The report generation tools described above are useful to produce a +synthetic report of the *scientific* outcomes of a simulation. +However, one often wants to monitor the execution of the code in a +more detailed manner, checking which functions have been called, how +often, etc. In this case, the best option is to write messages to the +terminal. Python provides the `logging +`_ module for this +purpose, and when you initialize a :class:`.Simulation` object, the +module is initialized with a set of sensible defaults. In your code you +can use the functions ``debug``, ``info``, ``warning``, ``error``, and +``critical`` to monitor what's happening during execution:: + + import litebird_sim as lbs + import logging as log # "log" is shorter to write + my_sim = lbs.Simulation(random_seed=12345) + log.info("the simulation starts here!") + pi = 3.15 + if pi != 3.14: + log.error("wrong value of pi!") + +The output of the code above is the following: + +.. code-block:: text + + [2020-07-18 06:25:27,653 INFO] the simulation starts here! + [2020-07-18 06:25:27,653 ERROR] wrong value of pi! + +Note that the messages are prepended with the date, time, and level of +severity of the message. + +A few environment variables can taylor the way logging is done: + +- ``LOG_DEBUG``: by default, debug messages are not printed to the + terminal, because they are often too verbose for typical uses. If + you want to debug your code, set a non-empty value to this variable. + +- ``LOG_ALL_MPI``: by default, if you are using MPI then only messages + from the process running with rank 0 will be printed. Setting this + environment variable will make all the processes print their message + to the terminal. (Caution: there might be overlapping messages, if + two processes happen to write at the same time.) + +The way you use these variable from the terminal is illustrated with +an example. Suppose that we changed our example above, so that +``log.debug`` is called instead of ``log.info``:: + + import litebird_sim as lbs + import logging as log # "log" is shorter to write + + my_sim = lbs.Simulation(random_seed=12345) + log.debug("the simulation starts here!") + pi = 3.15 + if pi != 3.14: + log.debug("wrong value of pi!") + +In this case, running the script will produce no messages, as the +default is to skip ``log.debug`` calls: + +.. code-block:: text + + $ python my_script.py + $ + +However, running the script with the environment variable +``LOG_DEBUG`` set will make the messages appear: + +.. code-block:: text + + $ LOG_DEBUG=1 python my_script.py + [2020-07-18 06:31:03,223 DEBUG] the simulation starts here! + [2020-07-18 06:31:03,224 DEBUG] wrong value of pi! + $ + + +Monitoring MPI processes +------------------------ + +When using MPI, the method :meth:`.Simulation.create_observations` +distributes detectors and time spans over all the available +MPI processes. The :class:`.Simulation` class provides a method +that enables the user to inspect how the TOD has been split +among the many MPI processes: :meth:`.Simulation.describe_mpi_distribution`. + +The method must be called at the same time on *all* the MPI +processess, once you have successfully called +:meth:`.Simulation.create_observations`:: + + import litebird_sim as lbs + + # Be sure to create a `Simulation` object and create + # the observations: + # + # sim = lbs.Simulation(…) + # sim.create_observations(…) + + # Now ask the framework to report how it's using each MPI process + descr = sim.describe_mpi_distribution() + +The return type for :meth:`.Simulation.describe_mpi_distribution` +is :class:`.MpiDistributionDescr`, which can be printed on the +spot using ``print(descr)``. The result looks like the following +example: + +.. code-block:: text + + # MPI rank #1 + + ## Observation #0 + - Start time: 0.0 + - Duration: 21600.0 s + - 1 detector(s) (0A) + - TOD(s): tod + - TOD shape: 1×216000 + - Types of the TODs: float64 + + ## Observation #1 + - Start time: 43200.0 + - Duration: 21600.0 s + - 1 detector(s) (0A) + - TOD(s): tod + - TOD shape: 1×216000 + - Types of the TODs: float64 + + # MPI rank #2 + + ## Observation #0 + - Start time: 21600.0 + - Duration: 21600.0 s + - 1 detector(s) (0A) + - TOD(s): tod + - TOD shape: 1×216000 + - Types of the TODs: float64 + + ## Observation #1 + - Start time: 64800.0 + - Duration: 21600.0 s + - 1 detector(s) (0A) + - TOD(s): tod + - TOD shape: 1×216000 + - Types of the TODs: float64 + + # MPI rank #3 + + ## Observation #0 + - Start time: 0.0 + - Duration: 21600.0 s + - 1 detector(s) (0B) + - TOD(s): tod + - TOD shape: 1×216000 + - Types of the TODs: float64 + + ## Observation #1 + - Start time: 43200.0 + - Duration: 21600.0 s + - 1 detector(s) (0B) + - TOD(s): tod + - TOD shape: 1×216000 + - Types of the TODs: float64 + + # MPI rank #4 + + ## Observation #0 + - Start time: 21600.0 + - Duration: 21600.0 s + - 1 detector(s) (0B) + - TOD(s): tod + - TOD shape: 1×216000 + - Types of the TODs: float64 + + ## Observation #1 + - Start time: 64800.0 + - Duration: 21600.0 s + - 1 detector(s) (0B) + - TOD(s): tod + - TOD shape: 1×216000 + - Types of the TODs: float64 + +The class :class:`.MpiDistributionDescr` contains a list +of :class:`.MpiProcessDescr`, which describe the «contents» +of each MPI process. The most important field in each +:class:`.MpiProcessDescr` instance is `observations`, which +is a list of :class:`.MpiObservationDescr` objects: it +contains the size of the TOD array, the names of the detectors, +and other useful information. Refer to the documentation of +each class to know what is inside. + +High level interface +-------------------- + +The class :class:`.Simulation` has a powerful high level +interface that allows to quickly generate a scanning strategy, +allocate the observations, generate simulated timelines +cointaing signal, noise and dipole, build maps, and +save(read) the entire simulation object. The syntax is +straightforward:: + + sim = lbs.Simulation(...) + + sim.set_scanning_strategy(...) + sim.set_instrument(...) + sim.create_observations(...) + sim.prepare_pointings() + + sim.compute_pos_and_vel() + sim.add_dipole() + + sim.fill_tods(...) + sim.add_noise(...) + + result = sim.make_destriped_map(nside=nside) + healpy.mollview(result.destriped_map) + + sim.write_observations(...) + sim.read_observations(...) + +See the documentation in :ref:`observations`, :ref:`scanning-strategy` +:ref:`dipole-anisotropy`, :ref:`noise`, :ref:`mapscanning`, :ref:`mapmaking` for +details of the single functions. + + +Data splits +^^^^^^^^^^^ + +Since the class :class:`.Simulation` is interfaced to +:func:`litebird_sim.make_binned_map` and :func:`litebird_sim.make_destriped_map`, +it is able to provide data splits +both in time and detector space (see :ref:`mapmaking` +for more details on the splitting and the available options). +In addition, the class contains :meth:`.Simulation.make_binned_map_splits`, +which is a wrapper around :func:`litebird_sim.make_binned_map`, +and :meth:`.Simulation.make_destriped_map_splits`, which is a wrapper +around :func:`litebird_sim.make_destriped_map`. +These allows to perform the mapmaking on multiple choices +of splits at once (passed as a list of strings). +Indeed, the functions will loop over the cartesian +product of the time and detector splits. The default +behavior is to perform the mapmaking in each combination +and save the result to disk, to avoid memory issues. +In particular, in the case of :meth:`.Simulation.make_binned_map_splits`, +only the binned maps are saved to disk, unless you set +`include_inv_covariance` to `True`. This saves the elements of +the inverse covariance as extra fields in the output FITS file. +This default behavior can be changed by setting the `write_to_disk` parameter +to `False`. Then, the function returns a dictionary +containing the full results of the mapmaking for each split. +The keys are strings that describe the split in the +format "{Dsplit}_{Tsplit}", such as "waferL00_year1". +On the other hand, the default behavior of +:meth:`.Simulation.make_destriped_map_splits` is to save to disk +the complete :class:`.mapmaking.DestriperResult` class for each +split as a FITS file. To avoid this and get a dictionary similar to the one returned +by :meth:`.Simulation.make_binned_map_splits`, you can set the +`write_to_disk` parameter to `False`. + +The method :meth:`.Simulation.make_destriped_map_splits` also offers the +possibility to recycle the baseline computed from the largest split available. +Indeed, if the flag `recycle_baselines` is set to `True`, that method enforces +the computation of the "full_full" split and then reuses the baselines +computed for that split for all the other splits. + +Before performing the computation, the function :meth:`.Simulation.check_valid_splits` +will check whether the requested split is valid (see :ref:`mapmaking`). +This is a wrapper around :func:`litebird_sim.check_valid_splits`. If the +split is not valid, the function will raise a ValueError. In addition, it +will check whether the requested split is compatible with the duration of +the observation and with the detector list. Thus, for example, if the +observation lasts 1 year, the split "year2" will raise an AsserionError. Similarly, +if the observation is performed with some detector contained in the L00 +wafer, the split "waferL03" will also raise an AsserionError. + +.. _simulation-profiling: + +Profiling a simulation +---------------------- + +The class :class:`.Simulation` provides an higher-level interface to the +functions described in the chapter :ref:`profiling`. The decorator ``@profile`` +automatically wraps a method of the :class:`.Simulation` class so that it measures +the time spent to run the method and adds it to an internal list of +:class:`.TimeProfiler` objects. When the :meth:`.Simulation.flush` method is +called, each MPI process creates its own JSON file containing the methods that +have been measured during the execution of the simulation. These files are +stored in the output directory and have names like ``profile_mpi00000.json``, +``profile_mpi00001.json``, etc. + + +API reference +------------- + +.. automodule:: litebird_sim.simulations + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/singularity.rst.txt b/docs/build/html/_sources/singularity.rst.txt new file mode 100644 index 00000000..dee4b1f5 --- /dev/null +++ b/docs/build/html/_sources/singularity.rst.txt @@ -0,0 +1,217 @@ +Using Singularity +================= + +`Singularity `_ is a container platform that +helps users create isolated environments where programs can be run +without interfering with other libraries installed on the system. You +can consider a container as a zipped file containing a Linux +distribution (Ubuntu), a Python distribution, and the LiteBIRD +Simulation Framework, which is already installed and ready to be used. +It might look similar to a virtual machine but is way faster to start +(no boot time delay) and easier to use. + +Let’s first state what are the *drawbacks* of running the framework in +a container: + +- It is not meant to be a tool used by people developing the LiteBIRD + Simulation Framework; it is only used by users because the source + code for ``litebird_sim`` is stored in a read-only directory. (You + could, however, use it to develop some new module in your home + directory and merge it later.) + +- It only runs under Linux. + +- You must run a recent version of the Linux kernel (at least 3.18, + released in 2015). + +- The container takes considerable space (~0.5 GB) and could be a + waste of disk space if you already have a working Python + distribution. + +- You cannot install it system-wide and call it within other Python + programs you already use on your system. For instance, if you have + installed library XYZ in your system, you cannot call XYZ *and* + functions/classes in ``litebird_sim`` from the same Python script. + (It's a *container*, after all.) + +- Although it is a container, it is *still* possible to have conflicts + with other programs installed on your machine (although there are + simple workarounds); + +However, the reason why we are providing this solution is because of +some significant advantages: + +- You do not need to install/upgrade Python; + +- No need to mess with virtual environments; + +- Existing Python versions won’t conflict with Singularity containers + (but see below for some caveats); + +- All the framework, its dependencies, and the Python compiler itself + are bundled in **one** file, which you can keep in your home + directory or move around; + +- It supports MPI, and thus it can be used on HPC clusters. + +Typically, you might want to use our Singularity container if you just +want to run a Python script that calls ``litebird_sim`` but do not +want/cannot install the framework because of conflicts on your system. + +To use the Singularity container, you must follow these steps: + +1. Build a ``Singularity`` file; using the scripts provided by the + LiteBIRD Simulation Framework is a matter of an instant; + +2. Build the container; this requires a working internet connection + and will take a few minutes; + +3. Once the container is built, a new executable file is ready. With + it, you can start IPython and JupyterLab or run Python programs + calling the LiteBIRD Simulation Framework. + +Let’s see the details of each step. + +Build a ``Singularity`` file +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To build a file for Singularity, you must first clone the +``litebird_sim`` repository: + +.. code-block:: text + + git clone https://github.com/litebird/litebird_sim litebird_sim + +Enter the directory ``litebird_sim/singularity`` and run the script +``create-singularity-file.sh``. It takes the following arguments: + +- The version number of the Ubuntu Linux distribution to use. Some + possible choices are ``22.04``, ``24.04``, …; you should use the + most recent LTS release, which is currently ``24.04``. + +- A flag telling which version of MPI to install. Possible choices + are: + + - ``openmpi``; + + - ``mpich``; + + - ``none`` (no MPI support). + + You should choose the same MPI implementation you are running on + your system. + +Here are a few usage examples; each of them creates a ``Singularity`` +file in the current directory (i.e., ``litebird_sim/singularity``): + +.. code-block:: text + + # Use Ubuntu Linux 24.04 and OpenMPI + $ ./create-singularity-file.sh 24.04 openmpi + + # Use Ubuntu Linux 22.04 and MPICH + $ ./create-singularity-file.sh 22.04 mpich + + # Use Ubuntu Linux 20.04 without MPI + $ ./create-singularity-file.sh 20.04 none + +Build the container +~~~~~~~~~~~~~~~~~~~ + +Once you have executed ``create-singularity-file.sh``, you will have a +``Singularity`` file. It's time to run ``singularity`` and create the +container: + +.. code-block:: text + + singularity build --fakeroot litebird_sim.img Singularity + +(The file name ``litebird_sim.img`` is the container to create. Of +course, you can pick the name you want; for example, if you are +creating several containers, you might name them +``litebird_sim_20.04_openmpi.img`` and so on.) The flag ``--fakeroot`` +allows you to create an image even if you do not have superuser +powers. + +If everything works as expected, in a few minutes you will have a +working container in file ``litebird_sim.img`` (which should be about +~0.5 GB in size). + +To check that the container works correctly, run a self-test on it: + +.. code-block:: text + + singularity test litebird_sim.img + + +Running the container +~~~~~~~~~~~~~~~~~~~~~ + +Once the container has been created, you can run it directly: the +IPython prompt will appear, and you can use ``litebird_sim`` +immediately. + +.. asciinema:: singularity_demo1.cast + :preload: 1 + +You can use it to run scripts as well: + +.. asciinema:: singularity_demo2.cast + :preload: 1 + +.. note:: + + You might wonder how the container could run the script + ``test.py`` if the file was created outside the container. The + reason is that Singularity, by default, mounts the home directory + and the current directory in the container, so you can always + access whatever you have in these directories while running stuff + from the container. + + This might lead to undesired effects, though. Suppose you have + installed Anaconda/Miniconda under your home directory: in this + case, clashes between the Python packages installed within the + container and Anaconda might happen! + + In this case, you can run the container using the syntax + ``singularity run -H /tmp/$USER``: this will mount the home + directory on a directory under ``/tmp``. (You can specify another + directory, of course.) + +To use MPI, you must call ``mpirun`` *outside* the container: + +.. asciinema:: singularity_demo3.cast + :preload: 1 + +To obtain a short help about how to use the container, you can use the +command ``singularity run-help``: + +.. asciinema:: singularity_help.cast + :preload: 1 + +Finally, the following demo shows how to test the correctness of the +LiteBIRD Simulation Framework and browse a local copy of the +documentation. The key feature shown here is that running +``singularity shell litebird_sim.img`` starts a shell within the +container; you can then move to ``/opt/litebird_sim`` (the directory +where the framework has been installed) and run commands from there. + +.. asciinema:: singularity_shell.cast + :preload: 1 + +Running ``python3 -m http.server`` starts an HTTP server connected to +http://0.0.0.0:8000/: browsing to that URL will open your own local +copy of the User's manual for the LiteBIRD Simulation Framework. + + +Accessing the IMO from the container +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you are exporting your home directory (the default), you should +have no problem accessing the IMO, provided that one of these +conditions apply: + +- You are accessing a remote copy of the IMO; + +- You are accessing a local copy of the IMO that resides within your + home directory. diff --git a/docs/build/html/_sources/sky_maps.rst.txt b/docs/build/html/_sources/sky_maps.rst.txt new file mode 100644 index 00000000..6e2cf0dd --- /dev/null +++ b/docs/build/html/_sources/sky_maps.rst.txt @@ -0,0 +1,110 @@ +.. _Mbs: + +Synthetic sky maps +================== + +The LiteBIRD Simulation Framework provides the tools necessary to +produce synthetic maps of the sky. These maps are handy for several +applications: + +1. The framework can synthesize realistic detector measurements and + assemble them in Time Ordered Data (TOD). + +2. Map-based simulations can be run without the need to generate + timelines. Although less accurate than a full end-to-end + simulation, this approach is much faster and requires fewer + resources. + +The LiteBIRD Simulation Framework utilizes the PySM3 library to +generate these sky maps. To fully understand and utilize the modules +described in this chapter, refer to the PySM3 manual. + +Here is an example showing how to use the facilities provided by the +framework to generate a CMB map:: + + import litebird_sim as lbs + + sim = lbs.Simulation(base_path="../output", random_seed=12345) + params = lbs.MbsParameters( + make_cmb=True, + fg_models=["pysm_synch_0", "pysm_freefree_1"], + ) + mbs = lbs.Mbs( + simulation=sim, + parameters=params, + channel_list=[ + lbs.FreqChannelInfo.from_imo( + sim.imo, + "/releases/v1.3/satellite/LFT/L1-040/channel_info", + ), + ], + ) + (healpix_maps, file_paths) = mbs.run_all() + + import healpy + healpy.mollview(healpix_maps["L1-040"][0]) + +.. image:: images/mbs_i.png + +In the dictionary containing the maps, Mbs returns also two variables: + +- The coordinates of the generated maps, in the key `Coordinates` + + - The parameters used for the synthetic map generation, in the key + `Mbs_parameters` + +If ``store_alms`` in :class:`.MbsParameters` is True, ``run_all`` +returns alms instead of pixel space maps. The user can set the maximum +multipole of these alms with ``lmax_alms``, the default value is +:math:`4\times N_{side}`. If ``gaussian_smooth`` is False, Mbs returns +the umbeamed maps or alms. + +Available emission models +------------------------- + +The list of foreground models currently available for the +``fg_models`` parameter of the :class:`.MbsParameters` class is the +following: + +- Anomalous emission: + + - ``pysm_ame_1`` + +- Dust: + + - ``pysm_dust_0`` + + - ``pysm_dust_1`` + + - ``pysm_dust_4`` + + - ``pysm_dust_5`` + + - ``pysm_dust_7`` + + - ``pysm_dust_8`` + +- Free-free: + + - ``pysm_freefree_1`` + +- Synchrotron: + + - ``pysm_synch_0`` + + - ``pysm_synch_1`` + + +Monte Carlo simulations +----------------------- + +To be written! + + +API reference +------------- + +.. automodule:: litebird_sim.mbs.mbs + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/tutorial.rst.txt b/docs/build/html/_sources/tutorial.rst.txt new file mode 100644 index 00000000..66ce91b8 --- /dev/null +++ b/docs/build/html/_sources/tutorial.rst.txt @@ -0,0 +1,574 @@ +.. _tutorial: + +Tutorial +======== + +This section contains a short tutorial that describes how to get ready +to use the framework. It assumes that you have already installed the +``litebird_sim`` framework; refer to :ref:`installation_procedure`. +For a nice and exhaustive example on how to use the framework in the +LiteBIRD case see the `example notebook `_. + +A «Hello world» example +----------------------- + +In this section we assume that you are running these command +interactively, either using the REPL (``python`` or IPython are both +fine) or a Jupyter notebook. + +The first thing to do is to create a folder where you will write your +script. The LiteBIRD Simulation Framework is a *library*, and thus it +should be listed as one of the dependencies of your project. It's +better to use virtual environments, so that dependencies are properly +tracked: + +.. code-block:: sh + + python -m virtualenv ./lbs_tutorial_venv + source lbs_tutorial_venv/bin/activate + +The last line of code might vary, depending on the Operating System +and the shell you are using; refer to the `Python documentation +`_ for more information. + +Once you have activated the virtual environment, you should install +the LiteBIRD Simulation Framework. As it is `registered on PyPI +`_, it's just a matter of +calling ``pip``: + +.. code-block:: sh + + pip install litebird_sim + +To ensure reproducibility of your results, it is good to keep track of +the version numbers used by your program. We will immediately create a +file ``requirements.txt``, which can be used by other people to ensure +that they are using the very same version of the LiteBIRD Simulation +Framework (as well as any other package you might want to install with +``pip``) as ours: + +.. code-block:: sh + + pip freeze > requirements.txt + +(If you use a Version Control System like ``git``, it is a good idea +to add ``requirements.txt`` to the repository.) Anybody will then be +able to install the same versions of each package as you by using the +command ``pip install -r requirements.txt``. + +If you got no errors, you are ready to write your first program! To +follow an ancient tradition, we will write a «Hello world!» program. +Create a new file called ``my_script.py`` in the folder you just +created, and write the following:: + + # File my_script.py + import litebird_sim as lbs + + print("Starting the program...") + sim = lbs.Simulation(base_path="./tut01", random_seed=12345) + sim.append_to_report("Hello, world!") + sim.flush() + print("Done!") + +Surprisingly, the program did not output ``Hello world`` as you might +have expected! Instead, it created a folder, named ``tut01``, and +wrote a few files in it: + +.. code-block:: sh + + $ ls ./tut01 + report.html report.md sakura.css + $ + +Open the file ``report.html`` using your browser (e.g., ``firefox +tut01/report.html``), and the following page will appear: + +.. image:: images/tutorial-bare-report.png + :width: 512 + :align: center + :alt: Screenshot of part of the tutorial produced by our script + +Among the many lines of text produced by the report, you can spot the +presence of our «Hello, world!» message. Hurrah! + +Let's have a look at what happened. The first line imports the +``litebird_sim`` framework; since the name is quite long, it's +customary to shorten it to ``lbs``:: + + import litebird_sim as lbs + +The next interesting stuff happens when we instantiate a +:class:`.Simulation` object:: + + sim = lbs.Simulation(base_path="./tut01", random_seed=12345,) + +Creating a :class:`.Simulation` object makes a lot of complicated +things happen behind the scenes. For example, the mandatory parameter +``random_seed`` is used to build a random number generator useful for +generating noise. In this short example, the important things are +the following: + +1. The code checks if a directory named ``tut01`` exists; if not, it + is created. +2. An empty report is created. + +The report is where the results of a simulation will be saved, and +sections can be appended to it using the method +:meth:`.Simulation.append_to_report`, like we did in our example:: + + sim.append_to_report("Hello, world!") + +The report is actually written to disk only when +:meth:`.Simulation.flush` is called:: + + sim.flush() + +This is the most basic usage of the :class:`.Simulation` class; for +more information, refer to :ref:`simulations`. + +In the next section, we will make something more interesting using the +framework. + + +Interacting with the IMO +------------------------ + +It's not clear why we should want to install a whole framework just to +create a HTML file, no matter how nice it looks. Things begin to get +interesting once we start using other facilities provided by our +framework. + +Simulations for real-life experiments often require to use several +parameters that describe the instruments being simulated: how many +detectors there are, what are their properties, etc. These information +are usually kept in an Instrument MOdel database, IMO for short. + +The LiteBIRD IMO is managed using `InstrumentDB +`_, a web-based database, +but it can be retrieved also as a bundle of files. The LiteBIRD +simulation framework seamlessy interacts with the IMO database and +permits to retrieve all the parameters that describe the LiteBIRD +instruments. + +The simulation framework contains a IMO containing a small +representation of the instruments as described in the paper +`*Probing cosmic inflation with the LiteBIRD cosmic microwave background +polarization survey* `_ +(PTEP, 2022). We will use this small IMO in the tutorial; if you +want to do some serious work, you should install your own copy +of the “full” official IMO. Refer to :ref:`imo-configuration` for +more information. + +Our next example will use the IMO to run something more interesting: + +.. testcode:: + + import litebird_sim as lbs + + imo = lbs.Imo(flatfile_location=lbs.PTEP_IMO_LOCATION) + + sim = lbs.Simulation(base_path="./tut02", random_seed=12345) + lft_file = sim.imo.query( + "/releases/vPTEP/satellite/LFT/instrument_info" + ) + sim.append_to_report( + "The instrument {{ name }} has {{ num }} channels.", + name=lft_file.metadata["name"], + num=lft_file.metadata['number_of_channels'], + ) + + html_report_path = sim.flush() + print(f"Done, the report has been saved in file {html_report_path.name}") + + +.. testoutput:: + + Done, the report has been saved in file report.html + +Let's dig into the code of the example. The first line looks almost +the same as in the previous example:: + + # Previous example + sim = lbs.Simulation(base_path="./tut01", random_seed=12345) + + # This example + sim = lbs.Simulation(base_path="./tut02", random_seed=12345) + +Yet a big difference went unnoticed: since you configured the IMO +using the ``install_imo`` module, the :class:`.Simulation` class +managed to read the database contents and initialize a set of member +variables. This is why we have been able to write the next line:: + + lft_file = sim.imo.query( + "/releases/vPTEP/satellite/LFT/instrument_info" + ) + +Although the parameter looks like a path to some file, it is a +reference to a bit of information in the IMO; specifically, a set of +parameters characterizing the instrument LFT (Low Frequency +Telescope). This call retrieves the parameters and returns a +:class:`.DataFile` object, which contains the information in its +``metadata`` field. These are used to fill the report:: + + sim.append_to_report( + "The instrument {{ name }} has {{ num }} channels.", + name=lft_file.metadata["name"], + num=lft_file.metadata['number_of_channels'], + ) + +The code should be self-evident: the keywords ``name`` and ``num`` are +used in the text to put some actual values within the placeholders +``{{ … }}``. This is the syntax used by `Jinja2 +`_, a powerful +templating library. + +The last lines write the report to disk and return the path to the +HTML file:: + + html_report_path = sim.flush() + print(f"Done, the report has been saved in file {html_report_path.name}") + + +This example showed you how to retrieve information from the IMO and +introduced some features of the method +:meth:`.Simulation.append_to_report`. To learn a bit more about the +the IMO, read :ref:`imo`; for reporting facilities, read +:ref:`reporting`. + + +Creating a coverage map +----------------------- + +We're now moving to something more «astrophysical»: we will write a +program that computes the sky coverage of a scanning +strategy over some time. + +The code is complex because it uses several concepts explained in the +section :ref:`scanning-strategy`; in fact, this example is very +similar to the one shown in that section. It's not needed that you +understand everything, just have a look at the code that generates the +report:: + + import litebird_sim as lbs + import healpy, numpy as np + import matplotlib.pylab as plt + import astropy.units as u + + imo = lbs.Imo(flatfile_location=lbs.PTEP_IMO_LOCATION) + + sim = lbs.Simulation( + base_path="./tut04", + name="Simulation tutorial", + start_time=0, + duration_s=86400., + random_seed=12345, + imo=imo, + ) + + sim.set_scanning_strategy( + scanning_strategy=lbs.SpinningScanningStrategy.from_imo( + imo=imo, + url="/releases/vPTEP/satellite/scanning_parameters", + ), + ) + + sim.set_instrument( + lbs.InstrumentInfo.from_imo( + imo=imo, + url="/releases/vPTEP/satellite/LFT/instrument_info", + ), + ) + + sim.set_hwp(lbs.IdealHWP(ang_speed_radpsec=0.1)) + + # It is entirely possible to mix up definitions taken from + # the IMO with hand-made objects. In this example, we create + # a mock detector instead of reading one from the PTEP IMO. + sim.create_observations( + detectors=lbs.DetectorInfo(name="foo", sampling_rate_hz=10), + ) + + sim.prepare_pointings() + + for cur_obs in sim.observations: + # We use `_` to ignore the HWP angle + cur_pointings, _ = cur_obs.get_pointings(0) + nside = 64 + pixidx = healpy.ang2pix( + nside, + cur_pointings[:, 0], + cur_pointings[:, 1], + ) + m = np.zeros(healpy.nside2npix(nside)) + m[pixidx] = 1 + healpy.mollview(m) + + sim.append_to_report(""" + + ## Coverage map + + Here is the coverage map: + + ![](coverage_map.png) + + The fraction of sky covered is {{ seen }}/{{ total }} pixels + ({{ "%.1f" | format(percentage) }}%). + """, + figures=[(plt.gcf(), "coverage_map.png")], + seen=len(m[m > 0]), + total=len(m), + percentage=100.0 * len(m[m > 0]) / len(m), + ) + + sim.flush() + +This example is interesting because it shows how to interface Healpy +with the report-generation facilities provided by our framework. As +explained in :ref:`scanning-strategy`, the code above does the +following things: + +1. It sets the scanning strategy, triggering the computation of set + of quaternions that encode the orientation of + the spacecraft for the whole duration of the simulation (86,400 + seconds, that is one day); +2. It creates an instance of the class :class:`.InstrumentInfo` and + it registers them using the method + :meth:`.Simulation.set_instrument`; +3. It instantiates a new class that represents an ideal Half-wave Plate + (HWP); +4. It sets the detectors to be simulated and allocates the TODs through + the call to :meth:`.Simulation.create_observations`; +5. It computes the quaternions needed to compute the actual pointings + through the call to :meth:`.Simulation.prepare_pointings`; +6. It produces a coverage map by setting to 1 all those pixels that + are visited by the directions encoded in the pointing information + matrix. To do this, it iterates over all the instances of the + class :class:`.Observation` in the + :class:`.Simulation` object. (In this simple example, there is only + one :class:`.Observation`, but in more complex examples there can + be many of them.) For each :class:`.Observation`, it uses the + method :meth:`.Observation.get_pointings` to compute the pointing + information for that observation. +7. The objects that were read from IMO are properly listed in the + report. + +If you run the example, you will see that the folder ``tut04`` will be +populated with the following files: + +.. code-block:: sh + + $ ls tut04 + coverage_map.png report.html report.md sakura.css + $ + +A new file has appeared: ``coverage_map.png``. If you open the file +``report.html``, you will see that the map has been included in the +report: + +.. image:: images/tutorial-coverage-map.png + :width: 512 + :align: center + :alt: Screenshot of the report produced by our script + + +Creating a signal plus noise timeline +------------------------------------- + +Here we generate a 10 minutes timeline which contains dipole, cmb signal, +galactic dust, and correlated noise. For the noise, we use the random +number generator provided by the :class:`.Simulation` and seeded with +``random_seed``:: + + import litebird_sim as lbs + import healpy, numpy as np + import matplotlib.pylab as plt + from astropy import units, time + + sim = lbs.Simulation( + base_path="./tut05", + name="Simulation tutorial", + start_time=time.Time("2025-01-01T00:00:00"), + duration_s=10 * units.minute.to("s"), + random_seed=12345, + ) + + sim.set_scanning_strategy( + scanning_strategy=lbs.SpinningScanningStrategy( + spin_sun_angle_rad=np.deg2rad(30), # CORE-specific parameter + spin_rate_hz=0.5 / 60, # Ditto + precession_rate_hz=1.0 / (4 * units.day).to("s").value, + ) + ) + + sim.set_instrument( + lbs.InstrumentInfo( + name="core", + spin_boresight_angle_rad=np.deg2rad(65), + ), + ) + + sim.set_hwp(lbs.IdealHWP(ang_speed_radpsec=0.1)) + + detector = lbs.DetectorInfo( + name="foo", + sampling_rate_hz=10.0, + bandcenter_ghz = 200.0, + net_ukrts = 50.0, + fknee_mhz = 20.0, + fmin_hz = 1e-05, + alpha=1.0, + ) + + Mbsparams = lbs.MbsParameters( + nside=128, + make_cmb=True, + make_fg=True, + fg_models=["pysm_dust_0"], + ) + + mbs = lbs.Mbs( + simulation=sim, + parameters=Mbsparams, + detector_list=detector + ) + maps = mbs.run_all()[0] + + sim.create_observations( + detectors=detector, + ) + + sim.prepare_pointings() + + sim.add_dipole() + + sim.add_noise(sim.random) + + sim.fill_tods(maps=maps) + + times = sim.observations[0].get_times() - sim.observations[0].start_time.cxcsec + + plt.plot(times,sim.observations[0].tod[0,:]) + plt.xlabel("Time [s]") + plt.ylabel("Signal [K]") + + sim.append_to_report(""" + + ## Timeline + + Here 10 minutes timeline: + + ![](timeline.png) + + """, + figures=[(plt.gcf(), "timeline.png")], + ) + + sim.flush() + +.. image:: images/tutorial-timeline.png + :width: 512 + :align: center + :alt: Screenshot of part of the tutorial produced by our script + + +Creating a signal plus noise timeline +------------------------------------- + +Here we generate a 1 year timeline which contains cmb signal, galactic +dust, and white noise. The we bin the timeline in a map. :: + + import litebird_sim as lbs + import numpy as np + import matplotlib.pylab as plt + import healpy as hp + from astropy import units, time + + sim = lbs.Simulation( + base_path="./tut06", + name="Simulation tutorial", + start_time=0, + duration_s=1 * units.year.to("s"), + random_seed=12345, + ) + + nside = 64 + + sim.set_scanning_strategy( + scanning_strategy=lbs.SpinningScanningStrategy( + spin_sun_angle_rad=np.deg2rad(30), # CORE-specific parameter + spin_rate_hz=0.5 / 60, # Ditto + precession_rate_hz=1.0 / (4 * units.day).to("s").value, + ) + ) + + sim.set_instrument( + lbs.InstrumentInfo( + name="core", + spin_boresight_angle_rad=np.deg2rad(65), + ), + ) + + sim.set_hwp(lbs.IdealHWP(ang_speed_radpsec=0.1)) + + detector = lbs.DetectorInfo( + name="foo", + sampling_rate_hz=3.0, + bandcenter_ghz = 150.0, + net_ukrts = 50.0, + ) + + Mbsparams = lbs.MbsParameters( + nside=nside, + make_cmb=True, + make_fg=True, + fg_models=["pysm_dust_0"], + ) + + mbs = lbs.Mbs( + simulation=sim, + parameters=Mbsparams, + detector_list=detector + ) + maps = mbs.run_all()[0] + + sim.create_observations( + detectors=detector, + ) + + sim.prepare_pointings() + + sim.fill_tods(maps) + + sim.add_noise(random=sim.random, noise_type="white") + + binner_results = sim.make_binned_map(nside=nside) + binned = binner_results.binned_map + + plt.figure(figsize=(15, 3.2)) + hp.mollview(binned[0], sub=131, title="T", unit=r"[K]") + hp.mollview(binned[1], sub=132, title="Q", unit=r"[K]") + hp.mollview(binned[2], sub=133, title="U", unit=r"[K]") + + sim.append_to_report(""" + + ## Maps + + Here 1 year maps: + + ![](maps.png) + + """, + figures=[(plt.gcf(), "maps.png")], + ) + + sim.flush() + +.. image:: images/tutorial-maps.png + :width: 512 + :align: center + :alt: Screenshot of part of the tutorial produced by our script + +The elements shown in these tutorials should allow you to generate more +complex scripts. The next sections detail the features of the framework +in greater detail. \ No newline at end of file diff --git a/docs/build/html/_static/PowerlineSymbols.otf b/docs/build/html/_static/PowerlineSymbols.otf new file mode 100644 index 00000000..b1582afb Binary files /dev/null and b/docs/build/html/_static/PowerlineSymbols.otf differ diff --git a/docs/build/html/_static/asciinema-custom.css b/docs/build/html/_static/asciinema-custom.css new file mode 100644 index 00000000..5afc281f --- /dev/null +++ b/docs/build/html/_static/asciinema-custom.css @@ -0,0 +1,13 @@ +@font-face { + font-family: 'Powerline Symbols'; + src: url('PowerlineSymbols.otf') format('opentype'); +} + +.asciinema-terminal { + font-family: Consolas, Menlo, 'Bitstream Vera Sans Mono', monospace, 'Powerline Symbols'; +} + +.asciinema-player-wrapper { + text-align: left; + padding-bottom: 25px; +} diff --git a/docs/build/html/_static/asciinema-player_2.6.1.css b/docs/build/html/_static/asciinema-player_2.6.1.css new file mode 100644 index 00000000..8d77df46 --- /dev/null +++ b/docs/build/html/_static/asciinema-player_2.6.1.css @@ -0,0 +1,2563 @@ +.asciinema-player-wrapper { + position: relative; + text-align: center; + outline: none; +} +.asciinema-player-wrapper .title-bar { + display: none; + top: -78px; + transition: top 0.15s linear; + position: absolute; + left: 0; + right: 0; + box-sizing: content-box; + font-size: 20px; + line-height: 1em; + padding: 15px; + font-family: sans-serif; + color: white; + background-color: rgba(0, 0, 0, 0.8); +} +.asciinema-player-wrapper .title-bar img { + vertical-align: middle; + height: 48px; + margin-right: 16px; +} +.asciinema-player-wrapper .title-bar a { + color: white; + text-decoration: underline; +} +.asciinema-player-wrapper .title-bar a:hover { + text-decoration: none; +} +.asciinema-player-wrapper:fullscreen { + background-color: #000; + width: 100%; + height: 100%; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-justify-content: center; + justify-content: center; + -webkit-align-items: center; + align-items: center; +} +.asciinema-player-wrapper:fullscreen .asciinema-player { + position: static; +} +.asciinema-player-wrapper:fullscreen .title-bar { + display: initial; +} +.asciinema-player-wrapper:fullscreen.hud .title-bar { + top: 0; +} +.asciinema-player-wrapper:-webkit-full-screen { + background-color: #000; + width: 100%; + height: 100%; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-justify-content: center; + justify-content: center; + -webkit-align-items: center; + align-items: center; +} +.asciinema-player-wrapper:-webkit-full-screen .asciinema-player { + position: static; +} +.asciinema-player-wrapper:-webkit-full-screen .title-bar { + display: initial; +} +.asciinema-player-wrapper:-webkit-full-screen.hud .title-bar { + top: 0; +} +.asciinema-player-wrapper:-moz-full-screen { + background-color: #000; + width: 100%; + height: 100%; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-justify-content: center; + justify-content: center; + -webkit-align-items: center; + align-items: center; +} +.asciinema-player-wrapper:-moz-full-screen .asciinema-player { + position: static; +} +.asciinema-player-wrapper:-moz-full-screen .title-bar { + display: initial; +} +.asciinema-player-wrapper:-moz-full-screen.hud .title-bar { + top: 0; +} +.asciinema-player-wrapper:-ms-fullscreen { + background-color: #000; + width: 100%; + height: 100%; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-justify-content: center; + justify-content: center; + -webkit-align-items: center; + align-items: center; +} +.asciinema-player-wrapper:-ms-fullscreen .asciinema-player { + position: static; +} +.asciinema-player-wrapper:-ms-fullscreen .title-bar { + display: initial; +} +.asciinema-player-wrapper:-ms-fullscreen.hud .title-bar { + top: 0; +} +.asciinema-player-wrapper .asciinema-player { + text-align: left; + display: inline-block; + padding: 0px; + position: relative; + box-sizing: content-box; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + overflow: hidden; + max-width: 100%; +} +.asciinema-terminal { + box-sizing: content-box; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + overflow: hidden; + padding: 0; + margin: 0px; + display: block; + white-space: pre; + border: 0; + word-wrap: normal; + word-break: normal; + border-radius: 0; + border-style: solid; + cursor: text; + border-width: 0.5em; + font-family: Consolas, Menlo, 'Bitstream Vera Sans Mono', monospace, 'Powerline Symbols'; + line-height: 1.3333333333em; +} +.asciinema-terminal .line { + letter-spacing: normal; + overflow: hidden; + height: 1.3333333333em; +} +.asciinema-terminal .line span { + padding: 0; + display: inline-block; + height: 1.3333333333em; +} +.asciinema-terminal .line { + display: block; + width: 200%; +} +.asciinema-terminal .bright { + font-weight: bold; +} +.asciinema-terminal .underline { + text-decoration: underline; +} +.asciinema-terminal .italic { + font-style: italic; +} +.asciinema-terminal.font-small { + font-size: 12px; +} +.asciinema-terminal.font-medium { + font-size: 18px; +} +.asciinema-terminal.font-big { + font-size: 24px; +} +.asciinema-player .control-bar { + width: 100%; + height: 32px; + background: rgba(0, 0, 0, 0.8); + /* no gradient fallback */ + background: -moz-linear-gradient(top, rgba(0, 0, 0, 0.5) 0%, #000000 25%, #000000 100%); + /* FF3.6-15 */ + background: -webkit-linear-gradient(top, rgba(0, 0, 0, 0.5) 0%, #000000 25%, #000000 100%); + /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to bottom, rgba(0, 0, 0, 0.5) 0%, #000000 25%, #000000 100%); + /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + color: #bbbbbb; + box-sizing: content-box; + line-height: 1; + position: absolute; + bottom: -35px; + left: 0; + transition: bottom 0.15s linear; +} +.asciinema-player .control-bar * { + box-sizing: inherit; + font-size: 0; +} +.asciinema-player .control-bar svg.icon path { + fill: #bbbbbb; +} +.asciinema-player .control-bar .playback-button { + display: block; + float: left; + cursor: pointer; + height: 12px; + width: 12px; + padding: 10px; +} +.asciinema-player .control-bar .playback-button svg { + height: 12px; + width: 12px; +} +.asciinema-player .control-bar .timer { + display: block; + float: left; + width: 50px; + height: 100%; + text-align: center; + font-family: Helvetica, Arial, sans-serif; + font-size: 11px; + font-weight: bold; + line-height: 32px; + cursor: default; +} +.asciinema-player .control-bar .timer span { + display: inline-block; + font-size: inherit; +} +.asciinema-player .control-bar .timer .time-remaining { + display: none; +} +.asciinema-player .control-bar .timer:hover .time-elapsed { + display: none; +} +.asciinema-player .control-bar .timer:hover .time-remaining { + display: inline; +} +.asciinema-player .control-bar .progressbar { + display: block; + overflow: hidden; + height: 100%; + padding: 0 10px; +} +.asciinema-player .control-bar .progressbar .bar { + display: block; + cursor: pointer; + height: 100%; + padding-top: 15px; + font-size: 0; +} +.asciinema-player .control-bar .progressbar .bar .gutter { + display: block; + height: 3px; + background-color: #333; +} +.asciinema-player .control-bar .progressbar .bar .gutter span { + display: inline-block; + height: 100%; + background-color: #bbbbbb; + border-radius: 3px; +} +.asciinema-player .control-bar.live .progressbar .bar { + cursor: default; +} +.asciinema-player .control-bar .fullscreen-button { + display: block; + float: right; + width: 14px; + height: 14px; + padding: 9px; + cursor: pointer; +} +.asciinema-player .control-bar .fullscreen-button svg { + width: 14px; + height: 14px; +} +.asciinema-player .control-bar .fullscreen-button svg:first-child { + display: inline; +} +.asciinema-player .control-bar .fullscreen-button svg:last-child { + display: none; +} +.asciinema-player-wrapper.hud .control-bar { + bottom: 0px; +} +.asciinema-player-wrapper:fullscreen .fullscreen-button svg:first-child { + display: none; +} +.asciinema-player-wrapper:fullscreen .fullscreen-button svg:last-child { + display: inline; +} +.asciinema-player-wrapper:-webkit-full-screen .fullscreen-button svg:first-child { + display: none; +} +.asciinema-player-wrapper:-webkit-full-screen .fullscreen-button svg:last-child { + display: inline; +} +.asciinema-player-wrapper:-moz-full-screen .fullscreen-button svg:first-child { + display: none; +} +.asciinema-player-wrapper:-moz-full-screen .fullscreen-button svg:last-child { + display: inline; +} +.asciinema-player-wrapper:-ms-fullscreen .fullscreen-button svg:first-child { + display: none; +} +.asciinema-player-wrapper:-ms-fullscreen .fullscreen-button svg:last-child { + display: inline; +} +.asciinema-player .loading { + z-index: 10; + background-repeat: no-repeat; + background-position: center; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 32px; + background-color: rgba(0, 0, 0, 0.5); +} +.asciinema-player .start-prompt { + z-index: 10; + background-repeat: no-repeat; + background-position: center; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 32px; + z-index: 20; + cursor: pointer; +} +.asciinema-player .start-prompt .play-button { + font-size: 0px; +} +.asciinema-player .start-prompt .play-button { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + text-align: center; + color: white; + display: table; + width: 100%; + height: 100%; +} +.asciinema-player .start-prompt .play-button div { + vertical-align: middle; + display: table-cell; +} +.asciinema-player .start-prompt .play-button div span { + width: 96px; + height: 96px; + display: inline-block; +} +@-webkit-keyframes expand { + 0% { + -webkit-transform: scale(0); + } + 50% { + -webkit-transform: scale(1); + } + 100% { + z-index: 1; + } +} +@-moz-keyframes expand { + 0% { + -moz-transform: scale(0); + } + 50% { + -moz-transform: scale(1); + } + 100% { + z-index: 1; + } +} +@-o-keyframes expand { + 0% { + -o-transform: scale(0); + } + 50% { + -o-transform: scale(1); + } + 100% { + z-index: 1; + } +} +@keyframes expand { + 0% { + transform: scale(0); + } + 50% { + transform: scale(1); + } + 100% { + z-index: 1; + } +} +.loader { + position: absolute; + left: 50%; + top: 50%; + margin: -20px 0 0 -20px; + background-color: white; + border-radius: 50%; + box-shadow: 0 0 0 6.66667px #141414; + width: 40px; + height: 40px; +} +.loader:before, +.loader:after { + content: ""; + position: absolute; + left: 50%; + top: 50%; + display: block; + margin: -21px 0 0 -21px; + border-radius: 50%; + z-index: 2; + width: 42px; + height: 42px; +} +.loader:before { + background-color: #141414; + -webkit-animation: expand 1.6s linear infinite both; + -moz-animation: expand 1.6s linear infinite both; + animation: expand 1.6s linear infinite both; +} +.loader:after { + background-color: white; + -webkit-animation: expand 1.6s linear 0.8s infinite both; + -moz-animation: expand 1.6s linear 0.8s infinite both; + animation: expand 1.6s linear 0.8s infinite both; +} +.asciinema-terminal .fg-16 { + color: #000000; +} +.asciinema-terminal .bg-16 { + background-color: #000000; +} +.asciinema-terminal .fg-17 { + color: #00005f; +} +.asciinema-terminal .bg-17 { + background-color: #00005f; +} +.asciinema-terminal .fg-18 { + color: #000087; +} +.asciinema-terminal .bg-18 { + background-color: #000087; +} +.asciinema-terminal .fg-19 { + color: #0000af; +} +.asciinema-terminal .bg-19 { + background-color: #0000af; +} +.asciinema-terminal .fg-20 { + color: #0000d7; +} +.asciinema-terminal .bg-20 { + background-color: #0000d7; +} +.asciinema-terminal .fg-21 { + color: #0000ff; +} +.asciinema-terminal .bg-21 { + background-color: #0000ff; +} +.asciinema-terminal .fg-22 { + color: #005f00; +} +.asciinema-terminal .bg-22 { + background-color: #005f00; +} +.asciinema-terminal .fg-23 { + color: #005f5f; +} +.asciinema-terminal .bg-23 { + background-color: #005f5f; +} +.asciinema-terminal .fg-24 { + color: #005f87; +} +.asciinema-terminal .bg-24 { + background-color: #005f87; +} +.asciinema-terminal .fg-25 { + color: #005faf; +} +.asciinema-terminal .bg-25 { + background-color: #005faf; +} +.asciinema-terminal .fg-26 { + color: #005fd7; +} +.asciinema-terminal .bg-26 { + background-color: #005fd7; +} +.asciinema-terminal .fg-27 { + color: #005fff; +} +.asciinema-terminal .bg-27 { + background-color: #005fff; +} +.asciinema-terminal .fg-28 { + color: #008700; +} +.asciinema-terminal .bg-28 { + background-color: #008700; +} +.asciinema-terminal .fg-29 { + color: #00875f; +} +.asciinema-terminal .bg-29 { + background-color: #00875f; +} +.asciinema-terminal .fg-30 { + color: #008787; +} +.asciinema-terminal .bg-30 { + background-color: #008787; +} +.asciinema-terminal .fg-31 { + color: #0087af; +} +.asciinema-terminal .bg-31 { + background-color: #0087af; +} +.asciinema-terminal .fg-32 { + color: #0087d7; +} +.asciinema-terminal .bg-32 { + background-color: #0087d7; +} +.asciinema-terminal .fg-33 { + color: #0087ff; +} +.asciinema-terminal .bg-33 { + background-color: #0087ff; +} +.asciinema-terminal .fg-34 { + color: #00af00; +} +.asciinema-terminal .bg-34 { + background-color: #00af00; +} +.asciinema-terminal .fg-35 { + color: #00af5f; +} +.asciinema-terminal .bg-35 { + background-color: #00af5f; +} +.asciinema-terminal .fg-36 { + color: #00af87; +} +.asciinema-terminal .bg-36 { + background-color: #00af87; +} +.asciinema-terminal .fg-37 { + color: #00afaf; +} +.asciinema-terminal .bg-37 { + background-color: #00afaf; +} +.asciinema-terminal .fg-38 { + color: #00afd7; +} +.asciinema-terminal .bg-38 { + background-color: #00afd7; +} +.asciinema-terminal .fg-39 { + color: #00afff; +} +.asciinema-terminal .bg-39 { + background-color: #00afff; +} +.asciinema-terminal .fg-40 { + color: #00d700; +} +.asciinema-terminal .bg-40 { + background-color: #00d700; +} +.asciinema-terminal .fg-41 { + color: #00d75f; +} +.asciinema-terminal .bg-41 { + background-color: #00d75f; +} +.asciinema-terminal .fg-42 { + color: #00d787; +} +.asciinema-terminal .bg-42 { + background-color: #00d787; +} +.asciinema-terminal .fg-43 { + color: #00d7af; +} +.asciinema-terminal .bg-43 { + background-color: #00d7af; +} +.asciinema-terminal .fg-44 { + color: #00d7d7; +} +.asciinema-terminal .bg-44 { + background-color: #00d7d7; +} +.asciinema-terminal .fg-45 { + color: #00d7ff; +} +.asciinema-terminal .bg-45 { + background-color: #00d7ff; +} +.asciinema-terminal .fg-46 { + color: #00ff00; +} +.asciinema-terminal .bg-46 { + background-color: #00ff00; +} +.asciinema-terminal .fg-47 { + color: #00ff5f; +} +.asciinema-terminal .bg-47 { + background-color: #00ff5f; +} +.asciinema-terminal .fg-48 { + color: #00ff87; +} +.asciinema-terminal .bg-48 { + background-color: #00ff87; +} +.asciinema-terminal .fg-49 { + color: #00ffaf; +} +.asciinema-terminal .bg-49 { + background-color: #00ffaf; +} +.asciinema-terminal .fg-50 { + color: #00ffd7; +} +.asciinema-terminal .bg-50 { + background-color: #00ffd7; +} +.asciinema-terminal .fg-51 { + color: #00ffff; +} +.asciinema-terminal .bg-51 { + background-color: #00ffff; +} +.asciinema-terminal .fg-52 { + color: #5f0000; +} +.asciinema-terminal .bg-52 { + background-color: #5f0000; +} +.asciinema-terminal .fg-53 { + color: #5f005f; +} +.asciinema-terminal .bg-53 { + background-color: #5f005f; +} +.asciinema-terminal .fg-54 { + color: #5f0087; +} +.asciinema-terminal .bg-54 { + background-color: #5f0087; +} +.asciinema-terminal .fg-55 { + color: #5f00af; +} +.asciinema-terminal .bg-55 { + background-color: #5f00af; +} +.asciinema-terminal .fg-56 { + color: #5f00d7; +} +.asciinema-terminal .bg-56 { + background-color: #5f00d7; +} +.asciinema-terminal .fg-57 { + color: #5f00ff; +} +.asciinema-terminal .bg-57 { + background-color: #5f00ff; +} +.asciinema-terminal .fg-58 { + color: #5f5f00; +} +.asciinema-terminal .bg-58 { + background-color: #5f5f00; +} +.asciinema-terminal .fg-59 { + color: #5f5f5f; +} +.asciinema-terminal .bg-59 { + background-color: #5f5f5f; +} +.asciinema-terminal .fg-60 { + color: #5f5f87; +} +.asciinema-terminal .bg-60 { + background-color: #5f5f87; +} +.asciinema-terminal .fg-61 { + color: #5f5faf; +} +.asciinema-terminal .bg-61 { + background-color: #5f5faf; +} +.asciinema-terminal .fg-62 { + color: #5f5fd7; +} +.asciinema-terminal .bg-62 { + background-color: #5f5fd7; +} +.asciinema-terminal .fg-63 { + color: #5f5fff; +} +.asciinema-terminal .bg-63 { + background-color: #5f5fff; +} +.asciinema-terminal .fg-64 { + color: #5f8700; +} +.asciinema-terminal .bg-64 { + background-color: #5f8700; +} +.asciinema-terminal .fg-65 { + color: #5f875f; +} +.asciinema-terminal .bg-65 { + background-color: #5f875f; +} +.asciinema-terminal .fg-66 { + color: #5f8787; +} +.asciinema-terminal .bg-66 { + background-color: #5f8787; +} +.asciinema-terminal .fg-67 { + color: #5f87af; +} +.asciinema-terminal .bg-67 { + background-color: #5f87af; +} +.asciinema-terminal .fg-68 { + color: #5f87d7; +} +.asciinema-terminal .bg-68 { + background-color: #5f87d7; +} +.asciinema-terminal .fg-69 { + color: #5f87ff; +} +.asciinema-terminal .bg-69 { + background-color: #5f87ff; +} +.asciinema-terminal .fg-70 { + color: #5faf00; +} +.asciinema-terminal .bg-70 { + background-color: #5faf00; +} +.asciinema-terminal .fg-71 { + color: #5faf5f; +} +.asciinema-terminal .bg-71 { + background-color: #5faf5f; +} +.asciinema-terminal .fg-72 { + color: #5faf87; +} +.asciinema-terminal .bg-72 { + background-color: #5faf87; +} +.asciinema-terminal .fg-73 { + color: #5fafaf; +} +.asciinema-terminal .bg-73 { + background-color: #5fafaf; +} +.asciinema-terminal .fg-74 { + color: #5fafd7; +} +.asciinema-terminal .bg-74 { + background-color: #5fafd7; +} +.asciinema-terminal .fg-75 { + color: #5fafff; +} +.asciinema-terminal .bg-75 { + background-color: #5fafff; +} +.asciinema-terminal .fg-76 { + color: #5fd700; +} +.asciinema-terminal .bg-76 { + background-color: #5fd700; +} +.asciinema-terminal .fg-77 { + color: #5fd75f; +} +.asciinema-terminal .bg-77 { + background-color: #5fd75f; +} +.asciinema-terminal .fg-78 { + color: #5fd787; +} +.asciinema-terminal .bg-78 { + background-color: #5fd787; +} +.asciinema-terminal .fg-79 { + color: #5fd7af; +} +.asciinema-terminal .bg-79 { + background-color: #5fd7af; +} +.asciinema-terminal .fg-80 { + color: #5fd7d7; +} +.asciinema-terminal .bg-80 { + background-color: #5fd7d7; +} +.asciinema-terminal .fg-81 { + color: #5fd7ff; +} +.asciinema-terminal .bg-81 { + background-color: #5fd7ff; +} +.asciinema-terminal .fg-82 { + color: #5fff00; +} +.asciinema-terminal .bg-82 { + background-color: #5fff00; +} +.asciinema-terminal .fg-83 { + color: #5fff5f; +} +.asciinema-terminal .bg-83 { + background-color: #5fff5f; +} +.asciinema-terminal .fg-84 { + color: #5fff87; +} +.asciinema-terminal .bg-84 { + background-color: #5fff87; +} +.asciinema-terminal .fg-85 { + color: #5fffaf; +} +.asciinema-terminal .bg-85 { + background-color: #5fffaf; +} +.asciinema-terminal .fg-86 { + color: #5fffd7; +} +.asciinema-terminal .bg-86 { + background-color: #5fffd7; +} +.asciinema-terminal .fg-87 { + color: #5fffff; +} +.asciinema-terminal .bg-87 { + background-color: #5fffff; +} +.asciinema-terminal .fg-88 { + color: #870000; +} +.asciinema-terminal .bg-88 { + background-color: #870000; +} +.asciinema-terminal .fg-89 { + color: #87005f; +} +.asciinema-terminal .bg-89 { + background-color: #87005f; +} +.asciinema-terminal .fg-90 { + color: #870087; +} +.asciinema-terminal .bg-90 { + background-color: #870087; +} +.asciinema-terminal .fg-91 { + color: #8700af; +} +.asciinema-terminal .bg-91 { + background-color: #8700af; +} +.asciinema-terminal .fg-92 { + color: #8700d7; +} +.asciinema-terminal .bg-92 { + background-color: #8700d7; +} +.asciinema-terminal .fg-93 { + color: #8700ff; +} +.asciinema-terminal .bg-93 { + background-color: #8700ff; +} +.asciinema-terminal .fg-94 { + color: #875f00; +} +.asciinema-terminal .bg-94 { + background-color: #875f00; +} +.asciinema-terminal .fg-95 { + color: #875f5f; +} +.asciinema-terminal .bg-95 { + background-color: #875f5f; +} +.asciinema-terminal .fg-96 { + color: #875f87; +} +.asciinema-terminal .bg-96 { + background-color: #875f87; +} +.asciinema-terminal .fg-97 { + color: #875faf; +} +.asciinema-terminal .bg-97 { + background-color: #875faf; +} +.asciinema-terminal .fg-98 { + color: #875fd7; +} +.asciinema-terminal .bg-98 { + background-color: #875fd7; +} +.asciinema-terminal .fg-99 { + color: #875fff; +} +.asciinema-terminal .bg-99 { + background-color: #875fff; +} +.asciinema-terminal .fg-100 { + color: #878700; +} +.asciinema-terminal .bg-100 { + background-color: #878700; +} +.asciinema-terminal .fg-101 { + color: #87875f; +} +.asciinema-terminal .bg-101 { + background-color: #87875f; +} +.asciinema-terminal .fg-102 { + color: #878787; +} +.asciinema-terminal .bg-102 { + background-color: #878787; +} +.asciinema-terminal .fg-103 { + color: #8787af; +} +.asciinema-terminal .bg-103 { + background-color: #8787af; +} +.asciinema-terminal .fg-104 { + color: #8787d7; +} +.asciinema-terminal .bg-104 { + background-color: #8787d7; +} +.asciinema-terminal .fg-105 { + color: #8787ff; +} +.asciinema-terminal .bg-105 { + background-color: #8787ff; +} +.asciinema-terminal .fg-106 { + color: #87af00; +} +.asciinema-terminal .bg-106 { + background-color: #87af00; +} +.asciinema-terminal .fg-107 { + color: #87af5f; +} +.asciinema-terminal .bg-107 { + background-color: #87af5f; +} +.asciinema-terminal .fg-108 { + color: #87af87; +} +.asciinema-terminal .bg-108 { + background-color: #87af87; +} +.asciinema-terminal .fg-109 { + color: #87afaf; +} +.asciinema-terminal .bg-109 { + background-color: #87afaf; +} +.asciinema-terminal .fg-110 { + color: #87afd7; +} +.asciinema-terminal .bg-110 { + background-color: #87afd7; +} +.asciinema-terminal .fg-111 { + color: #87afff; +} +.asciinema-terminal .bg-111 { + background-color: #87afff; +} +.asciinema-terminal .fg-112 { + color: #87d700; +} +.asciinema-terminal .bg-112 { + background-color: #87d700; +} +.asciinema-terminal .fg-113 { + color: #87d75f; +} +.asciinema-terminal .bg-113 { + background-color: #87d75f; +} +.asciinema-terminal .fg-114 { + color: #87d787; +} +.asciinema-terminal .bg-114 { + background-color: #87d787; +} +.asciinema-terminal .fg-115 { + color: #87d7af; +} +.asciinema-terminal .bg-115 { + background-color: #87d7af; +} +.asciinema-terminal .fg-116 { + color: #87d7d7; +} +.asciinema-terminal .bg-116 { + background-color: #87d7d7; +} +.asciinema-terminal .fg-117 { + color: #87d7ff; +} +.asciinema-terminal .bg-117 { + background-color: #87d7ff; +} +.asciinema-terminal .fg-118 { + color: #87ff00; +} +.asciinema-terminal .bg-118 { + background-color: #87ff00; +} +.asciinema-terminal .fg-119 { + color: #87ff5f; +} +.asciinema-terminal .bg-119 { + background-color: #87ff5f; +} +.asciinema-terminal .fg-120 { + color: #87ff87; +} +.asciinema-terminal .bg-120 { + background-color: #87ff87; +} +.asciinema-terminal .fg-121 { + color: #87ffaf; +} +.asciinema-terminal .bg-121 { + background-color: #87ffaf; +} +.asciinema-terminal .fg-122 { + color: #87ffd7; +} +.asciinema-terminal .bg-122 { + background-color: #87ffd7; +} +.asciinema-terminal .fg-123 { + color: #87ffff; +} +.asciinema-terminal .bg-123 { + background-color: #87ffff; +} +.asciinema-terminal .fg-124 { + color: #af0000; +} +.asciinema-terminal .bg-124 { + background-color: #af0000; +} +.asciinema-terminal .fg-125 { + color: #af005f; +} +.asciinema-terminal .bg-125 { + background-color: #af005f; +} +.asciinema-terminal .fg-126 { + color: #af0087; +} +.asciinema-terminal .bg-126 { + background-color: #af0087; +} +.asciinema-terminal .fg-127 { + color: #af00af; +} +.asciinema-terminal .bg-127 { + background-color: #af00af; +} +.asciinema-terminal .fg-128 { + color: #af00d7; +} +.asciinema-terminal .bg-128 { + background-color: #af00d7; +} +.asciinema-terminal .fg-129 { + color: #af00ff; +} +.asciinema-terminal .bg-129 { + background-color: #af00ff; +} +.asciinema-terminal .fg-130 { + color: #af5f00; +} +.asciinema-terminal .bg-130 { + background-color: #af5f00; +} +.asciinema-terminal .fg-131 { + color: #af5f5f; +} +.asciinema-terminal .bg-131 { + background-color: #af5f5f; +} +.asciinema-terminal .fg-132 { + color: #af5f87; +} +.asciinema-terminal .bg-132 { + background-color: #af5f87; +} +.asciinema-terminal .fg-133 { + color: #af5faf; +} +.asciinema-terminal .bg-133 { + background-color: #af5faf; +} +.asciinema-terminal .fg-134 { + color: #af5fd7; +} +.asciinema-terminal .bg-134 { + background-color: #af5fd7; +} +.asciinema-terminal .fg-135 { + color: #af5fff; +} +.asciinema-terminal .bg-135 { + background-color: #af5fff; +} +.asciinema-terminal .fg-136 { + color: #af8700; +} +.asciinema-terminal .bg-136 { + background-color: #af8700; +} +.asciinema-terminal .fg-137 { + color: #af875f; +} +.asciinema-terminal .bg-137 { + background-color: #af875f; +} +.asciinema-terminal .fg-138 { + color: #af8787; +} +.asciinema-terminal .bg-138 { + background-color: #af8787; +} +.asciinema-terminal .fg-139 { + color: #af87af; +} +.asciinema-terminal .bg-139 { + background-color: #af87af; +} +.asciinema-terminal .fg-140 { + color: #af87d7; +} +.asciinema-terminal .bg-140 { + background-color: #af87d7; +} +.asciinema-terminal .fg-141 { + color: #af87ff; +} +.asciinema-terminal .bg-141 { + background-color: #af87ff; +} +.asciinema-terminal .fg-142 { + color: #afaf00; +} +.asciinema-terminal .bg-142 { + background-color: #afaf00; +} +.asciinema-terminal .fg-143 { + color: #afaf5f; +} +.asciinema-terminal .bg-143 { + background-color: #afaf5f; +} +.asciinema-terminal .fg-144 { + color: #afaf87; +} +.asciinema-terminal .bg-144 { + background-color: #afaf87; +} +.asciinema-terminal .fg-145 { + color: #afafaf; +} +.asciinema-terminal .bg-145 { + background-color: #afafaf; +} +.asciinema-terminal .fg-146 { + color: #afafd7; +} +.asciinema-terminal .bg-146 { + background-color: #afafd7; +} +.asciinema-terminal .fg-147 { + color: #afafff; +} +.asciinema-terminal .bg-147 { + background-color: #afafff; +} +.asciinema-terminal .fg-148 { + color: #afd700; +} +.asciinema-terminal .bg-148 { + background-color: #afd700; +} +.asciinema-terminal .fg-149 { + color: #afd75f; +} +.asciinema-terminal .bg-149 { + background-color: #afd75f; +} +.asciinema-terminal .fg-150 { + color: #afd787; +} +.asciinema-terminal .bg-150 { + background-color: #afd787; +} +.asciinema-terminal .fg-151 { + color: #afd7af; +} +.asciinema-terminal .bg-151 { + background-color: #afd7af; +} +.asciinema-terminal .fg-152 { + color: #afd7d7; +} +.asciinema-terminal .bg-152 { + background-color: #afd7d7; +} +.asciinema-terminal .fg-153 { + color: #afd7ff; +} +.asciinema-terminal .bg-153 { + background-color: #afd7ff; +} +.asciinema-terminal .fg-154 { + color: #afff00; +} +.asciinema-terminal .bg-154 { + background-color: #afff00; +} +.asciinema-terminal .fg-155 { + color: #afff5f; +} +.asciinema-terminal .bg-155 { + background-color: #afff5f; +} +.asciinema-terminal .fg-156 { + color: #afff87; +} +.asciinema-terminal .bg-156 { + background-color: #afff87; +} +.asciinema-terminal .fg-157 { + color: #afffaf; +} +.asciinema-terminal .bg-157 { + background-color: #afffaf; +} +.asciinema-terminal .fg-158 { + color: #afffd7; +} +.asciinema-terminal .bg-158 { + background-color: #afffd7; +} +.asciinema-terminal .fg-159 { + color: #afffff; +} +.asciinema-terminal .bg-159 { + background-color: #afffff; +} +.asciinema-terminal .fg-160 { + color: #d70000; +} +.asciinema-terminal .bg-160 { + background-color: #d70000; +} +.asciinema-terminal .fg-161 { + color: #d7005f; +} +.asciinema-terminal .bg-161 { + background-color: #d7005f; +} +.asciinema-terminal .fg-162 { + color: #d70087; +} +.asciinema-terminal .bg-162 { + background-color: #d70087; +} +.asciinema-terminal .fg-163 { + color: #d700af; +} +.asciinema-terminal .bg-163 { + background-color: #d700af; +} +.asciinema-terminal .fg-164 { + color: #d700d7; +} +.asciinema-terminal .bg-164 { + background-color: #d700d7; +} +.asciinema-terminal .fg-165 { + color: #d700ff; +} +.asciinema-terminal .bg-165 { + background-color: #d700ff; +} +.asciinema-terminal .fg-166 { + color: #d75f00; +} +.asciinema-terminal .bg-166 { + background-color: #d75f00; +} +.asciinema-terminal .fg-167 { + color: #d75f5f; +} +.asciinema-terminal .bg-167 { + background-color: #d75f5f; +} +.asciinema-terminal .fg-168 { + color: #d75f87; +} +.asciinema-terminal .bg-168 { + background-color: #d75f87; +} +.asciinema-terminal .fg-169 { + color: #d75faf; +} +.asciinema-terminal .bg-169 { + background-color: #d75faf; +} +.asciinema-terminal .fg-170 { + color: #d75fd7; +} +.asciinema-terminal .bg-170 { + background-color: #d75fd7; +} +.asciinema-terminal .fg-171 { + color: #d75fff; +} +.asciinema-terminal .bg-171 { + background-color: #d75fff; +} +.asciinema-terminal .fg-172 { + color: #d78700; +} +.asciinema-terminal .bg-172 { + background-color: #d78700; +} +.asciinema-terminal .fg-173 { + color: #d7875f; +} +.asciinema-terminal .bg-173 { + background-color: #d7875f; +} +.asciinema-terminal .fg-174 { + color: #d78787; +} +.asciinema-terminal .bg-174 { + background-color: #d78787; +} +.asciinema-terminal .fg-175 { + color: #d787af; +} +.asciinema-terminal .bg-175 { + background-color: #d787af; +} +.asciinema-terminal .fg-176 { + color: #d787d7; +} +.asciinema-terminal .bg-176 { + background-color: #d787d7; +} +.asciinema-terminal .fg-177 { + color: #d787ff; +} +.asciinema-terminal .bg-177 { + background-color: #d787ff; +} +.asciinema-terminal .fg-178 { + color: #d7af00; +} +.asciinema-terminal .bg-178 { + background-color: #d7af00; +} +.asciinema-terminal .fg-179 { + color: #d7af5f; +} +.asciinema-terminal .bg-179 { + background-color: #d7af5f; +} +.asciinema-terminal .fg-180 { + color: #d7af87; +} +.asciinema-terminal .bg-180 { + background-color: #d7af87; +} +.asciinema-terminal .fg-181 { + color: #d7afaf; +} +.asciinema-terminal .bg-181 { + background-color: #d7afaf; +} +.asciinema-terminal .fg-182 { + color: #d7afd7; +} +.asciinema-terminal .bg-182 { + background-color: #d7afd7; +} +.asciinema-terminal .fg-183 { + color: #d7afff; +} +.asciinema-terminal .bg-183 { + background-color: #d7afff; +} +.asciinema-terminal .fg-184 { + color: #d7d700; +} +.asciinema-terminal .bg-184 { + background-color: #d7d700; +} +.asciinema-terminal .fg-185 { + color: #d7d75f; +} +.asciinema-terminal .bg-185 { + background-color: #d7d75f; +} +.asciinema-terminal .fg-186 { + color: #d7d787; +} +.asciinema-terminal .bg-186 { + background-color: #d7d787; +} +.asciinema-terminal .fg-187 { + color: #d7d7af; +} +.asciinema-terminal .bg-187 { + background-color: #d7d7af; +} +.asciinema-terminal .fg-188 { + color: #d7d7d7; +} +.asciinema-terminal .bg-188 { + background-color: #d7d7d7; +} +.asciinema-terminal .fg-189 { + color: #d7d7ff; +} +.asciinema-terminal .bg-189 { + background-color: #d7d7ff; +} +.asciinema-terminal .fg-190 { + color: #d7ff00; +} +.asciinema-terminal .bg-190 { + background-color: #d7ff00; +} +.asciinema-terminal .fg-191 { + color: #d7ff5f; +} +.asciinema-terminal .bg-191 { + background-color: #d7ff5f; +} +.asciinema-terminal .fg-192 { + color: #d7ff87; +} +.asciinema-terminal .bg-192 { + background-color: #d7ff87; +} +.asciinema-terminal .fg-193 { + color: #d7ffaf; +} +.asciinema-terminal .bg-193 { + background-color: #d7ffaf; +} +.asciinema-terminal .fg-194 { + color: #d7ffd7; +} +.asciinema-terminal .bg-194 { + background-color: #d7ffd7; +} +.asciinema-terminal .fg-195 { + color: #d7ffff; +} +.asciinema-terminal .bg-195 { + background-color: #d7ffff; +} +.asciinema-terminal .fg-196 { + color: #ff0000; +} +.asciinema-terminal .bg-196 { + background-color: #ff0000; +} +.asciinema-terminal .fg-197 { + color: #ff005f; +} +.asciinema-terminal .bg-197 { + background-color: #ff005f; +} +.asciinema-terminal .fg-198 { + color: #ff0087; +} +.asciinema-terminal .bg-198 { + background-color: #ff0087; +} +.asciinema-terminal .fg-199 { + color: #ff00af; +} +.asciinema-terminal .bg-199 { + background-color: #ff00af; +} +.asciinema-terminal .fg-200 { + color: #ff00d7; +} +.asciinema-terminal .bg-200 { + background-color: #ff00d7; +} +.asciinema-terminal .fg-201 { + color: #ff00ff; +} +.asciinema-terminal .bg-201 { + background-color: #ff00ff; +} +.asciinema-terminal .fg-202 { + color: #ff5f00; +} +.asciinema-terminal .bg-202 { + background-color: #ff5f00; +} +.asciinema-terminal .fg-203 { + color: #ff5f5f; +} +.asciinema-terminal .bg-203 { + background-color: #ff5f5f; +} +.asciinema-terminal .fg-204 { + color: #ff5f87; +} +.asciinema-terminal .bg-204 { + background-color: #ff5f87; +} +.asciinema-terminal .fg-205 { + color: #ff5faf; +} +.asciinema-terminal .bg-205 { + background-color: #ff5faf; +} +.asciinema-terminal .fg-206 { + color: #ff5fd7; +} +.asciinema-terminal .bg-206 { + background-color: #ff5fd7; +} +.asciinema-terminal .fg-207 { + color: #ff5fff; +} +.asciinema-terminal .bg-207 { + background-color: #ff5fff; +} +.asciinema-terminal .fg-208 { + color: #ff8700; +} +.asciinema-terminal .bg-208 { + background-color: #ff8700; +} +.asciinema-terminal .fg-209 { + color: #ff875f; +} +.asciinema-terminal .bg-209 { + background-color: #ff875f; +} +.asciinema-terminal .fg-210 { + color: #ff8787; +} +.asciinema-terminal .bg-210 { + background-color: #ff8787; +} +.asciinema-terminal .fg-211 { + color: #ff87af; +} +.asciinema-terminal .bg-211 { + background-color: #ff87af; +} +.asciinema-terminal .fg-212 { + color: #ff87d7; +} +.asciinema-terminal .bg-212 { + background-color: #ff87d7; +} +.asciinema-terminal .fg-213 { + color: #ff87ff; +} +.asciinema-terminal .bg-213 { + background-color: #ff87ff; +} +.asciinema-terminal .fg-214 { + color: #ffaf00; +} +.asciinema-terminal .bg-214 { + background-color: #ffaf00; +} +.asciinema-terminal .fg-215 { + color: #ffaf5f; +} +.asciinema-terminal .bg-215 { + background-color: #ffaf5f; +} +.asciinema-terminal .fg-216 { + color: #ffaf87; +} +.asciinema-terminal .bg-216 { + background-color: #ffaf87; +} +.asciinema-terminal .fg-217 { + color: #ffafaf; +} +.asciinema-terminal .bg-217 { + background-color: #ffafaf; +} +.asciinema-terminal .fg-218 { + color: #ffafd7; +} +.asciinema-terminal .bg-218 { + background-color: #ffafd7; +} +.asciinema-terminal .fg-219 { + color: #ffafff; +} +.asciinema-terminal .bg-219 { + background-color: #ffafff; +} +.asciinema-terminal .fg-220 { + color: #ffd700; +} +.asciinema-terminal .bg-220 { + background-color: #ffd700; +} +.asciinema-terminal .fg-221 { + color: #ffd75f; +} +.asciinema-terminal .bg-221 { + background-color: #ffd75f; +} +.asciinema-terminal .fg-222 { + color: #ffd787; +} +.asciinema-terminal .bg-222 { + background-color: #ffd787; +} +.asciinema-terminal .fg-223 { + color: #ffd7af; +} +.asciinema-terminal .bg-223 { + background-color: #ffd7af; +} +.asciinema-terminal .fg-224 { + color: #ffd7d7; +} +.asciinema-terminal .bg-224 { + background-color: #ffd7d7; +} +.asciinema-terminal .fg-225 { + color: #ffd7ff; +} +.asciinema-terminal .bg-225 { + background-color: #ffd7ff; +} +.asciinema-terminal .fg-226 { + color: #ffff00; +} +.asciinema-terminal .bg-226 { + background-color: #ffff00; +} +.asciinema-terminal .fg-227 { + color: #ffff5f; +} +.asciinema-terminal .bg-227 { + background-color: #ffff5f; +} +.asciinema-terminal .fg-228 { + color: #ffff87; +} +.asciinema-terminal .bg-228 { + background-color: #ffff87; +} +.asciinema-terminal .fg-229 { + color: #ffffaf; +} +.asciinema-terminal .bg-229 { + background-color: #ffffaf; +} +.asciinema-terminal .fg-230 { + color: #ffffd7; +} +.asciinema-terminal .bg-230 { + background-color: #ffffd7; +} +.asciinema-terminal .fg-231 { + color: #ffffff; +} +.asciinema-terminal .bg-231 { + background-color: #ffffff; +} +.asciinema-terminal .fg-232 { + color: #080808; +} +.asciinema-terminal .bg-232 { + background-color: #080808; +} +.asciinema-terminal .fg-233 { + color: #121212; +} +.asciinema-terminal .bg-233 { + background-color: #121212; +} +.asciinema-terminal .fg-234 { + color: #1c1c1c; +} +.asciinema-terminal .bg-234 { + background-color: #1c1c1c; +} +.asciinema-terminal .fg-235 { + color: #262626; +} +.asciinema-terminal .bg-235 { + background-color: #262626; +} +.asciinema-terminal .fg-236 { + color: #303030; +} +.asciinema-terminal .bg-236 { + background-color: #303030; +} +.asciinema-terminal .fg-237 { + color: #3a3a3a; +} +.asciinema-terminal .bg-237 { + background-color: #3a3a3a; +} +.asciinema-terminal .fg-238 { + color: #444444; +} +.asciinema-terminal .bg-238 { + background-color: #444444; +} +.asciinema-terminal .fg-239 { + color: #4e4e4e; +} +.asciinema-terminal .bg-239 { + background-color: #4e4e4e; +} +.asciinema-terminal .fg-240 { + color: #585858; +} +.asciinema-terminal .bg-240 { + background-color: #585858; +} +.asciinema-terminal .fg-241 { + color: #626262; +} +.asciinema-terminal .bg-241 { + background-color: #626262; +} +.asciinema-terminal .fg-242 { + color: #6c6c6c; +} +.asciinema-terminal .bg-242 { + background-color: #6c6c6c; +} +.asciinema-terminal .fg-243 { + color: #767676; +} +.asciinema-terminal .bg-243 { + background-color: #767676; +} +.asciinema-terminal .fg-244 { + color: #808080; +} +.asciinema-terminal .bg-244 { + background-color: #808080; +} +.asciinema-terminal .fg-245 { + color: #8a8a8a; +} +.asciinema-terminal .bg-245 { + background-color: #8a8a8a; +} +.asciinema-terminal .fg-246 { + color: #949494; +} +.asciinema-terminal .bg-246 { + background-color: #949494; +} +.asciinema-terminal .fg-247 { + color: #9e9e9e; +} +.asciinema-terminal .bg-247 { + background-color: #9e9e9e; +} +.asciinema-terminal .fg-248 { + color: #a8a8a8; +} +.asciinema-terminal .bg-248 { + background-color: #a8a8a8; +} +.asciinema-terminal .fg-249 { + color: #b2b2b2; +} +.asciinema-terminal .bg-249 { + background-color: #b2b2b2; +} +.asciinema-terminal .fg-250 { + color: #bcbcbc; +} +.asciinema-terminal .bg-250 { + background-color: #bcbcbc; +} +.asciinema-terminal .fg-251 { + color: #c6c6c6; +} +.asciinema-terminal .bg-251 { + background-color: #c6c6c6; +} +.asciinema-terminal .fg-252 { + color: #d0d0d0; +} +.asciinema-terminal .bg-252 { + background-color: #d0d0d0; +} +.asciinema-terminal .fg-253 { + color: #dadada; +} +.asciinema-terminal .bg-253 { + background-color: #dadada; +} +.asciinema-terminal .fg-254 { + color: #e4e4e4; +} +.asciinema-terminal .bg-254 { + background-color: #e4e4e4; +} +.asciinema-terminal .fg-255 { + color: #eeeeee; +} +.asciinema-terminal .bg-255 { + background-color: #eeeeee; +} +.asciinema-theme-asciinema .asciinema-terminal { + color: #cccccc; + background-color: #121314; + border-color: #121314; +} +.asciinema-theme-asciinema .fg-bg { + color: #121314; +} +.asciinema-theme-asciinema .bg-fg { + background-color: #cccccc; +} +.asciinema-theme-asciinema .fg-0 { + color: #000000; +} +.asciinema-theme-asciinema .bg-0 { + background-color: #000000; +} +.asciinema-theme-asciinema .fg-1 { + color: #dd3c69; +} +.asciinema-theme-asciinema .bg-1 { + background-color: #dd3c69; +} +.asciinema-theme-asciinema .fg-2 { + color: #4ebf22; +} +.asciinema-theme-asciinema .bg-2 { + background-color: #4ebf22; +} +.asciinema-theme-asciinema .fg-3 { + color: #ddaf3c; +} +.asciinema-theme-asciinema .bg-3 { + background-color: #ddaf3c; +} +.asciinema-theme-asciinema .fg-4 { + color: #26b0d7; +} +.asciinema-theme-asciinema .bg-4 { + background-color: #26b0d7; +} +.asciinema-theme-asciinema .fg-5 { + color: #b954e1; +} +.asciinema-theme-asciinema .bg-5 { + background-color: #b954e1; +} +.asciinema-theme-asciinema .fg-6 { + color: #54e1b9; +} +.asciinema-theme-asciinema .bg-6 { + background-color: #54e1b9; +} +.asciinema-theme-asciinema .fg-7 { + color: #d9d9d9; +} +.asciinema-theme-asciinema .bg-7 { + background-color: #d9d9d9; +} +.asciinema-theme-asciinema .fg-8 { + color: #4d4d4d; +} +.asciinema-theme-asciinema .bg-8 { + background-color: #4d4d4d; +} +.asciinema-theme-asciinema .fg-9 { + color: #dd3c69; +} +.asciinema-theme-asciinema .bg-9 { + background-color: #dd3c69; +} +.asciinema-theme-asciinema .fg-10 { + color: #4ebf22; +} +.asciinema-theme-asciinema .bg-10 { + background-color: #4ebf22; +} +.asciinema-theme-asciinema .fg-11 { + color: #ddaf3c; +} +.asciinema-theme-asciinema .bg-11 { + background-color: #ddaf3c; +} +.asciinema-theme-asciinema .fg-12 { + color: #26b0d7; +} +.asciinema-theme-asciinema .bg-12 { + background-color: #26b0d7; +} +.asciinema-theme-asciinema .fg-13 { + color: #b954e1; +} +.asciinema-theme-asciinema .bg-13 { + background-color: #b954e1; +} +.asciinema-theme-asciinema .fg-14 { + color: #54e1b9; +} +.asciinema-theme-asciinema .bg-14 { + background-color: #54e1b9; +} +.asciinema-theme-asciinema .fg-15 { + color: #ffffff; +} +.asciinema-theme-asciinema .bg-15 { + background-color: #ffffff; +} +.asciinema-theme-asciinema .fg-8, +.asciinema-theme-asciinema .fg-9, +.asciinema-theme-asciinema .fg-10, +.asciinema-theme-asciinema .fg-11, +.asciinema-theme-asciinema .fg-12, +.asciinema-theme-asciinema .fg-13, +.asciinema-theme-asciinema .fg-14, +.asciinema-theme-asciinema .fg-15 { + font-weight: bold; +} +.asciinema-theme-tango .asciinema-terminal { + color: #cccccc; + background-color: #121314; + border-color: #121314; +} +.asciinema-theme-tango .fg-bg { + color: #121314; +} +.asciinema-theme-tango .bg-fg { + background-color: #cccccc; +} +.asciinema-theme-tango .fg-0 { + color: #000000; +} +.asciinema-theme-tango .bg-0 { + background-color: #000000; +} +.asciinema-theme-tango .fg-1 { + color: #cc0000; +} +.asciinema-theme-tango .bg-1 { + background-color: #cc0000; +} +.asciinema-theme-tango .fg-2 { + color: #4e9a06; +} +.asciinema-theme-tango .bg-2 { + background-color: #4e9a06; +} +.asciinema-theme-tango .fg-3 { + color: #c4a000; +} +.asciinema-theme-tango .bg-3 { + background-color: #c4a000; +} +.asciinema-theme-tango .fg-4 { + color: #3465a4; +} +.asciinema-theme-tango .bg-4 { + background-color: #3465a4; +} +.asciinema-theme-tango .fg-5 { + color: #75507b; +} +.asciinema-theme-tango .bg-5 { + background-color: #75507b; +} +.asciinema-theme-tango .fg-6 { + color: #06989a; +} +.asciinema-theme-tango .bg-6 { + background-color: #06989a; +} +.asciinema-theme-tango .fg-7 { + color: #d3d7cf; +} +.asciinema-theme-tango .bg-7 { + background-color: #d3d7cf; +} +.asciinema-theme-tango .fg-8 { + color: #555753; +} +.asciinema-theme-tango .bg-8 { + background-color: #555753; +} +.asciinema-theme-tango .fg-9 { + color: #ef2929; +} +.asciinema-theme-tango .bg-9 { + background-color: #ef2929; +} +.asciinema-theme-tango .fg-10 { + color: #8ae234; +} +.asciinema-theme-tango .bg-10 { + background-color: #8ae234; +} +.asciinema-theme-tango .fg-11 { + color: #fce94f; +} +.asciinema-theme-tango .bg-11 { + background-color: #fce94f; +} +.asciinema-theme-tango .fg-12 { + color: #729fcf; +} +.asciinema-theme-tango .bg-12 { + background-color: #729fcf; +} +.asciinema-theme-tango .fg-13 { + color: #ad7fa8; +} +.asciinema-theme-tango .bg-13 { + background-color: #ad7fa8; +} +.asciinema-theme-tango .fg-14 { + color: #34e2e2; +} +.asciinema-theme-tango .bg-14 { + background-color: #34e2e2; +} +.asciinema-theme-tango .fg-15 { + color: #eeeeec; +} +.asciinema-theme-tango .bg-15 { + background-color: #eeeeec; +} +.asciinema-theme-tango .fg-8, +.asciinema-theme-tango .fg-9, +.asciinema-theme-tango .fg-10, +.asciinema-theme-tango .fg-11, +.asciinema-theme-tango .fg-12, +.asciinema-theme-tango .fg-13, +.asciinema-theme-tango .fg-14, +.asciinema-theme-tango .fg-15 { + font-weight: bold; +} +.asciinema-theme-solarized-dark .asciinema-terminal { + color: #839496; + background-color: #002b36; + border-color: #002b36; +} +.asciinema-theme-solarized-dark .fg-bg { + color: #002b36; +} +.asciinema-theme-solarized-dark .bg-fg { + background-color: #839496; +} +.asciinema-theme-solarized-dark .fg-0 { + color: #073642; +} +.asciinema-theme-solarized-dark .bg-0 { + background-color: #073642; +} +.asciinema-theme-solarized-dark .fg-1 { + color: #dc322f; +} +.asciinema-theme-solarized-dark .bg-1 { + background-color: #dc322f; +} +.asciinema-theme-solarized-dark .fg-2 { + color: #859900; +} +.asciinema-theme-solarized-dark .bg-2 { + background-color: #859900; +} +.asciinema-theme-solarized-dark .fg-3 { + color: #b58900; +} +.asciinema-theme-solarized-dark .bg-3 { + background-color: #b58900; +} +.asciinema-theme-solarized-dark .fg-4 { + color: #268bd2; +} +.asciinema-theme-solarized-dark .bg-4 { + background-color: #268bd2; +} +.asciinema-theme-solarized-dark .fg-5 { + color: #d33682; +} +.asciinema-theme-solarized-dark .bg-5 { + background-color: #d33682; +} +.asciinema-theme-solarized-dark .fg-6 { + color: #2aa198; +} +.asciinema-theme-solarized-dark .bg-6 { + background-color: #2aa198; +} +.asciinema-theme-solarized-dark .fg-7 { + color: #eee8d5; +} +.asciinema-theme-solarized-dark .bg-7 { + background-color: #eee8d5; +} +.asciinema-theme-solarized-dark .fg-8 { + color: #002b36; +} +.asciinema-theme-solarized-dark .bg-8 { + background-color: #002b36; +} +.asciinema-theme-solarized-dark .fg-9 { + color: #cb4b16; +} +.asciinema-theme-solarized-dark .bg-9 { + background-color: #cb4b16; +} +.asciinema-theme-solarized-dark .fg-10 { + color: #586e75; +} +.asciinema-theme-solarized-dark .bg-10 { + background-color: #586e75; +} +.asciinema-theme-solarized-dark .fg-11 { + color: #657b83; +} +.asciinema-theme-solarized-dark .bg-11 { + background-color: #657b83; +} +.asciinema-theme-solarized-dark .fg-12 { + color: #839496; +} +.asciinema-theme-solarized-dark .bg-12 { + background-color: #839496; +} +.asciinema-theme-solarized-dark .fg-13 { + color: #6c71c4; +} +.asciinema-theme-solarized-dark .bg-13 { + background-color: #6c71c4; +} +.asciinema-theme-solarized-dark .fg-14 { + color: #93a1a1; +} +.asciinema-theme-solarized-dark .bg-14 { + background-color: #93a1a1; +} +.asciinema-theme-solarized-dark .fg-15 { + color: #fdf6e3; +} +.asciinema-theme-solarized-dark .bg-15 { + background-color: #fdf6e3; +} +.asciinema-theme-solarized-light .asciinema-terminal { + color: #657b83; + background-color: #fdf6e3; + border-color: #fdf6e3; +} +.asciinema-theme-solarized-light .fg-bg { + color: #fdf6e3; +} +.asciinema-theme-solarized-light .bg-fg { + background-color: #657b83; +} +.asciinema-theme-solarized-light .fg-0 { + color: #073642; +} +.asciinema-theme-solarized-light .bg-0 { + background-color: #073642; +} +.asciinema-theme-solarized-light .fg-1 { + color: #dc322f; +} +.asciinema-theme-solarized-light .bg-1 { + background-color: #dc322f; +} +.asciinema-theme-solarized-light .fg-2 { + color: #859900; +} +.asciinema-theme-solarized-light .bg-2 { + background-color: #859900; +} +.asciinema-theme-solarized-light .fg-3 { + color: #b58900; +} +.asciinema-theme-solarized-light .bg-3 { + background-color: #b58900; +} +.asciinema-theme-solarized-light .fg-4 { + color: #268bd2; +} +.asciinema-theme-solarized-light .bg-4 { + background-color: #268bd2; +} +.asciinema-theme-solarized-light .fg-5 { + color: #d33682; +} +.asciinema-theme-solarized-light .bg-5 { + background-color: #d33682; +} +.asciinema-theme-solarized-light .fg-6 { + color: #2aa198; +} +.asciinema-theme-solarized-light .bg-6 { + background-color: #2aa198; +} +.asciinema-theme-solarized-light .fg-7 { + color: #eee8d5; +} +.asciinema-theme-solarized-light .bg-7 { + background-color: #eee8d5; +} +.asciinema-theme-solarized-light .fg-8 { + color: #002b36; +} +.asciinema-theme-solarized-light .bg-8 { + background-color: #002b36; +} +.asciinema-theme-solarized-light .fg-9 { + color: #cb4b16; +} +.asciinema-theme-solarized-light .bg-9 { + background-color: #cb4b16; +} +.asciinema-theme-solarized-light .fg-10 { + color: #586e75; +} +.asciinema-theme-solarized-light .bg-10 { + background-color: #586e75; +} +.asciinema-theme-solarized-light .fg-11 { + color: #657c83; +} +.asciinema-theme-solarized-light .bg-11 { + background-color: #657c83; +} +.asciinema-theme-solarized-light .fg-12 { + color: #839496; +} +.asciinema-theme-solarized-light .bg-12 { + background-color: #839496; +} +.asciinema-theme-solarized-light .fg-13 { + color: #6c71c4; +} +.asciinema-theme-solarized-light .bg-13 { + background-color: #6c71c4; +} +.asciinema-theme-solarized-light .fg-14 { + color: #93a1a1; +} +.asciinema-theme-solarized-light .bg-14 { + background-color: #93a1a1; +} +.asciinema-theme-solarized-light .fg-15 { + color: #fdf6e3; +} +.asciinema-theme-solarized-light .bg-15 { + background-color: #fdf6e3; +} +.asciinema-theme-seti .asciinema-terminal { + color: #cacecd; + background-color: #111213; + border-color: #111213; +} +.asciinema-theme-seti .fg-bg { + color: #111213; +} +.asciinema-theme-seti .bg-fg { + background-color: #cacecd; +} +.asciinema-theme-seti .fg-0 { + color: #323232; +} +.asciinema-theme-seti .bg-0 { + background-color: #323232; +} +.asciinema-theme-seti .fg-1 { + color: #c22832; +} +.asciinema-theme-seti .bg-1 { + background-color: #c22832; +} +.asciinema-theme-seti .fg-2 { + color: #8ec43d; +} +.asciinema-theme-seti .bg-2 { + background-color: #8ec43d; +} +.asciinema-theme-seti .fg-3 { + color: #e0c64f; +} +.asciinema-theme-seti .bg-3 { + background-color: #e0c64f; +} +.asciinema-theme-seti .fg-4 { + color: #43a5d5; +} +.asciinema-theme-seti .bg-4 { + background-color: #43a5d5; +} +.asciinema-theme-seti .fg-5 { + color: #8b57b5; +} +.asciinema-theme-seti .bg-5 { + background-color: #8b57b5; +} +.asciinema-theme-seti .fg-6 { + color: #8ec43d; +} +.asciinema-theme-seti .bg-6 { + background-color: #8ec43d; +} +.asciinema-theme-seti .fg-7 { + color: #eeeeee; +} +.asciinema-theme-seti .bg-7 { + background-color: #eeeeee; +} +.asciinema-theme-seti .fg-8 { + color: #323232; +} +.asciinema-theme-seti .bg-8 { + background-color: #323232; +} +.asciinema-theme-seti .fg-9 { + color: #c22832; +} +.asciinema-theme-seti .bg-9 { + background-color: #c22832; +} +.asciinema-theme-seti .fg-10 { + color: #8ec43d; +} +.asciinema-theme-seti .bg-10 { + background-color: #8ec43d; +} +.asciinema-theme-seti .fg-11 { + color: #e0c64f; +} +.asciinema-theme-seti .bg-11 { + background-color: #e0c64f; +} +.asciinema-theme-seti .fg-12 { + color: #43a5d5; +} +.asciinema-theme-seti .bg-12 { + background-color: #43a5d5; +} +.asciinema-theme-seti .fg-13 { + color: #8b57b5; +} +.asciinema-theme-seti .bg-13 { + background-color: #8b57b5; +} +.asciinema-theme-seti .fg-14 { + color: #8ec43d; +} +.asciinema-theme-seti .bg-14 { + background-color: #8ec43d; +} +.asciinema-theme-seti .fg-15 { + color: #ffffff; +} +.asciinema-theme-seti .bg-15 { + background-color: #ffffff; +} +.asciinema-theme-seti .fg-8, +.asciinema-theme-seti .fg-9, +.asciinema-theme-seti .fg-10, +.asciinema-theme-seti .fg-11, +.asciinema-theme-seti .fg-12, +.asciinema-theme-seti .fg-13, +.asciinema-theme-seti .fg-14, +.asciinema-theme-seti .fg-15 { + font-weight: bold; +} +/* Based on Monokai from base16 collection - https://github.com/chriskempson/base16 */ +.asciinema-theme-monokai .asciinema-terminal { + color: #f8f8f2; + background-color: #272822; + border-color: #272822; +} +.asciinema-theme-monokai .fg-bg { + color: #272822; +} +.asciinema-theme-monokai .bg-fg { + background-color: #f8f8f2; +} +.asciinema-theme-monokai .fg-0 { + color: #272822; +} +.asciinema-theme-monokai .bg-0 { + background-color: #272822; +} +.asciinema-theme-monokai .fg-1 { + color: #f92672; +} +.asciinema-theme-monokai .bg-1 { + background-color: #f92672; +} +.asciinema-theme-monokai .fg-2 { + color: #a6e22e; +} +.asciinema-theme-monokai .bg-2 { + background-color: #a6e22e; +} +.asciinema-theme-monokai .fg-3 { + color: #f4bf75; +} +.asciinema-theme-monokai .bg-3 { + background-color: #f4bf75; +} +.asciinema-theme-monokai .fg-4 { + color: #66d9ef; +} +.asciinema-theme-monokai .bg-4 { + background-color: #66d9ef; +} +.asciinema-theme-monokai .fg-5 { + color: #ae81ff; +} +.asciinema-theme-monokai .bg-5 { + background-color: #ae81ff; +} +.asciinema-theme-monokai .fg-6 { + color: #a1efe4; +} +.asciinema-theme-monokai .bg-6 { + background-color: #a1efe4; +} +.asciinema-theme-monokai .fg-7 { + color: #f8f8f2; +} +.asciinema-theme-monokai .bg-7 { + background-color: #f8f8f2; +} +.asciinema-theme-monokai .fg-8 { + color: #75715e; +} +.asciinema-theme-monokai .bg-8 { + background-color: #75715e; +} +.asciinema-theme-monokai .fg-9 { + color: #f92672; +} +.asciinema-theme-monokai .bg-9 { + background-color: #f92672; +} +.asciinema-theme-monokai .fg-10 { + color: #a6e22e; +} +.asciinema-theme-monokai .bg-10 { + background-color: #a6e22e; +} +.asciinema-theme-monokai .fg-11 { + color: #f4bf75; +} +.asciinema-theme-monokai .bg-11 { + background-color: #f4bf75; +} +.asciinema-theme-monokai .fg-12 { + color: #66d9ef; +} +.asciinema-theme-monokai .bg-12 { + background-color: #66d9ef; +} +.asciinema-theme-monokai .fg-13 { + color: #ae81ff; +} +.asciinema-theme-monokai .bg-13 { + background-color: #ae81ff; +} +.asciinema-theme-monokai .fg-14 { + color: #a1efe4; +} +.asciinema-theme-monokai .bg-14 { + background-color: #a1efe4; +} +.asciinema-theme-monokai .fg-15 { + color: #f9f8f5; +} +.asciinema-theme-monokai .bg-15 { + background-color: #f9f8f5; +} +.asciinema-theme-monokai .fg-8, +.asciinema-theme-monokai .fg-9, +.asciinema-theme-monokai .fg-10, +.asciinema-theme-monokai .fg-11, +.asciinema-theme-monokai .fg-12, +.asciinema-theme-monokai .fg-13, +.asciinema-theme-monokai .fg-14, +.asciinema-theme-monokai .fg-15 { + font-weight: bold; +} diff --git a/docs/build/html/_static/asciinema-player_2.6.1.js b/docs/build/html/_static/asciinema-player_2.6.1.js new file mode 100644 index 00000000..5ad47e08 --- /dev/null +++ b/docs/build/html/_static/asciinema-player_2.6.1.js @@ -0,0 +1,1213 @@ +/** + * asciinema-player v2.6.1 + * + * Copyright 2011-2018, Marcin Kulik + * + */ + +// CustomEvent polyfill from MDN (https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent) + +(function () { + if (typeof window.CustomEvent === "function") return false; + + function CustomEvent ( event, params ) { + params = params || { bubbles: false, cancelable: false, detail: undefined }; + var evt = document.createEvent( 'CustomEvent'); + evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); + return evt; + } + + CustomEvent.prototype = window.Event.prototype; + + window.CustomEvent = CustomEvent; +})(); + +/** + * @license + * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt + */ +// @version 0.7.22 +"undefined"==typeof WeakMap&&!function(){var e=Object.defineProperty,t=Date.now()%1e9,n=function(){this.name="__st"+(1e9*Math.random()>>>0)+(t++ +"__")};n.prototype={set:function(t,n){var o=t[this.name];return o&&o[0]===t?o[1]=n:e(t,this.name,{value:[t,n],writable:!0}),this},get:function(e){var t;return(t=e[this.name])&&t[0]===e?t[1]:void 0},"delete":function(e){var t=e[this.name];return t&&t[0]===e?(t[0]=t[1]=void 0,!0):!1},has:function(e){var t=e[this.name];return t?t[0]===e:!1}},window.WeakMap=n}(),function(e){function t(e){E.push(e),b||(b=!0,w(o))}function n(e){return window.ShadowDOMPolyfill&&window.ShadowDOMPolyfill.wrapIfNeeded(e)||e}function o(){b=!1;var e=E;E=[],e.sort(function(e,t){return e.uid_-t.uid_});var t=!1;e.forEach(function(e){var n=e.takeRecords();r(e),n.length&&(e.callback_(n,e),t=!0)}),t&&o()}function r(e){e.nodes_.forEach(function(t){var n=v.get(t);n&&n.forEach(function(t){t.observer===e&&t.removeTransientObservers()})})}function i(e,t){for(var n=e;n;n=n.parentNode){var o=v.get(n);if(o)for(var r=0;r0){var r=n[o-1],i=p(r,e);if(i)return void(n[o-1]=i)}else t(this.observer);n[o]=e},addListeners:function(){this.addListeners_(this.target)},addListeners_:function(e){var t=this.options;t.attributes&&e.addEventListener("DOMAttrModified",this,!0),t.characterData&&e.addEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.addEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.addEventListener("DOMNodeRemoved",this,!0)},removeListeners:function(){this.removeListeners_(this.target)},removeListeners_:function(e){var t=this.options;t.attributes&&e.removeEventListener("DOMAttrModified",this,!0),t.characterData&&e.removeEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.removeEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.removeEventListener("DOMNodeRemoved",this,!0)},addTransientObserver:function(e){if(e!==this.target){this.addListeners_(e),this.transientObservedNodes.push(e);var t=v.get(e);t||v.set(e,t=[]),t.push(this)}},removeTransientObservers:function(){var e=this.transientObservedNodes;this.transientObservedNodes=[],e.forEach(function(e){this.removeListeners_(e);for(var t=v.get(e),n=0;n=0)){n.push(e);for(var o,r=e.querySelectorAll("link[rel="+a+"]"),d=0,s=r.length;s>d&&(o=r[d]);d++)o["import"]&&i(o["import"],t,n);t(e)}}var a=window.HTMLImports?window.HTMLImports.IMPORT_LINK_TYPE:"none";e.forDocumentTree=r,e.forSubtree=t}),window.CustomElements.addModule(function(e){function t(e,t){return n(e,t)||o(e,t)}function n(t,n){return e.upgrade(t,n)?!0:void(n&&a(t))}function o(e,t){b(e,function(e){return n(e,t)?!0:void 0})}function r(e){N.push(e),y||(y=!0,setTimeout(i))}function i(){y=!1;for(var e,t=N,n=0,o=t.length;o>n&&(e=t[n]);n++)e();N=[]}function a(e){_?r(function(){d(e)}):d(e)}function d(e){e.__upgraded__&&!e.__attached&&(e.__attached=!0,e.attachedCallback&&e.attachedCallback())}function s(e){u(e),b(e,function(e){u(e)})}function u(e){_?r(function(){c(e)}):c(e)}function c(e){e.__upgraded__&&e.__attached&&(e.__attached=!1,e.detachedCallback&&e.detachedCallback())}function l(e){for(var t=e,n=window.wrap(document);t;){if(t==n)return!0;t=t.parentNode||t.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&t.host}}function f(e){if(e.shadowRoot&&!e.shadowRoot.__watched){g.dom&&console.log("watching shadow-root for: ",e.localName);for(var t=e.shadowRoot;t;)w(t),t=t.olderShadowRoot}}function p(e,n){if(g.dom){var o=n[0];if(o&&"childList"===o.type&&o.addedNodes&&o.addedNodes){for(var r=o.addedNodes[0];r&&r!==document&&!r.host;)r=r.parentNode;var i=r&&(r.URL||r._URL||r.host&&r.host.localName)||"";i=i.split("/?").shift().split("/").pop()}console.group("mutations (%d) [%s]",n.length,i||"")}var a=l(e);n.forEach(function(e){"childList"===e.type&&(M(e.addedNodes,function(e){e.localName&&t(e,a)}),M(e.removedNodes,function(e){e.localName&&s(e)}))}),g.dom&&console.groupEnd()}function m(e){for(e=window.wrap(e),e||(e=window.wrap(document));e.parentNode;)e=e.parentNode;var t=e.__observer;t&&(p(e,t.takeRecords()),i())}function w(e){if(!e.__observer){var t=new MutationObserver(p.bind(this,e));t.observe(e,{childList:!0,subtree:!0}),e.__observer=t}}function v(e){e=window.wrap(e),g.dom&&console.group("upgradeDocument: ",e.baseURI.split("/").pop());var n=e===window.wrap(document);t(e,n),w(e),g.dom&&console.groupEnd()}function h(e){E(e,v)}var g=e.flags,b=e.forSubtree,E=e.forDocumentTree,_=window.MutationObserver._isPolyfilled&&g["throttle-attached"];e.hasPolyfillMutations=_,e.hasThrottledAttached=_;var y=!1,N=[],M=Array.prototype.forEach.call.bind(Array.prototype.forEach),O=Element.prototype.createShadowRoot;O&&(Element.prototype.createShadowRoot=function(){var e=O.call(this);return window.CustomElements.watchShadow(this),e}),e.watchShadow=f,e.upgradeDocumentTree=h,e.upgradeDocument=v,e.upgradeSubtree=o,e.upgradeAll=t,e.attached=a,e.takeRecords=m}),window.CustomElements.addModule(function(e){function t(t,o){if("template"===t.localName&&window.HTMLTemplateElement&&HTMLTemplateElement.decorate&&HTMLTemplateElement.decorate(t),!t.__upgraded__&&t.nodeType===Node.ELEMENT_NODE){var r=t.getAttribute("is"),i=e.getRegisteredDefinition(t.localName)||e.getRegisteredDefinition(r);if(i&&(r&&i.tag==t.localName||!r&&!i["extends"]))return n(t,i,o)}}function n(t,n,r){return a.upgrade&&console.group("upgrade:",t.localName),n.is&&t.setAttribute("is",n.is),o(t,n),t.__upgraded__=!0,i(t),r&&e.attached(t),e.upgradeSubtree(t,r),a.upgrade&&console.groupEnd(),t}function o(e,t){Object.__proto__?e.__proto__=t.prototype:(r(e,t.prototype,t["native"]),e.__proto__=t.prototype)}function r(e,t,n){for(var o={},r=t;r!==n&&r!==HTMLElement.prototype;){for(var i,a=Object.getOwnPropertyNames(r),d=0;i=a[d];d++)o[i]||(Object.defineProperty(e,i,Object.getOwnPropertyDescriptor(r,i)),o[i]=1);r=Object.getPrototypeOf(r)}}function i(e){e.createdCallback&&e.createdCallback()}var a=e.flags;e.upgrade=t,e.upgradeWithDefinition=n,e.implementPrototype=o}),window.CustomElements.addModule(function(e){function t(t,o){var s=o||{};if(!t)throw new Error("document.registerElement: first argument `name` must not be empty");if(t.indexOf("-")<0)throw new Error("document.registerElement: first argument ('name') must contain a dash ('-'). Argument provided was '"+String(t)+"'.");if(r(t))throw new Error("Failed to execute 'registerElement' on 'Document': Registration failed for type '"+String(t)+"'. The type name is invalid.");if(u(t))throw new Error("DuplicateDefinitionError: a type with name '"+String(t)+"' is already registered");return s.prototype||(s.prototype=Object.create(HTMLElement.prototype)),s.__name=t.toLowerCase(),s["extends"]&&(s["extends"]=s["extends"].toLowerCase()),s.lifecycle=s.lifecycle||{},s.ancestry=i(s["extends"]),a(s),d(s),n(s.prototype),c(s.__name,s),s.ctor=l(s),s.ctor.prototype=s.prototype,s.prototype.constructor=s.ctor,e.ready&&v(document),s.ctor}function n(e){if(!e.setAttribute._polyfilled){var t=e.setAttribute;e.setAttribute=function(e,n){o.call(this,e,n,t)};var n=e.removeAttribute;e.removeAttribute=function(e){o.call(this,e,null,n)},e.setAttribute._polyfilled=!0}}function o(e,t,n){e=e.toLowerCase();var o=this.getAttribute(e);n.apply(this,arguments);var r=this.getAttribute(e);this.attributeChangedCallback&&r!==o&&this.attributeChangedCallback(e,o,r)}function r(e){for(var t=0;t<_.length;t++)if(e===_[t])return!0}function i(e){var t=u(e);return t?i(t["extends"]).concat([t]):[]}function a(e){for(var t,n=e["extends"],o=0;t=e.ancestry[o];o++)n=t.is&&t.tag;e.tag=n||e.__name,n&&(e.is=e.__name)}function d(e){if(!Object.__proto__){var t=HTMLElement.prototype;if(e.is){var n=document.createElement(e.tag);t=Object.getPrototypeOf(n)}for(var o,r=e.prototype,i=!1;r;)r==t&&(i=!0),o=Object.getPrototypeOf(r),o&&(r.__proto__=o),r=o;i||console.warn(e.tag+" prototype not found in prototype chain for "+e.is),e["native"]=t}}function s(e){return g(M(e.tag),e)}function u(e){return e?y[e.toLowerCase()]:void 0}function c(e,t){y[e]=t}function l(e){return function(){return s(e)}}function f(e,t,n){return e===N?p(t,n):O(e,t)}function p(e,t){e&&(e=e.toLowerCase()),t&&(t=t.toLowerCase());var n=u(t||e);if(n){if(e==n.tag&&t==n.is)return new n.ctor;if(!t&&!n.is)return new n.ctor}var o;return t?(o=p(e),o.setAttribute("is",t),o):(o=M(e),e.indexOf("-")>=0&&b(o,HTMLElement),o)}function m(e,t){var n=e[t];e[t]=function(){var e=n.apply(this,arguments);return h(e),e}}var w,v=(e.isIE,e.upgradeDocumentTree),h=e.upgradeAll,g=e.upgradeWithDefinition,b=e.implementPrototype,E=e.useNative,_=["annotation-xml","color-profile","font-face","font-face-src","font-face-uri","font-face-format","font-face-name","missing-glyph"],y={},N="http://www.w3.org/1999/xhtml",M=document.createElement.bind(document),O=document.createElementNS.bind(document);w=Object.__proto__||E?function(e,t){return e instanceof t}:function(e,t){if(e instanceof t)return!0;for(var n=e;n;){if(n===t.prototype)return!0;n=n.__proto__}return!1},m(Node.prototype,"cloneNode"),m(document,"importNode"),document.registerElement=t,document.createElement=p,document.createElementNS=f,e.registry=y,e["instanceof"]=w,e.reservedTagList=_,e.getRegisteredDefinition=u,document.register=document.registerElement}),function(e){function t(){i(window.wrap(document)),window.CustomElements.ready=!0;var e=window.requestAnimationFrame||function(e){setTimeout(e,16)};e(function(){setTimeout(function(){window.CustomElements.readyTime=Date.now(),window.HTMLImports&&(window.CustomElements.elapsed=window.CustomElements.readyTime-window.HTMLImports.readyTime),document.dispatchEvent(new CustomEvent("WebComponentsReady",{bubbles:!0}))})})}var n=e.useNative,o=e.initializeModules;e.isIE;if(n){var r=function(){};e.watchShadow=r,e.upgrade=r,e.upgradeAll=r,e.upgradeDocumentTree=r,e.upgradeSubtree=r,e.takeRecords=r,e["instanceof"]=function(e,t){return e instanceof t}}else o();var i=e.upgradeDocumentTree,a=e.upgradeDocument;if(window.wrap||(window.ShadowDOMPolyfill?(window.wrap=window.ShadowDOMPolyfill.wrapIfNeeded,window.unwrap=window.ShadowDOMPolyfill.unwrapIfNeeded):window.wrap=window.unwrap=function(e){return e}),window.HTMLImports&&(window.HTMLImports.__importsParsingHook=function(e){e["import"]&&a(wrap(e["import"]))}),"complete"===document.readyState||e.flags.eager)t();else if("interactive"!==document.readyState||window.attachEvent||window.HTMLImports&&!window.HTMLImports.ready){var d=window.HTMLImports&&!window.HTMLImports.ready?"HTMLImportsLoaded":"DOMContentLoaded";window.addEventListener(d,t)}else t()}(window.CustomElements); +if(typeof Math.imul == "undefined" || (Math.imul(0xffffffff,5) == 0)) { + Math.imul = function (a, b) { + var ah = (a >>> 16) & 0xffff; + var al = a & 0xffff; + var bh = (b >>> 16) & 0xffff; + var bl = b & 0xffff; + // the shift by 0 fixes the sign on the high part + // the final |0 converts the unsigned value into a signed value + return ((al * bl) + (((ah * bl + al * bh) << 16) >>> 0)|0); + } +} + +/** + * React v15.5.4 + * + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ +!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.React=t()}}(function(){return function t(e,n,r){function o(u,a){if(!n[u]){if(!e[u]){var s="function"==typeof require&&require;if(!a&&s)return s(u,!0);if(i)return i(u,!0);var c=new Error("Cannot find module '"+u+"'");throw c.code="MODULE_NOT_FOUND",c}var l=n[u]={exports:{}};e[u][0].call(l.exports,function(t){var n=e[u][1][t];return o(n||t)},l,l.exports,t,e,n,r)}return n[u].exports}for(var i="function"==typeof require&&require,u=0;u1){for(var y=Array(d),h=0;h1){for(var m=Array(v),b=0;b8&&C<=11),x=32,w=String.fromCharCode(x),T={beforeInput:{phasedRegistrationNames:{bubbled:"onBeforeInput",captured:"onBeforeInputCapture"},dependencies:["topCompositionEnd","topKeyPress","topTextInput","topPaste"]},compositionEnd:{phasedRegistrationNames:{bubbled:"onCompositionEnd",captured:"onCompositionEndCapture"},dependencies:["topBlur","topCompositionEnd","topKeyDown","topKeyPress","topKeyUp","topMouseDown"]},compositionStart:{phasedRegistrationNames:{bubbled:"onCompositionStart",captured:"onCompositionStartCapture"},dependencies:["topBlur","topCompositionStart","topKeyDown","topKeyPress","topKeyUp","topMouseDown"]},compositionUpdate:{phasedRegistrationNames:{bubbled:"onCompositionUpdate",captured:"onCompositionUpdateCapture"},dependencies:["topBlur","topCompositionUpdate","topKeyDown","topKeyPress","topKeyUp","topMouseDown"]}},k=!1,P=null,S={eventTypes:T,extractEvents:function(e,t,n,r){return[u(e,t,n,r),p(e,t,n,r)]}};t.exports=S},{123:123,19:19,20:20,78:78,82:82}],4:[function(e,t,n){"use strict";function r(e,t){return e+t.charAt(0).toUpperCase()+t.substring(1)}var o={animationIterationCount:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridRow:!0,gridColumn:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},i=["Webkit","ms","Moz","O"];Object.keys(o).forEach(function(e){i.forEach(function(t){o[r(t,e)]=o[e]})});var a={background:{backgroundAttachment:!0,backgroundColor:!0,backgroundImage:!0,backgroundPositionX:!0,backgroundPositionY:!0,backgroundRepeat:!0},backgroundPosition:{backgroundPositionX:!0,backgroundPositionY:!0},border:{borderWidth:!0,borderStyle:!0,borderColor:!0},borderBottom:{borderBottomWidth:!0,borderBottomStyle:!0,borderBottomColor:!0},borderLeft:{borderLeftWidth:!0,borderLeftStyle:!0,borderLeftColor:!0},borderRight:{borderRightWidth:!0,borderRightStyle:!0,borderRightColor:!0},borderTop:{borderTopWidth:!0,borderTopStyle:!0,borderTopColor:!0},font:{fontStyle:!0,fontVariant:!0,fontWeight:!0,fontSize:!0,lineHeight:!0,fontFamily:!0},outline:{outlineWidth:!0,outlineStyle:!0,outlineColor:!0}},s={isUnitlessNumber:o,shorthandPropertyExpansions:a};t.exports=s},{}],5:[function(e,t,n){"use strict";var r=e(4),o=e(123),i=(e(58),e(125),e(94)),a=e(136),s=e(140),u=(e(142),s(function(e){return a(e)})),l=!1,c="cssFloat";if(o.canUseDOM){var p=document.createElement("div").style;try{p.font=""}catch(e){l=!0}void 0===document.documentElement.style.cssFloat&&(c="styleFloat")}var d={createMarkupForStyles:function(e,t){var n="";for(var r in e)if(e.hasOwnProperty(r)){var o=e[r];null!=o&&(n+=u(r)+":",n+=i(r,o,t)+";")}return n||null},setValueForStyles:function(e,t,n){var o=e.style;for(var a in t)if(t.hasOwnProperty(a)){var s=i(a,t[a],n);if("float"!==a&&"cssFloat"!==a||(a=c),s)o[a]=s;else{var u=l&&r.shorthandPropertyExpansions[a];if(u)for(var p in u)o[p]="";else o[a]=""}}}};t.exports=d},{123:123,125:125,136:136,140:140,142:142,4:4,58:58,94:94}],6:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=e(112),i=e(24),a=(e(137),function(){function e(t){r(this,e),this._callbacks=null,this._contexts=null,this._arg=t}return e.prototype.enqueue=function(e,t){this._callbacks=this._callbacks||[],this._callbacks.push(e),this._contexts=this._contexts||[],this._contexts.push(t)},e.prototype.notifyAll=function(){var e=this._callbacks,t=this._contexts,n=this._arg;if(e&&t){e.length!==t.length&&o("24"),this._callbacks=null,this._contexts=null;for(var r=0;r8));var A=!1;b.canUseDOM&&(A=k("input")&&(!document.documentMode||document.documentMode>11));var D={get:function(){return O.get.call(this)},set:function(e){I=""+e,O.set.call(this,e)}},L={eventTypes:S,extractEvents:function(e,t,n,o){var i,a,s=t?E.getNodeFromInstance(t):window;if(r(s)?R?i=u:a=l:P(s)?A?i=f:(i=m,a=h):v(s)&&(i=g),i){var c=i(e,t);if(c){var p=w.getPooled(S.change,c,n,o);return p.type="change",C.accumulateTwoPhaseDispatches(p),p}}a&&a(e,s,t),"topBlur"===e&&y(t,s)}};t.exports=L},{102:102,109:109,110:110,123:123,16:16,19:19,33:33,71:71,80:80}],8:[function(e,t,n){"use strict";function r(e,t){return Array.isArray(t)&&(t=t[1]),t?t.nextSibling:e.firstChild}function o(e,t,n){c.insertTreeBefore(e,t,n)}function i(e,t,n){Array.isArray(t)?s(e,t[0],t[1],n):m(e,t,n)}function a(e,t){if(Array.isArray(t)){var n=t[1];t=t[0],u(e,t,n),e.removeChild(n)}e.removeChild(t)}function s(e,t,n,r){for(var o=t;;){var i=o.nextSibling;if(m(e,o,r),o===n)break;o=i}}function u(e,t,n){for(;;){var r=t.nextSibling;if(r===n)break;e.removeChild(r)}}function l(e,t,n){var r=e.parentNode,o=e.nextSibling;o===t?n&&m(r,document.createTextNode(n),o):n?(h(o,n),u(r,o,t)):u(r,e,t)}var c=e(9),p=e(13),d=(e(33),e(58),e(93)),f=e(114),h=e(115),m=d(function(e,t,n){e.insertBefore(t,n)}),v=p.dangerouslyReplaceNodeWithMarkup,g={dangerouslyReplaceNodeWithMarkup:v,replaceDelimitedText:l,processUpdates:function(e,t){for(var n=0;n-1||a("96",e),!l.plugins[n]){t.extractEvents||a("97",e),l.plugins[n]=t;var r=t.eventTypes;for(var i in r)o(r[i],t,i)||a("98",i,e)}}}function o(e,t,n){l.eventNameDispatchConfigs.hasOwnProperty(n)&&a("99",n),l.eventNameDispatchConfigs[n]=e;var r=e.phasedRegistrationNames;if(r){for(var o in r)if(r.hasOwnProperty(o)){var s=r[o];i(s,t,n)}return!0}return!!e.registrationName&&(i(e.registrationName,t,n),!0)}function i(e,t,n){l.registrationNameModules[e]&&a("100",e),l.registrationNameModules[e]=t,l.registrationNameDependencies[e]=t.eventTypes[n].dependencies}var a=e(112),s=(e(137),null),u={},l={plugins:[],eventNameDispatchConfigs:{},registrationNameModules:{},registrationNameDependencies:{},possibleRegistrationNames:null,injectEventPluginOrder:function(e){s&&a("101"),s=Array.prototype.slice.call(e),r()},injectEventPluginsByName:function(e){var t=!1;for(var n in e)if(e.hasOwnProperty(n)){var o=e[n];u.hasOwnProperty(n)&&u[n]===o||(u[n]&&a("102",n),u[n]=o,t=!0)}t&&r()},getPluginModuleForEvent:function(e){var t=e.dispatchConfig;if(t.registrationName)return l.registrationNameModules[t.registrationName]||null;if(void 0!==t.phasedRegistrationNames){var n=t.phasedRegistrationNames;for(var r in n)if(n.hasOwnProperty(r)){var o=l.registrationNameModules[n[r]];if(o)return o}}return null},_resetEventPlugins:function(){s=null;for(var e in u)u.hasOwnProperty(e)&&delete u[e];l.plugins.length=0;var t=l.eventNameDispatchConfigs;for(var n in t)t.hasOwnProperty(n)&&delete t[n];var r=l.registrationNameModules;for(var o in r)r.hasOwnProperty(o)&&delete r[o]}};t.exports=l},{112:112,137:137}],18:[function(e,t,n){"use strict";function r(e){return"topMouseUp"===e||"topTouchEnd"===e||"topTouchCancel"===e}function o(e){return"topMouseMove"===e||"topTouchMove"===e}function i(e){return"topMouseDown"===e||"topTouchStart"===e}function a(e,t,n,r){var o=e.type||"unknown-event";e.currentTarget=g.getNodeFromInstance(r),t?m.invokeGuardedCallbackWithCatch(o,n,e):m.invokeGuardedCallback(o,n,e),e.currentTarget=null}function s(e,t){var n=e._dispatchListeners,r=e._dispatchInstances;if(Array.isArray(n))for(var o=0;o1?1-t:void 0;return this._fallbackText=o.slice(e,s),this._fallbackText}}),i.addPoolingTo(r),t.exports=r},{106:106,143:143,24:24}],21:[function(e,t,n){"use strict";var r=e(11),o=r.injection.MUST_USE_PROPERTY,i=r.injection.HAS_BOOLEAN_VALUE,a=r.injection.HAS_NUMERIC_VALUE,s=r.injection.HAS_POSITIVE_NUMERIC_VALUE,u=r.injection.HAS_OVERLOADED_BOOLEAN_VALUE,l={isCustomAttribute:RegExp.prototype.test.bind(new RegExp("^(data|aria)-["+r.ATTRIBUTE_NAME_CHAR+"]*$")),Properties:{accept:0,acceptCharset:0,accessKey:0,action:0,allowFullScreen:i,allowTransparency:0,alt:0,as:0,async:i,autoComplete:0,autoPlay:i,capture:i,cellPadding:0,cellSpacing:0,charSet:0,challenge:0,checked:o|i,cite:0,classID:0,className:0,cols:s,colSpan:0,content:0,contentEditable:0,contextMenu:0,controls:i,coords:0,crossOrigin:0,data:0,dateTime:0,default:i,defer:i,dir:0,disabled:i,download:u,draggable:0,encType:0,form:0,formAction:0,formEncType:0,formMethod:0,formNoValidate:i,formTarget:0,frameBorder:0,headers:0,height:0,hidden:i,high:0,href:0,hrefLang:0,htmlFor:0,httpEquiv:0,icon:0,id:0,inputMode:0,integrity:0,is:0,keyParams:0,keyType:0,kind:0,label:0,lang:0,list:0,loop:i,low:0,manifest:0,marginHeight:0,marginWidth:0,max:0,maxLength:0,media:0,mediaGroup:0,method:0,min:0,minLength:0,multiple:o|i,muted:o|i,name:0,nonce:0,noValidate:i,open:i,optimum:0,pattern:0,placeholder:0,playsInline:i,poster:0,preload:0,profile:0,radioGroup:0,readOnly:i,referrerPolicy:0,rel:0,required:i,reversed:i,role:0,rows:s,rowSpan:a,sandbox:0,scope:0,scoped:i,scrolling:0,seamless:i,selected:o|i,shape:0,size:s,sizes:0,span:s,spellCheck:0,src:0,srcDoc:0,srcLang:0,srcSet:0,start:a,step:0,style:0,summary:0,tabIndex:0,target:0,title:0,type:0,useMap:0,value:0,width:0,wmode:0,wrap:0,about:0,datatype:0,inlist:0,prefix:0,property:0,resource:0,typeof:0,vocab:0,autoCapitalize:0,autoCorrect:0,autoSave:0,color:0,itemProp:0,itemScope:i,itemType:0,itemID:0,itemRef:0,results:0,security:0,unselectable:0},DOMAttributeNames:{acceptCharset:"accept-charset",className:"class",htmlFor:"for",httpEquiv:"http-equiv"},DOMPropertyNames:{},DOMMutationMethods:{value:function(e,t){if(null==t)return e.removeAttribute("value");"number"!==e.type||!1===e.hasAttribute("value")?e.setAttribute("value",""+t):e.validity&&!e.validity.badInput&&e.ownerDocument.activeElement!==e&&e.setAttribute("value",""+t)}}};t.exports=l},{11:11}],22:[function(e,t,n){"use strict";function r(e){var t={"=":"=0",":":"=2"};return"$"+(""+e).replace(/[=:]/g,function(e){return t[e]})}function o(e){var t={"=0":"=","=2":":"};return(""+("."===e[0]&&"$"===e[1]?e.substring(2):e.substring(1))).replace(/(=0|=2)/g,function(e){return t[e]})}var i={escape:r,unescape:o};t.exports=i},{}],23:[function(e,t,n){"use strict";function r(e){null!=e.checkedLink&&null!=e.valueLink&&s("87")}function o(e){r(e),(null!=e.value||null!=e.onChange)&&s("88")}function i(e){r(e),(null!=e.checked||null!=e.onChange)&&s("89")}function a(e){if(e){var t=e.getName();if(t)return" Check the render method of `"+t+"`."}return""}var s=e(112),u=e(64),l=e(145),c=e(120),p=l(c.isValidElement),d=(e(137),e(142),{button:!0,checkbox:!0,image:!0,hidden:!0,radio:!0,reset:!0,submit:!0}),f={value:function(e,t,n){return!e[t]||d[e.type]||e.onChange||e.readOnly||e.disabled?null:new Error("You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.")},checked:function(e,t,n){return!e[t]||e.onChange||e.readOnly||e.disabled?null:new Error("You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`.")},onChange:p.func},h={},m={checkPropTypes:function(e,t,n){for(var r in f){if(f.hasOwnProperty(r))var o=f[r](t,r,e,"prop",null,u);o instanceof Error&&!(o.message in h)&&(h[o.message]=!0,a(n))}},getValue:function(e){return e.valueLink?(o(e),e.valueLink.value):e.value},getChecked:function(e){return e.checkedLink?(i(e),e.checkedLink.value):e.checked},executeOnChange:function(e,t){return e.valueLink?(o(e),e.valueLink.requestChange(t.target.value)):e.checkedLink?(i(e),e.checkedLink.requestChange(t.target.checked)):e.onChange?e.onChange.call(void 0,t):void 0}};t.exports=m},{112:112,120:120,137:137,142:142,145:145,64:64}],24:[function(e,t,n){"use strict";var r=e(112),o=(e(137),function(e){var t=this;if(t.instancePool.length){var n=t.instancePool.pop();return t.call(n,e),n}return new t(e)}),i=function(e,t){var n=this;if(n.instancePool.length){var r=n.instancePool.pop();return n.call(r,e,t),r}return new n(e,t)},a=function(e,t,n){var r=this;if(r.instancePool.length){var o=r.instancePool.pop();return r.call(o,e,t,n),o}return new r(e,t,n)},s=function(e,t,n,r){var o=this;if(o.instancePool.length){var i=o.instancePool.pop();return o.call(i,e,t,n,r),i}return new o(e,t,n,r)},u=function(e){var t=this;e instanceof t||r("25"),e.destructor(),t.instancePool.length=0||null!=t.is}function h(e){var t=e.type;d(t),this._currentElement=e,this._tag=t.toLowerCase(),this._namespaceURI=null,this._renderedChildren=null,this._previousStyle=null,this._previousStyleCopy=null,this._hostNode=null,this._hostParent=null,this._rootNodeID=0,this._domID=0,this._hostContainerInfo=null,this._wrapperState=null,this._topLevelWrapper=null,this._flags=0}var m=e(112),v=e(143),g=e(2),y=e(5),_=e(9),C=e(10),b=e(11),E=e(12),x=e(16),w=e(17),T=e(25),k=e(32),P=e(33),S=e(38),N=e(39),M=e(40),I=e(43),O=(e(58),e(61)),R=e(68),A=(e(129),e(95)),D=(e(137),e(109),e(141),e(118),e(142),k),L=x.deleteListener,U=P.getNodeFromInstance,F=T.listenTo,j=w.registrationNameModules,V={string:!0,number:!0},B="__html",W={children:null,dangerouslySetInnerHTML:null,suppressContentEditableWarning:null},H=11,q={topAbort:"abort",topCanPlay:"canplay",topCanPlayThrough:"canplaythrough",topDurationChange:"durationchange",topEmptied:"emptied",topEncrypted:"encrypted",topEnded:"ended",topError:"error",topLoadedData:"loadeddata",topLoadedMetadata:"loadedmetadata",topLoadStart:"loadstart",topPause:"pause",topPlay:"play",topPlaying:"playing",topProgress:"progress",topRateChange:"ratechange",topSeeked:"seeked",topSeeking:"seeking",topStalled:"stalled",topSuspend:"suspend",topTimeUpdate:"timeupdate",topVolumeChange:"volumechange",topWaiting:"waiting"},K={area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0},z={listing:!0,pre:!0,textarea:!0},Y=v({menuitem:!0},K),X=/^[a-zA-Z][a-zA-Z:_\.\-\d]*$/,Q={},G={}.hasOwnProperty,$=1;h.displayName="ReactDOMComponent",h.Mixin={mountComponent:function(e,t,n,r){this._rootNodeID=$++,this._domID=n._idCounter++,this._hostParent=t,this._hostContainerInfo=n;var i=this._currentElement.props;switch(this._tag){case"audio":case"form":case"iframe":case"img":case"link":case"object":case"source":case"video":this._wrapperState={listeners:null},e.getReactMountReady().enqueue(c,this);break;case"input":S.mountWrapper(this,i,t),i=S.getHostProps(this,i),e.getReactMountReady().enqueue(c,this);break;case"option":N.mountWrapper(this,i,t),i=N.getHostProps(this,i);break;case"select":M.mountWrapper(this,i,t),i=M.getHostProps(this,i),e.getReactMountReady().enqueue(c,this);break;case"textarea":I.mountWrapper(this,i,t),i=I.getHostProps(this,i),e.getReactMountReady().enqueue(c,this)}o(this,i);var a,p;null!=t?(a=t._namespaceURI,p=t._tag):n._tag&&(a=n._namespaceURI,p=n._tag),(null==a||a===C.svg&&"foreignobject"===p)&&(a=C.html),a===C.html&&("svg"===this._tag?a=C.svg:"math"===this._tag&&(a=C.mathml)),this._namespaceURI=a;var d;if(e.useCreateElement){var f,h=n._ownerDocument;if(a===C.html)if("script"===this._tag){var m=h.createElement("div"),v=this._currentElement.type;m.innerHTML="<"+v+">",f=m.removeChild(m.firstChild)}else f=i.is?h.createElement(this._currentElement.type,i.is):h.createElement(this._currentElement.type);else f=h.createElementNS(a,this._currentElement.type);P.precacheNode(this,f),this._flags|=D.hasCachedChildNodes,this._hostParent||E.setAttributeForRoot(f),this._updateDOMProperties(null,i,e);var y=_(f);this._createInitialChildren(e,i,r,y),d=y}else{var b=this._createOpenTagMarkupAndPutListeners(e,i),x=this._createContentMarkup(e,i,r);d=!x&&K[this._tag]?b+"/>":b+">"+x+""}switch(this._tag){case"input":e.getReactMountReady().enqueue(s,this),i.autoFocus&&e.getReactMountReady().enqueue(g.focusDOMComponent,this);break;case"textarea":e.getReactMountReady().enqueue(u,this),i.autoFocus&&e.getReactMountReady().enqueue(g.focusDOMComponent,this);break;case"select":case"button":i.autoFocus&&e.getReactMountReady().enqueue(g.focusDOMComponent,this);break;case"option":e.getReactMountReady().enqueue(l,this)}return d},_createOpenTagMarkupAndPutListeners:function(e,t){var n="<"+this._currentElement.type;for(var r in t)if(t.hasOwnProperty(r)){var o=t[r];if(null!=o)if(j.hasOwnProperty(r))o&&i(this,r,o,e);else{"style"===r&&(o&&(o=this._previousStyleCopy=v({},t.style)),o=y.createMarkupForStyles(o,this));var a=null;null!=this._tag&&f(this._tag,t)?W.hasOwnProperty(r)||(a=E.createMarkupForCustomAttribute(r,o)):a=E.createMarkupForProperty(r,o),a&&(n+=" "+a)}}return e.renderToStaticMarkup?n:(this._hostParent||(n+=" "+E.createMarkupForRoot()),n+=" "+E.createMarkupForID(this._domID))},_createContentMarkup:function(e,t,n){var r="",o=t.dangerouslySetInnerHTML;if(null!=o)null!=o.__html&&(r=o.__html);else{var i=V[typeof t.children]?t.children:null,a=null!=i?null:t.children;if(null!=i)r=A(i);else if(null!=a){var s=this.mountChildren(a,e,n);r=s.join("")}}return z[this._tag]&&"\n"===r.charAt(0)?"\n"+r:r},_createInitialChildren:function(e,t,n,r){var o=t.dangerouslySetInnerHTML;if(null!=o)null!=o.__html&&_.queueHTML(r,o.__html);else{var i=V[typeof t.children]?t.children:null,a=null!=i?null:t.children;if(null!=i)""!==i&&_.queueText(r,i);else if(null!=a)for(var s=this.mountChildren(a,e,n),u=0;u"},receiveComponent:function(){},getHostNode:function(){return i.getNodeFromInstance(this)},unmountComponent:function(){i.uncacheNode(this)}}),t.exports=a},{143:143,33:33,9:9}],36:[function(e,t,n){"use strict";var r={useCreateElement:!0,useFiber:!1};t.exports=r},{}],37:[function(e,t,n){"use strict";var r=e(8),o=e(33),i={dangerouslyProcessChildrenUpdates:function(e,t){var n=o.getNodeFromInstance(e);r.processUpdates(n,t)}};t.exports=i},{33:33,8:8}],38:[function(e,t,n){"use strict";function r(){this._rootNodeID&&d.updateWrapper(this)}function o(e){return"checkbox"===e.type||"radio"===e.type?null!=e.checked:null!=e.value}function i(e){var t=this._currentElement.props,n=l.executeOnChange(t,e);p.asap(r,this);var o=t.name;if("radio"===t.type&&null!=o){for(var i=c.getNodeFromInstance(this),s=i;s.parentNode;)s=s.parentNode;for(var u=s.querySelectorAll("input[name="+JSON.stringify(""+o)+'][type="radio"]'),d=0;dt.end?(n=t.end,r=t.start):(n=t.start,r=t.end),o.moveToElementText(e),o.moveStart("character",n),o.setEndPoint("EndToStart",o),o.moveEnd("character",r-n),o.select()}function s(e,t){if(window.getSelection){var n=window.getSelection(),r=e[c()].length,o=Math.min(t.start,r),i=void 0===t.end?o:Math.min(t.end,r);if(!n.extend&&o>i){var a=i;i=o,o=a}var s=l(e,o),u=l(e,i);if(s&&u){var p=document.createRange();p.setStart(s.node,s.offset),n.removeAllRanges(),o>i?(n.addRange(p),n.extend(u.node,u.offset)):(p.setEnd(u.node,u.offset),n.addRange(p))}}}var u=e(123),l=e(105),c=e(106),p=u.canUseDOM&&"selection"in document&&!("getSelection"in window),d={getOffsets:p?o:i,setOffsets:p?a:s};t.exports=d},{105:105,106:106,123:123}],42:[function(e,t,n){"use strict";var r=e(112),o=e(143),i=e(8),a=e(9),s=e(33),u=e(95),l=(e(137),e(118),function(e){this._currentElement=e,this._stringText=""+e, +this._hostNode=null,this._hostParent=null,this._domID=0,this._mountIndex=0,this._closingComment=null,this._commentNodes=null});o(l.prototype,{mountComponent:function(e,t,n,r){var o=n._idCounter++,i=" react-text: "+o+" ";if(this._domID=o,this._hostParent=t,e.useCreateElement){var l=n._ownerDocument,c=l.createComment(i),p=l.createComment(" /react-text "),d=a(l.createDocumentFragment());return a.queueChild(d,a(c)),this._stringText&&a.queueChild(d,a(l.createTextNode(this._stringText))),a.queueChild(d,a(p)),s.precacheNode(this,c),this._closingComment=p,d}var f=u(this._stringText);return e.renderToStaticMarkup?f:""+f+""},receiveComponent:function(e,t){if(e!==this._currentElement){this._currentElement=e;var n=""+e;if(n!==this._stringText){this._stringText=n;var r=this.getHostNode();i.replaceDelimitedText(r[0],r[1],n)}}},getHostNode:function(){var e=this._commentNodes;if(e)return e;if(!this._closingComment)for(var t=s.getNodeFromInstance(this),n=t.nextSibling;;){if(null==n&&r("67",this._domID),8===n.nodeType&&" /react-text "===n.nodeValue){this._closingComment=n;break}n=n.nextSibling}return e=[this._hostNode,this._closingComment],this._commentNodes=e,e},unmountComponent:function(){this._closingComment=null,this._commentNodes=null,s.uncacheNode(this)}}),t.exports=l},{112:112,118:118,137:137,143:143,33:33,8:8,9:9,95:95}],43:[function(e,t,n){"use strict";function r(){this._rootNodeID&&c.updateWrapper(this)}function o(e){var t=this._currentElement.props,n=s.executeOnChange(t,e);return l.asap(r,this),n}var i=e(112),a=e(143),s=e(23),u=e(33),l=e(71),c=(e(137),e(142),{getHostProps:function(e,t){return null!=t.dangerouslySetInnerHTML&&i("91"),a({},t,{value:void 0,defaultValue:void 0,children:""+e._wrapperState.initialValue,onChange:e._wrapperState.onChange})},mountWrapper:function(e,t){var n=s.getValue(t),r=n;if(null==n){var a=t.defaultValue,u=t.children;null!=u&&(null!=a&&i("92"),Array.isArray(u)&&(u.length<=1||i("93"),u=u[0]),a=""+u),null==a&&(a=""),r=a}e._wrapperState={initialValue:""+r,listeners:null,onChange:o.bind(e)}},updateWrapper:function(e){var t=e._currentElement.props,n=u.getNodeFromInstance(e),r=s.getValue(t);if(null!=r){var o=""+r;o!==n.value&&(n.value=o),null==t.defaultValue&&(n.defaultValue=o)}null!=t.defaultValue&&(n.defaultValue=t.defaultValue)},postMountWrapper:function(e){var t=u.getNodeFromInstance(e),n=t.textContent;n===e._wrapperState.initialValue&&(t.value=n)}});t.exports=c},{112:112,137:137,142:142,143:143,23:23,33:33,71:71}],44:[function(e,t,n){"use strict";function r(e,t){"_hostNode"in e||u("33"),"_hostNode"in t||u("33");for(var n=0,r=e;r;r=r._hostParent)n++;for(var o=0,i=t;i;i=i._hostParent)o++;for(;n-o>0;)e=e._hostParent,n--;for(;o-n>0;)t=t._hostParent,o--;for(var a=n;a--;){if(e===t)return e;e=e._hostParent,t=t._hostParent}return null}function o(e,t){"_hostNode"in e||u("35"),"_hostNode"in t||u("35");for(;t;){if(t===e)return!0;t=t._hostParent}return!1}function i(e){return"_hostNode"in e||u("36"),e._hostParent}function a(e,t,n){for(var r=[];e;)r.push(e),e=e._hostParent;var o;for(o=r.length;o-- >0;)t(r[o],"captured",n);for(o=0;o0;)n(u[l],"captured",i)}var u=e(112);e(137);t.exports={isAncestor:o,getLowestCommonAncestor:r,getParentInstance:i,traverseTwoPhase:a,traverseEnterLeave:s}},{112:112,137:137}],45:[function(e,t,n){"use strict";var r=e(120),o=e(30),i=o;r.addons&&(r.__SECRET_INJECTED_REACT_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=i),t.exports=i},{120:120,30:30}],46:[function(e,t,n){"use strict";function r(){this.reinitializeTransaction()}var o=e(143),i=e(71),a=e(89),s=e(129),u={initialize:s,close:function(){d.isBatchingUpdates=!1}},l={initialize:s,close:i.flushBatchedUpdates.bind(i)},c=[l,u];o(r.prototype,a,{getTransactionWrappers:function(){return c}});var p=new r,d={isBatchingUpdates:!1,batchedUpdates:function(e,t,n,r,o,i){var a=d.isBatchingUpdates;return d.isBatchingUpdates=!0,a?e(t,n,r,o,i):p.perform(e,null,t,n,r,o,i)}};t.exports=d},{129:129,143:143,71:71,89:89}],47:[function(e,t,n){"use strict";function r(){x||(x=!0,y.EventEmitter.injectReactEventListener(g),y.EventPluginHub.injectEventPluginOrder(s),y.EventPluginUtils.injectComponentTree(d),y.EventPluginUtils.injectTreeTraversal(h),y.EventPluginHub.injectEventPluginsByName({SimpleEventPlugin:E,EnterLeaveEventPlugin:u,ChangeEventPlugin:a,SelectEventPlugin:b,BeforeInputEventPlugin:i}),y.HostComponent.injectGenericComponentClass(p),y.HostComponent.injectTextComponentClass(m),y.DOMProperty.injectDOMPropertyConfig(o),y.DOMProperty.injectDOMPropertyConfig(l),y.DOMProperty.injectDOMPropertyConfig(C),y.EmptyComponent.injectEmptyComponentFactory(function(e){return new f(e)}),y.Updates.injectReconcileTransaction(_),y.Updates.injectBatchingStrategy(v),y.Component.injectEnvironment(c))}var o=e(1),i=e(3),a=e(7),s=e(14),u=e(15),l=e(21),c=e(27),p=e(31),d=e(33),f=e(35),h=e(44),m=e(42),v=e(46),g=e(52),y=e(55),_=e(65),C=e(73),b=e(74),E=e(75),x=!1;t.exports={inject:r}},{1:1,14:14,15:15,21:21,27:27,3:3,31:31,33:33,35:35,42:42,44:44,46:46,52:52,55:55,65:65,7:7,73:73,74:74,75:75}],48:[function(e,t,n){"use strict";var r="function"==typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103;t.exports=r},{}],49:[function(e,t,n){"use strict";var r,o={injectEmptyComponentFactory:function(e){r=e}},i={create:function(e){return r(e)}};i.injection=o,t.exports=i},{}],50:[function(e,t,n){"use strict";function r(e,t,n){try{t(n)}catch(e){null===o&&(o=e)}}var o=null,i={invokeGuardedCallback:r,invokeGuardedCallbackWithCatch:r,rethrowCaughtError:function(){if(o){var e=o;throw o=null,e}}};t.exports=i},{}],51:[function(e,t,n){"use strict";function r(e){o.enqueueEvents(e),o.processEventQueue(!1)}var o=e(16),i={handleTopLevel:function(e,t,n,i){r(o.extractEvents(e,t,n,i))}};t.exports=i},{16:16}],52:[function(e,t,n){"use strict";function r(e){for(;e._hostParent;)e=e._hostParent;var t=p.getNodeFromInstance(e),n=t.parentNode;return p.getClosestInstanceFromNode(n)}function o(e,t){this.topLevelType=e,this.nativeEvent=t,this.ancestors=[]}function i(e){var t=f(e.nativeEvent),n=p.getClosestInstanceFromNode(t),o=n;do{e.ancestors.push(o),o=o&&r(o)}while(o);for(var i=0;i/," "+i.CHECKSUM_ATTR_NAME+'="'+t+'"$&')},canReuseMarkup:function(e,t){var n=t.getAttribute(i.CHECKSUM_ATTR_NAME);return n=n&&parseInt(n,10),r(e)===n}};t.exports=i},{92:92}],60:[function(e,t,n){"use strict";function r(e,t){for(var n=Math.min(e.length,t.length),r=0;r.":"function"==typeof t?" Instead of passing a class like Foo, pass React.createElement(Foo) or .":null!=t&&void 0!==t.props?" This may be caused by unintentionally loading two independent copies of React.":"");var a,s=v.createElement(F,{child:t});if(e){var u=E.get(e);a=u._processChildContext(u._context)}else a=P;var c=d(n);if(c){var p=c._currentElement,h=p.props.child;if(M(h,t)){var m=c._renderedComponent.getPublicInstance(),g=r&&function(){r.call(m)};return j._updateRootComponent(c,s,a,n,g),m}j.unmountComponentAtNode(n)}var y=o(n),_=y&&!!i(y),C=l(n),b=_&&!c&&!C,x=j._renderNewRootComponent(s,n,b,a)._renderedComponent.getPublicInstance();return r&&r.call(x),x},render:function(e,t,n){return j._renderSubtreeIntoContainer(null,e,t,n)},unmountComponentAtNode:function(e){c(e)||f("40");var t=d(e);return t?(delete L[t._instance.rootID],k.batchedUpdates(u,t,e,!1),!0):(l(e),1===e.nodeType&&e.hasAttribute(O),!1)},_mountImageIntoNode:function(e,t,n,i,a){if(c(t)||f("41"),i){var s=o(t);if(x.canReuseMarkup(e,s))return void y.precacheNode(n,s);var u=s.getAttribute(x.CHECKSUM_ATTR_NAME);s.removeAttribute(x.CHECKSUM_ATTR_NAME);var l=s.outerHTML;s.setAttribute(x.CHECKSUM_ATTR_NAME,u);var p=e,d=r(p,l),m=" (client) "+p.substring(d-20,d+20)+"\n (server) "+l.substring(d-20,d+20);t.nodeType===A&&f("42",m)}if(t.nodeType===A&&f("43"),a.useCreateElement){for(;t.lastChild;)t.removeChild(t.lastChild);h.insertTreeBefore(t,e,null)}else N(t,e),y.precacheNode(n,t.firstChild)}};t.exports=j},{108:108,11:11,112:112,114:114,116:116,119:119,120:120,130:130,137:137,142:142,25:25,33:33,34:34,36:36,53:53,57:57,58:58,59:59,66:66,70:70,71:71,9:9}],61:[function(e,t,n){"use strict";function r(e,t,n){return{type:"INSERT_MARKUP",content:e,fromIndex:null,fromNode:null,toIndex:n,afterNode:t}}function o(e,t,n){return{type:"MOVE_EXISTING",content:null,fromIndex:e._mountIndex,fromNode:d.getHostNode(e),toIndex:n,afterNode:t}}function i(e,t){return{type:"REMOVE_NODE",content:null,fromIndex:e._mountIndex,fromNode:t,toIndex:null,afterNode:null}}function a(e){return{type:"SET_MARKUP",content:e,fromIndex:null,fromNode:null,toIndex:null,afterNode:null}}function s(e){return{type:"TEXT_CONTENT",content:e,fromIndex:null,fromNode:null,toIndex:null,afterNode:null}}function u(e,t){return t&&(e=e||[],e.push(t)),e}function l(e,t){p.processChildrenUpdates(e,t)}var c=e(112),p=e(28),d=(e(57),e(58),e(119),e(66)),f=e(26),h=(e(129),e(97)),m=(e(137),{Mixin:{_reconcilerInstantiateChildren:function(e,t,n){return f.instantiateChildren(e,t,n)},_reconcilerUpdateChildren:function(e,t,n,r,o,i){var a;return a=h(t,0),f.updateChildren(e,a,n,r,o,this,this._hostContainerInfo,i,0),a},mountChildren:function(e,t,n){var r=this._reconcilerInstantiateChildren(e,t,n);this._renderedChildren=r;var o=[],i=0;for(var a in r)if(r.hasOwnProperty(a)){var s=r[a],u=d.mountComponent(s,t,this,this._hostContainerInfo,n,0);s._mountIndex=i++,o.push(u)}return o},updateTextContent:function(e){var t=this._renderedChildren;f.unmountChildren(t,!1);for(var n in t)t.hasOwnProperty(n)&&c("118");l(this,[s(e)])},updateMarkup:function(e){var t=this._renderedChildren;f.unmountChildren(t,!1);for(var n in t)t.hasOwnProperty(n)&&c("118");l(this,[a(e)])},updateChildren:function(e,t,n){this._updateChildren(e,t,n)},_updateChildren:function(e,t,n){var r=this._renderedChildren,o={},i=[],a=this._reconcilerUpdateChildren(r,e,i,o,t,n);if(a||r){var s,c=null,p=0,f=0,h=0,m=null;for(s in a)if(a.hasOwnProperty(s)){var v=r&&r[s],g=a[s];v===g?(c=u(c,this.moveChild(v,m,p,f)),f=Math.max(v._mountIndex,f),v._mountIndex=p):(v&&(f=Math.max(v._mountIndex,f)),c=u(c,this._mountChildAtIndex(g,i[h],m,p,t,n)),h++),p++,m=d.getHostNode(g)}for(s in o)o.hasOwnProperty(s)&&(c=u(c,this._unmountChild(r[s],o[s])));c&&l(this,c),this._renderedChildren=a}},unmountChildren:function(e){var t=this._renderedChildren;f.unmountChildren(t,e),this._renderedChildren=null},moveChild:function(e,t,n,r){if(e._mountIndex0&&r.length<20?n+" (keys: "+r.join(", ")+")":n}function i(e,t){var n=s.get(e);return n||null}var a=e(112),s=(e(119),e(57)),u=(e(58),e(71)),l=(e(137),e(142),{isMounted:function(e){var t=s.get(e);return!!t&&!!t._renderedComponent},enqueueCallback:function(e,t,n){l.validateCallback(t,n);var o=i(e);if(!o)return null;o._pendingCallbacks?o._pendingCallbacks.push(t):o._pendingCallbacks=[t],r(o)},enqueueCallbackInternal:function(e,t){e._pendingCallbacks?e._pendingCallbacks.push(t):e._pendingCallbacks=[t],r(e)},enqueueForceUpdate:function(e){var t=i(e,"forceUpdate");t&&(t._pendingForceUpdate=!0,r(t))},enqueueReplaceState:function(e,t,n){var o=i(e,"replaceState");o&&(o._pendingStateQueue=[t],o._pendingReplaceState=!0,void 0!==n&&null!==n&&(l.validateCallback(n,"replaceState"),o._pendingCallbacks?o._pendingCallbacks.push(n):o._pendingCallbacks=[n]),r(o))},enqueueSetState:function(e,t){var n=i(e,"setState");n&&((n._pendingStateQueue||(n._pendingStateQueue=[])).push(t),r(n))},enqueueElementInternal:function(e,t,n){e._pendingElement=t,e._context=n,r(e)},validateCallback:function(e,t){e&&"function"!=typeof e&&a("122",t,o(e))}});t.exports=l},{112:112,119:119,137:137,142:142,57:57,58:58,71:71}],71:[function(e,t,n){"use strict";function r(){P.ReactReconcileTransaction&&b||c("123")}function o(){this.reinitializeTransaction(),this.dirtyComponentsLength=null,this.callbackQueue=d.getPooled(),this.reconcileTransaction=P.ReactReconcileTransaction.getPooled(!0)}function i(e,t,n,o,i,a){return r(),b.batchedUpdates(e,t,n,o,i,a)}function a(e,t){return e._mountOrder-t._mountOrder}function s(e){var t=e.dirtyComponentsLength;t!==g.length&&c("124",t,g.length),g.sort(a),y++;for(var n=0;n]/;t.exports=o},{}],96:[function(e,t,n){"use strict";function r(e){if(null==e)return null;if(1===e.nodeType)return e;var t=a.get(e);if(t)return t=s(t),t?i.getNodeFromInstance(t):null;"function"==typeof e.render?o("44"):o("45",Object.keys(e))}var o=e(112),i=(e(119),e(33)),a=e(57),s=e(103);e(137),e(142);t.exports=r},{103:103,112:112,119:119,137:137,142:142,33:33,57:57}],97:[function(e,t,n){(function(n){"use strict";function r(e,t,n,r){if(e&&"object"==typeof e){var o=e;void 0===o[n]&&null!=t&&(o[n]=t)}}function o(e,t){if(null==e)return e;var n={};return i(e,r,n),n}var i=(e(22),e(117));e(142);void 0!==n&&n.env,t.exports=o}).call(this,void 0)},{117:117,142:142,22:22}],98:[function(e,t,n){"use strict";function r(e,t,n){Array.isArray(e)?e.forEach(t,n):e&&t.call(n,e)}t.exports=r},{}],99:[function(e,t,n){"use strict";function r(e){var t,n=e.keyCode;return"charCode"in e?0===(t=e.charCode)&&13===n&&(t=13):t=n,t>=32||13===t?t:0}t.exports=r},{}],100:[function(e,t,n){"use strict";function r(e){if(e.key){var t=i[e.key]||e.key;if("Unidentified"!==t)return t}if("keypress"===e.type){var n=o(e);return 13===n?"Enter":String.fromCharCode(n)}return"keydown"===e.type||"keyup"===e.type?a[e.keyCode]||"Unidentified":""}var o=e(99),i={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},a={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"};t.exports=r},{99:99}],101:[function(e,t,n){"use strict";function r(e){var t=this,n=t.nativeEvent;if(n.getModifierState)return n.getModifierState(e);var r=i[e];return!!r&&!!n[r]}function o(e){return r}var i={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};t.exports=o},{}],102:[function(e,t,n){"use strict";function r(e){var t=e.target||e.srcElement||window;return t.correspondingUseElement&&(t=t.correspondingUseElement),3===t.nodeType?t.parentNode:t}t.exports=r},{}],103:[function(e,t,n){"use strict";function r(e){for(var t;(t=e._renderedNodeType)===o.COMPOSITE;)e=e._renderedComponent;return t===o.HOST?e._renderedComponent:t===o.EMPTY?null:void 0}var o=e(62);t.exports=r},{62:62}],104:[function(e,t,n){"use strict";function r(e){var t=e&&(o&&e[o]||e[i]);if("function"==typeof t)return t}var o="function"==typeof Symbol&&Symbol.iterator,i="@@iterator";t.exports=r},{}],105:[function(e,t,n){"use strict";function r(e){for(;e&&e.firstChild;)e=e.firstChild;return e}function o(e){for(;e;){if(e.nextSibling)return e.nextSibling;e=e.parentNode}}function i(e,t){for(var n=r(e),i=0,a=0;n;){if(3===n.nodeType){if(a=i+n.textContent.length,i<=t&&a>=t)return{node:n,offset:t-i};i=a}n=r(o(n))}}t.exports=i},{}],106:[function(e,t,n){"use strict";function r(){return!i&&o.canUseDOM&&(i="textContent"in document.documentElement?"textContent":"innerText"),i}var o=e(123),i=null;t.exports=r},{123:123}],107:[function(e,t,n){"use strict";function r(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n["Webkit"+e]="webkit"+t,n["Moz"+e]="moz"+t,n["ms"+e]="MS"+t,n["O"+e]="o"+t.toLowerCase(),n}function o(e){if(s[e])return s[e];if(!a[e])return e;var t=a[e];for(var n in t)if(t.hasOwnProperty(n)&&n in u)return s[e]=t[n];return""}var i=e(123),a={animationend:r("Animation","AnimationEnd"),animationiteration:r("Animation","AnimationIteration"),animationstart:r("Animation","AnimationStart"),transitionend:r("Transition","TransitionEnd")},s={},u={};i.canUseDOM&&(u=document.createElement("div").style,"AnimationEvent"in window||(delete a.animationend.animation,delete a.animationiteration.animation,delete a.animationstart.animation),"TransitionEvent"in window||delete a.transitionend.transition),t.exports=o},{123:123}],108:[function(e,t,n){"use strict";function r(e){if(e){var t=e.getName();if(t)return" Check the render method of `"+t+"`."}return""}function o(e){return"function"==typeof e&&void 0!==e.prototype&&"function"==typeof e.prototype.mountComponent&&"function"==typeof e.prototype.receiveComponent}function i(e,t){var n;if(null===e||!1===e)n=l.create(i);else if("object"==typeof e){var s=e,u=s.type;if("function"!=typeof u&&"string"!=typeof u){var d="";d+=r(s._owner),a("130",null==u?u:typeof u,d)}"string"==typeof s.type?n=c.createInternalComponent(s):o(s.type)?(n=new s.type(s),n.getHostNode||(n.getHostNode=n.getNativeNode)):n=new p(s)}else"string"==typeof e||"number"==typeof e?n=c.createInstanceForText(e):a("131",typeof e);return n._mountIndex=0,n._mountImage=null,n}var a=e(112),s=e(143),u=e(29),l=e(49),c=e(54),p=(e(121),e(137),e(142),function(e){this.construct(e)});s(p.prototype,u,{_instantiateReactComponent:i}),t.exports=i},{112:112,121:121,137:137,142:142,143:143,29:29,49:49,54:54}],109:[function(e,t,n){"use strict";function r(e,t){if(!i.canUseDOM||t&&!("addEventListener"in document))return!1;var n="on"+e,r=n in document;if(!r){var a=document.createElement("div");a.setAttribute(n,"return;"),r="function"==typeof a[n]}return!r&&o&&"wheel"===e&&(r=document.implementation.hasFeature("Events.wheel","3.0")),r}var o,i=e(123);i.canUseDOM&&(o=document.implementation&&document.implementation.hasFeature&&!0!==document.implementation.hasFeature("","")),t.exports=r},{123:123}],110:[function(e,t,n){"use strict";function r(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return"input"===t?!!o[e.type]:"textarea"===t}var o={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};t.exports=r},{}],111:[function(e,t,n){"use strict";function r(e){return'"'+o(e)+'"'}var o=e(95);t.exports=r},{95:95}],112:[function(e,t,n){"use strict";function r(e){for(var t=arguments.length-1,n="Minified React error #"+e+"; visit http://facebook.github.io/react/docs/error-decoder.html?invariant="+e,r=0;r]/,u=e(93),l=u(function(e,t){if(e.namespaceURI!==i.svg||"innerHTML"in e)e.innerHTML=t;else{r=r||document.createElement("div"),r.innerHTML=""+t+"";for(var n=r.firstChild;n.firstChild;)e.appendChild(n.firstChild)}});if(o.canUseDOM){var c=document.createElement("div");c.innerHTML=" ",""===c.innerHTML&&(l=function(e,t){if(e.parentNode&&e.parentNode.replaceChild(e,e),a.test(t)||"<"===t[0]&&s.test(t)){e.innerHTML=String.fromCharCode(65279)+t;var n=e.firstChild;1===n.data.length?e.removeChild(n):n.deleteData(0,1)}else e.innerHTML=t}),c=null}t.exports=l},{10:10,123:123,93:93}],115:[function(e,t,n){"use strict";var r=e(123),o=e(95),i=e(114),a=function(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&3===n.nodeType)return void(n.nodeValue=t)}e.textContent=t};r.canUseDOM&&("textContent"in document.documentElement||(a=function(e,t){if(3===e.nodeType)return void(e.nodeValue=t);i(e,o(t))})),t.exports=a},{114:114,123:123,95:95}],116:[function(e,t,n){"use strict";function r(e,t){var n=null===e||!1===e,r=null===t||!1===t;if(n||r)return n===r;var o=typeof e,i=typeof t;return"string"===o||"number"===o?"string"===i||"number"===i:"object"===i&&e.type===t.type&&e.key===t.key}t.exports=r},{}],117:[function(e,t,n){"use strict";function r(e,t){return e&&"object"==typeof e&&null!=e.key?l.escape(e.key):t.toString(36)}function o(e,t,n,i){var d=typeof e;if("undefined"!==d&&"boolean"!==d||(e=null),null===e||"string"===d||"number"===d||"object"===d&&e.$$typeof===s)return n(i,e,""===t?c+r(e,0):t),1;var f,h,m=0,v=""===t?c:t+p;if(Array.isArray(e))for(var g=0;g":"<"+e+">",s[e]=!a.firstChild),s[e]?d[e]:null}var o=e(123),i=e(137),a=o.canUseDOM?document.createElement("div"):null,s={},u=[1,'"],l=[1,"","
"],c=[3,"","
"],p=[1,'',""],d={"*":[1,"?
","
"],area:[1,"",""],col:[2,"","
"],legend:[1,"
","
"],param:[1,"",""],tr:[2,"","
"],optgroup:u,option:u,caption:l,colgroup:l,tbody:l,tfoot:l,thead:l,td:c,th:c};["circle","clipPath","defs","ellipse","g","image","line","linearGradient","mask","path","pattern","polygon","polyline","radialGradient","rect","stop","text","tspan"].forEach(function(e){d[e]=p,s[e]=!0}),t.exports=r},{123:123,137:137}],134:[function(e,t,n){"use strict";function r(e){return e.Window&&e instanceof e.Window?{x:e.pageXOffset||e.document.documentElement.scrollLeft,y:e.pageYOffset||e.document.documentElement.scrollTop}:{x:e.scrollLeft,y:e.scrollTop}}t.exports=r},{}],135:[function(e,t,n){"use strict";function r(e){return e.replace(o,"-$1").toLowerCase()}var o=/([A-Z])/g;t.exports=r},{}],136:[function(e,t,n){"use strict";function r(e){return o(e).replace(i,"-ms-")}var o=e(135),i=/^ms-/;t.exports=r},{135:135}],137:[function(e,t,n){"use strict";function r(e,t,n,r,i,a,s,u){if(o(t),!e){var l;if(void 0===t)l=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var c=[n,r,i,a,s,u],p=0;l=new Error(t.replace(/%s/g,function(){return c[p++]})),l.name="Invariant Violation"}throw l.framesToPop=1,l}}var o=function(e){};t.exports=r},{}],138:[function(e,t,n){"use strict";function r(e){var t=e?e.ownerDocument||e:document,n=t.defaultView||window;return!(!e||!("function"==typeof n.Node?e instanceof n.Node:"object"==typeof e&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName))}t.exports=r},{}],139:[function(e,t,n){"use strict";function r(e){return o(e)&&3==e.nodeType}var o=e(138);t.exports=r},{138:138}],140:[function(e,t,n){"use strict";function r(e){var t={};return function(n){return t.hasOwnProperty(n)||(t[n]=e.call(this,n)),t[n]}}t.exports=r},{}],141:[function(e,t,n){"use strict";function r(e,t){return e===t?0!==e||0!==t||1/e==1/t:e!==e&&t!==t}function o(e,t){if(r(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;var n=Object.keys(e),o=Object.keys(t);if(n.length!==o.length)return!1;for(var a=0;a 0x10FFFF || // not a valid Unicode code point + floor(codePoint) != codePoint // not an integer + ) { + throw RangeError('Invalid code point: ' + codePoint); + } + if (codePoint <= 0xFFFF) { // BMP code point + codeUnits.push(codePoint); + } else { // Astral code point; split in surrogate halves + // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + codePoint -= 0x10000; + highSurrogate = (codePoint >> 10) + 0xD800; + lowSurrogate = (codePoint % 0x400) + 0xDC00; + codeUnits.push(highSurrogate, lowSurrogate); + } + if (index + 1 == length || codeUnits.length > MAX_SIZE) { + result += stringFromCharCode.apply(null, codeUnits); + codeUnits.length = 0; + } + } + return result; + }; + if (defineProperty) { + defineProperty(String, 'fromCodePoint', { + 'value': fromCodePoint, + 'configurable': true, + 'writable': true + }); + } else { + String.fromCodePoint = fromCodePoint; + } + }()); +} + +/*! http://mths.be/codepointat v0.1.0 by @mathias */ +if (!String.prototype.codePointAt) { + (function() { + 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` + var codePointAt = function(position) { + if (this == null) { + throw TypeError(); + } + var string = String(this); + var size = string.length; + // `ToInteger` + var index = position ? Number(position) : 0; + if (index != index) { // better `isNaN` + index = 0; + } + // Account for out-of-bounds indices: + if (index < 0 || index >= size) { + return undefined; + } + // Get the first code unit + var first = string.charCodeAt(index); + var second; + if ( // check if it’s the start of a surrogate pair + first >= 0xD800 && first <= 0xDBFF && // high surrogate + size > index + 1 // there is a next code unit + ) { + second = string.charCodeAt(index + 1); + if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate + // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + } + } + return first; + }; + if (Object.defineProperty) { + Object.defineProperty(String.prototype, 'codePointAt', { + 'value': codePointAt, + 'configurable': true, + 'writable': true + }); + } else { + String.prototype.codePointAt = codePointAt; + } + }()); +} + +function registerAsciinemaPlayerElement() { + var AsciinemaPlayerProto = Object.create(HTMLElement.prototype); + + function merge() { + var merged = {}; + for (var i=0; i>>0),ma=0;function na(a,b,c){return a.call.apply(a.bind,arguments)} +function oa(a,b,c){if(!a)throw Error();if(2b?1:0};var ua=Array.prototype.indexOf?function(a,b,c){return Array.prototype.indexOf.call(a,b,c)}:function(a,b,c){c=null==c?0:0>c?Math.max(0,a.length+c):c;if(ca(a))return ca(b)&&1==b.length?a.indexOf(b,c):-1;for(;cb?null:ca(a)?a.charAt(b):a[b]}function ya(a,b){var c=ua(a,b),d;(d=0<=c)&&Array.prototype.splice.call(a,c,1);return d}function za(a,b){a.sort(b||Aa)}function Ca(a,b){for(var c=Array(a.length),d=0;db?1:a2*this.Fc&&Na(this),!0):!1};function Na(a){if(a.Fc!=a.ib.length){for(var b=0,c=0;ba){var b=Ra[a];if(b)return b}b=new Qa([a|0],0>a?-1:0);-128<=a&&128>a&&(Ra[a]=b);return b}function Ta(a){if(isNaN(a)||!isFinite(a))return Ua;if(0>a)return Ta(-a).kb();for(var b=[],c=1,d=0;a>=c;d++)b[d]=a/c|0,c*=Va;return new Qa(b,0)}var Va=4294967296,Ua=Sa(0),Wa=Sa(1),Xa=Sa(16777216);g=Qa.prototype; +g.Of=function(){return 0a||36>>0).toString(a);c=e;if(c.hc())return f+d;for(;6>f.length;)f="0"+f;d=""+f+d}};function Ya(a,b){return 0>b?0:bthis.compare(Xa)};g.Ve=function(a){return 0>=this.compare(a)};g.compare=function(a){a=this.ze(a);return a.Eb()?-1:a.hc()?0:1};g.kb=function(){return this.Hf().add(Wa)}; +g.add=function(a){for(var b=Math.max(this.Ma.length,a.Ma.length),c=[],d=0,e=0;e<=b;e++){var f=d+(Ya(this,e)&65535)+(Ya(a,e)&65535),h=(f>>>16)+(Ya(this,e)>>>16)+(Ya(a,e)>>>16);d=h>>>16;f&=65535;h&=65535;c[e]=h<<16|f}return new Qa(c,c[c.length-1]&-2147483648?-1:0)};g.ze=function(a){return this.add(a.kb())}; +g.multiply=function(a){if(this.hc()||a.hc())return Ua;if(this.Eb())return a.Eb()?this.kb().multiply(a.kb()):this.kb().multiply(a).kb();if(a.Eb())return this.multiply(a.kb()).kb();if(this.Ue()&&a.Ue())return Ta(this.vd()*a.vd());for(var b=this.Ma.length+a.Ma.length,c=[],d=0;d<2*b;d++)c[d]=0;for(d=0;d>>16,h=Ya(this,d)&65535,k=Ya(a,e)>>>16,l=Ya(a,e)&65535;c[2*d+2*e]+=h*l;ab(c,2*d+2*e);c[2*d+2*e+1]+=f*l;ab(c,2*d+2*e+1);c[2*d+2*e+1]+= +h*k;ab(c,2*d+2*e+1);c[2*d+2*e+2]+=f*k;ab(c,2*d+2*e+2)}for(d=0;d>>16,a[b]&=65535,b++} +function Za(a,b){if(b.hc())throw Error("division by zero");if(a.hc())return Ua;if(a.Eb())return b.Eb()?Za(a.kb(),b.kb()):Za(a.kb(),b).kb();if(b.Eb())return Za(a,b.kb()).kb();if(30=f?1:Math.pow(2,f-48);h=Ta(e);for(var k=h.multiply(b);k.Eb()||k.xf(d);)e-=f,h=Ta(e),k=h.multiply(b);h.hc()&&(h=Wa);c=c.add(h);d=d.ze(k)}return c}g.Hf=function(){for(var a=this.Ma.length,b=[],c=0;c>5;a%=32;for(var c=this.Ma.length+b+(0>>32-a:Ya(this,e-b);return new Qa(d,this.Lc)}; +g.ad=function(a){var b=a>>5;a%=32;for(var c=this.Ma.length-b,d=[],e=0;e>>a|Ya(this,e+b+1)<<32-a:Ya(this,e+b);return new Qa(d,this.Lc)};function cb(a,b){null!=a&&this.append.apply(this,arguments)}g=cb.prototype;g.xc="";g.set=function(a){this.xc=""+a};g.append=function(a,b,c){this.xc+=String(a);if(null!=b)for(var d=1;d>>16&65535)*d+c*(b>>>16&65535)<<16>>>0)|0};function hd(a){a=gd(a|0,-862048943);return gd(a<<15|a>>>-15,461845907)} +function id(a,b){var c=(a|0)^(b|0);return gd(c<<13|c>>>-13,5)+-430675100|0}function jd(a,b){var c=(a|0)^b;c=gd(c^c>>>16,-2048144789);c=gd(c^c>>>13,-1028477387);return c^c>>>16}function kd(a){a:{var b=1;for(var c=0;;)if(b>2)}function qd(a){return a instanceof rd} +function sd(a,b){if(a.Zb===b.Zb)return 0;var c=wb(a.fb);if(t(c?b.fb:c))return-1;if(t(a.fb)){if(wb(b.fb))return 1;c=Aa(a.fb,b.fb);return 0===c?Aa(a.name,b.name):c}return Aa(a.name,b.name)}function rd(a,b,c,d,e){this.fb=a;this.name=b;this.Zb=c;this.Oc=d;this.hb=e;this.m=2154168321;this.J=4096}g=rd.prototype;g.toString=function(){return this.Zb};g.equiv=function(a){return this.K(null,a)};g.K=function(a,b){return b instanceof rd?this.Zb===b.Zb:!1}; +g.call=function(){var a=null;a=function(a,c,d){switch(arguments.length){case 2:return D.c(c,this);case 3:return D.l(c,this,d)}throw Error("Invalid arity: "+(arguments.length-1));};a.c=function(a,c){return D.c(c,this)};a.l=function(a,c,d){return D.l(c,this,d)};return a}();g.apply=function(a,b){return this.call.apply(this,[this].concat(Gb(b)))};g.h=function(a){return D.c(a,this)};g.c=function(a,b){return D.l(a,this,b)};g.P=function(){return this.hb}; +g.T=function(a,b){return new rd(this.fb,this.name,this.Zb,this.Oc,b)};g.U=function(){var a=this.Oc;return null!=a?a:this.Oc=a=pd(kd(this.name),nd(this.fb))};g.hd=function(){return this.name};g.jd=function(){return this.fb};g.R=function(a,b){return Jc(b,this.Zb)};var td=function td(a){switch(arguments.length){case 1:return td.h(arguments[0]);case 2:return td.c(arguments[0],arguments[1]);default:throw Error(["Invalid arity: ",v.h(arguments.length)].join(""));}}; +td.h=function(a){if(a instanceof rd)return a;var b=a.indexOf("/");return 1>b?td.c(null,a):td.c(a.substring(0,b),a.substring(b+1,a.length))};td.c=function(a,b){var c=null!=a?[v.h(a),"/",v.h(b)].join(""):b;return new rd(a,b,c,null,null)};td.L=2;function ud(a){return null!=a?a.J&131072||q===a.Tf?!0:a.J?!1:Ab(cd,a):Ab(cd,a)} +function E(a){if(null==a)return null;if(null!=a&&(a.m&8388608||q===a.Pe))return a.S(null);if(vb(a)||"string"===typeof a)return 0===a.length?null:new Jb(a,0,null);if(Ab(Bc,a))return Cc(a);throw Error([v.h(a)," is not ISeqable"].join(""));}function y(a){if(null==a)return null;if(null!=a&&(a.m&64||q===a.G))return a.Ia(null);a=E(a);return null==a?null:Wb(a)}function vd(a){return null!=a?null!=a&&(a.m&64||q===a.G)?a.bb(null):(a=E(a))?Yb(a):wd:wd} +function z(a){return null==a?null:null!=a&&(a.m&128||q===a.Id)?a.Ka(null):E(vd(a))}var G=function G(a){switch(arguments.length){case 1:return G.h(arguments[0]);case 2:return G.c(arguments[0],arguments[1]);default:for(var c=[],d=arguments.length,e=0;;)if(e=d)return-1;!(0c&&(c+=d,c=0>c?0:c);for(;;)if(cc?d+c:c;for(;;)if(0<=c){if(G.c(Vd(a,c),b))return c;--c}else return-1}function Yd(a,b){this.o=a;this.i=b} +Yd.prototype.ja=function(){return this.ia?0:a};g.Rc=function(){var a=this.W(null);return 0d)c=1;else if(0===c)c=0;else a:for(d=0;;){var e=Ke(Vd(a,d),Vd(b,d));if(0===e&&d+1>1&1431655765;a=(a&858993459)+(a>>2&858993459);return 16843009*(a+(a>>4)&252645135)>>24} +var v=function v(a){switch(arguments.length){case 0:return v.B();case 1:return v.h(arguments[0]);default:for(var c=[],d=arguments.length,e=0;;)if(ed:e))c[d]=a.next(),d+=1;else return qf(new nf(c,0,d),Rf.h?Rf.h(a):Rf.call(null,a))}else return null},null,null)};function Sf(a,b,c,d,e,f){this.buffer=a;this.ub=b;this.pe=c;this.Rb=d;this.ye=e;this.Gf=f} +Sf.prototype.step=function(){if(this.ub!==Nf)return!0;for(;;)if(this.ub===Nf)if(this.buffer.Td()){if(this.pe)return!1;if(this.ye.ja()){if(this.Gf)var a=P(this.Rb,ae(null,this.ye.next()));else a=this.ye.next(),a=this.Rb.c?this.Rb.c(null,a):this.Rb.call(null,null,a);Hd(a)&&(this.Rb.h?this.Rb.h(null):this.Rb.call(null,null),this.pe=!0)}else this.Rb.h?this.Rb.h(null):this.Rb.call(null,null),this.pe=!0}else this.ub=this.buffer.remove();else return!0};Sf.prototype.ja=function(){return this.step()}; +Sf.prototype.next=function(){if(this.ja()){var a=this.ub;this.ub=Nf;return a}throw Error("No such element");};Sf.prototype.remove=function(){return Error("Unsupported operation")};Sf.prototype[Fb]=function(){return yd(this)}; +function Tf(a,b){var c=new Sf(Qf,Nf,!1,null,b,!1);c.Rb=function(){var b=function(a){return function(){function b(b,c){a.buffer=a.buffer.add(c);return b}var c=null;c=function(a,c){switch(arguments.length){case 0:return null;case 1:return a;case 2:return b.call(this,a,c)}throw Error("Invalid arity: "+(arguments.length-1));};c.B=function(){return null};c.h=function(a){return a};c.c=b;return c}()}(c);return a.h?a.h(b):a.call(null,b)}();return c} +function Uf(a,b){var c=Kf(b);c=Tf(a,c);c=Rf(c);return t(c)?c:wd}function Vf(a,b){for(;;){if(null==E(b))return!0;var c=y(b);c=a.h?a.h(c):a.call(null,c);if(t(c)){c=a;var d=z(b);a=c;b=d}else return!1}}function Wf(a,b){for(;;)if(E(b)){var c=y(b);c=a.h?a.h(c):a.call(null,c);if(t(c))return c;c=a;var d=z(b);a=c;b=d}else return null}function Xf(a){if(Ge(a))return 0===(a&1);throw Error(["Argument must be an integer: ",v.h(a)].join(""));} +function Yf(a){return function(){function b(b,c){return wb(a.c?a.c(b,c):a.call(null,b,c))}function c(b){return wb(a.h?a.h(b):a.call(null,b))}function d(){return wb(a.B?a.B():a.call(null))}var e=null,f=function(){function b(a,b,d){var e=null;if(2a?0:a-1>>>5<<5}function Jg(a,b,c){for(;;){if(0===b)return c;var d=Gg(a);d.o[0]=c;c=d;b-=5}} +var Kg=function Kg(a,b,c,d){var f=Hg(c),h=a.F-1>>>b&31;5===b?f.o[h]=d:(c=c.o[h],null!=c?(b-=5,a=Kg.M?Kg.M(a,b,c,d):Kg.call(null,a,b,c,d)):a=Jg(null,b-5,d),f.o[h]=a);return f};function Lg(a,b){throw Error(["No item ",v.h(a)," in vector of length ",v.h(b)].join(""));}function Mg(a,b){if(b>=Ig(a))return a.fa;for(var c=a.root,d=a.shift;;)if(0>>d&31];d=e}else return c.o} +var Ng=function Ng(a,b,c,d,e){var h=Hg(c);if(0===b)h.o[d&31]=e;else{var k=d>>>b&31;b-=5;c=c.o[k];a=Ng.Z?Ng.Z(a,b,c,d,e):Ng.call(null,a,b,c,d,e);h.o[k]=a}return h},Og=function Og(a,b,c){var e=a.F-2>>>b&31;if(5=this.F)a=new Jb(this.fa,0,null);else{a:{a=this.root;for(var b=this.shift;;)if(0this.F-Ig(this)){for(var c=this.fa.length,d=Array(c+1),e=0;;)if(e>>5>1<b)return new R(null,b,5,T,a,null);for(var c=32,d=(new R(null,32,5,T,a.slice(0,32),null)).Pc(null);;)if(cb||this.end<=this.start+b?Lg(b,this.end-this.start):A.c(this.Ja,this.start+b)};g.ka=function(a,b,c){return 0>b||this.end<=this.start+b?c:A.l(this.Ja,this.start+b,c)}; +g.dc=function(a,b,c){a=this.start+b;if(0>b||this.end+1<=a)throw Error(["Index ",v.h(b)," out of bounds [0,",v.h(this.W(null)),"]"].join(""));b=this.meta;c=K.l(this.Ja,a,c);var d=this.end;a+=1;return Zg(b,c,this.start,d>a?d:a,null)};g.ba=function(){return null!=this.Ja&&q===this.Ja.fe?Qg(this.Ja,this.start,this.end):new Jf(Hf,this)};g.P=function(){return this.meta};g.W=function(){return this.end-this.start};g.Ac=function(){return A.c(this.Ja,this.end-1)}; +g.Bc=function(){if(this.start===this.end)throw Error("Can't pop empty vector");return Zg(this.meta,this.Ja,this.start,this.end-1,null)};g.Rc=function(){return this.start!==this.end?new Zd(this,this.end-this.start-1,null):null};g.U=function(){var a=this.w;return null!=a?a:this.w=a=Ad(this)};g.K=function(a,b){return $d(this,b)};g.oa=function(){return tc(he,this.meta)};g.Fa=function(a,b){return null!=this.Ja&&q===this.Ja.fe?Rg(this.Ja,b,this.start,this.end):Kd(this,b)}; +g.Ga=function(a,b,c){return null!=this.Ja&&q===this.Ja.fe?Sg(this.Ja,b,c,this.start,this.end):Ld(this,b,c)};g.O=function(a,b,c){if("number"===typeof b)return this.dc(null,b,c);throw Error("Subvec's key for assoc must be a number.");};g.S=function(){var a=this;return function(b){return function e(d){return d===a.end?null:ae(A.c(a.Ja,d),new kf(null,function(){return function(){return e(d+1)}}(b),null,null))}}(this)(a.start)};g.T=function(a,b){return Zg(b,this.Ja,this.start,this.end,this.w)}; +g.X=function(a,b){return Zg(this.meta,qc(this.Ja,this.end,b),this.start,this.end+1,null)};g.call=function(){var a=null;a=function(a,c,d){switch(arguments.length){case 2:return this.$(null,c);case 3:return this.ka(null,c,d)}throw Error("Invalid arity: "+(arguments.length-1));};a.c=function(a,c){return this.$(null,c)};a.l=function(a,c,d){return this.ka(null,c,d)};return a}();g.apply=function(a,b){return this.call.apply(this,[this].concat(Gb(b)))};g.h=function(a){return this.$(null,a)}; +g.c=function(a,b){return this.ka(null,a,b)};Yg.prototype[Fb]=function(){return yd(this)};function Zg(a,b,c,d,e){for(;;)if(b instanceof Yg)c=b.start+c,d=b.start+d,b=b.Ja;else{if(!ze(b))throw Error("v must satisfy IVector");var f=H(b);if(0>c||0>d||c>f||d>f)throw Error("Index out of bounds");return new Yg(a,b,c,d,e)}}function $g(a,b){return a===b.la?b:new Fg(a,Gb(b.o))} +var ah=function ah(a,b,c,d){c=$g(a.root.la,c);var f=a.F-1>>>b&31;if(5===b)a=d;else{var h=c.o[f];null!=h?(b-=5,a=ah.M?ah.M(a,b,h,d):ah.call(null,a,b,h,d)):a=Jg(a.root.la,b-5,d)}c.o[f]=a;return c};function Tg(a,b,c,d){this.F=a;this.shift=b;this.root=c;this.fa=d;this.J=88;this.m=275}g=Tg.prototype; +g.Dc=function(a,b){if(this.root.la){if(32>this.F-Ig(this))this.fa[this.F&31]=b;else{var c=new Fg(this.root.la,this.fa),d=[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null];d[0]=b;this.fa=d;if(this.F>>>5>1<>>d&31,m=k(d-5,f.o[p]);f.o[p]=m}return f}}(a)(a.shift,a.root)}();a.root=d}return a}if(b===a.F)return a.Dc(null,c);throw Error(["Index ",v.h(b)," out of bounds for TransientVector of length",v.h(a.F)].join(""));}throw Error("assoc! after persistent!");} +g.W=function(){if(this.root.la)return this.F;throw Error("count after persistent!");};g.$=function(a,b){if(this.root.la)return(0<=b&&b=c)return new r(this.meta,this.F-1,d,null);G.c(b,this.o[e])||(d[f]=this.o[e],d[f+1]=this.o[e+1],f+=2);e+=2}}else return this}; +g.O=function(a,b,c){a=ih(this.o,b);if(-1===a){if(this.Fb?4:2*(b+1));Be(this.o,0,c,0,2*b);return new xh(a,this.na,c)};g.qd=function(){return yh(this.o,0,null)};g.Jc=function(a,b){return vh(this.o,a,b)};g.sc=function(a,b,c,d){var e=1<<(b>>>a&31);if(0===(this.na&e))return d;var f=$e(this.na&e-1);e=this.o[2*f];f=this.o[2*f+1];return null==e?f.sc(a+5,b,c,d):rh(c,e)?f:d}; +g.Kb=function(a,b,c,d,e,f){var h=1<<(c>>>b&31),k=$e(this.na&h-1);if(0===(this.na&h)){var l=$e(this.na);if(2*l>>b&31]=zh.Kb(a,b+5,c,d,e,f);for(e=d=0;;)if(32>d)0!== +(this.na>>>d&1)&&(k[d]=null!=this.o[e]?zh.Kb(a,b+5,od(this.o[e]),this.o[e],this.o[e+1],f):this.o[e+1],e+=2),d+=1;else break;return new Ah(a,l+1,k)}b=Array(2*(l+4));Be(this.o,0,b,0,2*k);b[2*k]=d;b[2*k+1]=e;Be(this.o,2*k,b,2*(k+1),2*(l-k));f.H=!0;a=this.Gc(a);a.o=b;a.na|=h;return a}l=this.o[2*k];h=this.o[2*k+1];if(null==l)return l=h.Kb(a,b+5,c,d,e,f),l===h?this:uh(this,a,2*k+1,l);if(rh(d,l))return e===h?this:uh(this,a,2*k+1,e);f.H=!0;f=b+5;b=od(l);if(b===c)e=new Bh(null,b,2,[l,h,d,e]);else{var p=new qh; +e=zh.Kb(a,f,b,l,h,p).Kb(a,f,c,d,e,p)}d=2*k;k=2*k+1;a=this.Gc(a);a.o[d]=null;a.o[k]=e;return a}; +g.Jb=function(a,b,c,d,e){var f=1<<(b>>>a&31),h=$e(this.na&f-1);if(0===(this.na&f)){var k=$e(this.na);if(16<=k){h=[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null];h[b>>>a&31]=zh.Jb(a+5,b,c,d,e);for(d=c=0;;)if(32>c)0!==(this.na>>>c&1)&&(h[c]=null!=this.o[d]?zh.Jb(a+5,od(this.o[d]),this.o[d],this.o[d+1],e):this.o[d+1],d+=2),c+=1;else break;return new Ah(null,k+1,h)}a=Array(2*(k+1));Be(this.o, +0,a,0,2*h);a[2*h]=c;a[2*h+1]=d;Be(this.o,2*h,a,2*(h+1),2*(k-h));e.H=!0;return new xh(null,this.na|f,a)}var l=this.o[2*h];f=this.o[2*h+1];if(null==l)return k=f.Jb(a+5,b,c,d,e),k===f?this:new xh(null,this.na,sh(this.o,2*h+1,k));if(rh(c,l))return d===f?this:new xh(null,this.na,sh(this.o,2*h+1,d));e.H=!0;e=this.na;k=this.o;a+=5;var p=od(l);if(p===b)c=new Bh(null,p,2,[l,f,c,d]);else{var m=new qh;c=zh.Jb(a,p,l,f,m).Jb(a,b,c,d,m)}a=2*h;h=2*h+1;d=Gb(k);d[a]=null;d[h]=c;return new xh(null,e,d)}; +g.rd=function(a,b,c){var d=1<<(b>>>a&31);if(0===(this.na&d))return this;var e=$e(this.na&d-1),f=this.o[2*e],h=this.o[2*e+1];return null==f?(a=h.rd(a+5,b,c),a===h?this:null!=a?new xh(null,this.na,sh(this.o,2*e+1,a)):this.na===d?null:new xh(null,this.na^d,th(this.o,e))):rh(c,f)?new xh(null,this.na^d,th(this.o,e)):this};g.ba=function(){return new wh(this.o,0,null,null)};var zh=new xh(null,0,[]);function Ch(a,b,c){this.o=a;this.i=b;this.Lb=c} +Ch.prototype.ja=function(){for(var a=this.o.length;;){if(null!=this.Lb&&this.Lb.ja())return!0;if(this.i>>a&31];return null!=e?e.sc(a+5,b,c,d):d};g.Kb=function(a,b,c,d,e,f){var h=c>>>b&31,k=this.o[h];if(null==k)return a=uh(this,a,h,zh.Kb(a,b+5,c,d,e,f)),a.F+=1,a;b=k.Kb(a,b+5,c,d,e,f);return b===k?this:uh(this,a,h,b)}; +g.Jb=function(a,b,c,d,e){var f=b>>>a&31,h=this.o[f];if(null==h)return new Ah(null,this.F+1,sh(this.o,f,zh.Jb(a+5,b,c,d,e)));a=h.Jb(a+5,b,c,d,e);return a===h?this:new Ah(null,this.F,sh(this.o,f,a))}; +g.rd=function(a,b,c){var d=b>>>a&31,e=this.o[d];if(null!=e){a=e.rd(a+5,b,c);if(a===e)d=this;else if(null==a)if(8>=this.F)a:{e=this.o;a=e.length;b=Array(2*(this.F-1));c=0;for(var f=1,h=0;;)if(ca?d:rh(c,this.o[a])?this.o[a+1]:d}; +g.Kb=function(a,b,c,d,e,f){if(c===this.ec){b=Eh(this.o,this.F,d);if(-1===b){if(this.o.length>2*this.F)return b=2*this.F,c=2*this.F+1,a=this.Gc(a),a.o[b]=d,a.o[c]=e,f.H=!0,a.F+=1,a;c=this.o.length;b=Array(c+2);Be(this.o,0,b,0,c);b[c]=d;b[c+1]=e;f.H=!0;d=this.F+1;a===this.la?(this.o=b,this.F=d,a=this):a=new Bh(this.la,this.ec,d,b);return a}return this.o[b+1]===e?this:uh(this,a,b+1,e)}return(new xh(a,1<<(this.ec>>>b&31),[null,this,null,null])).Kb(a,b,c,d,e,f)}; +g.Jb=function(a,b,c,d,e){return b===this.ec?(a=Eh(this.o,this.F,c),-1===a?(a=2*this.F,b=Array(a+2),Be(this.o,0,b,0,a),b[a]=c,b[a+1]=d,e.H=!0,new Bh(null,this.ec,this.F+1,b)):G.c(this.o[a+1],d)?this:new Bh(null,this.ec,this.F,sh(this.o,a+1,d))):(new xh(null,1<<(this.ec>>>a&31),[null,this])).Jb(a,b,c,d,e)};g.rd=function(a,b,c){a=Eh(this.o,this.F,c);return-1===a?this:1===this.F?null:new Bh(null,this.ec,this.F-1,th(this.o,Ze(a)))};g.ba=function(){return new wh(this.o,0,null,null)}; +function Fh(a,b,c,d,e){this.meta=a;this.Mb=b;this.i=c;this.s=d;this.w=e;this.m=32374988;this.J=0}g=Fh.prototype;g.toString=function(){return fd(this)};g.equiv=function(a){return this.K(null,a)};g.indexOf=function(){var a=null;a=function(a,c){switch(arguments.length){case 1:return Ud(this,a,0);case 2:return Ud(this,a,c)}throw Error("Invalid arity: "+(arguments.length-1));};a.h=function(a){return Ud(this,a,0)};a.c=function(a,c){return Ud(this,a,c)};return a}(); +g.lastIndexOf=function(){function a(a){return Xd(this,a,H(this))}var b=null;b=function(b,d){switch(arguments.length){case 1:return a.call(this,b);case 2:return Xd(this,b,d)}throw Error("Invalid arity: "+(arguments.length-1));};b.h=a;b.c=function(a,b){return Xd(this,a,b)};return b}();g.P=function(){return this.meta};g.Ka=function(){return null==this.s?yh(this.Mb,this.i+2,null):yh(this.Mb,this.i,z(this.s))};g.U=function(){var a=this.w;return null!=a?a:this.w=a=Ad(this)}; +g.K=function(a,b){return $d(this,b)};g.oa=function(){return tc(wd,this.meta)};g.Fa=function(a,b){return ce(b,this)};g.Ga=function(a,b,c){return de(b,c,this)};g.Ia=function(){return null==this.s?new R(null,2,5,T,[this.Mb[this.i],this.Mb[this.i+1]],null):y(this.s)};g.bb=function(){var a=null==this.s?yh(this.Mb,this.i+2,null):yh(this.Mb,this.i,z(this.s));return null!=a?a:wd};g.S=function(){return this};g.T=function(a,b){return new Fh(b,this.Mb,this.i,this.s,this.w)};g.X=function(a,b){return ae(b,this)}; +Fh.prototype[Fb]=function(){return yd(this)};function yh(a,b,c){if(null==c)for(c=a.length;;)if(bthis.F?H(z(this))+1:this.F};g.U=function(){var a=this.w;return null!=a?a:this.w=a=Ad(this)};g.K=function(a,b){return $d(this,b)};g.oa=function(){return tc(wd,this.meta)};g.Fa=function(a,b){return ce(b,this)};g.Ga=function(a,b,c){return de(b,c,this)};g.Ia=function(){var a=this.stack;return null==a?null:nc(a)};g.bb=function(){var a=y(this.stack);a=Mh(this.vc?a.right:a.left,z(this.stack),this.vc);return null!=a?new Nh(null,a,this.vc,this.F-1,null):wd};g.S=function(){return this}; +g.T=function(a,b){return new Nh(b,this.stack,this.vc,this.F,this.w)};g.X=function(a,b){return ae(b,this)};Nh.prototype[Fb]=function(){return yd(this)};function Oh(a,b,c){return new Nh(null,Mh(a,null,b),b,c,null)} +function Ph(a,b,c,d){return c instanceof Qh?c.left instanceof Qh?new Qh(c.key,c.H,c.left.bc(),new Rh(a,b,c.right,d,null),null):c.right instanceof Qh?new Qh(c.right.key,c.right.H,new Rh(c.key,c.H,c.left,c.right.left,null),new Rh(a,b,c.right.right,d,null),null):new Rh(a,b,c,d,null):new Rh(a,b,c,d,null)} +function Sh(a,b,c,d){return d instanceof Qh?d.right instanceof Qh?new Qh(d.key,d.H,new Rh(a,b,c,d.left,null),d.right.bc(),null):d.left instanceof Qh?new Qh(d.left.key,d.left.H,new Rh(a,b,c,d.left.left,null),new Rh(d.key,d.H,d.left.right,d.right,null),null):new Rh(a,b,c,d,null):new Rh(a,b,c,d,null)} +function Th(a,b,c,d){if(c instanceof Qh)return new Qh(a,b,c.bc(),d,null);if(d instanceof Rh)return Sh(a,b,c,d.ud());if(d instanceof Qh&&d.left instanceof Rh)return new Qh(d.left.key,d.left.H,new Rh(a,b,c,d.left.left,null),Sh(d.key,d.H,d.left.right,d.right.ud()),null);throw Error("red-black tree invariant violation");} +function Uh(a,b,c,d){if(d instanceof Qh)return new Qh(a,b,c,d.bc(),null);if(c instanceof Rh)return Ph(a,b,c.ud(),d);if(c instanceof Qh&&c.right instanceof Rh)return new Qh(c.right.key,c.right.H,Ph(c.key,c.H,c.left.ud(),c.right.left),new Rh(a,b,c.right.right,d,null),null);throw Error("red-black tree invariant violation");} +var Vh=function Vh(a,b,c){var e=null!=a.left?function(){var e=a.left;return Vh.l?Vh.l(e,b,c):Vh.call(null,e,b,c)}():c;if(Hd(e))return e;var f=function(){var c=a.key,f=a.H;return b.l?b.l(e,c,f):b.call(null,e,c,f)}();if(Hd(f))return f;if(null!=a.right){var h=a.right;return Vh.l?Vh.l(h,b,f):Vh.call(null,h,b,f)}return f};function Rh(a,b,c,d,e){this.key=a;this.H=b;this.left=c;this.right=d;this.w=e;this.m=32402207;this.J=0}g=Rh.prototype; +g.lastIndexOf=function(){function a(a){return Xd(this,a,H(this))}var b=null;b=function(b,d){switch(arguments.length){case 1:return a.call(this,b);case 2:return Xd(this,b,d)}throw Error("Invalid arity: "+(arguments.length-1));};b.h=a;b.c=function(a,b){return Xd(this,a,b)};return b}(); +g.indexOf=function(){var a=null;a=function(a,c){switch(arguments.length){case 1:return Ud(this,a,0);case 2:return Ud(this,a,c)}throw Error("Invalid arity: "+(arguments.length-1));};a.h=function(a){return Ud(this,a,0)};a.c=function(a,c){return Ud(this,a,c)};return a}();g.Ee=function(a){return a.He(this)};g.ud=function(){return new Qh(this.key,this.H,this.left,this.right,null)};g.bc=function(){return this};g.De=function(a){return a.Ge(this)};g.replace=function(a,b,c,d){return new Rh(a,b,c,d,null)}; +g.Ge=function(a){return new Rh(a.key,a.H,this,a.right,null)};g.He=function(a){return new Rh(a.key,a.H,a.left,this,null)};g.Jc=function(a,b){return Vh(this,a,b)};g.V=function(a,b){return this.ka(null,b,null)};g.I=function(a,b,c){return this.ka(null,b,c)};g.$=function(a,b){if(0===b)return this.key;if(1===b)return this.H;throw Error("Index out of bounds");};g.ka=function(a,b,c){return 0===b?this.key:1===b?this.H:c};g.dc=function(a,b,c){return(new R(null,2,5,T,[this.key,this.H],null)).dc(null,b,c)}; +g.P=function(){return null};g.W=function(){return 2};g.fd=function(){return this.key};g.gd=function(){return this.H};g.Ac=function(){return this.H};g.Bc=function(){return new R(null,1,5,T,[this.key],null)};g.U=function(){var a=this.w;return null!=a?a:this.w=a=Ad(this)};g.K=function(a,b){return $d(this,b)};g.oa=function(){return he};g.Fa=function(a,b){return Kd(this,b)};g.Ga=function(a,b,c){return Ld(this,b,c)};g.O=function(a,b,c){return K.l(new R(null,2,5,T,[this.key,this.H],null),b,c)}; +g.yc=function(a,b){return 0===b||1===b};g.S=function(){var a=this.key;return Tb(Tb(wd,this.H),a)};g.T=function(a,b){return tc(new R(null,2,5,T,[this.key,this.H],null),b)};g.X=function(a,b){return new R(null,3,5,T,[this.key,this.H,b],null)}; +g.call=function(){var a=null;a=function(a,c,d){switch(arguments.length){case 2:return this.$(null,c);case 3:return this.ka(null,c,d)}throw Error("Invalid arity: "+(arguments.length-1));};a.c=function(a,c){return this.$(null,c)};a.l=function(a,c,d){return this.ka(null,c,d)};return a}();g.apply=function(a,b){return this.call.apply(this,[this].concat(Gb(b)))};g.h=function(a){return this.$(null,a)};g.c=function(a,b){return this.ka(null,a,b)};Rh.prototype[Fb]=function(){return yd(this)}; +function Qh(a,b,c,d,e){this.key=a;this.H=b;this.left=c;this.right=d;this.w=e;this.m=32402207;this.J=0}g=Qh.prototype;g.lastIndexOf=function(){function a(a){return Xd(this,a,H(this))}var b=null;b=function(b,d){switch(arguments.length){case 1:return a.call(this,b);case 2:return Xd(this,b,d)}throw Error("Invalid arity: "+(arguments.length-1));};b.h=a;b.c=function(a,b){return Xd(this,a,b)};return b}(); +g.indexOf=function(){var a=null;a=function(a,c){switch(arguments.length){case 1:return Ud(this,a,0);case 2:return Ud(this,a,c)}throw Error("Invalid arity: "+(arguments.length-1));};a.h=function(a){return Ud(this,a,0)};a.c=function(a,c){return Ud(this,a,c)};return a}();g.Ee=function(a){return new Qh(this.key,this.H,this.left,a,null)};g.ud=function(){throw Error("red-black tree invariant violation");};g.bc=function(){return new Rh(this.key,this.H,this.left,this.right,null)}; +g.De=function(a){return new Qh(this.key,this.H,a,this.right,null)};g.replace=function(a,b,c,d){return new Qh(a,b,c,d,null)};g.Ge=function(a){return this.left instanceof Qh?new Qh(this.key,this.H,this.left.bc(),new Rh(a.key,a.H,this.right,a.right,null),null):this.right instanceof Qh?new Qh(this.right.key,this.right.H,new Rh(this.key,this.H,this.left,this.right.left,null),new Rh(a.key,a.H,this.right.right,a.right,null),null):new Rh(a.key,a.H,this,a.right,null)}; +g.He=function(a){return this.right instanceof Qh?new Qh(this.key,this.H,new Rh(a.key,a.H,a.left,this.left,null),this.right.bc(),null):this.left instanceof Qh?new Qh(this.left.key,this.left.H,new Rh(a.key,a.H,a.left,this.left.left,null),new Rh(this.key,this.H,this.left.right,this.right,null),null):new Rh(a.key,a.H,a.left,this,null)};g.Jc=function(a,b){return Vh(this,a,b)};g.V=function(a,b){return this.ka(null,b,null)};g.I=function(a,b,c){return this.ka(null,b,c)}; +g.$=function(a,b){if(0===b)return this.key;if(1===b)return this.H;throw Error("Index out of bounds");};g.ka=function(a,b,c){return 0===b?this.key:1===b?this.H:c};g.dc=function(a,b,c){return(new R(null,2,5,T,[this.key,this.H],null)).dc(null,b,c)};g.P=function(){return null};g.W=function(){return 2};g.fd=function(){return this.key};g.gd=function(){return this.H};g.Ac=function(){return this.H};g.Bc=function(){return new R(null,1,5,T,[this.key],null)}; +g.U=function(){var a=this.w;return null!=a?a:this.w=a=Ad(this)};g.K=function(a,b){return $d(this,b)};g.oa=function(){return he};g.Fa=function(a,b){return Kd(this,b)};g.Ga=function(a,b,c){return Ld(this,b,c)};g.O=function(a,b,c){return K.l(new R(null,2,5,T,[this.key,this.H],null),b,c)};g.yc=function(a,b){return 0===b||1===b};g.S=function(){var a=this.key;return Tb(Tb(wd,this.H),a)};g.T=function(a,b){return tc(new R(null,2,5,T,[this.key,this.H],null),b)}; +g.X=function(a,b){return new R(null,3,5,T,[this.key,this.H,b],null)};g.call=function(){var a=null;a=function(a,c,d){switch(arguments.length){case 2:return this.$(null,c);case 3:return this.ka(null,c,d)}throw Error("Invalid arity: "+(arguments.length-1));};a.c=function(a,c){return this.$(null,c)};a.l=function(a,c,d){return this.ka(null,c,d)};return a}();g.apply=function(a,b){return this.call.apply(this,[this].concat(Gb(b)))};g.h=function(a){return this.$(null,a)}; +g.c=function(a,b){return this.ka(null,a,b)};Qh.prototype[Fb]=function(){return yd(this)}; +var Wh=function Wh(a,b,c,d,e){if(null==b)return new Qh(c,d,null,null,null);var h=function(){var d=b.key;return a.c?a.c(c,d):a.call(null,c,d)}();if(0===h)return e[0]=b,null;if(0>h)return h=function(){var h=b.left;return Wh.Z?Wh.Z(a,h,c,d,e):Wh.call(null,a,h,c,d,e)}(),null!=h?b.De(h):null;h=function(){var h=b.right;return Wh.Z?Wh.Z(a,h,c,d,e):Wh.call(null,a,h,c,d,e)}();return null!=h?b.Ee(h):null},Xh=function Xh(a,b){if(null==a)return b;if(null==b)return a;if(a instanceof Qh){if(b instanceof Qh){var d= +function(){var d=a.right,f=b.left;return Xh.c?Xh.c(d,f):Xh.call(null,d,f)}();return d instanceof Qh?new Qh(d.key,d.H,new Qh(a.key,a.H,a.left,d.left,null),new Qh(b.key,b.H,d.right,b.right,null),null):new Qh(a.key,a.H,a.left,new Qh(b.key,b.H,d,b.right,null),null)}return new Qh(a.key,a.H,a.left,function(){var d=a.right;return Xh.c?Xh.c(d,b):Xh.call(null,d,b)}(),null)}if(b instanceof Qh)return new Qh(b.key,b.H,function(){var d=b.left;return Xh.c?Xh.c(a,d):Xh.call(null,a,d)}(),b.right,null);d=function(){var d= +a.right,f=b.left;return Xh.c?Xh.c(d,f):Xh.call(null,d,f)}();return d instanceof Qh?new Qh(d.key,d.H,new Rh(a.key,a.H,a.left,d.left,null),new Rh(b.key,b.H,d.right,b.right,null),null):Th(a.key,a.H,a.left,new Rh(b.key,b.H,d,b.right,null))},Yh=function Yh(a,b,c,d){if(null!=b){var f=function(){var d=b.key;return a.c?a.c(c,d):a.call(null,c,d)}();if(0===f)return d[0]=b,Xh(b.left,b.right);if(0>f)return f=function(){var f=b.left;return Yh.M?Yh.M(a,f,c,d):Yh.call(null,a,f,c,d)}(),null!=f||null!=d[0]?b.left instanceof +Rh?Th(b.key,b.H,f,b.right):new Qh(b.key,b.H,f,b.right,null):null;f=function(){var f=b.right;return Yh.M?Yh.M(a,f,c,d):Yh.call(null,a,f,c,d)}();return null!=f||null!=d[0]?b.right instanceof Rh?Uh(b.key,b.H,b.left,f):new Qh(b.key,b.H,b.left,f,null):null}return null},Zh=function Zh(a,b,c,d){var f=b.key,h=a.c?a.c(c,f):a.call(null,c,f);return 0===h?b.replace(f,d,b.left,b.right):0>h?b.replace(f,b.H,function(){var f=b.left;return Zh.M?Zh.M(a,f,c,d):Zh.call(null,a,f,c,d)}(),b.right):b.replace(f,b.H,b.left, +function(){var f=b.right;return Zh.M?Zh.M(a,f,c,d):Zh.call(null,a,f,c,d)}())};function $h(a,b,c,d,e){this.Bb=a;this.mc=b;this.F=c;this.meta=d;this.w=e;this.m=418776847;this.J=8192}g=$h.prototype;g.forEach=function(a){for(var b=E(this),c=null,d=0,e=0;;)if(ed?c.left:c.right}else return null}g.has=function(a){return He(this,a)};g.V=function(a,b){return this.I(null,b,null)}; +g.I=function(a,b,c){a=ai(this,b);return null!=a?a.H:c};g.Qc=function(a,b,c){return null!=this.mc?Jd(Vh(this.mc,b,c)):c};g.P=function(){return this.meta};g.W=function(){return this.F};g.Rc=function(){return 0(a.h?a.h(c):a.call(null,c))?b:c};Ai.A=function(a,b,c,d){return Mb(function(b,c){return Ai.l(a,b,c)},Ai.l(a,b,c),d)};Ai.N=function(a){var b=y(a),c=z(a);a=y(c);var d=z(c);c=y(d);d=z(d);return Ai.A(b,a,c,d)};Ai.L=3;function Bi(a,b){return new kf(null,function(){var c=E(b);if(c){var d=y(c);d=a.h?a.h(d):a.call(null,d);c=t(d)?ae(y(c),Bi(a,vd(c))):null}else c=null;return c},null,null)}function Di(a,b,c){this.i=a;this.end=b;this.step=c} +Di.prototype.ja=function(){return 0this.end};Di.prototype.next=function(){var a=this.i;this.i+=this.step;return a};function Ei(a,b,c,d,e){this.meta=a;this.start=b;this.end=c;this.step=d;this.w=e;this.m=32375006;this.J=139264}g=Ei.prototype;g.toString=function(){return fd(this)};g.equiv=function(a){return this.K(null,a)}; +g.indexOf=function(){var a=null;a=function(a,c){switch(arguments.length){case 1:return Ud(this,a,0);case 2:return Ud(this,a,c)}throw Error("Invalid arity: "+(arguments.length-1));};a.h=function(a){return Ud(this,a,0)};a.c=function(a,c){return Ud(this,a,c)};return a}(); +g.lastIndexOf=function(){function a(a){return Xd(this,a,H(this))}var b=null;b=function(b,d){switch(arguments.length){case 1:return a.call(this,b);case 2:return Xd(this,b,d)}throw Error("Invalid arity: "+(arguments.length-1));};b.h=a;b.c=function(a,b){return Xd(this,a,b)};return b}();g.$=function(a,b){if(0<=b&&bthis.end&&0===this.step)return this.start;throw Error("Index out of bounds");}; +g.ka=function(a,b,c){return 0<=b&&bthis.end&&0===this.step?this.start:c};g.ba=function(){return new Di(this.start,this.end,this.step)};g.P=function(){return this.meta};g.Ka=function(){return 0this.end?new Ei(this.meta,this.start+this.step,this.end,this.step,null):null}; +g.W=function(){return wb(this.S(null))?0:Math.ceil((this.end-this.start)/this.step)};g.U=function(){var a=this.w;return null!=a?a:this.w=a=Ad(this)};g.K=function(a,b){return $d(this,b)};g.oa=function(){return tc(wd,this.meta)};g.Fa=function(a,b){return Kd(this,b)};g.Ga=function(a,b,c){for(a=this.start;;)if(0this.end){c=b.c?b.c(c,a):b.call(null,c,a);if(Hd(c))return B(c);a+=this.step}else return c};g.Ia=function(){return null==this.S(null)?null:this.start}; +g.bb=function(){return null!=this.S(null)?new Ei(this.meta,this.start+this.step,this.end,this.step,null):wd};g.S=function(){return 0this.step?this.start>this.end?this:null:this.start===this.end?null:this};g.T=function(a,b){return new Ei(b,this.start,this.end,this.step,this.w)};g.X=function(a,b){return ae(b,this)};Ei.prototype[Fb]=function(){return yd(this)};function Fi(a,b,c){return new Ei(null,a,b,c,null)} +function Gi(a,b){return new R(null,2,5,T,[Bi(a,b),ng(a,b)],null)} +function Hi(a){var b=y;return function(){function c(c,d,e){return new R(null,2,5,T,[b.l?b.l(c,d,e):b.call(null,c,d,e),a.l?a.l(c,d,e):a.call(null,c,d,e)],null)}function d(c,d){return new R(null,2,5,T,[b.c?b.c(c,d):b.call(null,c,d),a.c?a.c(c,d):a.call(null,c,d)],null)}function e(c){return new R(null,2,5,T,[b.h?b.h(c):b.call(null,c),a.h?a.h(c):a.call(null,c)],null)}function f(){return new R(null,2,5,T,[b.B?b.B():b.call(null),a.B?a.B():a.call(null)],null)}var h=null,k=function(){function c(a,b,c,e){var f= +null;if(3lb)return Jc(a,"#");Jc(a,c);if(0===tb.h(f))E(h)&&Jc(a,function(){var a=Ki.h(f);return t(a)?a:"..."}());else{if(E(h)){var l=y(h);b.l?b.l(l,a,f):b.call(null,l,a,f)}for(var p=z(h),m=tb.h(f)-1;;)if(!p||null!=m&&0===m){E(p)&&0===m&&(Jc(a,d),Jc(a,function(){var a=Ki.h(f);return t(a)?a:"..."}()));break}else{Jc(a,d);var u=y(p);c=a;h=f;b.l?b.l(u,c,h):b.call(null,u,c,h);var w=z(p);c=m-1;p=w;m=c}}return Jc(a,e)}finally{lb=k}} +function Li(a,b){for(var c=E(b),d=null,e=0,f=0;;)if(fH(a)?a.toUpperCase():[v.h(a.substring(0,1).toUpperCase()),v.h(a.substring(1))].join("")} +function Qo(a){if("string"===typeof a)return a;a=jf(a);var b=Fo(a,/-/),c=E(b);b=y(c);c=z(c);return t(Oo.h?Oo.h(b):Oo.call(null,b))?a:Kb(v,b,ig.c(Po,c))}function Ro(a){var b=function(){var b=function(){var b=me(a);return b?(b=a.displayName,t(b)?b:a.name):b}();if(t(b))return b;b=function(){var b=null!=a?a.J&4096||q===a.Oe?!0:!1:!1;return b?jf(a):b}();if(t(b))return b;b=qe(a);return xe(b)?Tk.h(b):null}();return Do(""+v.h(b),"$",".")}var So=!1;if("undefined"===typeof To)var To=0;function Uo(a){return setTimeout(a,16)}var Vo="undefined"===typeof window||null==window.document?Uo:function(){var a=window,b=a.requestAnimationFrame;if(t(b))return b;b=a.webkitRequestAnimationFrame;if(t(b))return b;b=a.mozRequestAnimationFrame;if(t(b))return b;a=a.msRequestAnimationFrame;return t(a)?a:Uo}();function Wo(a,b){return a.cljsMountOrder-b.cljsMountOrder}if("undefined"===typeof Xo)var Xo=function(){return null};function Yo(a){this.Yd=a} +function Zo(a,b){var c=a[b];if(null==c)return null;a[b]=null;for(var d=c.length,e=0;;)if(e=d&&a.push(gq(c));return a}}(e),[b,c],a))}};if("undefined"===typeof jq)var jq=null;function kq(){if(null!=jq)return jq;if("undefined"!==typeof ReactDOM)return jq=ReactDOM;if("undefined"!==typeof require){var a=jq=require("react-dom");if(t(a))return a;throw Error("require('react-dom') failed");}throw Error("js/ReactDOM is missing");}if("undefined"===typeof lq)var lq=dg.h(Ef); +function mq(a,b,c){var d=So;So=!0;try{return kq().render(a.B?a.B():a.call(null),b,function(){return function(){var d=So;So=!1;try{return gg.M(lq,K,b,new R(null,2,5,T,[a,b],null)),Zo(bp,"afterRender"),null!=c?c.B?c.B():c.call(null):null}finally{So=d}}}(d))}finally{So=d}}function nq(a,b){return mq(a,b,null)}function oq(a,b,c){qp();return mq(function(){return gq(me(a)?a.B?a.B():a.call(null):a)},b,c)}Wp=function(a){return kq().findDOMNode(a)};function pq(a){switch(arguments.length){case 2:return oq(arguments[0],arguments[1],null);case 3:return oq(arguments[0],arguments[1],arguments[2]);default:throw Error(["Invalid arity: ",v.h(arguments.length)].join(""));}}function qq(a,b){return oq(a,b,null)} +da("reagent.core.force_update_all",function(){qp();qp();for(var a=E(mh(B(lq))),b=null,c=0,d=0;;)if(d=Number(c)?a:a=-1Number(a)?"-":0<=b.indexOf("+")?"+":0<=b.indexOf(" ")?" ":"";0<=Number(a)&&(d=f+d);if(isNaN(c)||d.length>=Number(c))return d;d=isNaN(e)?Math.abs(Number(a)).toString():Math.abs(Number(a)).toFixed(e);a=Number(c)-d.length-f.length;0<=b.indexOf("-",0)?d=f+d+sa(" ",a):(b=0<=b.indexOf("0",0)?"0":" ",d=f+sa(b,a)+d);return d};yq.fc.d=function(a,b,c,d,e,f,h,k){return yq.fc.f(parseInt(a,10),b,c,d,0,f,h,k)}; +yq.fc.i=yq.fc.d;yq.fc.u=yq.fc.d;function zq(a){var b=be([Vk,null]);return wg.c(t(a)?a:Ef,function(){return function e(a){return new kf(null,function(){for(var b=a;;)if(b=E(b)){if(Ae(b)){var d=Wc(b),k=H(d),l=of(k);a:for(var p=0;;)if(p=H(h)&&Vf(function(){return function(a){return!(a instanceof Xq)}}(b,c,d,e,f,h),h)))throw Error(Bq("%s is not a valid sequence schema; %s%s%s",be([a,"a valid sequence schema consists of zero or more `one` elements, ","followed by zero or more `optional` elements, followed by an optional ", +"schema that will match the remaining elements."])));return new R(null,2,5,T,[O.c(c,f),y(h)],null)} +R.prototype.xb=function(){var a=this,b=Zq(a),c=J(b,0,null),d=J(b,1,null);return Wg(O.c(function(){return function(a,b,c,d){return function m(e){return new kf(null,function(){return function(){for(;;){var a=E(e);if(a){if(Ae(a)){var b=Wc(a),c=H(b),d=of(c);return function(){for(var a=0;;)if(ac?f:c;return $r(a,ea?0:a}():function(){var a=e-b;return f>a?f:a}())} +function gs(a,b){var c=null!=a&&(a.m&64||q===a.G)?P(U,a):a,d=D.c(c,pl);d=null!=d&&(d.m&64||q===d.G)?P(U,d):d;var e=D.c(d,Aj),f=D.c(c,Yj),h=D.c(c,no);return $r(c,e>f?function(){var a=h-1,c=e+b;return a=a}}(l,p,a,c,c,d,e,f,h,k),h),l,p);return Zr(c,d)} +function it(a,b){var c=null!=a&&(a.m&64||q===a.G)?P(U,a):a,d=D.c(c,pl),e=null!=d&&(d.m&64||q===d.G)?P(U,d):d,f=D.c(e,zn),h=D.c(c,tk),k=D.c(c,fl),l=b-1;d=J(cf(Bi(function(a,b,c,d,e,f,h){return function(a){return h>a}}(l,a,c,c,d,e,f,h,k),h)),l,0);return Zr(c,d)}function jt(a){return K.l(a,im,Ve)}function kt(a){return K.l(a,im,Hr)}function lt(a,b,c){return K.l(a,b,c)}function mt(a,b,c){return Wg(O.A(jg(b,a),new R(null,1,5,T,[c],null),be([jg(H(a)-b-1,kg(b,a))])))} +function nt(a,b){var c=null!=a&&(a.m&64||q===a.G)?P(U,a):a,d=D.c(c,pl),e=null!=d&&(d.m&64||q===d.G)?P(U,d):d;d=D.c(e,zn);e=D.c(e,Aj);var f=D.c(c,fl);D.c(c,no);var h=D.c(c,Oj),k=D.c(c,Rj),l=D.c(c,$l),p=D.c(c,im);p=95b?p.h?p.h(b):p.call(null,b):b;h=tr(p,h);return G.c(f,d+1)?t(k)?K.l(Yr(zg(c,new R(null,3,5,T,[il,e,d],null),h),d+1),vk,!0):zg(c,new R(null,3,5,T,[il,e,d],null),h):Yr(Ag.Z(c,new R(null,2,5,T,[il,e],null),t(l)?mt:lt,d,h),d+1)} +function ot(a,b){var c=null!=a&&(a.m&64||q===a.G)?P(U,a):a,d=D.c(c,Rj),e=D.c(c,vk);t(t(d)?e:d)&&(c=null!=c&&(c.m&64||q===c.G)?P(U,c):c,d=D.c(c,pl),d=null!=d&&(d.m&64||q===d.G)?P(U,d):d,d=D.c(d,Aj),e=D.c(c,no),c=Yr(c,0),c=G.c(e,d+1)?Tr.h(c):$r(c,d+1));return c=nt(c,b)}function pt(a){a=null!=a&&(a.m&64||q===a.G)?P(U,a):a;var b=D.c(a,fl),c=D.c(a,no);return K.l(a,il,Wg(qg(c,Wg(qg(b,new R(null,2,5,T,[69,Ef],null))))))} +function qt(a){a=null!=a&&(a.m&64||q===a.G)?P(U,a):a;var b=D.c(a,pl);b=null!=b&&(b.m&64||q===b.G)?P(U,b):b;b=D.c(b,Aj);var c=D.c(a,fl),d=D.c(a,Oj);return zg(a,new R(null,2,5,T,[il,b],null),gr.c(c,d))}function rt(a,b,c){return Wg(O.c(jg(b,a),qg(H(a)-b,vr(c))))}function st(a,b,c){return Wg(O.c(qg(b+1,vr(c)),kg(b+1,a)))} +function tt(a){a=null!=a&&(a.m&64||q===a.G)?P(U,a):a;var b=D.c(a,pl),c=null!=b&&(b.m&64||q===b.G)?P(U,b):b;b=D.c(c,zn);c=D.c(c,Aj);var d=D.c(a,fl),e=D.c(a,Oj);--d;return Ag.Z(a,new R(null,2,5,T,[il,c],null),rt,b=k?Zr(c,k-1):c,m=Mb(D,p,new R(null,2,5,T,[pl,zn],null));return Ag.l(p,new R(null,2,5,T,[il,h],null),function(a,b,c,d,e,f,h,k,m,l,p,Q){return function(a){return Wg(O.A(jg(b,a),kg(b+c,a),be([qg(c,vr(Q))])))}}(p,m,function(){var a=k-m;return b=a}}(c,b)(b)}()))return Gu(a,b+64);throw Jt;}catch(h){if(h instanceof Error){var d=h;if(d===Jt)try{if(55===b)return Bg(a,V,ms);throw Jt;}catch(k){if(k instanceof Error){var e=k;if(e===Jt)try{if(56===b)return Bg(a,V,ns);throw Jt;}catch(l){if(l instanceof Error){var f=l;if(f===Jt)try{if(99===b)return du(a); +throw Jt;}catch(p){if(p instanceof Error){d=p;if(d===Jt)throw Jt;throw d;}throw p;}else throw f;}else throw l;}else throw e;}else throw k;}else throw d;}else throw h;}else throw Jt;}catch(h){if(h instanceof Error)if(d=h,d===Jt)try{if(35===c)try{if(56===b)return Bg(a,V,pt);throw Jt;}catch(k){if(k instanceof Error){e=k;if(e===Jt)throw Jt;throw e;}throw k;}else throw Jt;}catch(k){if(k instanceof Error)if(e=k,e===Jt)try{if(40===c)try{if(48===b)return Zt(a);throw Jt;}catch(l){if(l instanceof Error){f= +l;if(f===Jt)return $t(a);throw f;}throw l;}else throw Jt;}catch(l){if(l instanceof Error){f=l;if(f===Jt)return a;throw f;}throw l;}else throw e;else throw k;}else throw d;else throw h;}},function(a){return a},function(a){return a},Gu,function(a,b){return Cg(a,V,ot,b)},function(a,b){var c=function(){switch(b){case 64:return eu;case 65:return fu;case 66:return gu;case 67:return hu;case 68:return iu;case 69:return ju;case 70:return ku;case 71:return lu;case 72:return mu;case 73:return nu;case 74:return ou; +case 75:return pu;case 76:return su;case 77:return tu;case 80:return uu;case 83:return qu;case 84:return ru;case 87:return vu;case 88:return wu;case 90:return xu;case 96:return lu;case 97:return hu;case 100:return Du;case 101:return fu;case 102:return mu;case 103:return yu;case 104:return zu;case 108:return Au;case 109:return Cu;case 112:return Eu;case 114:return Fu;default:return null}}();return t(c)?c.h?c.h(a):c.call(null,a):a},function(a){return a},function(a,b){return K.l(a,kk,ge.c(kk.h(a),b))}, +function(a){return a},function(a,b){return K.l(a,rk,ge.c(rk.h(a),b))},function(a){return a},function(a){return a},function(a){return K.A(a,rk,he,be([kk,he]))}]);function Iu(a,b){for(var c=a,d=Tl.h(c),e=b;;){var f=y(e);if(t(f)){var h=160<=f?65:f;h=D.c(d.h?d.h(xq):d.call(null,xq),h);d=J(h,0,null);h=J(h,1,null);a:for(;;)if(E(h)){var k=y(h);k=Hu.h?Hu.h(k):Hu.call(null,k);c=k.c?k.c(c,f):k.call(null,c,f);h=z(h)}else break a;e=vd(e)}else return K.l(c,Tl,d)}} +function Ju(a,b){var c=xg(function(a){return a.codePointAt(0)},b);return Iu(a,c)} +function Ku(a,b){try{if(ze(b)&&3===H(b)){var c=Vd(b,0),d=Vd(b,1),e=Vd(b,2);return[v.h(a+8),";2;",v.h(c),";",v.h(d),";",v.h(e)].join("")}throw Jt;}catch(k){if(k instanceof Error){var f=k;if(f===Jt)try{if(t(function(){return function(){return function(a){return 8>a}}(f)(b)}()))return""+v.h(a+b);throw Jt;}catch(l){if(l instanceof Error){var h=l;if(h===Jt)try{if(t(function(){return function(){return function(a){return 16>a}}(h,f)(b)}()))return""+v.h(a+52+b);throw Jt;}catch(p){if(p instanceof Error){c= +p;if(c===Jt)return[v.h(a+8),";5;",v.h(b)].join("");throw c;}throw p;}else throw h;}else throw l;}else throw f;}else throw k;}}ag.c(Ku,30);ag.c(Ku,40);var Lu=function Lu(a){if(null!=a&&null!=a.yd)return a.yd(a);var c=Lu[n(null==a?null:a)];if(null!=c)return c.h?c.h(a):c.call(null,a);c=Lu._;if(null!=c)return c.h?c.h(a):c.call(null,a);throw Cb("Screen.lines",a);},Mu=function Mu(a){if(null!=a&&null!=a.xd)return a.xd(a);var c=Mu[n(null==a?null:a)];if(null!=c)return c.h?c.h(a):c.call(null,a);c=Mu._;if(null!=c)return c.h?c.h(a):c.call(null,a);throw Cb("Screen.cursor",a);};function Nu(a,b){var c=0parseFloat(Iv)){Hv=String(Kv);break a}}Hv=Iv}var gb={}; +function Lv(a){return fb(a,function(){for(var b=0,c=ra(String(Hv)).split("."),d=ra(String(a)).split("."),e=Math.max(c.length,d.length),f=0;0==b&&f=a.keyCode)a.keyCode=-1}catch(b){}};var Uv="closure_listenable_"+(1E6*Math.random()|0),Vv=0;function Wv(a,b,c,d,e){this.listener=a;this.Xd=null;this.src=b;this.type=c;this.capture=!!d;this.Ub=e;this.key=++Vv;this.$c=this.Fd=!1}function Xv(a){a.$c=!0;a.listener=null;a.Xd=null;a.src=null;a.Ub=null};function Yv(a){this.src=a;this.rb={};this.wd=0}Yv.prototype.add=function(a,b,c,d,e){var f=a.toString();a=this.rb[f];a||(a=this.rb[f]=[],this.wd++);var h=Zv(a,b,d,e);-1e.keyCode||void 0!=e.returnValue)){a:{var f=!1;if(0==e.keyCode)try{e.keyCode=-1;break a}catch(l){f=!0}if(f||void 0==e.returnValue)e.returnValue=!0}e=[];for(f=c.currentTarget;f;f=f.parentNode)e.push(f);f=a.type;for(var h=e.length-1;!c.Kc&&0<=h;h--){c.currentTarget=e[h];var k=nw(e[h],f,!0,c);d=d&&k}for(h=0;!c.Kc&& +h>>0);function fw(a){if(ha(a))return a;a[pw]||(a[pw]=function(b){return a.handleEvent(b)});return a[pw]};function qw(){wv.call(this);this.Ib=new Yv(this);this.ff=this;this.ve=null}qa(qw,wv);qw.prototype[Uv]=!0;g=qw.prototype;g.addEventListener=function(a,b,c,d){dw(this,a,b,c,d)};g.removeEventListener=function(a,b,c,d){lw(this,a,b,c,d)}; +g.dispatchEvent=function(a){var b,c=this.ve;if(c)for(b=[];c;c=c.ve)b.push(c);c=this.ff;var d=a.type||a;if(ca(a))a=new Sv(a,c);else if(a instanceof Sv)a.target=a.target||c;else{var e=a;a=new Sv(d,c);Ia(a,e)}e=!0;if(b)for(var f=b.length-1;!a.Kc&&0<=f;f--){var h=a.currentTarget=b[f];e=rw(h,d,!0,a)&&e}a.Kc||(h=a.currentTarget=c,e=rw(h,d,!0,a)&&e,a.Kc||(e=rw(h,d,!1,a)&&e));if(b)for(f=0;!a.Kc&&fthis.head?(Yw(this.o,this.fa,a,0,this.o.length-this.fa),Yw(this.o,0,a,this.o.length-this.fa,this.head),this.fa=0,this.head=this.length,this.o=a):this.fa===this.head?(this.head=this.fa=0,this.o=a):null};function ax(a,b){for(var c=a.length,d=0;;)if(da)){a+=1;continue}break}hx=!1;return 0c)return a;a:for(;;){var e=cMath.random()&&15>d)d+=1;else break a;if(d>this.level){for(var e=this.level+1;;)if(e<=d+1)c[e]=this.header,e+=1;else break;this.level=d}for(d=Ex(a,b,Array(d));;)return 0<=this.level?(c=c[0].forward,d.forward[0]=c[0],c[0]=d):null}; +Gx.prototype.remove=function(a){var b=Array(15),c=Fx(this.header,a,this.level,b);c=0===c.forward.length?null:c.forward[0];if(null!=c&&c.key===a){for(a=0;;)if(a<=this.level){var d=b[a].forward;c===(ad)return c===b.header?null:c;var e;a:for(e=c;;){e=d=a)break a}null!=e?(--d,c=e):--d}}Gx.prototype.S=function(){return function(a){return function d(c){return new kf(null,function(){return function(){return null==c?null:ae(new R(null,2,5,T,[c.key,c.H],null),d(c.forward[0]))}}(a),null,null)}}(this)(this.header.forward[0])}; +Gx.prototype.R=function(a,b,c){return Y(b,function(){return function(a){return Y(b,Qi,""," ","",c,a)}}(this),"{",", ","}",c,this)};var Ix=new Gx(Ex(null,null,0),0);function Jx(a){var b=(new Date).valueOf()+a,c=Hx(b),d=t(t(c)?c.keya:b)?a+8:a,[v.h(c),v.h(a)].join("")):null} +function Vy(a){var b=J(a,0,null),c=J(a,1,null);a=J(a,2,null);return["rgb(",v.h(b),",",v.h(c),",",v.h(a),")"].join("")} +var Wy=hj(function(a){a=null!=a&&(a.m&64||q===a.G)?P(U,a):a;var b=D.c(a,Nk),c=D.c(a,pl);a=K.l(a,Nk,t(c)?wb(b):b);var d=null!=a&&(a.m&64||q===a.G)?P(U,a):a,e=D.c(d,Ok),f=D.c(d,Tn);b=D.c(d,Kj);var h=D.c(d,dk);c=D.c(d,Vl);var k=D.c(d,Nk),l=D.c(d,Yn);d=D.c(d,pl);var p=t(k)?t(e)?e:"fg":f;e=Uy(t(k)?t(f)?f:"bg":e,b,"fg-");h=Uy(p,h,"bg-");c=vg(ub,new R(null,6,5,T,[e,h,t(b)?"bright":null,t(l)?"italic":null,t(c)?"underline":null,t(d)?"cursor":null],null));if(E(c))a:for(b=new cb,c=E(c);;)if(null!=c)b.append(""+ +v.h(y(c))),c=z(c),null!=c&&b.append(" ");else{b=b.toString();break a}else b=null;l=null!=a&&(a.m&64||q===a.G)?P(U,a):a;a=D.c(l,Ok);c=D.c(l,Tn);h=D.c(l,Nk);l=t(h)?c:a;a=t(h)?a:c;a=hi.A(be([t(ze.h?ze.h(l):ze.call(null,l))?new r(null,1,[ik,Vy(l)],null):null,t(ze.h?ze.h(a):ze.call(null,a))?new r(null,1,[al,Vy(a)],null):null]));return hi.A(be([t(b)?new r(null,1,[vn,b],null):null,t(a)?new r(null,1,[fm,a],null):null]))}); +function Xy(a,b){var c=J(a,0,null),d=J(a,1,null);d=Bg(d,pl,function(){return function(a){return t(a)?B(b):a}}(a,c,d));return new R(null,3,5,T,[ro,Wy.h?Wy.h(d):Wy.call(null,d),c],null)}function Yy(a,b){var c=J(a,0,null),d=J(a,1,null),e=jg(b,c);e=E(e)?new R(null,2,5,T,[Eo(e),d],null):null;var f=K.l(d,pl,!0);f=new R(null,2,5,T,[Vd(c,b),f],null);c=kg(b+1,c);d=E(c)?new R(null,2,5,T,[Eo(c),d],null):null;return vg(ub,new R(null,3,5,T,[e,f,d],null))} +function Zy(a,b){for(var c=he,d=a,e=b;;)if(E(d)){var f=y(d),h=J(f,0,null);J(f,1,null);h=H(h);if(h<=e)c=ge.c(c,f),d=vd(d),e-=h;else return O.A(c,Yy(f,e),be([vd(d)]))}else return c}function $y(a,b,c){a=t(B(b))?Zy(B(a),B(b)):B(a);return new R(null,2,5,T,[Lm,Ii(bg(function(){return function(a,b){return pe(new R(null,3,5,T,[Xy,b,c],null),new r(null,1,[mk,a],null))}}(a),a))],null)}var qA=new ti(null,new r(null,3,["small",null,"medium",null,"big",null],null),null); +function rA(a,b,c,d,e){var f=yp(function(){var a=B(c);return t(qA.h?qA.h(a):qA.call(null,a))?["font-",v.h(a)].join(""):null}),h=yp(function(){return function(){var d=B(a),e=B(b),f=B(c);f=t(qA.h?qA.h(f):qA.call(null,f))?null:new r(null,1,[wk,f],null);return hi.A(be([new r(null,2,[fl,[v.h(d),"ch"].join(""),no,[v.h(1.3333333333*e),"em"].join("")],null),f]))}}(f)),k=yp(function(){return function(){return Lu(B(d))}}(f,h)),l=yp(function(a,c,d){return function(){return xg(function(a,b,c){return function(d){return yp(function(a, +b,c){return function(){return D.c(B(c),d)}}(a,b,c))}}(a,c,d),Fi(0,B(b),1))}}(f,h,k)),p=yp(function(){return function(){return Mu(B(d))}}(f,h,k,l)),m=yp(function(a,b,c,d,e){return function(){return zn.h(B(e))}}(f,h,k,l,p)),u=yp(function(a,b,c,d,e){return function(){return Aj.h(B(e))}}(f,h,k,l,p,m)),w=yp(function(a,b,c,d,e){return function(){return On.h(B(e))}}(f,h,k,l,p,m,u));return function(a,b,c,d,f,h,k,l){return function(){return new R(null,3,5,T,[Gm,new r(null,2,[vn,B(a),fm,B(b)],null),bg(function(a, +b,c,d,f,h,k,l){return function(m,p){var u=yp(function(a,b,c,d,e,f,h,k){return function(){var a=B(k);return t(a)?(a=G.c(m,B(h)))?B(f):a:a}}(a,b,c,d,f,h,k,l));return pe(new R(null,4,5,T,[$y,p,u,e],null),new r(null,1,[mk,m],null))}}(a,b,c,d,f,h,k,l),B(d))],null)}}(f,h,k,l,p,m,u,w)} +function sA(){return new R(null,2,5,T,[Ym,new r(null,4,[Mn,"1.1",Fl,"0 0 866.0254037844387 866.0254037844387",vn,"icon",mo,new r(null,1,[An,'\x3cdefs\x3e \x3cmask id\x3d"small-triangle-mask"\x3e \x3crect width\x3d"100%" height\x3d"100%" fill\x3d"white"/\x3e \x3cpolygon points\x3d"508.01270189221935 433.01270189221935, 208.0127018922194 259.8076211353316, 208.01270189221927 606.217782649107" fill\x3d"black"\x3e\x3c/polygon\x3e \x3c/mask\x3e \x3c/defs\x3e \x3cpolygon points\x3d"808.0127018922194 433.01270189221935, 58.01270189221947 -1.1368683772161603e-13, 58.01270189221913 866.0254037844386" mask\x3d"url(#small-triangle-mask)" fill\x3d"white"\x3e\x3c/polygon\x3e \x3cpolyline points\x3d"481.2177826491071 333.0127018922194, 134.80762113533166 533.0127018922194" stroke\x3d"white" stroke-width\x3d"90"\x3e\x3c/polyline\x3e'],null)], +null)],null)}function tA(){return new R(null,3,5,T,[Ym,new r(null,3,[Mn,"1.1",Fl,"0 0 12 12",vn,"icon"],null),new R(null,2,5,T,[Fj,new r(null,1,[pn,"M1,0 L11,6 L1,12 Z"],null)],null)],null)}function uA(){return new R(null,4,5,T,[Ym,new r(null,3,[Mn,"1.1",Fl,"0 0 12 12",vn,"icon"],null),new R(null,2,5,T,[Fj,new r(null,1,[pn,"M1,0 L4,0 L4,12 L1,12 Z"],null)],null),new R(null,2,5,T,[Fj,new r(null,1,[pn,"M8,0 L11,0 L11,12 L8,12 Z"],null)],null)],null)} +function vA(){return new R(null,4,5,T,[Ym,new r(null,3,[Mn,"1.1",Fl,"0 0 12 12",vn,"icon"],null),new R(null,2,5,T,[Fj,new r(null,1,[pn,"M12,0 L7,0 L9,2 L7,4 L8,5 L10,3 L12,5 Z"],null)],null),new R(null,2,5,T,[Fj,new r(null,1,[pn,"M0,12 L0,7 L2,9 L4,7 L5,8 L3,10 L5,12 Z"],null)],null)],null)} +function wA(){return new R(null,4,5,T,[Ym,new r(null,3,[Mn,"1.1",Fl,"0 0 12 12",vn,"icon"],null),new R(null,2,5,T,[Fj,new r(null,1,[pn,"M7,5 L7,0 L9,2 L11,0 L12,1 L10,3 L12,5 Z"],null)],null),new R(null,2,5,T,[Fj,new r(null,1,[pn,"M5,7 L0,7 L2,9 L0,11 L1,12 L3,10 L5,12 Z"],null)],null)],null)}function xA(a,b){return function(b){return function(){return new R(null,3,5,T,[cl,new r(null,1,[Sl,b],null),new R(null,1,5,T,[t(B(a))?uA:tA],null)],null)}}(Ty(b,new fy(null,null,null)))} +function yA(a){return 10>a?["0",v.h(a)].join(""):a}function zA(a){var b=Math.floor((a%60+60)%60);return[v.h(yA(Math.floor(a/60))),":",v.h(yA(b))].join("")}function AA(a,b){var c=T,d=new R(null,2,5,T,[Yk,zA(B(a))],null),e=T;var f=B(a);var h=B(b);f=["-",v.h(zA(h-f))].join("");return new R(null,3,5,c,[Ml,d,new R(null,2,5,e,[co,f],null)],null)} +function BA(){function a(a){a.preventDefault();return Ry(a.currentTarget.parentNode.parentNode.parentNode)}return function(){return new R(null,4,5,T,[un,new r(null,1,[Sl,a],null),new R(null,1,5,T,[vA],null),new R(null,1,5,T,[wA],null)],null)}} +function CA(a,b){var c=Sy(b,function(a){var b=a.currentTarget.offsetWidth,c=a.currentTarget.getBoundingClientRect();return cy(Nu(a.clientX-c.left,b)/b)}),d=yp(function(){return function(){return[v.h(100*B(a)),"%"].join("")}}(c));return function(a,b){return function(){return new R(null,2,5,T,[Vj,new R(null,3,5,T,[Bl,new r(null,1,[Ql,a],null),new R(null,2,5,T,[Cj,new R(null,2,5,T,[ro,new r(null,1,[fm,new r(null,1,[fl,B(b)],null)],null)],null)],null)],null)],null)}}(c,d)} +function DA(a,b,c,d){return function(e){return function(){return new R(null,5,5,T,[Kk,new R(null,3,5,T,[xA,a,d],null),new R(null,3,5,T,[AA,b,c],null),new R(null,1,5,T,[BA],null),new R(null,3,5,T,[CA,e,d],null)],null)}}(yp(function(){return B(b)/B(c)}))} +function EA(a){return function(a){return function(){return new R(null,3,5,T,[ol,new r(null,1,[Sl,a],null),new R(null,2,5,T,[Xk,new R(null,2,5,T,[km,new R(null,2,5,T,[ro,new R(null,1,5,T,[sA],null)],null)],null)],null)],null)}}(Ty(a,new fy(null,null,null)))}function FA(){return new R(null,2,5,T,[Ek,new R(null,1,5,T,[xn],null)],null)}function GA(a){return Wf(function(b){return a[b]},new R(null,4,5,T,["altKey","shiftKey","metaKey","ctrlKey"],null))} +function HA(a){var b=t(GA(a))?null:function(){switch(a.key){case " ":return new fy(null,null,null);case "f":return bm;case "0":return cy(0);case "1":return cy(.1);case "2":return cy(.2);case "3":return cy(.3);case "4":return cy(.4);case "5":return cy(.5);case "6":return cy(.6);case "7":return cy(.7);case "8":return cy(.8);case "9":return cy(.9);default:return null}}();if(t(b))return b;switch(a.key){case "\x3e":return new ey(null,null,null);case "\x3c":return new dy(null,null,null);default:return null}} +function IA(a){if(t(GA(a)))return null;switch(a.which){case 37:return new ay(null,null,null);case 39:return new $x(null,null,null);default:return null}}function JA(a){var b=HA(a);return t(b)?(a.preventDefault(),G.c(b,bm)?(Ry(a.currentTarget),null):b):null}function KA(a){var b=IA(a);return t(b)?(a.preventDefault(),b):null} +function LA(a,b,c,d){a=t(a)?['"',v.h(a),'"'].join(""):"untitled";return new R(null,4,5,T,[dl,t(d)?new R(null,2,5,T,[jo,new r(null,1,[zl,d],null)],null):null,a,t(b)?new R(null,3,5,T,[ro," by ",t(c)?new R(null,3,5,T,[lo,new r(null,1,[ho,c],null),b],null):b],null):null],null)} +function MA(a){var b=Mx(1,ig.h(iy)),c=Kx(1);lx(function(c){return function(){var d=function(){return function(a){return function(){function b(b){for(;;){a:try{for(;;){var c=a(b);if(!N(c,Z)){var d=c;break a}}}catch(x){if(x instanceof Object)b[5]=x,Cx(b),d=Z;else throw x;}if(!N(d,Z))return d}}function c(){var a=[null,null,null,null,null,null,null,null,null,null,null,null];a[0]=d;a[1]=1;return a}var d=null;d=function(a){switch(arguments.length){case 0:return c.call(this);case 1:return b.call(this,a)}throw Error("Invalid arity: "+ +(arguments.length-1));};d.B=c;d.h=b;return d}()}(function(){return function(c){var d=c[1];if(7===d)return c[7]=c[2],Ax(c,12,b,!1);if(1===d)return c[2]=null,c[1]=2,Z;if(4===d)return c[8]=c[2],Ax(c,5,b,!0);if(6===d)return d=Jx(3E3),Ux(c,8,new R(null,2,5,T,[a,d],null));if(3===d)return Bx(c,c[2]);if(12===d)return c[9]=c[2],c[2]=null,c[1]=2,Z;if(2===d)return zx(c,4,a);if(11===d)return c[2]=c[2],c[1]=7,Z;if(9===d)return c[2]=null,c[1]=6,Z;if(5===d)return c[10]=c[2],c[2]=null,c[1]=6,Z;if(10===d)return c[2]= +null,c[1]=11,Z;if(8===d){var e=c[2];d=J(e,0,null);e=J(e,1,null);e=G.c(e,a);c[11]=d;c[1]=e?9:10;return Z}return null}}(c),c)}(),f=function(){var a=d.B?d.B():d.call(null);a[6]=c;return a}();return yx(f)}}(c));return b} +function NA(a,b){var c=dg.h(b),d=Kx(1);lx(function(b,c){return function(){var d=function(){return function(a){return function(){function b(b){for(;;){a:try{for(;;){var c=a(b);if(!N(c,Z)){var d=c;break a}}}catch(F){if(F instanceof Object)b[5]=F,Cx(b),d=Z;else throw F;}if(!N(d,Z))return d}}function c(){var a=[null,null,null,null,null,null,null,null,null,null,null,null,null];a[0]=d;a[1]=1;return a}var d=null;d=function(a){switch(arguments.length){case 0:return c.call(this);case 1:return b.call(this, +a)}throw Error("Invalid arity: "+(arguments.length-1));};d.B=c;d.h=b;return d}()}(function(b,c){return function(d){var e=d[1];if(7===e){var f=d[7],h=wb(null==f);d[8]=d[2];d[1]=h?8:9;return Z}if(20===e)return f=d[7],d[1]=t(q===f.Fe)?23:24,Z;if(27===e)return d[2]=!1,d[1]=28,Z;if(1===e)return d[2]=null,d[1]=2,Z;if(24===e)return f=d[7],d[1]=t(!f.Tc)?26:27,Z;if(4===e){f=d[7];var k=d[9];h=d[2];var l=J(h,0,null),m=J(h,1,null);d[10]=m;d[7]=l;d[9]=h;d[1]=t(null==l)?5:6;return Z}return 15===e?(d[2]=!1,d[1]= +16,Z):21===e?(f=d[7],h=Ab(Yx,f),d[2]=h,d[1]=22,Z):31===e?(d[11]=d[2],d[2]=null,d[1]=2,Z):13===e?(d[2]=d[2],d[1]=10,Z):22===e?(d[1]=t(d[2])?29:30,Z):29===e?(f=d[7],h=B(a),h=Zx(f,h),h=gg.l(c,wo,h),d[2]=h,d[1]=31,Z):6===e?(d[2]=null,d[1]=7,Z):28===e?(d[2]=d[2],d[1]=25,Z):25===e?(d[2]=d[2],d[1]=22,Z):17===e?(m=d[10],f=d[7],k=d[9],h=gg.c(a,function(){return function(a,b){return function(a){return Xx(b,a)}}(k,f,m,m,f,k,e,b,c)}()),d[2]=h,d[1]=19,Z):3===e?Bx(d,d[2]):12===e?(f=d[7],d[1]=t(!f.Tc)?14:15,Z): +2===e?(h=B(c),h=E(h),Ux(d,4,h)):23===e?(d[2]=!0,d[1]=25,Z):19===e?(f=d[7],h=wb(null==f),d[12]=d[2],d[1]=h?20:21,Z):11===e?(d[2]=!0,d[1]=13,Z):9===e?(f=d[7],h=Ab(Wx,f),d[2]=h,d[1]=10,Z):5===e?(m=d[10],h=gg.l(c,re,m),d[2]=h,d[1]=7,Z):14===e?(f=d[7],h=Ab(Wx,f),d[2]=h,d[1]=16,Z):26===e?(f=d[7],h=Ab(Yx,f),d[2]=h,d[1]=28,Z):16===e?(d[2]=d[2],d[1]=13,Z):30===e?(d[2]=null,d[1]=31,Z):10===e?(d[1]=t(d[2])?17:18,Z):18===e?(d[2]=null,d[1]=19,Z):8===e?(f=d[7],d[1]=t(q===f.sb)?11:12,Z):null}}(b,c),b,c)}(),e=function(){var a= +d.B?d.B():d.call(null);a[6]=b;return a}();return yx(e)}}(d,c));return d} +function OA(a,b,c){c=Ty(c,!0);var d=Sy(b,JA),e=Sy(b,KA),f=yp(function(){return function(){return Hm.h(B(a))}}(c,d,e)),h=yp(function(){return function(){return el.h(B(a))}}(c,d,e,f)),k=yp(function(a,b,c,d,e){return function(){var a=B(d);return t(a)?a:B(e)}}(c,d,e,f,h)),l=yp(function(b,c,d,e,f,h){return function(){var b=Gk.h(B(a));b=t(b)?b:wb(B(h));return t(b)?"hud":null}}(c,d,e,f,h,k)),p=yp(function(){return function(){return["asciinema-theme-",v.h(gm.h(B(a)))].join("")}}(c,d,e,f,h,k,l)),m=yp(function(){return function(){var b= +fl.h(B(a));return t(b)?b:80}}(c,d,e,f,h,k,l,p)),u=yp(function(){return function(){var b=no.h(B(a));return t(b)?b:24}}(c,d,e,f,h,k,l,p,m)),w=yp(function(){return function(){return wk.h(B(a))}}(c,d,e,f,h,k,l,p,m,u)),x=yp(function(){return function(){return V.h(B(a))}}(c,d,e,f,h,k,l,p,m,u,w)),C=yp(function(){return function(){return ml.h(B(a))}}(c,d,e,f,h,k,l,p,m,u,w,x)),F=yp(function(){return function(){return jn.h(B(a))}}(c,d,e,f,h,k,l,p,m,u,w,x,C)),I=yp(function(){return function(){return Uj.h(B(a))}}(c, +d,e,f,h,k,l,p,m,u,w,x,C,F)),M=yp(function(){return function(){return wl.h(B(a))}}(c,d,e,f,h,k,l,p,m,u,w,x,C,F,I)),S=B(a),X=null!=S&&(S.m&64||q===S.G)?P(U,S):S,Ga=D.c(X,ki),db=D.c(X,li),Q=D.c(X,mi),xb=D.c(X,ni);return function(a,c,d,e,f,h,k,l,m,p,u,w,x,C,F,I,M,S,Q,X,Ga,db){return function(){return new R(null,3,5,T,[Cn,new r(null,5,[Jj,-1,Zj,c,Rn,d,Vm,a,vn,B(k)],null),new R(null,7,5,T,[Sm,new r(null,1,[vn,B(l)],null),new R(null,6,5,T,[rA,m,p,u,w,x],null),new R(null,5,5,T,[DA,C,F,I,b],null),t(t(Q)?Q: +X)?new R(null,5,5,T,[LA,Q,X,Ga,db],null):null,t(B(h))?null:new R(null,2,5,T,[EA,b],null),t(B(e))?new R(null,1,5,T,[FA],null):null],null)],null)}}(c,d,e,f,h,k,l,p,m,u,w,x,C,F,I,M,S,X,Ga,db,Q,xb)} +function PA(a){var b=Kx(null),c=Kx(new dx(bx(1),1));return function(b,c){return function(){return Pp(new r(null,4,[ln,"asciinema-player",Dm,function(b,c){return function(){return OA(a,b,c)}}(b,c),$k,function(b,c){return function(){var d=ty(Gl.h(B(a))),e=MA(c);Tx(e,b);return NA(a,Je([b,d]))}}(b,c),Wm,function(){return function(){return uy(Gl.h(B(a)))}}(b,c)],null))}}(b,c)};function QA(a,b){var c=null!=b&&(b.m&64||q===b.G)?P(U,b):b,d=D.c(c,Ak),e=D.c(c,Gl);d=a.h?a.h(d):a.call(null,d);zy(e,d);return K.l(c,Ak,d)}$x.prototype.sb=q;$x.prototype.qb=function(a,b){var c=null!=b&&(b.m&64||q===b.G)?P(U,b):b,d=D.c(c,Uj),e=D.c(c,wl),f=D.c(c,Gl);t(e)&&yy(f,Nu(d+5,e));return c};ay.prototype.sb=q;ay.prototype.qb=function(a,b){var c=null!=b&&(b.m&64||q===b.G)?P(U,b):b,d=D.c(c,Uj),e=D.c(c,wl),f=D.c(c,Gl);t(e)&&yy(f,Nu(d+-5,e));return c};by.prototype.sb=q; +by.prototype.qb=function(a,b){var c=null!=b&&(b.m&64||q===b.G)?P(U,b):b,d=D.c(c,wl),e=D.c(c,Gl);t(d)&&(d*=nn.h(this),yy(e,d));return c};dy.prototype.sb=q;dy.prototype.qb=function(a,b){return QA(function(){return function(a){return a/2}}(this),b)};ey.prototype.sb=q;ey.prototype.qb=function(a,b){return QA(function(){return function(a){return 2*a}}(this),b)};fy.prototype.sb=q;fy.prototype.qb=function(a,b){xy(Gl.h(b));return b};gy.prototype.sb=q;gy.prototype.qb=function(a,b){return K.l(b,ml,so.h(this))}; +hy.prototype.sb=q;hy.prototype.qb=function(a,b){return K.l(b,Gk,so.h(this))};jy.prototype.sb=q;jy.prototype.qb=function(a,b){var c=null!=a&&(a.m&64||q===a.G)?P(U,a):a;D.c(c,fl);D.c(c,no);D.c(c,wl);c=null!=b&&(b.m&64||q===b.G)?P(U,b):b;var d=D.c(c,fl),e=D.c(c,no),f=null!=this&&(this.m&64||q===this.G)?P(U,this):this,h=D.c(f,fl),k=D.c(f,no);f=D.c(f,wl);return K.A(c,fl,t(d)?d:h,be([no,t(e)?e:k,wl,f]))};ky.prototype.sb=q;ky.prototype.qb=function(a,b){return K.l(b,Hm,Hm.h(this))};oy.prototype.sb=q; +oy.prototype.qb=function(a,b){var c=null!=b&&(b.m&64||q===b.G)?P(U,b):b,d=D.c(c,oi);t(d)&&(ap(bp),d.B?d.B():d.call(null));return c};ry.prototype.sb=q;ry.prototype.qb=function(a,b){return K.l(b,Uj,Zk.h(this))};function RA(){return ig.l(function(a,b){return new R(null,2,5,T,[a,new gy(b,null,null,null)],null)},rg(function(a){return a+.5},.5),og(new R(null,2,5,T,[!1,!0],null)))}function SA(a){var b=Dy(RA());return K.l(K.l(a,ml,!0),Ol,b)} +function TA(a){a=null!=a&&(a.m&64||q===a.G)?P(U,a):a;var b=D.c(a,Ol);Tw(b);return K.l(K.l(a,ml,!0),Ol,null)}function UA(a){a=null!=a&&(a.m&64||q===a.G)?P(U,a):a;a=D.c(a,Ol);return t(a)?Je([a]):vi}my.prototype.sb=q; +my.prototype.qb=function(a,b){var c=null!=a&&(a.m&64||q===a.G)?P(U,a):a;D.c(c,jn);var d=null!=b&&(b.m&64||q===b.G)?P(U,b):b,e=D.c(d,jn);c=D.c(d,pi);var f=D.c(d,qi),h=null!=this&&(this.m&64||q===this.G)?P(U,this):this;h=D.c(h,jn);if(G.c(e,h))return d;d=K.A(d,jn,h,be([el,!0]));if(t(h))return t(c)&&(c.B?c.B():c.call(null)),SA(d);t(f)&&(f.B?f.B():f.call(null));return TA(d)};my.prototype.Fe=q;my.prototype.de=function(a,b){return UA(b)};py.prototype.sb=q; +py.prototype.qb=function(a,b){var c=K.l(b,V,V.h(this));c=null!=c&&(c.m&64||q===c.G)?P(U,c):c;var d=D.c(c,Ol);return t(d)?SA(TA(c)):c};py.prototype.Fe=q;py.prototype.de=function(a,b){return UA(b)};function VA(a){return t(a)?(a=ig.c(parseFloat,Fo(""+v.h(a),/:/)),a=ig.l(Ye,cf(a),rg(function(){return function(a){return 60*a}}(a),1)),P(Xe,a)):null} +function WA(a,b,c){t(a)?"string"===typeof a?t(0===a.indexOf("data:application/json;base64,"))?(b=a.substring(29).replace(RegExp("\\s","g"),""),b=JSON.parse(atob(b)),b=fj(b),b=new r(null,1,[V,new r(null,1,[il,b],null)],null)):t(0===a.indexOf("data:text/plain,"))?(a=a.substring(16),b=Ju(Ot(t(b)?b:80,t(c)?c:24),a),b=new r(null,1,[V,b],null)):b=t(0===a.indexOf("npt:"))?new r(null,1,[Zk,VA(a.substring(4))],null):null:b=new r(null,1,[V,new r(null,1,[il,a],null)],null):b=null;return b} +var XA=new r(null,2,[pl,new r(null,1,[On,!1],null),il,he],null); +function YA(a,b){var c=null!=b&&(b.m&64||q===b.G)?P(U,b):b,d=D.c(c,no),e=D.l(c,wk,"small"),f=D.l(c,Ak,1),h=D.c(c,Hk),k=D.c(c,fl),l=D.c(c,rl),p=D.l(c,cm,!1),m=D.l(c,gm,"asciinema"),u=D.c(c,qm),w=D.c(c,Bm),x=D.l(c,vm,!1),C=D.l(c,Em,!1),F=function(){var a=VA(h);return t(a)?a:0}();w=WA(w,k,d);var I=null!=w&&(w.m&64||q===w.G)?P(U,w):w;w=D.c(I,V);I=D.c(I,Zk);var M=t(I)?I:wb(w)&&0 tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/build/html/_static/contentui.css b/docs/build/html/_static/contentui.css new file mode 100644 index 00000000..80d42ef4 --- /dev/null +++ b/docs/build/html/_static/contentui.css @@ -0,0 +1,133 @@ +/* + * right column for sphinx_rtd_theme + */ +.clear { + clear: both; +} + +@media screen and (min-width: 1300px) { + .wy-nav-content { + max-width: none; + } + + .with-columns .wy-nav-content { + background: linear-gradient(90deg, #fcfcfc 52%, #eeeeee 52%); + } + + .with-columns .wy-nav-content .wy-breadcrumbs , + .with-columns .section > h1, + .with-columns .section > h2, + .with-columns .section > h3, + .with-columns footer { + width: 50%; + } + + .with-columns .section { + clear: both; + } + + .left-col.container { + float: left; + width: 50%; + margin-right: 4%; + } + + .right-col.container { + float: left; + width: 45%; + } + + /* + * tab selector fixed in top + */ + .with-columns .contenttab-selector.in-right-col { + display: block; + position: fixed; + top: 0; + right: 0; + width: calc(48% - 144px); + background: #444; + padding: 5px 10px; + } + +} + +/** + * + */ +.toggle-tab { + margin-bottom: 40px; +} + +.toggle-header { + display: block; + clear: both; + cursor: pointer; +} +.toggle-header p {display: inline; } +.toggle-header strong {color: #2980b9 } + +.toggle-header:after { + content: " ▼"; +} + +.toggle-header.open:after { + content: " ▲"; +} + +.toggle-content { + display: none; + margin-bottom: 20px; +} + +/* + * tab menu + */ +ul.contenttab-selector { + display:block; + list-style-type: none; + margin: 0 0 10px; + padding: 0; + line-height: normal; + overflow: auto; +} +ul.contenttab-selector li { + display: block; + cursor: pointer; + font-weight: bold; + margin: 0 5px 0 0; + padding: 5px 10px; + float: left; + background-color: #999; + color: #fff; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; + -khtml-border-radius: 5px; +} +.rst-content .section ul.contenttab-selector, +.rst-content .toctree-wrapper ul.contenttab-selector, +article ul.contenttab-selector{ + line-height: normal; + margin: 0 0 10px; +} +.rst-content .section ul.contenttab-selector li, +.rst-content .toctree-wrapper ul.contenttab-selector li, +article ul.contenttab-selector li{ + margin-left: 0; +} +ul.contenttab-selector li:hover { + background-color: #777; +} +ul.contenttab-selector li.selected { + background-color: #2980b9; +} +ul.contenttab-selector li.selected:hover { + background-color: #333; +} +.content-tabs { + margin: 10px 0 20px 0; +} +.tab-content { + clear: both; +} diff --git a/docs/build/html/_static/contentui.js b/docs/build/html/_static/contentui.js new file mode 100644 index 00000000..f9515cfd --- /dev/null +++ b/docs/build/html/_static/contentui.js @@ -0,0 +1,74 @@ + +$(function() { + /* + * Right column logic + */ + if ($(".right-col").length) { + $(".right-col").after('
'); + $(".right-col").parents('body').addClass('with-columns'); + } + + /** + * Toggle logic + */ + $('.toggle-content').hide() + $('.toggle-header').click(function () { + $(this).toggleClass("open"); + $(this).next('.toggle-content').toggle('400'); + }) + + /** + * Dynamic multiple content block. + */ + var top_sel = {} + + $('div.content-tabs').each(function() { + var contenttab_sel = $('