spectral module

FFT-based measurements of spectral content.

Detailed information is provided in the documentation for each class.

Spectral

Use the hardware in spectral mode.

SpecMode

Specify the spectral measurement mode.

SpecResult

Container for measurement results in spectral mode.

SpecReceiver

A receiver of spectral data running in the background.


Spectral class

class presto.spectral.Spectral(*, nr_inputs=1, ext_ref_clk=False, force_reload=False, dry_run=False, address=None, port=None, adc_mode=AdcMode.Mixed, adc_fsample=None, dac_mode=DacMode.Mixed, dac_fsample=None, force_config=False)

Use the hardware in spectral mode.

Warning

Create only one instance at a time. This class is designed to be instantiated with Python’s with statement, see Examples section.

Parameters:
  • nr_inputs (int) – number of input ports that are active simultaneously. The spectral content (FFT or PSD) will be measured in parallel from this many ports.

  • ext_ref_clk (Union[None, bool, int, float]) – if None or False use internal reference clock; if True use external reference clock (10 MHz); if float or int use external reference clock at ext_ref_clk Hz

  • force_reload (bool) – if True re-configure clock and reload firmware even if there’s no change from the previous settings.

  • dry_run (bool) – if True don’t connect to hardware, for testing only.

  • address (Optional[str]) – IP address or hostname of the hardware. If None, use factory default "192.168.42.50".

  • port (Optional[int]) – port number of the server running on the hardware. If None, use factory default 7878.

  • adc_mode (AdcMode or list) – must be AdcMode.Mixed (default), using the digital mixers for downconversion. Direct mode is not supported in spectral mode.

  • adc_fsample (AdcFSample or list) – configure the inputs sampling rate. If None (default), choose optimal rate automatically. See Notes.

  • dac_mode (DacMode or list) – must be DacMode.Mixed (default) or another of the mixed variants, using the digital mixers for upconversion. Direct mode is not supported in spectral mode. See Notes.

  • dac_fsample (DacFSample or list) – configure the outputs sampling rate. If None (default), choose optimal rate automatically. See Notes.

  • force_config (bool) – if True skip checks on DacModes not recommended for high DAC sampling rates.

Raises:

ValueError – if adc_mode or dac_mode are not valid Converter modes; if adc_fsample or dac_fsample are not valid Converter rates.

Notes

In all the Examples, it is assumed that the imports import numpy as np and from presto.spectral import Spectral, SpecMode have been performed, and that this class has been instantiated in the form spec = Spectral(), or, much much better, using the with Spectral() as spec construct as below.

For an introduction to adc_mode and dac_mode, see Direct and Mixed mode. For valid values of adc_mode and dac_mode, see Converter modes. For valid values of adc_fsample and dac_fsample, see Converter rates. For advanced configuration, e.g. different converter rates on different ports, see Advanced tile configuration.

Examples

Drive two continuous-wave signals from output port 1 and 2, and measure averaged FFT from input port 1. Plot the results.

>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>>
>>> IN_PORT = 1
>>> OUT_PORT_1 = 1
>>> OUT_PORT_2 = 2
>>>
>>> with Spectral(address="192.168.20.10") as spec:
>>>     # configure 1.5 GHz carrier for up- and down-conversion
>>>     spec.hardware.configure_mixer(
>>>         freq=1.5e9,
>>>         in_ports=IN_PORT,
>>>         out_ports=[OUT_PORT_1, OUT_PORT_2],
>>>     )
>>>
>>>     # 1 MHz resolution in FFT
>>>     period = spec.tune_period(1.0e-6)
>>>
>>>     # on the first output port,
>>>     # drive two tones at 110 and 112 MHz from the carrier
>>>     # with same phase but different amplitude
>>>     # one below the carrier (lower sideband) and one above (upper sideband)
>>>     f1 = [-110.0e6, +120.0e6]
>>>     a1 = [0.1, 0.2]
>>>     p1 = [0.0, 0.0]
>>>     spec.output_multicos(OUT_PORT_1, period, f1, a1, p1)
>>>
>>>     # on the second output port,
>>>     # drive one tone on port 2 at the carrier frequency
>>>     f2 = 0.0
>>>     a2 = 0.5
>>>     p2 = 0.0
>>>     spec.output_multicos(OUT_PORT_2, period, f2, a2, p2)
>>>
>>>     # measure FFT data, averaged 1000 times
>>>     res = spec.measure(SpecMode.FftAvg, IN_PORT, 1000, period)
>>>
>>> # plot data
>>>
>>> # frequencies in MHz
>>> x = np.fft.fftshift(res.freqs) / 1.0e6
>>> # data in dBFS
>>> y = np.fft.fftshift(res.to_db()[0])
>>>
>>> fig, ax = plt.subplots()
>>> ax.plot(x, y)
>>> ax.set_xlabel("Offset frequency [MHz]")
>>> ax.set_ylabel("Amplitude [dBFS]")
>>> ax.set_title("Center frequency 1.5 GHz")
>>> ax.grid()
>>> plt.show()

