Create & Inject Pulses

Show how we can inject a pulse into a GREENBURST filterbank.

[1]:
import tempfile
from urllib import request
from typing import Union

import matplotlib.pyplot as plt
import numpy as np
from jess.dispersion import dedisperse
from jess.fitters import median_fitter
from scipy.stats import median_abs_deviation
from will import create, inject
from your import Your
[2]:
def show_dynamic(
    dynamic_spectra: np.ndarray,
    title: Union[str, None] = None,
    save: Union[bool, None] = False,
) -> None:
    """
    Show a dynamic spectra by first flattening it
    in frequency. Do this by getting the medians of
    each channel and then run a median filter along the
    bandpass.

    Then set the limits of the imshow so we get good detail
    for the majority of the data.

    Args:
        dynmaic_spectra - the dynamic spectra to plot

        title - Title of plot

        save - Save the plot as `title` + `.png`
    """
    spectra_mads = median_fitter(np.median(dynamic_spectra, axis=0))
    flat = dynamic_spectra - spectra_mads
    std = median_abs_deviation(flat, axis=None)
    med = np.median(flat)
    plt.figure(figsize=(20, 10))
    plt.imshow(flat.T, vmin=med - 3 * std, vmax=med + 6 * std, aspect="auto")
    plt.xlabel("Time Sample #", size=20)
    plt.ylabel("Channel #", size=20)
    plt.colorbar()
    plt.tight_layout()
    if title is not None:
        plt.title(title, size=28)
    if save:
        plt.savefig(title.replace(" ", "_") + ".png", dpi=75, bbox_inches="tight")

Get a pulse search mode file

[3]:
# Get a 33MB filbterbank from zenodo, put it in
# a temp directory
temp_dir = tempfile.TemporaryDirectory()
empty_fil = temp_dir.name + "/empty_example.fil"
request.urlretrieve(
    "https://zenodo.org/record/5860557/files/empty_example.fil", empty_fil
)
[3]:
('/tmp/tmpuzymcx9r/empty_example.fil',
 <http.client.HTTPMessage at 0x7f4406a43280>)
[4]:
yr_obj = Your(empty_fil)
dynamic_spectra = yr_obj.get_data(0, 8192)
[5]:
# Show the base dynamic spectra
show_dynamic(dynamic_spectra, title="Dynamic Spectra")
../_images/examples_inject_pulse_7_0.png

Find a bandpass to scale the frequency structure

[6]:
plt.title("Channels Standard Devations")
plt.xlabel("Channel #")
plt.ylabel("Standard Devation")
medians = np.median(dynamic_spectra, axis=0)
plt.plot(medians, label="Channel medians")
filter_weights = create.filter_weights(dynamic_spectra)
# mutlipy by 10 to make it easier to see
plt.plot(10 * filter_weights, label="Filter weights")
plt.legend()
[6]:
<matplotlib.legend.Legend at 0x7f440437d3d0>
../_images/examples_inject_pulse_9_1.png

Create the pulse

[7]:
dm = 200
# create the pulse object
pulse_obj = create.SimpleGaussPulse(
    sigma_time=0.001,
    sigma_freq=350,
    center_freq=yr_obj.your_header.center_freq,
    dm=dm,
    tau=20,
    phi=np.pi / 3,
    spectral_index_alpha=0,
    chan_freqs=yr_obj.chan_freqs,
    tsamp=yr_obj.your_header.tsamp,
    nscint=0,
    bandpass=filter_weights,
)
# pulse with 3e5 samples
pulse = pulse_obj.sample_pulse(nsamp=int(3e5))
[8]:
show_dynamic(pulse, title=f"{dm} DM Pulse")
../_images/examples_inject_pulse_12_0.png

We can see that there is not pulse in areas with zero variance (Like the cavity filter).

Inject the pulse

[9]:
dynamic_spectra_w_pulse = inject.inject_constant_into_file(
    yr_input=yr_obj,
    pulse=pulse,
    start=yr_obj.your_header.nspectra // 2 - 1024,
    gulp=yr_obj.your_header.nspectra,
)
[10]:
show_dynamic(dynamic_spectra_w_pulse, title=f"Dynamic Spectra + {dm} dm Pulse")
../_images/examples_inject_pulse_16_0.png

