A tutorial for PRBS generation

2 minute read

Published:

This script was used to automatically generate 24- and 48-hour binary signals for our paper Design of a short perturbation method for on-site estimation of a building envelope thermal performance (2022).

The signals are generated by a maximum length sequence generator, conveniently available in the scipy library.

The objective here is to enter physically relatable values to building 24-hour signals.

from scipy.signal import max_len_seq
import matplotlib.pyplot as plt
import numpy as np
def generate_signal(char_time, duration=24, timestep=5, ax=None):
    char_time_MLS = [char_time[0], char_time[0] - char_time[1], char_time[1] - char_time[2]]
    duration_MLS = duration * 60 / timestep
    taps = [int(time * 60 / timestep) for time in char_time_MLS]
    nbits = max(taps)
    signal, _ = max_len_seq(nbits=nbits,
                        taps=taps,
                        length=duration_MLS)
    return signal

Signal generation step-by-step

The signal is built with a 5-minute timestep.

timestep = 5  #min

The variable char_time defines 3 step durations (or 5 or 7 or any odd number), supposed to be in hours.

Here, we expect a first 14-hour step, followed by a 7-hour step and a 1.5-hour step.

char_time = [14,7,1.5]  #hours
char_time_MLS = [char_time[0], char_time[0] - char_time[1], char_time[1] - char_time[2]]

The signal will be generated here to cover 24 hours.

duration = 24  # hours
duration_MLS = duration * 60 / timestep

Variables taps and nbits are inputs for the maximum length sequance generator.

taps = [int(time * 60 / timestep) for time in char_time_MLS]
nbits = max(taps)

And finally, the signal is generated. Only the first output serves here.

signal, _ = max_len_seq(nbits=nbits,
                        taps=taps,
                        length=duration_MLS)

Let us take a look at the generated signal. It has either 1 (On) or 0 (Off) values.

As planned, the first On step has a 14-hour duration, followed by a 7-hour Off step, and again a 1.5h On step.

plt.figure(figsize=(8,3))
plt.plot(signal)
plt.xticks([0, 14 * 12, 21 * 12, 22.5 * 12, 24 * 12], ['start', '14h', '21h', '22.5h', '24h'])
plt.yticks([0,1])
plt.xlim(xmin=0, xmax=duration_MLS);

Sometimes, the char_time duration do not end up as On/Off/On durations, such as this example :

plt.figure(figsize=(8,3))
char_time = [10,8,1]
plt.plot(generate_signal(char_time))
plt.xticks([0,14*6,(14+8)*12,(14+8+1)*18,12*24], ['0', '14', '14+8', '14+8+1', '24'])
plt.yticks([0,1])
plt.xlim(xmin=0, xmax=duration_MLS);

Nevertheless, this code trick generates physically relatable signals, with variability in the hour order of magnitude.

Nota : with this trick, it matters that char_time is given in descending order.

A multitude of signal shapes

fig, axes = plt.subplots(5,5, figsize=(10,6), sharex=True, sharey=True)

for ax in axes.flat:
    random_char_times = np.sort(np.random.random(size=3)*22)[::-1]
    ax.plot(generate_signal(random_char_times))
    ax.set_yticks([0,1])
    ax.set_xticks([])