Drive two random signals from output ports 1 and 2 into input ports 1 and 2, one delayed 100 ns from the other, and measure the correlation of the two.

>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>>
>>> IN_PORTS = [1, 2]
>>> OUT_PORTS = [1, 2]
>>>
>>> with Spectral(
>>>     nr_inputs=len(IN_PORTS),
>>>     address="192.168.20.10",
>>> ) as spec:
>>>     # configure 1.5 GHz carrier for up- and down-conversion
>>>     spec.hardware.configure_mixer(1.5e9, in_ports=IN_PORTS, out_ports=OUT_PORTS)
>>>
>>>     # 1 MHz resolution in FFT
>>>     period = spec.tune_period(1.0e-6)
>>>
>>>     # generate random data and output from first port
>>>     def random_complex(ns):
>>>         rng = np.random.default_rng()
>>>         real = 2.0 * rng.random(ns) - 1.0
>>>         imag = 2.0 * rng.random(ns) - 1.0
>>>         return real + 1j * imag
>>>
>>>     duration = 10 * period
>>>     nr_samples = int(round(duration * spec.get_fs("dac")))
>>>     data1 = 0.1 * random_complex(nr_samples)
>>>     spec.output_waveform(OUT_PORTS[0], data1)
>>>
>>>     # output same data on second port,
>>>     # but delayed by 100 samples (100 ns)
>>>     data2 = np.roll(data1, 100)
>>>     spec.output_waveform(OUT_PORTS[1], data2)
>>>
>>>     # measure cross power spectral density (CPSD) data
>>>     # averaged 10 times
>>>     res = spec.measure(SpecMode.Cpsd, IN_PORTS, 10, period)
>>>
>>> # calculate correlation from measured CPSD
>>> # and plot results
>>>
>>> x, y = res.to_correlation()
>>> fig, ax = plt.subplots()
>>> ax.plot(1e9 * x, np.real(y[0]))
>>> ax.plot(1e9 * x, np.imag(y[0]))
>>> ax.set_xlabel("Time lag [ns]")
>>> ax.set_ylabel("Correlation [arb. units]")
>>> ax.set_title("Center frequency 1.5 GHz")
>>> ax.grid()
>>> plt.show()
clear_outputs()

Clear the waveforms set up on all output ports.

close()

Gracefully disconnect from the hardware.

Call this method if the class was instantiated without a with statement. This method is called automatically when exiting a with block and before the object is destructed.

fftfreq(period)

Return the fast Fourier transform sample frequencies.

The returned array contains the frequency bin centers in hertz (cycles second), with zero at the start. This method is similar to numpy.fft.fftfreq(), but with different arguments.

Parameters:

period (float) – the FFT measurement period, or window length, in seconds.

Return type:

ndarray[tuple[int, ...], dtype[floating]]

Note

The period is assumed to be tuned. See tune_period() for details.

get_fs(which)

Get sampling frequency for which converter in Hz.

Note

This is the sampling rate in the intermediate-frequency (IF) domain, and not the (higher) sampling rate in the radio-frequency (RF) domain.

Parameters:

which (str) – "adc" for input and "dac" for output.

Raises:

ValueError – if which is unknown.

Return type:

float

get_nr_ports()

The number of available input/output ports in the hardware.

Return type:

int

hardware: Hardware

interface to hardware features common to all modes of operation

measure(mode, input_ports, nr_meas, period, auto_sync=True, quiet=False)

Measure the spectral content of the signal at input_ports.

Depending on mode, measure the averaged fast Fourier transform (FFT), multiple FFT windows, the power spectral density (PSD), or the cross PSD (CPSD).

Parameters:
  • mode (SpecMode) – select measurement mode, see SpecMode for details

  • input_ports (int or list) – input port, can be a list of up to 4 ports

  • nr_meas (int) – number of individual FFT/PSD windows to measure or to average, depending on mode

  • period (float) – requested measurement period (integration time) for the FFT/PSD, corresponding to the inverse of the frequency resolution. See Notes section below for details

  • auto_sync (bool) – if True (default), trigger signal generation and acquisition internally. Set to False to synchronize to an external source, e.g. a Metronomo unit.

  • quiet (bool) – set to True to inhibit printing timing information

Return type:

SpecResult

Returns:

res - a container for the measurement results, see SpecResult for documentation

Notes

The period is assumed to be tuned, see tune_period() for details. If period is not tuned, the backend will choose the available period that is closest to the user-requested value.

The shape and data type of the returned res.data depends on the measurement mode, see SpecResult.data for documentation.

Raises:

Examples

See documentation for the Spectral class for examples.

property nr_inputs

The current number of interleaved input ports.

This value is read only. To change it, create a new instance of the Spectral class and pass the desired number of input ports as the nr_inputs parameter.

output_multicos(output_ports, period, frequencies, amplitudes, phases=None)

Set up a multi-tone waveform on one or more output ports.

Parameters:
  • output_ports (int or list) – output port, can be a list of up to 4 ports

  • period (float) – the repetition period of the output waveform, in seconds

  • frequencies (list of float) – frequencies of the individual tones that comprise the waveform, in hertz

  • amplitudes (list of float) – amplitudes of the individual tones that comprise the waveform, in full-scale units between -1.0 and +1.0 FS

  • phases (list of float, optional) – phases of the individual tones that comprise the waveform, in radians. If None (default), all tones have zero phase

Notes

Positive frequencies are up-coverted to the upper sideband (USB) by the digital up-conversion mixer, and negative frequencies are up-coverted to the lower sideband (LSB).

If sum(abs(amplitudes)) > 1.0, it is possible for the tones in the waveform to interfere constructively and cause an overflow (clipping), resulting in a heavily-distorted output waveform. To prevent this effect, ensure that sum(abs(amplitudes)) <= 1.0, or adjust the phases of the tones to prevent isolated peaks in the resulting waveform.

Raises:
output_waveform(output_ports, data)

Set up an arbitrary waveform on one or more output ports.

Parameters:
  • output_ports (int or list) – output port, can be a list of up to 4 ports

  • data (list of complex) – an array of complex-valued arbitrary data points

Notes

The real part of data is fed to the I port of the digital up-conversion mixer, while the imaginary part of data is fed to the Q port.

Raises:
setup_delay(*, pre_delay=0.0, start_delay=0.0, end_delay=0.0)

Configure FFT measurement delays.

Parameters:
  • pre_delay (float) – delay between the beginning of the experiment and the first input window

  • start_delay (float) – delay between the start of each input window and the start of the FFT window

  • end_delay (float) – delay between the end of each FFT window and the end of the input window

Notes

pre_delay adds a delay at the very beginning of the measurement sequence, between the start of the output waveform (DAC) and the first input window (ADC). start_delay and end_delay add some “dead time” within each input window. Data measured during this dead time is discarded and not included in the FFT calculation:

../_images/spec_delays_dark.svg ../_images/spec_delays_light.svg
stream(input_ports, period, indexes=None, freqs=None, auto_sync=True, quiet=False)

Measure continuously the spectral content of the signal at input_ports.

This will measure the fast Fourier transform (FFT) of the signal.