The pulse added to the dynamic spectra. We see that the pulse doesn’t exist where the channel variances are zero.

Pulse with complex frequency-time structure

This method samples all of the channels independently. It can accommodate changes in frequency and time, allowing for drifts, etc. The disadvantage that this is computationally more expansive making it ~10 times slower

[11]:
dm_1 = 155
pulse_obj_complex = create.GaussPulse(
    relative_intensities=(1, 0.8, 0.8, 0.8),
    sigma_times=(0.005, 0.001, 0.001, 0.006),
    sigma_freqs=(150, 120, 120, 90),
    pulse_thetas=(0, 0, 0, -np.pi / 60),
    center_freqs=(1500, 1400, 1350, 1200),
    dm=dm_1,
    tau=25,
    offsets=(0, 0.01536, 0.02304, 0.03968),  # all from start of window
    chan_freqs=yr_obj.chan_freqs,
    tsamp=0.000256,
    spectral_index_alpha=0,
    nscint=2,
    phi=0,
    bandpass=filter_weights,
)
# pulse with 3e5 samples
pulse_complex = pulse_obj_complex.sample_pulse(nsamp=int(3e5))
[12]:
show_dynamic(pulse_complex, title=f"{dm_1} DM Pulse")
../_images/examples_inject_pulse_21_0.png
[13]:
dynamic_spectra_w_complex_pulse = inject.inject_constant_into_file(
    yr_input=yr_obj,
    pulse=pulse_complex,
    start=yr_obj.your_header.nspectra // 2 - pulse_obj_complex.pulse_center,
    gulp=yr_obj.your_header.nspectra,
)
[14]:
show_dynamic(
    dynamic_spectra_w_complex_pulse, title=f"Dynamic Spectra + {dm_1} dm Pulse"
)
../_images/examples_inject_pulse_23_0.png
[15]:
show_dynamic(
    dedisperse(
        dynamic_spectra_w_complex_pulse,
        dm=dm_1,
        chan_freqs=yr_obj.chan_freqs,
        tsamp=yr_obj.your_header.tsamp,
    )[3072:-3072],
    title="Multi-Component Pulse",
    save=True,
)
../_images/examples_inject_pulse_24_0.png

The dedispersed pulse showing the downward drift. The final components is angled relative the the first three.

Periodic pulses w/ constant Fluence

[16]:
dynamic_spectra_w_pulse_perodic = inject.inject_constant_into_file(
    yr_input=yr_obj,
    pulse=pulse,
    start=0,
    period=1234,
    gulp=yr_obj.your_header.nspectra,
)
[17]:
show_dynamic(dynamic_spectra_w_pulse_perodic, title="Dynamic Spectra + Pulsar")
../_images/examples_inject_pulse_28_0.png

Now we have a pulsar with a period of 1234 samples

Variable Fluence

[18]:
# Calculate the number of pulses needed
n_pulses = inject.num_pulses(period=1234, nspectra=yr_obj.your_header.nspectra)
print(f"Number of pulses: {n_pulses}")
Number of pulses: 7
[19]:
np.random.seed(2021)
pulse_energies = np.random.normal(loc=3e5, scale=2e5, size=n_pulses)
# pulse engergies must be an interger number of samples
np.around(pulse_energies, out=pulse_energies)
# must be positive
pulse_energies = pulse_energies.astype(int)
np.clip(pulse_energies, 0, None, out=pulse_energies)
print(f"{pulse_energies=}")
pulse_energies=array([597722, 435202, 216310, 138696, 411175, 158899, 526172])
[20]:
dynamic_spectra_w_pulse_perodic_diff_energies = inject.inject_distribution_into_file(
    yr_input=yr_obj,
    pulse_obj=pulse_obj,
    pulse_counts=pulse_energies,
    start=0,
    period=1234,
    gulp=yr_obj.your_header.nspectra,
)
[21]:
show_dynamic(
    dynamic_spectra_w_pulse_perodic_diff_energies,
    title="Pulsar with Varying Intensities",
    save=True,
)
../_images/examples_inject_pulse_34_0.png

Now we have pulse-to-pulse variation of intensities.

[ ]: