Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add warning if digital carrier exceeds Nyquist frequency in pulse -> signal conversion #242

Merged
merged 6 commits into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions qiskit_dynamics/pulse/pulse_to_signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from typing import Callable, Dict, List, Optional
import functools
from warnings import warn

import numpy as np
import sympy as sym
Expand All @@ -40,6 +41,12 @@
from qiskit_dynamics.array import Array
from qiskit_dynamics.signals import DiscreteSignal

try:
import jax
import jax.numpy as jnp
except ImportError:
pass


class InstructionToSignals:
"""Converts pulse instructions to signals to be used in models.
Expand Down Expand Up @@ -133,6 +140,9 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]:
Similarly to ``ShiftFrequency``, the shift rule for :math:`\phi_a` is defined to maintain
carrier wave continuity.

If, at any sample point :math:`k`, :math:`\Delta\nu(k)` is larger than the Nyquist sampling
rate given by ``dt``, a warning will be raised.

Args:
schedule: The schedule to represent in terms of signals. Instances of
:class:`~qiskit.pulse.ScheduleBlock` must first be converted to
Expand Down Expand Up @@ -188,14 +198,15 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]:
if isinstance(inst, ShiftPhase):
phases[chan] += inst.phase

if isinstance(inst, SetPhase):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this related to the warning?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not related to the warning: I saw that the SetPhase and ShiftPhase handling were not grouped together, so I moved this block up.

phases[chan] = inst.phase

if isinstance(inst, ShiftFrequency):
frequency_shifts[chan] = frequency_shifts[chan] + Array(inst.frequency)
phase_accumulations[chan] = (
phase_accumulations[chan] - inst.frequency * start_sample * self._dt
)

if isinstance(inst, SetPhase):
phases[chan] = inst.phase
_nyquist_warn(frequency_shifts[chan], self._dt, chan)

if isinstance(inst, SetFrequency):
phase_accumulations[chan] = phase_accumulations[chan] - (
Expand All @@ -204,6 +215,7 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]:
* self._dt
)
frequency_shifts[chan] = inst.frequency - signals[chan].carrier_freq
_nyquist_warn(frequency_shifts[chan], self._dt, chan)

# ensure all signals have the same number of samples
max_duration = 0
Expand Down Expand Up @@ -367,3 +379,17 @@ def _lru_cache_expr(expr: sym.Expr, backend) -> Callable:
continue
params.append(param)
return sym.lambdify(params, expr, modules=backend)


def _nyquist_warn(frequency_shift: Array, dt: float, channel: str):
"""Raise a warning if the frequency shift is above the Nyquist frequency given by ``dt``."""

if (
Array(frequency_shift).backend != "jax" or not isinstance(jnp.array(0), jax.core.Tracer)
) and np.abs(frequency_shift) > 0.5 / dt:
warn(
"Due to SetFrequency and ShiftFrequency instructions, the digital carrier frequency "
f"of channel {channel} is larger than the Nyquist frequency of the envelope sample "
"size dt. As shifts of the frequency from the analog frequency are handled digitally, "
"this will result in aliasing effects."
)
13 changes: 13 additions & 0 deletions test/dynamics/pulse/test_pulse_to_signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ def setUp(self):
# Typical length of samples in units of dt in IBM real backends is 1/4.5.
self._dt = 1 / 4.5

def test_nyquist_warning(self):
"""Test Nyquist warning is raised."""
converter = InstructionToSignals(dt=1, carriers={"d0": 0.0})

sched = Schedule(name="Schedule")
sched += pulse.SetFrequency(1.0, pulse.DriveChannel(0))
sched += pulse.Play(
pulse.Drag(duration=20, amp=0.5, sigma=4, beta=0.5), pulse.DriveChannel(0)
)

with self.assertWarnsRegex(Warning, "Due to SetFrequency and ShiftFrequency"):
converter.get_signals(sched)

def test_pulse_to_signals(self):
"""Generic test."""

Expand Down