Parameters:
  • input_ports (int or list) – input port, can be a list of up to 4 ports

  • period (float) – requested measurement period (integration time) for the FFT, corresponding to the inverse of the frequency resolution. See Notes section below for details

  • indexes (list of int, optional) – which FFT frequency components (bin indexes) should be measured. If None (default), all of them

  • freqs (list of float, optional) – which FFT frequency components (bin indexes) should be measured. If None (default), all of them

  • auto_sync (bool) – if True (default), trigger signal generation and acquisition internally. Set to False to synchronize to an external source, e.g. a Metronomo unit.

  • quiet (bool) – set to True to inhibit printing timing information

Return type:

SpecReceiver

Returns:

recv - a handle to the spectral receiver, see SpecReceiver for documentation

Notes

The period is assumed to be tuned, see tune_period() for details. If period is not tuned, the backend will choose the available period that is closest to the user-requested value.

By default, all measured FFT frequencies will be streamed, see fftfreq(). Optional arguments indexes and freqs can be used to stream only a subset of all the measured frequencies and therefore save bandwidth and memory.

Use freqs to select components based on their value in hertz (Hz). If the requested value is not a valid FFT frequency, the closest valid value will be selected. Use indexes to select components based on their index in the array returned by fftfreq(). It is an error to specify both freqs and indexes.

Specifying frequency components with indexes or freqs will ensure that at least those components are streamed. The backend might stream more components than requested, see recv.freqs for a list of all the frequencies that are being streamed.

Raises:

Examples

See documentation for the SpecReceiver class for examples.

tune_period(period, *, strategy=None)

Tune the provided measurement period to a value supported by the hardware.

Only a limited number of measurement periods are supported by the hardware. The default tuning strategy will choose the available period that is closest to the user-requested value.

Parameters:
  • period (float) – the desired FFT measurement period is seconds, equal to the inverse of the desired frequency resolution in hertz

  • strategy (Optional[TuningStrategy]) – optional tuning strategy

Return type:

float

Returns:

the tuned period


SpecMode class

class presto.spectral.SpecMode(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)

Specify the spectral measurement mode.

Used as parameter to Spectral.measure().

Cpsd

Measure the cross power spectral density (CPSD).

FftAvg

Measure the averaged fast Fourier transform (FFT).

FftStream

Measure multiple (stacked) fast Fourier transform (FFT) windows.

Psd

Measure the averaged power spectral density (PSD).


SpecResult class

class presto.spectral.SpecResult(*, mode, freqs, data, input_ports)

Container for measurement results in spectral mode.

Used as return type by Spectral.measure().

Warning

Do not instantiate this class directly! Call Spectral.measure() to obtain a valid instance.

data: ndarray[tuple[int, ...], dtype[TypeVar(SR_T, bound= Union[float64, complex64, complex128])]]

Array of measured spectral data (the y axis).

The shape and data type depend on the parameters mode, input_ports and nr_meas to Spectral.measure():

Mode

Shape

Type

SpecMode.FftStream

(nr_meas, nr_ports, nr_freqs)

complex64

SpecMode.FftAvg

(nr_ports, nr_freqs)

complex128

SpecMode.Psd

(nr_ports, nr_freqs)

float64

SpecMode.Cpsd

(nr_ports // 2, nr_freqs)

complex128

where nr_meas is the number of independent measurements, nr_ports = len(input_ports) is the number of measured input ports, and nr_freqs = len(freqs) is the number of measured frequency components.

freqs: ndarray[tuple[int, ...], dtype[floating]]

Array of measured frequencies (the x axis)

input_ports: List[int]

Input ports the data was measured from.

mode: SpecMode

Measurement mode.

to_correlation()

Convenience function to convert measured PSD/CPSD data to auto/cross correlation.

Return type:

Tuple[ndarray[tuple[int, ...], dtype[float64]], ndarray[tuple[int, ...], dtype[complex128]]]

Returns:

a tuple of (lags, corr) where
  • lags: array of time lags of length data.shape[-1]

  • corr: array of auto/cross correlation with same shape as data

Raises:

TypeError – if the measurement mode is not SpecMode.Psd or SpecMode.Cpsd.

to_db()

Convenience function to convert measured spectral data to decibel (dB) units.

Return type:

ndarray[tuple[int, ...], dtype[floating]]

Returns:

an array of measured data in decibel (dB)

Notes

to_db(data) is equivalent to 20.0 * np.log10(np.abs(data)) in FFT mode, and to 10.0 * np.log10(np.abs(data)) in PSD and CPSD mode.


SpecReceiver class

class presto.spectral.SpecReceiver(*, sock, freqs, input_ports, nr_inputs, bytes_per_meas, map, buf_size=1073741824)

A receiver of spectral data running in the background.

Used as return type by Spectral.stream().

Warning

Do not instantiate this class directly! Call Spectral.stream() to obtain a valid instance.

Examples

>>> with Spectral(
>>>     address=PRESTOs_IP_ADDRESS,
>>> ) as spec:
>>>     # configure 1.5 GHz carrier for up- and down-conversion
>>>     spec.hardware.configure_mixer(
>>>         freq=1.5e9,
>>>         in_ports=1,
>>>         out_ports=1,
>>>     )
>>>
>>>     # 1 MHz resolution in FFT
>>>     period = spec.tune_period(1.0e-6)
>>>
>>>     # drive two tones at 110 and 112 MHz
>>>     # with same phase but different amplitude
>>>     # on output port 1
>>>     freq = [110.0e6, 112.0e6]
>>>     amp = [0.1, 0.2]
>>>     phase = [0.0, 0.0]
>>>     spec.output_multicos(1, period, freq, amp, phase)
>>>
>>>     # continuously receive FFT measurements
>>>     # on input port 1
>>>     # in chunks of 100 measurements
>>>     with spec.stream(1, period) as recv:
>>>         data = recv.get_last(100)
close()

Gracefully terminate the stream receiver.

Call this method if the class was instantiated without a with statement. This method is called automatically when exiting a with block and before the object is destructed.

freqs

Array of measured frequencies (the x axis)

get(n)

Return n measurements received since the last call to this method.

This method might block the current thread until n new measurements are available.

Return type:

TypeVar(SR_T)

Notes

This method keeps track of what measurements have already been returned, and always returns contiguous measurements (without gaps). If the requested measurements are available in the local buffer, they are returned immediately. Otherwise, the thread will block until all the requested measurements have been received.

That means that calling get(n) twice results in the same data as calling get(2 * n) once. It also means that the data returned might have been already buffered locally and not be the latest received from the hardware.

See See also section below for getter methods with different semantics.

See also

  • get_new(): always receive new data from the hardware

  • get_last(): return latest data from local buffer

get_last(n)

Return the last n measurements available in the local buffer.

This method might block the current thread, if fewer than n measurements are available in the local buffer.

Return type:

TypeVar(SR_T)

Notes

This method returns the most up-to-date data available in the local buffer, regardless of whether this data has already been returned to the user.

That means that calling get_last(n) twice is not equivalent to calling get_last(2 * n) once: in the former case, the two data sets might overlap.

See See also section below for getter methods with different semantics.

See also

  • get(): keep track of previously returned data

  • get_new(): always receive new data from the hardware

get_new(n)

Receive n new measurements.

This method blocks the current thread until n new measurements are received.

Return type:

TypeVar(SR_T)

Notes

This method always waits for n new measurements to be received from the hardware, without regard for what measurements have already been received in the background but not yet returned to the user.

That means that calling get_new(n) twice is not equivalent to calling get_new(2 * n) once: in the former case an arbitrary time gap might be present between the two data sets. Nevertheless, the two data sets will be distinct and non overlapping.

See See also section below for getter methods with different semantics.

See also

  • get(): keep track of previously returned data

  • get_last(): return latest data from local buffer

property nr_inputs

The number of interleaved input ports.

nr_meas_received()

The number of measurements received so far from the stream.

Note that not all the received measurements might be available in the internal buffer.

Return type:

int


Other

class presto.spectral.TuningStrategy(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)

Specify an optional tuning strategy for the FFT period.

Used as parameter to Spectral.tune_period().

AtLeast

Choose the period supported by the hardware that is greater than or equal to the user-requested value. If the requested value is greater than the maximum-supported period, choose the latter.

AtMost

Choose the period supported by the hardware that is less than or equal to the user-requested value. If the requested value is smaller than the minimum-supported period, choose the latter.

Closest

DEFAULT – Choose the period supported by the hardware that is closest to the user-requested value. This is the default strategy.