From e0e75e2d663d653ff016974035ecb38663fb3a43 Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Mon, 1 Oct 2018 09:54:12 -0400 Subject: [PATCH 01/28] Update remote simulator test for backend IBMQ/Aer changes --- test/python/test_remote_simulator.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/python/test_remote_simulator.py b/test/python/test_remote_simulator.py index 4ffe6af0566d..427a4c4d0146 100644 --- a/test/python/test_remote_simulator.py +++ b/test/python/test_remote_simulator.py @@ -32,13 +32,12 @@ def setUp(self): self._testing_device = os.getenv('IBMQ_QOBJ_DEVICE', None) self._qe_token = os.getenv('IBMQ_TOKEN', None) self._qe_url = os.getenv('IBMQ_QOBJ_URL') - if not self._testing_device or not self._qe_token or not self._qe_url: self.skipTest("No credentials or testing device available for " "testing Qobj capabilities.") IBMQ.use_account(self._qe_token, self._qe_url) - self._local_backend = Aer.get_backend('local_qasm_simulator') + self._local_backend = Aer.get_backend('qasm_simulator_py') self._remote_backend = IBMQ.get_backend(self._testing_device) self.log.info('Remote backend: %s', self._remote_backend.name()) self.log.info('Local backend: %s', self._local_backend.name()) From 9d6144d96ea3754b3c2cd49c8bac3233ba4d7aee Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Thu, 28 Mar 2019 14:40:47 -0400 Subject: [PATCH 02/28] Added test module and case for samplers. --- test/python/pulse/test_samplers.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 test/python/pulse/test_samplers.py diff --git a/test/python/pulse/test_samplers.py b/test/python/pulse/test_samplers.py new file mode 100644 index 000000000000..0458ed90fb8f --- /dev/null +++ b/test/python/pulse/test_samplers.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +# pylint: disable=invalid-name,unexpected-keyword-arg + +"""Tests pulse function samplers.""" + +import unittest +import numpy as np + +from qiskit.test import QiskitTestCase + + +class TestSampler(QiskitTestCase): + pass From 070fc7d07d4da0683e43d09aec3b04fb96c04af4 Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Thu, 28 Mar 2019 17:46:31 -0400 Subject: [PATCH 03/28] Added base sampling decorator along with left, right, and midpoint samplers --- qiskit/pulse/samplers.py | 106 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 qiskit/pulse/samplers.py diff --git a/qiskit/pulse/samplers.py b/qiskit/pulse/samplers.py new file mode 100644 index 000000000000..6bc8496a389d --- /dev/null +++ b/qiskit/pulse/samplers.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""Sampler module for sampling of analytic pulses to discrete pulses.""" + +import functools +from typing import Callable + +import numpy as np + +from .commands import FunctionalPulse + + +def sampler(sample_function: Callable) -> Callable: + """Sampler decorator base method. + + Samplers are used for converting an analytic function to a discretized pulse. + + They operate on a function with the signature: + `def f(times: np.ndarray, *args, **kwargs) -> np.ndarray` + Where `times` is a numpy array of floats with length `n_times` and the output array + is a complex numpy array with length `n_times`. The output of the decorator is an + instance of `FunctionalPulse` with signature: + `def g(duration: int, *args, **kwargs) -> SamplePulse` + + Note if your analytic pulse function outputs a `complex` scalar rather than a + `np.array`, you should first vectorize it before applying a sampler. + + This class implements the sampler boilerplate for the sampler. + + Args: + sample_function: A sampler function to be decorated. + """ + + @functools.wraps(sample_function) + def generate_sampler(analytic_pulse: Callable) -> Callable: + """Return a decorated sampler function.""" + + @functools.wraps(analytic_pulse) + def call_sampler(duration: int, *args, **kwargs) -> FunctionalPulse: + """Replace the call to the analytic function with a call to the sampler applied + to the anlytic pulse function.""" + + return sample_function(analytic_pulse, duration, *args, **kwargs) + + # wrap with functional pulse + return FunctionalPulse(call_sampler) + + return generate_sampler + + +@sampler +def left(analytic_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: + """Left sampling strategy decorator. + + See `pulse.samplers.sampler` for more information. + + For `duration`, return: + $$\{f(t) \in \mathbb{C} | t \in \mathbb{Z} \wedge 0<=t<\texttt{duration}\}$$ + + Args: + analytic_pulse: Analytic pulse function to sample. + duration: Duration to sample for. + *args: Analytic pulse function args. + *kkwargs: Analytic pulse function kwargs. + """ + times = np.arange(duration) + return analytic_pulse(times, *args, **kwargs) + + +@sampler +def right(analytic_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: + """Right sampling strategy decorator. + + For `duration`, return: + $$\{f(t) \in \mathbb{C} | t \in \mathbb{Z} \wedge 0 np.ndarray: + """Midpoint sampling strategy decorator. + + For `duration`, return: + $$\{f(t+0.5) \in \mathbb{C} | t \in \mathbb{Z} \wedge 0<=t<\texttt{duration}\}$$ + + Args: + analytic_pulse: Analytic pulse function to sample. + duration: Duration to sample for. + *args: Analytic pulse function args. + **kwargs: Analytic pulse function kwargs. + """ + times = np.arange(1/2, (duration) + 1/2) + return analytic_pulse(times, *args, **kwargs) From 7d1726651607a48389174a100dc021a35d3f6ce7 Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Thu, 28 Mar 2019 18:13:25 -0400 Subject: [PATCH 04/28] Added tests to samplers. --- qiskit/pulse/samplers.py | 14 ++++---- test/python/pulse/test_samplers.py | 55 ++++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/qiskit/pulse/samplers.py b/qiskit/pulse/samplers.py index 6bc8496a389d..c095f558a518 100644 --- a/qiskit/pulse/samplers.py +++ b/qiskit/pulse/samplers.py @@ -5,6 +5,8 @@ # This source code is licensed under the Apache License, Version 2.0 found in # the LICENSE.txt file in the root directory of this source tree. +# pylint: disable=missing-return-doc + """Sampler module for sampling of analytic pulses to discrete pulses.""" import functools @@ -44,8 +46,8 @@ def generate_sampler(analytic_pulse: Callable) -> Callable: def call_sampler(duration: int, *args, **kwargs) -> FunctionalPulse: """Replace the call to the analytic function with a call to the sampler applied to the anlytic pulse function.""" - - return sample_function(analytic_pulse, duration, *args, **kwargs) + sampled_pulse = sample_function(analytic_pulse, duration, *args, **kwargs) + return np.asarray(sampled_pulse, dtype=np.complex) # wrap with functional pulse return FunctionalPulse(call_sampler) @@ -55,7 +57,7 @@ def call_sampler(duration: int, *args, **kwargs) -> FunctionalPulse: @sampler def left(analytic_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: - """Left sampling strategy decorator. + r"""Left sampling strategy decorator. See `pulse.samplers.sampler` for more information. @@ -66,7 +68,7 @@ def left(analytic_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray analytic_pulse: Analytic pulse function to sample. duration: Duration to sample for. *args: Analytic pulse function args. - *kkwargs: Analytic pulse function kwargs. + *kwargs: Analytic pulse function kwargs. """ times = np.arange(duration) return analytic_pulse(times, *args, **kwargs) @@ -74,7 +76,7 @@ def left(analytic_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray @sampler def right(analytic_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: - """Right sampling strategy decorator. + r"""Right sampling strategy decorator. For `duration`, return: $$\{f(t) \in \mathbb{C} | t \in \mathbb{Z} \wedge 0 np.ndarra @sampler def midpoint(analytic_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: - """Midpoint sampling strategy decorator. + r"""Midpoint sampling strategy decorator. For `duration`, return: $$\{f(t+0.5) \in \mathbb{C} | t \in \mathbb{Z} \wedge 0<=t<\texttt{duration}\}$$ diff --git a/test/python/pulse/test_samplers.py b/test/python/pulse/test_samplers.py index 0458ed90fb8f..d30cdac6ea39 100644 --- a/test/python/pulse/test_samplers.py +++ b/test/python/pulse/test_samplers.py @@ -5,15 +5,64 @@ # This source code is licensed under the Apache License, Version 2.0 found in # the LICENSE.txt file in the root directory of this source tree. -# pylint: disable=invalid-name,unexpected-keyword-arg +# pylint: disable=invalid-name, unexpected-keyword-arg, no-value-for-parameter """Tests pulse function samplers.""" -import unittest import numpy as np from qiskit.test import QiskitTestCase +from qiskit.pulse.commands.functional_pulse import FunctionalPulseCommand +import qiskit.pulse.samplers as samplers + + +def linear(x: np.ndarray, m: float, b: float) -> np.ndarray: + """Linear test function + Args: + x: Input times. + m: Slope. + b: Intercept + Returns: + np.ndarray + """ + return m*x+b class TestSampler(QiskitTestCase): - pass + """Test analytic pulse function samplers.""" + + def test_left_sampler(self): + """Test left sampler.""" + m = 0.1 + b = 0.1 + duration = 2 + left_linear_pulse_fun = samplers.left(linear) + reference = np.array([0.1, 0.2], dtype=np.complex) + + pulse = left_linear_pulse_fun(duration, m=m, b=b) + self.assertIsInstance(pulse, FunctionalPulseCommand) + np.testing.assert_array_almost_equal(pulse.samples, reference) + + def test_right_sampler(self): + """Test right sampler.""" + m = 0.1 + b = 0.1 + duration = 2 + right_linear_pulse_fun = samplers.right(linear) + reference = np.array([0.2, 0.3], dtype=np.complex) + + pulse = right_linear_pulse_fun(duration, m=m, b=b) + self.assertIsInstance(pulse, FunctionalPulseCommand) + np.testing.assert_array_almost_equal(pulse.samples, reference) + + def test_midpoint_sampler(self): + """Test midpoint sampler.""" + m = 0.1 + b = 0.1 + duration = 2 + midpoint_linear_pulse_fun = samplers.midpoint(linear) + reference = np.array([0.15, 0.25], dtype=np.complex) + + pulse = midpoint_linear_pulse_fun(duration, m=m, b=b) + self.assertIsInstance(pulse, FunctionalPulseCommand) + np.testing.assert_array_almost_equal(pulse.samples, reference) From 5a219a39032d153228e8103586390c757f716967 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Fri, 29 Mar 2019 10:50:00 +0900 Subject: [PATCH 05/28] change FunctionalPulse to decorator --- qiskit/pulse/commands/__init__.py | 5 +- qiskit/pulse/commands/functional_pulse.py | 102 ---------------------- qiskit/pulse/commands/pulse_decorators.py | 35 ++++++++ qiskit/pulse/commands/sample_pulse.py | 15 ++-- qiskit/pulse/exceptions.py | 63 +++++++++++++ 5 files changed, 111 insertions(+), 109 deletions(-) delete mode 100644 qiskit/pulse/commands/functional_pulse.py create mode 100644 qiskit/pulse/commands/pulse_decorators.py create mode 100644 qiskit/pulse/exceptions.py diff --git a/qiskit/pulse/commands/__init__.py b/qiskit/pulse/commands/__init__.py index 49fbc98b2293..e785384b784f 100644 --- a/qiskit/pulse/commands/__init__.py +++ b/qiskit/pulse/commands/__init__.py @@ -8,10 +8,11 @@ """Command classes for pulse.""" from .pulse_command import PulseCommand -from .sample_pulse import SamplePulse +from .sample_pulse import SamplePulse from .acquire import Acquire, Discriminator, Kernel from .frame_change import FrameChange from .persistent_value import PersistentValue -from .functional_pulse import FunctionalPulse from .snapshot import Snapshot + +from .pulse_decorators import functional_pulse diff --git a/qiskit/pulse/commands/functional_pulse.py b/qiskit/pulse/commands/functional_pulse.py deleted file mode 100644 index df9969281769..000000000000 --- a/qiskit/pulse/commands/functional_pulse.py +++ /dev/null @@ -1,102 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright 2019, IBM. -# -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. - -# pylint: disable=missing-param-doc - -""" -Functional pulse. -""" - -import numpy as np - -from qiskit.exceptions import QiskitError -from qiskit.pulse.commands.sample_pulse import SamplePulse - - -class FunctionalPulse: - """Decorator of pulse envelope function.""" - - def __init__(self, pulse_fun): - """Create new pulse envelope function. - - Args: - pulse_fun (callable): A function describing pulse envelope. - Pulse function must contain argument "duration". - - Raises: - QiskitError: when incorrect envelope function is specified. - """ - - if callable(pulse_fun): - self.pulse_fun = pulse_fun - else: - raise QiskitError('Pulse function is not callable.') - - def __call__(self, duration, name=None, **params): - """Create new functional pulse. - """ - return FunctionalPulseCommand(self.pulse_fun, duration=duration, name=name, **params) - - -class FunctionalPulseCommand(SamplePulse): - """Functional pulse.""" - - def __init__(self, pulse_fun, duration, name, **params): - """Generate new pulse instance. - - Args: - pulse_fun (callable): A function describing pulse envelope. - duration (int): Duration of pulse. - name (str): Unique name to identify the pulse. - Raises: - QiskitError: when first argument of pulse function is not duration. - """ - - if isinstance(duration, int) and duration > 0: - super(FunctionalPulseCommand, self).__init__(duration=duration, - samples=None, - name=name) - - self.pulse_fun = pulse_fun - self._params = params - else: - raise QiskitError('The first argument of pulse function must be duration.') - - @property - def params(self): - """Get parameters for describing pulse envelope. - - Returns: - dict: Pulse parameters. - """ - return self._params - - def update_params(self, **params): - """Set parameters for describing pulse envelope. - """ - self._params.update(params) - - @property - def samples(self): - """Output pulse envelope as a list of complex values. - - Returns: - ndarray: Complex array of pulse envelope. - Raises: - QiskitError: when invalid pulse data is generated. - """ - samples = self.pulse_fun(self.duration, **self.params) - - _samples = np.asarray(samples, dtype=np.complex128) - - if len(_samples) != self.duration: - raise QiskitError('Number of Data point is not consistent with duration.') - - if np.any(np.abs(_samples) > 1): - raise QiskitError('Absolute value of pulse amplitude exceeds 1.') - - return _samples diff --git a/qiskit/pulse/commands/pulse_decorators.py b/qiskit/pulse/commands/pulse_decorators.py new file mode 100644 index 000000000000..7112b77a8611 --- /dev/null +++ b/qiskit/pulse/commands/pulse_decorators.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +# pylint: disable=redefined-builtin + +""" +Pulse decorators. +""" + +import numpy as np + +from .sample_pulse import SamplePulse +from qiskit.pulse.exceptions import CommandsError + + +def functional_pulse(func): + """A decorator for generating SamplePulse from python callable. + Args: + func (callable): A function describing pulse envelope. + Raises: + CommandsError: when invalid function is specified. + """ + def to_pulse(duration, *args, name=None, **kwargs): + """Return SamplePulse.""" + if isinstance(duration, int) and duration > 0: + samples = func(duration, *args, **kwargs) + samples = np.asarray(samples, dtype=np.complex128) + return SamplePulse(samples=samples, name=name) + raise CommandsError('The first argument must be an integer value representing duration.') + + return to_pulse diff --git a/qiskit/pulse/commands/sample_pulse.py b/qiskit/pulse/commands/sample_pulse.py index 98dbff016e5b..5652f07463ac 100644 --- a/qiskit/pulse/commands/sample_pulse.py +++ b/qiskit/pulse/commands/sample_pulse.py @@ -9,28 +9,31 @@ Sample pulse. """ -from qiskit.pulse.commands.pulse_command import PulseCommand +import numpy as np +from .pulse_command import PulseCommand +from qiskit.pulse.exceptions import CommandsError class SamplePulse(PulseCommand): """Container for functional pulse.""" - def __init__(self, duration, samples, name): + def __init__(self, samples, name=None): """Create new sample pulse command. Args: - duration (int): Duration of pulse. samples (ndarray): Complex array of pulse envelope. name (str): Unique name to identify the pulse. + Raises: + CommandsError: when pulse envelope amplitude exceeds 1. """ if not name: _name = str('pulse_object_%s' % id(self)) else: _name = name - super(SamplePulse, self).__init__(duration=duration, name=_name) + super(SamplePulse, self).__init__(duration=len(samples), name=_name) - self._samples = samples + self.samples = samples @property def samples(self): @@ -42,6 +45,8 @@ def samples(self): def samples(self, samples): """Set sample. """ + if np.any(np.abs(samples) > 1): + raise CommandsError('Absolute value of pulse envelope amplitude exceeds 1.') self._samples = samples self.duration = len(samples) diff --git a/qiskit/pulse/exceptions.py b/qiskit/pulse/exceptions.py new file mode 100644 index 000000000000..04d9f3408092 --- /dev/null +++ b/qiskit/pulse/exceptions.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +""" +Exception for errors raised by pulse module. +""" +from qiskit.exceptions import QiskitError + + +class PulseError(QiskitError): + """Errors raised by the pulse module.""" + + def __init__(self, *message): + """Set the error message.""" + super().__init__(*message) + self.message = ' '.join(message) + + def __str__(self): + """Return the message.""" + return repr(self.message) + + +class ChannelsError(PulseError): + """Errors raised by the channel module.""" + + def __init__(self, *message): + """Set the error message.""" + super().__init__(*message) + self.message = ' '.join(message) + + def __str__(self): + """Return the message.""" + return repr(self.message) + + +class CommandsError(PulseError): + """Errors raised by the commands module.""" + + def __init__(self, *msg): + """Set the error message.""" + super().__init__(*msg) + self.msg = ' '.join(msg) + + def __str__(self): + """Return the message.""" + return repr(self.msg) + + +class ScheduleError(PulseError): + """Errors raised by the schedule module.""" + + def __init__(self, *msg): + """Set the error message.""" + super().__init__(*msg) + self.msg = ' '.join(msg) + + def __str__(self): + """Return the message.""" + return repr(self.msg) From 4e7be106e144884df6b8f69226251fca540c5ca7 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Fri, 29 Mar 2019 10:58:16 +0900 Subject: [PATCH 06/28] update error function and imports --- qiskit/pulse/__init__.py | 4 ++++ qiskit/pulse/commands/acquire.py | 12 ++++++------ qiskit/pulse/commands/frame_change.py | 2 +- qiskit/pulse/commands/persistent_value.py | 8 ++++---- qiskit/pulse/commands/pulse_command.py | 6 +++--- qiskit/pulse/commands/pulse_decorators.py | 2 +- qiskit/pulse/commands/sample_pulse.py | 3 ++- qiskit/pulse/commands/snapshot.py | 2 +- 8 files changed, 22 insertions(+), 17 deletions(-) diff --git a/qiskit/pulse/__init__.py b/qiskit/pulse/__init__.py index d18d269ac6dd..ffa6b4c56c05 100644 --- a/qiskit/pulse/__init__.py +++ b/qiskit/pulse/__init__.py @@ -6,3 +6,7 @@ # the LICENSE.txt file in the root directory of this source tree. """Module for Pulses.""" + +from qiskit.pulse.commands import (Acquire, FrameChange, PersistentValue, + SamplePulse, Snapshot, + Kernel, Discriminator, functional_pulse) diff --git a/qiskit/pulse/commands/acquire.py b/qiskit/pulse/commands/acquire.py index 499936550b13..3bd82b5ee88c 100644 --- a/qiskit/pulse/commands/acquire.py +++ b/qiskit/pulse/commands/acquire.py @@ -11,9 +11,9 @@ Acquire. """ -from qiskit.exceptions import QiskitError -from qiskit.pulse.commands.meas_opts import MeasOpts -from qiskit.pulse.commands.pulse_command import PulseCommand +from .meas_opts import MeasOpts +from .pulse_command import PulseCommand +from qiskit.pulse.exceptions import CommandsError class Acquire(PulseCommand): @@ -31,7 +31,7 @@ def __init__(self, duration, discriminator=None, kernel=None): (if applicable) if the measurement level is 1 or 2. Raises: - QiskitError: when invalid discriminator or kernel object is input. + CommandsError: when invalid discriminator or kernel object is input. """ super(Acquire, self).__init__(duration=duration, name='acquire') @@ -40,7 +40,7 @@ def __init__(self, duration, discriminator=None, kernel=None): if isinstance(discriminator, Discriminator): self.discriminator = discriminator else: - raise QiskitError('Invalid discriminator object is specified.') + raise CommandsError('Invalid discriminator object is specified.') else: self.discriminator = Discriminator() @@ -48,7 +48,7 @@ def __init__(self, duration, discriminator=None, kernel=None): if isinstance(kernel, Kernel): self.kernel = kernel else: - raise QiskitError('Invalid kernel object is specified.') + raise CommandsError('Invalid kernel object is specified.') else: self.kernel = Kernel() diff --git a/qiskit/pulse/commands/frame_change.py b/qiskit/pulse/commands/frame_change.py index 86763f8770bf..2f3146612939 100644 --- a/qiskit/pulse/commands/frame_change.py +++ b/qiskit/pulse/commands/frame_change.py @@ -9,7 +9,7 @@ Frame change pulse. """ -from qiskit.pulse.commands.pulse_command import PulseCommand +from .pulse_command import PulseCommand class FrameChange(PulseCommand): diff --git a/qiskit/pulse/commands/persistent_value.py b/qiskit/pulse/commands/persistent_value.py index d3e49f973976..5099d204e824 100644 --- a/qiskit/pulse/commands/persistent_value.py +++ b/qiskit/pulse/commands/persistent_value.py @@ -9,8 +9,8 @@ Persistent value. """ -from qiskit.exceptions import QiskitError -from qiskit.pulse.commands.pulse_command import PulseCommand +from qiskit.pulse.exceptions import CommandsError +from .pulse_command import PulseCommand class PersistentValue(PulseCommand): @@ -23,13 +23,13 @@ def __init__(self, value): value (complex): Complex value to apply, bounded by an absolute value of 1. The allowable precision is device specific. Raises: - QiskitError: when input value exceed 1. + CommandsError: when input value exceed 1. """ super(PersistentValue, self).__init__(duration=0, name='pv') if abs(value) > 1: - raise QiskitError("Absolute value of PV amplitude exceeds 1.") + raise CommandsError("Absolute value of PV amplitude exceeds 1.") self.value = value diff --git a/qiskit/pulse/commands/pulse_command.py b/qiskit/pulse/commands/pulse_command.py index 59aca42f7599..c1b2cf164d6b 100644 --- a/qiskit/pulse/commands/pulse_command.py +++ b/qiskit/pulse/commands/pulse_command.py @@ -11,7 +11,7 @@ Base command. """ -from qiskit.exceptions import QiskitError +from qiskit.pulse.exceptions import CommandsError class PulseCommand: @@ -24,13 +24,13 @@ def __init__(self, duration, name): duration (int): Duration of pulse. name (str): Name of pulse command. Raises: - QiskitError: when duration is not number of points. + CommandsError: when duration is not number of points. """ if isinstance(duration, int): self.duration = duration else: - raise QiskitError('Pulse duration should be integer.') + raise CommandsError('Pulse duration should be integer.') self.name = name diff --git a/qiskit/pulse/commands/pulse_decorators.py b/qiskit/pulse/commands/pulse_decorators.py index 7112b77a8611..bac4e32235c9 100644 --- a/qiskit/pulse/commands/pulse_decorators.py +++ b/qiskit/pulse/commands/pulse_decorators.py @@ -13,8 +13,8 @@ import numpy as np -from .sample_pulse import SamplePulse from qiskit.pulse.exceptions import CommandsError +from .sample_pulse import SamplePulse def functional_pulse(func): diff --git a/qiskit/pulse/commands/sample_pulse.py b/qiskit/pulse/commands/sample_pulse.py index 5652f07463ac..5563e71823fc 100644 --- a/qiskit/pulse/commands/sample_pulse.py +++ b/qiskit/pulse/commands/sample_pulse.py @@ -10,8 +10,9 @@ """ import numpy as np -from .pulse_command import PulseCommand + from qiskit.pulse.exceptions import CommandsError +from .pulse_command import PulseCommand class SamplePulse(PulseCommand): diff --git a/qiskit/pulse/commands/snapshot.py b/qiskit/pulse/commands/snapshot.py index ee5ffd312d54..d0a5e7465eab 100644 --- a/qiskit/pulse/commands/snapshot.py +++ b/qiskit/pulse/commands/snapshot.py @@ -9,7 +9,7 @@ Snapshot. """ -from qiskit.pulse.commands.pulse_command import PulseCommand +from .pulse_command import PulseCommand class Snapshot(PulseCommand): From df05d7795768516e148a79ad85183056ab4c4891 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Fri, 29 Mar 2019 11:11:11 +0900 Subject: [PATCH 07/28] update unittest --- test/python/pulse/test_commands.py | 70 ++++++++++++++++-------------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/test/python/pulse/test_commands.py b/test/python/pulse/test_commands.py index f1c72cd2c521..65f51c03b9db 100644 --- a/test/python/pulse/test_commands.py +++ b/test/python/pulse/test_commands.py @@ -12,11 +12,8 @@ import unittest import numpy as np -from qiskit.pulse.commands import (Acquire, - FrameChange, - FunctionalPulse, - PersistentValue, - Snapshot) +from qiskit.pulse import (Acquire, FrameChange, PersistentValue, + Snapshot, Kernel, Discriminator, functional_pulse) from qiskit.test import QiskitTestCase @@ -26,13 +23,26 @@ class TestAcquire(QiskitTestCase): def test_default(self): """Test default discriminator and kernel. """ - acq_comm = Acquire(duration=10) + kernel_opts = { + 'start_window': 0, + 'stop_window': 10 + } + kernel = Kernel(name='boxcar', **kernel_opts) - self.assertEqual(acq_comm.duration, 10) - self.assertEqual(acq_comm.discriminator.name, None) - self.assertEqual(acq_comm.discriminator.params, {}) - self.assertEqual(acq_comm.kernel.name, None) - self.assertEqual(acq_comm.kernel.params, {}) + discriminator_opts = { + 'neighborhoods': [{'qubits': 1, 'channels': 1}], + 'cal': 'coloring', + 'resample': False + } + discriminator = Discriminator(name='linear_discriminator', **discriminator_opts) + + acq_command = Acquire(duration=10, kernel=kernel, discriminator=discriminator) + + self.assertEqual(acq_command.duration, 10) + self.assertEqual(acq_command.discriminator.name, 'linear_discriminator') + self.assertEqual(acq_command.discriminator.params, discriminator_opts) + self.assertEqual(acq_command.kernel.name, 'boxcar') + self.assertEqual(acq_command.kernel.params, kernel_opts) class TestFrameChange(QiskitTestCase): @@ -41,40 +51,34 @@ class TestFrameChange(QiskitTestCase): def test_default(self): """Test default frame change. """ - fc_comm = FrameChange(phase=1.57-0.785j) + fc_command = FrameChange(phase=1.57 - 0.785j) - self.assertEqual(fc_comm.phase, 1.57-0.785j) - self.assertEqual(fc_comm.duration, 0) + self.assertEqual(fc_command.phase, 1.57-0.785j) + self.assertEqual(fc_command.duration, 0) class TestFunctionalPulse(QiskitTestCase): - """FunctionalPulse tests.""" + """SamplePulse tests.""" def test_gaussian(self): """Test gaussian pulse. """ - @FunctionalPulse + @functional_pulse def gaussian(duration, amp, t0, sig): x = np.linspace(0, duration - 1, duration) return amp * np.exp(-(x - t0) ** 2 / sig ** 2) - pulse_instance = gaussian(10, name='gaussian', amp=1, t0=5, sig=1) + pulse_command = gaussian(name='gaussian', duration=10, amp=1, t0=5, sig=1) _y = 1 * np.exp(-(np.linspace(0, 9, 10) - 5)**2 / 1**2) - self.assertListEqual(list(pulse_instance.samples), list(_y)) - - # Parameter update (complex pulse) - pulse_instance.update_params(amp=0.5-0.5j) - _y = (0.5-0.5j) * np.exp(-(np.linspace(0, 9, 10) - 5)**2 / 1**2) - - self.assertListEqual(list(pulse_instance.samples), list(_y)) + self.assertListEqual(list(pulse_command.samples), list(_y)) # check duration - self.assertEqual(pulse_instance.duration, 10) + self.assertEqual(pulse_command.duration, 10) # check name - self.assertEqual(pulse_instance.name, 'gaussian') + self.assertEqual(pulse_command.name, 'gaussian') class TestPersistentValue(QiskitTestCase): @@ -83,10 +87,10 @@ class TestPersistentValue(QiskitTestCase): def test_default(self): """Test default persistent value. """ - pv_comm = PersistentValue(value=0.5-0.5j) + pv_command = PersistentValue(value=0.5 - 0.5j) - self.assertEqual(pv_comm.value, 0.5-0.5j) - self.assertEqual(pv_comm.duration, 0) + self.assertEqual(pv_command.value, 0.5-0.5j) + self.assertEqual(pv_command.duration, 0) class TestSnapshot(QiskitTestCase): @@ -95,11 +99,11 @@ class TestSnapshot(QiskitTestCase): def test_default(self): """Test default snapshot. """ - snap_comm = Snapshot(label='test_label', snap_type='state') + snap_command = Snapshot(label='test_label', snap_type='state') - self.assertEqual(snap_comm.label, "test_label") - self.assertEqual(snap_comm.type, "state") - self.assertEqual(snap_comm.duration, 0) + self.assertEqual(snap_command.label, "test_label") + self.assertEqual(snap_command.type, "state") + self.assertEqual(snap_command.duration, 0) if __name__ == '__main__': From d0bdb32ebb3a9d8829e4bcc746c493416e778f05 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Fri, 29 Mar 2019 11:37:04 +0900 Subject: [PATCH 08/28] add functools.wraps --- qiskit/pulse/commands/pulse_decorators.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qiskit/pulse/commands/pulse_decorators.py b/qiskit/pulse/commands/pulse_decorators.py index bac4e32235c9..49048ba0b804 100644 --- a/qiskit/pulse/commands/pulse_decorators.py +++ b/qiskit/pulse/commands/pulse_decorators.py @@ -11,6 +11,7 @@ Pulse decorators. """ +import functools import numpy as np from qiskit.pulse.exceptions import CommandsError @@ -24,6 +25,7 @@ def functional_pulse(func): Raises: CommandsError: when invalid function is specified. """ + @functools.wraps(func) def to_pulse(duration, *args, name=None, **kwargs): """Return SamplePulse.""" if isinstance(duration, int) and duration > 0: From dbd7c28335afe3be1660999b15709c961683f713 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Fri, 29 Mar 2019 11:50:02 +0900 Subject: [PATCH 09/28] remove getter, setter from sample pulse --- qiskit/pulse/commands/sample_pulse.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/qiskit/pulse/commands/sample_pulse.py b/qiskit/pulse/commands/sample_pulse.py index 5563e71823fc..08a32fa056dc 100644 --- a/qiskit/pulse/commands/sample_pulse.py +++ b/qiskit/pulse/commands/sample_pulse.py @@ -34,22 +34,10 @@ def __init__(self, samples, name=None): super(SamplePulse, self).__init__(duration=len(samples), name=_name) - self.samples = samples - - @property - def samples(self): - """Return sample. - """ - return self._samples - - @samples.setter - def samples(self, samples): - """Set sample. - """ if np.any(np.abs(samples) > 1): raise CommandsError('Absolute value of pulse envelope amplitude exceeds 1.') - self._samples = samples - self.duration = len(samples) + + self.samples = samples def draw(self, **kwargs): """Plot the interpolated envelope of pulse. From a57c08a9c77d6dc2810ac0a1c9b11c2104ca054b Mon Sep 17 00:00:00 2001 From: knzwnao Date: Fri, 29 Mar 2019 11:56:10 +0900 Subject: [PATCH 10/28] fix lint --- qiskit/pulse/commands/acquire.py | 2 +- qiskit/pulse/commands/pulse_decorators.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/pulse/commands/acquire.py b/qiskit/pulse/commands/acquire.py index 3bd82b5ee88c..44d70af6d31e 100644 --- a/qiskit/pulse/commands/acquire.py +++ b/qiskit/pulse/commands/acquire.py @@ -11,9 +11,9 @@ Acquire. """ +from qiskit.pulse.exceptions import CommandsError from .meas_opts import MeasOpts from .pulse_command import PulseCommand -from qiskit.pulse.exceptions import CommandsError class Acquire(PulseCommand): diff --git a/qiskit/pulse/commands/pulse_decorators.py b/qiskit/pulse/commands/pulse_decorators.py index 49048ba0b804..4e9f2ac4cfc2 100644 --- a/qiskit/pulse/commands/pulse_decorators.py +++ b/qiskit/pulse/commands/pulse_decorators.py @@ -5,7 +5,7 @@ # This source code is licensed under the Apache License, Version 2.0 found in # the LICENSE.txt file in the root directory of this source tree. -# pylint: disable=redefined-builtin +# pylint: disable=missing-return-doc, missing-return-type-doc """ Pulse decorators. From 936ff28999b9d8d86c773da469b6c435f6b6470d Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Thu, 28 Mar 2019 20:59:42 -0400 Subject: [PATCH 11/28] Added dynamic updating of docstrings for decorated sampled functions. --- qiskit/pulse/samplers.py | 51 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/qiskit/pulse/samplers.py b/qiskit/pulse/samplers.py index c095f558a518..eeaf99e24deb 100644 --- a/qiskit/pulse/samplers.py +++ b/qiskit/pulse/samplers.py @@ -17,6 +17,51 @@ from .commands import FunctionalPulse +def _update_annotations(discretized_pulse: Callable) -> Callable: + """Update annotations of discretized analytic pulse function with duration. + + Args: + discretized_pulse: Discretized decorated analytic pulse. + """ + undecorated_annotations = list(discretized_pulse.__annotations__.items()) + decorated_annotations = undecorated_annotations[1:] + decorated_annotations.insert(0, ('duration', int)) + discretized_pulse.__annotations__ = dict(decorated_annotations) + return discretized_pulse + + +def _update_docstring(discretized_pulse: Callable, sampler: Callable) -> Callable: + """Update annotations of discretized analytic pulse function. + + Args: + discretized_pulse: Discretized decorated analytic pulse. + sampler: Applied sampler. + """ + + updated_ds = """ + Discretized analytic pulse function: `{analytic_name}` using sampler: `{sampler_name}`. + + The first argument (time) of the analytic pulse function has been replaced with + a discretized `duration` of type (int). + + Args: + duration (int) + *args: Remaining arguments of analytic pulse function. + See analytic pulse function signature below. + **kwargs: Remaining kwargs of analytic pulse function. + See analytic pulse function signature below. + + Analytic function docstring: + + {analytic_doc} + """.format(analytic_name=discretized_pulse.__qualname__, + sampler_name=sampler.__qualname__, + analytic_doc=discretized_pulse.__doc__) + + discretized_pulse.__doc__ = updated_ds + return discretized_pulse + + def sampler(sample_function: Callable) -> Callable: """Sampler decorator base method. @@ -42,13 +87,17 @@ def sampler(sample_function: Callable) -> Callable: def generate_sampler(analytic_pulse: Callable) -> Callable: """Return a decorated sampler function.""" - @functools.wraps(analytic_pulse) + @functools.wraps(analytic_pulse, updated=tuple()) def call_sampler(duration: int, *args, **kwargs) -> FunctionalPulse: """Replace the call to the analytic function with a call to the sampler applied to the anlytic pulse function.""" sampled_pulse = sample_function(analytic_pulse, duration, *args, **kwargs) return np.asarray(sampled_pulse, dtype=np.complex) + # update type annotations for wrapped analytic function to be discrete + call_sampler = _update_annotations(call_sampler) + call_sampler = _update_docstring(call_sampler, sample_function) + call_sampler.__dict__.pop('__wrapped__') # wrap with functional pulse return FunctionalPulse(call_sampler) From 14a39cde882328b87b91683d0b2f9701a2a40b1d Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Fri, 29 Mar 2019 11:16:24 -0400 Subject: [PATCH 12/28] Added explicit documentation for standard library samplers. --- qiskit/pulse/samplers.py | 115 +++++++++++++++++++++++++-------------- 1 file changed, 73 insertions(+), 42 deletions(-) diff --git a/qiskit/pulse/samplers.py b/qiskit/pulse/samplers.py index eeaf99e24deb..90cb6fbdbe51 100644 --- a/qiskit/pulse/samplers.py +++ b/qiskit/pulse/samplers.py @@ -11,6 +11,8 @@ import functools from typing import Callable +import textwrap +import pydoc import numpy as np @@ -37,26 +39,30 @@ def _update_docstring(discretized_pulse: Callable, sampler: Callable) -> Callabl discretized_pulse: Discretized decorated analytic pulse. sampler: Applied sampler. """ - + wrapped_docstring = pydoc.render_doc(discretized_pulse, '%s') + header, body = wrapped_docstring.split('\n', 1) + body = textwrap.indent(body, ' ') + wrapped_docstring = header+body updated_ds = """ - Discretized analytic pulse function: `{analytic_name}` using sampler: `{sampler_name}`. + Discretized analytic pulse function: `{analytic_name}` using + sampler: `{sampler_name}`. - The first argument (time) of the analytic pulse function has been replaced with - a discretized `duration` of type (int). + The first argument (time) of the analytic pulse function has been replaced with + a discretized `duration` of type (int). - Args: - duration (int) - *args: Remaining arguments of analytic pulse function. - See analytic pulse function signature below. - **kwargs: Remaining kwargs of analytic pulse function. - See analytic pulse function signature below. + Args: + duration (int) + *args: Remaining arguments of analytic pulse function. + See analytic pulse function documentation below. + **kwargs: Remaining kwargs of analytic pulse function. + See analytic pulse function documentation below. - Analytic function docstring: + Sampled analytic function: - {analytic_doc} - """.format(analytic_name=discretized_pulse.__qualname__, - sampler_name=sampler.__qualname__, - analytic_doc=discretized_pulse.__doc__) + {analytic_doc} + """.format(analytic_name=discretized_pulse.__name__, + sampler_name=sampler.__name__, + analytic_doc=wrapped_docstring) discretized_pulse.__doc__ = updated_ds return discretized_pulse @@ -83,20 +89,23 @@ def sampler(sample_function: Callable) -> Callable: sample_function: A sampler function to be decorated. """ - @functools.wraps(sample_function) def generate_sampler(analytic_pulse: Callable) -> Callable: """Return a decorated sampler function.""" - @functools.wraps(analytic_pulse, updated=tuple()) + @functools.wraps(analytic_pulse) def call_sampler(duration: int, *args, **kwargs) -> FunctionalPulse: """Replace the call to the analytic function with a call to the sampler applied to the anlytic pulse function.""" sampled_pulse = sample_function(analytic_pulse, duration, *args, **kwargs) return np.asarray(sampled_pulse, dtype=np.complex) - # update type annotations for wrapped analytic function to be discrete + # Update type annotations for wrapped analytic function to be discrete call_sampler = _update_annotations(call_sampler) + # Update docstring with that of the sampler and include sampled function documentation. call_sampler = _update_docstring(call_sampler, sample_function) + # Unset wrapped to return base sampler signature + # but still get rest of benefits of wraps + # such as __name__, __qualname__ call_sampler.__dict__.pop('__wrapped__') # wrap with functional pulse return FunctionalPulse(call_sampler) @@ -104,8 +113,7 @@ def call_sampler(duration: int, *args, **kwargs) -> FunctionalPulse: return generate_sampler -@sampler -def left(analytic_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: +def left(analytic_pulse: Callable): r"""Left sampling strategy decorator. See `pulse.samplers.sampler` for more information. @@ -114,44 +122,67 @@ def left(analytic_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray $$\{f(t) \in \mathbb{C} | t \in \mathbb{Z} \wedge 0<=t<\texttt{duration}\}$$ Args: - analytic_pulse: Analytic pulse function to sample. - duration: Duration to sample for. - *args: Analytic pulse function args. - *kwargs: Analytic pulse function kwargs. + analytic_pulse: To sample. """ - times = np.arange(duration) - return analytic_pulse(times, *args, **kwargs) + def _left(analytic_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: + """Sampling strategy for decorator. + Args: + analytic_pulse: Analytic pulse function to sample. + duration: Duration to sample for. + *args: Analytic pulse function args. + *kwargs: Analytic pulse function kwargs. + """ + times = np.arange(duration) + return analytic_pulse(times, *args, **kwargs) + + return sampler(_left)(analytic_pulse) -@sampler -def right(analytic_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: +def right(analytic_pulse: Callable): r"""Right sampling strategy decorator. + See `pulse.samplers.sampler` for more information. + For `duration`, return: $$\{f(t) \in \mathbb{C} | t \in \mathbb{Z} \wedge 0 np.ndarray: + """Sampling strategy for decorator. + Args: + analytic_pulse: Analytic pulse function to sample. + duration: Duration to sample for. + *args: Analytic pulse function args. + *kwargs: Analytic pulse function kwargs. + """ + times = np.arange(1, duration+1) + return analytic_pulse(times, *args, **kwargs) + return sampler(_right)(analytic_pulse) -@sampler -def midpoint(analytic_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: + +def midpoint(analytic_pulse: Callable): r"""Midpoint sampling strategy decorator. + See `pulse.samplers.sampler` for more information. + For `duration`, return: $$\{f(t+0.5) \in \mathbb{C} | t \in \mathbb{Z} \wedge 0<=t<\texttt{duration}\}$$ Args: - analytic_pulse: Analytic pulse function to sample. - duration: Duration to sample for. - *args: Analytic pulse function args. - **kwargs: Analytic pulse function kwargs. + analytic_pulse: To sample. """ - times = np.arange(1/2, (duration) + 1/2) - return analytic_pulse(times, *args, **kwargs) + def _midpoint(analytic_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: + """Sampling strategy for decorator. + Args: + analytic_pulse: Analytic pulse function to sample. + duration: Duration to sample for. + *args: Analytic pulse function args. + *kwargs: Analytic pulse function kwargs. + """ + times = np.arange(1/2, (duration) + 1/2) + return analytic_pulse(times, *args, **kwargs) + + return sampler(_midpoint)(analytic_pulse) From 3d83a516ef07ab0ba53604dcdbc5f425965a24d7 Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Fri, 29 Mar 2019 14:37:37 -0400 Subject: [PATCH 13/28] Added extensive sampler module docstring for developers. --- qiskit/pulse/samplers.py | 126 +++++++++++++++++++++++++++-- test/python/pulse/test_samplers.py | 6 +- 2 files changed, 121 insertions(+), 11 deletions(-) diff --git a/qiskit/pulse/samplers.py b/qiskit/pulse/samplers.py index 90cb6fbdbe51..c217daf850b1 100644 --- a/qiskit/pulse/samplers.py +++ b/qiskit/pulse/samplers.py @@ -7,7 +7,117 @@ # pylint: disable=missing-return-doc -"""Sampler module for sampling of analytic pulses to discrete pulses.""" +"""Sampler module for sampling of analytic pulses to discrete pulses. + +Some atypical boilerplate has been added to solve the problem of decorators not preserving +their wrapped function signatures. Below we explain the problem that samplers solve and how +we implement this. + +A sampler is a function that takes an analytic pulse function with signature: + ```python + def f(times: np.ndarray, *args, **kwargs) -> np.ndarray: + ... + ``` +and returns a new function + def f(duration: int, *args, **kwargs) -> SamplePulse: + ... + +Samplers are used to build up pulse commands from analytic pulse functions. + +In Python the creation of a dynamic function that wraps another function will cause +the underlying signature and documentation of the underlying function to be overwritten. +In order to circumvent this issue the Python standard library provides the decorator +`functools.wraps` which allows the programmer to expose the names and signature of the +wrapped function as those of the dynamic function. + +Samplers are implemented by creating a function with signature + @sampler + def left(analytic_pulse: Callable, duration: int, *args, **kwargs) + ... + +This will create a sampler function for `left`. Since it is a dynamic function it would not +have the docstring of `left` available too `help`. This could be fixed by wrapping with +`functools.wraps` in the `sampler`, but this would then cause the signature to be that of the +sampler function which is called on the analytic pulse, below: + `(analytic_pulse: Callable, duration: int, *args, **kwargs)`` +This is not correct for the sampler as the output sampled functions accept only a function. +For the standard sampler we get around this by not using `functools.wraps` and +explicitly defining our samplers such as `left`, `right` and `midpoint` and +calling `sampler` internally on the function that implements the sampling schemes such as +`_left`, `_right` and `_midpoint` respectively. See `left` for an example of this. + +In this way our standard samplers will expose the proper help signature, but a user can +still create their own sampler with + @sampler + def custom_sampler(time, *args, **kwargs): + ... +However, in this case it will be missing documentation of the underlying sampling methods. +We believe that the definition of custom samplers will be rather infrequent. + +However, users will frequently apply sampler instances too analytic pulses. Therefore, a different +approach was required for sampled analytic functions (the output of an analytic pulse function +decorated by a sampler instance). + +A sampler instance is a decorator that may be used to wrap analytic pulse functions such as +linear below: +```python + @left + def linear(times: np.ndarray, m: float, b: float) -> np.ndarray: + ```Linear test function + Args: + times: Input times. + m: Slope. + b: Intercept + Returns: + np.ndarray + ``` + return m*times+b +``` +Which after decoration may be called with a duration rather than an array of times + ```python + duration = 10 + pulse_command = linear(10, 0.1, 0.1) + ``` +If one calls help on `linear` they will find + ``` + linear(duration:int, *args, **kwargs) -> numpy.ndarray + Discretized analytic pulse function: `linear` using + sampler: `_left`. + + The first argument (time) of the analytic pulse function has been replaced with + a discretized `duration` of type (int). + + Args: + duration (int) + *args: Remaining arguments of analytic pulse function. + See analytic pulse function documentation below. + **kwargs: Remaining kwargs of analytic pulse function. + See analytic pulse function documentation below. + + Sampled analytic function: + + function linear in module test.python.pulse.test_samplers + linear(x:numpy.ndarray, m:float, b:float) -> numpy.ndarray + Linear test function + Args: + x: Input times. + m: Slope. + b: Intercept + Returns: + np.ndarray + ``` +This is partly because `functools.wraps` has been used on the underlying function. +This in itself is not sufficient as the signature of the sampled function has +`duration`, whereas the signature of the analytic function is `time`. + +This is acheived by removing `__wrapped__` set by `functools.wraps` in order to preserve +the correct signature and also applying `_update_annotations` and `_update_docstring` +to the generated function which corrects the function annotations and adds an informative +docstring respectively. + +The user therefore has access to the correct sampled function docstring in its entirety, while +still seeing the signature for the analytic pulse function and all of its arguments. +""" import functools from typing import Callable @@ -32,12 +142,12 @@ def _update_annotations(discretized_pulse: Callable) -> Callable: return discretized_pulse -def _update_docstring(discretized_pulse: Callable, sampler: Callable) -> Callable: +def _update_docstring(discretized_pulse: Callable, sampler_inst: Callable) -> Callable: """Update annotations of discretized analytic pulse function. Args: discretized_pulse: Discretized decorated analytic pulse. - sampler: Applied sampler. + sampler_inst: Applied sampler. """ wrapped_docstring = pydoc.render_doc(discretized_pulse, '%s') header, body = wrapped_docstring.split('\n', 1) @@ -61,7 +171,7 @@ def _update_docstring(discretized_pulse: Callable, sampler: Callable) -> Callabl {analytic_doc} """.format(analytic_name=discretized_pulse.__name__, - sampler_name=sampler.__name__, + sampler_name=sampler_inst.__name__, analytic_doc=wrapped_docstring) discretized_pulse.__doc__ = updated_ds @@ -113,7 +223,7 @@ def call_sampler(duration: int, *args, **kwargs) -> FunctionalPulse: return generate_sampler -def left(analytic_pulse: Callable): +def left(analytic_pulse: Callable) -> Callable: r"""Left sampling strategy decorator. See `pulse.samplers.sampler` for more information. @@ -138,7 +248,7 @@ def _left(analytic_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarra return sampler(_left)(analytic_pulse) -def right(analytic_pulse: Callable): +def right(analytic_pulse: Callable) -> Callable: r"""Right sampling strategy decorator. See `pulse.samplers.sampler` for more information. @@ -163,7 +273,7 @@ def _right(analytic_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarr return sampler(_right)(analytic_pulse) -def midpoint(analytic_pulse: Callable): +def midpoint(analytic_pulse: Callable) -> Callable: r"""Midpoint sampling strategy decorator. See `pulse.samplers.sampler` for more information. @@ -182,7 +292,7 @@ def _midpoint(analytic_pulse: Callable, duration: int, *args, **kwargs) -> np.nd *args: Analytic pulse function args. *kwargs: Analytic pulse function kwargs. """ - times = np.arange(1/2, (duration) + 1/2) + times = np.arange(1/2, duration + 1/2) return analytic_pulse(times, *args, **kwargs) return sampler(_midpoint)(analytic_pulse) diff --git a/test/python/pulse/test_samplers.py b/test/python/pulse/test_samplers.py index d30cdac6ea39..4298043b78e5 100644 --- a/test/python/pulse/test_samplers.py +++ b/test/python/pulse/test_samplers.py @@ -16,16 +16,16 @@ import qiskit.pulse.samplers as samplers -def linear(x: np.ndarray, m: float, b: float) -> np.ndarray: +def linear(times: np.ndarray, m: float, b: float) -> np.ndarray: """Linear test function Args: - x: Input times. + times: Input times. m: Slope. b: Intercept Returns: np.ndarray """ - return m*x+b + return m*times+b class TestSampler(QiskitTestCase): From d45746ec3c51875b0613c5cf947c4058b57ec9e7 Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Fri, 29 Mar 2019 14:55:35 -0400 Subject: [PATCH 14/28] Update changelog with sampler. --- CHANGELOG.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ebfee3f7f37d..ecdf43a4b107 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -22,7 +22,8 @@ The format is based on `Keep a Changelog`_. Added ----- - +- Sampler decorator and standard sampler library for conversion of analytic pulses + to `SamplePulse`s (#2042). - Core StochasticSwap routine implimented in Cython (#1789). - New EnlargeWithAncilla pass for adding ancilla qubits after a Layout selection pass (#1603). From 05e3686e0726e1c70cb04af71a4096676a00abf9 Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Fri, 29 Mar 2019 15:01:48 -0400 Subject: [PATCH 15/28] Separate standard library and external libraries --- qiskit/pulse/commands/pulse_decorators.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit/pulse/commands/pulse_decorators.py b/qiskit/pulse/commands/pulse_decorators.py index 4e9f2ac4cfc2..5b6b734c342f 100644 --- a/qiskit/pulse/commands/pulse_decorators.py +++ b/qiskit/pulse/commands/pulse_decorators.py @@ -12,6 +12,7 @@ """ import functools + import numpy as np from qiskit.pulse.exceptions import CommandsError From d612719322a1adc22a1151fa14354917aa9c3cc3 Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Fri, 29 Mar 2019 15:14:32 -0400 Subject: [PATCH 16/28] Updated changelog for functional_pulse. --- CHANGELOG.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ebfee3f7f37d..4177a9b86b99 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -55,7 +55,8 @@ Added Changed ------- - +- FunctionalPulse is no longer a class and instead is a decorator, `functional_pulse` + that returns a `SamplePulse` when called. (#2043) - Changed ``average_data`` to accept observable input in matrix form (#1858) - Change random_state to take in dim over number of qubits (#1857) - The ``Exception`` subclasses have been moved to an ``.exceptions`` module From 8bd7708bcd84e0f94b3b526554f47909e43b0bd0 Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Fri, 29 Mar 2019 16:46:14 -0400 Subject: [PATCH 17/28] update sampler for functional_pulse decorator. Add additional tests. --- qiskit/pulse/samplers.py | 6 +++--- test/python/pulse/test_samplers.py | 32 +++++++++++++++++++++++++----- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/qiskit/pulse/samplers.py b/qiskit/pulse/samplers.py index c217daf850b1..e8b30a1a5bac 100644 --- a/qiskit/pulse/samplers.py +++ b/qiskit/pulse/samplers.py @@ -126,7 +126,7 @@ def linear(times: np.ndarray, m: float, b: float) -> np.ndarray: import numpy as np -from .commands import FunctionalPulse +import qiskit.pulse.commands as commands def _update_annotations(discretized_pulse: Callable) -> Callable: @@ -203,7 +203,7 @@ def generate_sampler(analytic_pulse: Callable) -> Callable: """Return a decorated sampler function.""" @functools.wraps(analytic_pulse) - def call_sampler(duration: int, *args, **kwargs) -> FunctionalPulse: + def call_sampler(duration: int, *args, **kwargs) -> commands.SamplePulse: """Replace the call to the analytic function with a call to the sampler applied to the anlytic pulse function.""" sampled_pulse = sample_function(analytic_pulse, duration, *args, **kwargs) @@ -218,7 +218,7 @@ def call_sampler(duration: int, *args, **kwargs) -> FunctionalPulse: # such as __name__, __qualname__ call_sampler.__dict__.pop('__wrapped__') # wrap with functional pulse - return FunctionalPulse(call_sampler) + return commands.functional_pulse(call_sampler) return generate_sampler diff --git a/test/python/pulse/test_samplers.py b/test/python/pulse/test_samplers.py index 4298043b78e5..6ec04f0dc4d6 100644 --- a/test/python/pulse/test_samplers.py +++ b/test/python/pulse/test_samplers.py @@ -12,11 +12,11 @@ import numpy as np from qiskit.test import QiskitTestCase -from qiskit.pulse.commands.functional_pulse import FunctionalPulseCommand +import qiskit.pulse.commands as commands import qiskit.pulse.samplers as samplers -def linear(times: np.ndarray, m: float, b: float) -> np.ndarray: +def linear(times: np.ndarray, m: float, b: float = 0.1) -> np.ndarray: """Linear test function Args: times: Input times. @@ -40,7 +40,7 @@ def test_left_sampler(self): reference = np.array([0.1, 0.2], dtype=np.complex) pulse = left_linear_pulse_fun(duration, m=m, b=b) - self.assertIsInstance(pulse, FunctionalPulseCommand) + self.assertIsInstance(pulse, commands.SamplePulse) np.testing.assert_array_almost_equal(pulse.samples, reference) def test_right_sampler(self): @@ -52,7 +52,7 @@ def test_right_sampler(self): reference = np.array([0.2, 0.3], dtype=np.complex) pulse = right_linear_pulse_fun(duration, m=m, b=b) - self.assertIsInstance(pulse, FunctionalPulseCommand) + self.assertIsInstance(pulse, commands.SamplePulse) np.testing.assert_array_almost_equal(pulse.samples, reference) def test_midpoint_sampler(self): @@ -64,5 +64,27 @@ def test_midpoint_sampler(self): reference = np.array([0.15, 0.25], dtype=np.complex) pulse = midpoint_linear_pulse_fun(duration, m=m, b=b) - self.assertIsInstance(pulse, FunctionalPulseCommand) + self.assertIsInstance(pulse, commands.SamplePulse) + np.testing.assert_array_almost_equal(pulse.samples, reference) + + def test_sampler_name(self): + """Test that sampler setting of pulse name works.""" + m = 0.1 + b = 0.1 + duration = 2 + left_linear_pulse_fun = samplers.left(linear) + + pulse = left_linear_pulse_fun(duration, m=m, b=b, name='test') + self.assertIsInstance(pulse, commands.SamplePulse) + self.assertEqual(pulse.name, 'test') + + def test_default_arg_sampler(self): + """Test that default arguments work with sampler.""" + m = 0.1 + duration = 2 + left_linear_pulse_fun = samplers.left(linear) + reference = np.array([0.1, 0.2], dtype=np.complex) + + pulse = left_linear_pulse_fun(duration, m=m) + self.assertIsInstance(pulse, commands.SamplePulse) np.testing.assert_array_almost_equal(pulse.samples, reference) From 6aa07e51eb6af646a94c32ad26a1e96d0b4eaa05 Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Fri, 29 Mar 2019 17:49:15 -0400 Subject: [PATCH 18/28] Changed all instances of 'analytic' to 'continuous' --- CHANGELOG.rst | 4 +- qiskit/pulse/samplers.py | 126 ++++++++++++++--------------- test/python/pulse/test_samplers.py | 2 +- 3 files changed, 66 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c77f6d8b120e..450c5ebe99cf 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -22,8 +22,8 @@ The format is based on `Keep a Changelog`_. Added ----- -- Sampler decorator and standard sampler library for conversion of analytic pulses - to `SamplePulse`s (#2042). +- Sampler decorator and standard sampler library for conversion of continuous pulses + to discrete `SamplePulse`s (#2042). - Core StochasticSwap routine implimented in Cython (#1789). - New EnlargeWithAncilla pass for adding ancilla qubits after a Layout selection pass (#1603). diff --git a/qiskit/pulse/samplers.py b/qiskit/pulse/samplers.py index e8b30a1a5bac..03dcce1025a9 100644 --- a/qiskit/pulse/samplers.py +++ b/qiskit/pulse/samplers.py @@ -7,13 +7,13 @@ # pylint: disable=missing-return-doc -"""Sampler module for sampling of analytic pulses to discrete pulses. +"""Sampler module for sampling of continuous pulses to discrete pulses. Some atypical boilerplate has been added to solve the problem of decorators not preserving their wrapped function signatures. Below we explain the problem that samplers solve and how we implement this. -A sampler is a function that takes an analytic pulse function with signature: +A sampler is a function that takes an continuous pulse function with signature: ```python def f(times: np.ndarray, *args, **kwargs) -> np.ndarray: ... @@ -22,7 +22,7 @@ def f(times: np.ndarray, *args, **kwargs) -> np.ndarray: def f(duration: int, *args, **kwargs) -> SamplePulse: ... -Samplers are used to build up pulse commands from analytic pulse functions. +Samplers are used to build up pulse commands from continuous pulse functions. In Python the creation of a dynamic function that wraps another function will cause the underlying signature and documentation of the underlying function to be overwritten. @@ -32,14 +32,14 @@ def f(duration: int, *args, **kwargs) -> SamplePulse: Samplers are implemented by creating a function with signature @sampler - def left(analytic_pulse: Callable, duration: int, *args, **kwargs) + def left(continuous_pulse: Callable, duration: int, *args, **kwargs) ... This will create a sampler function for `left`. Since it is a dynamic function it would not have the docstring of `left` available too `help`. This could be fixed by wrapping with `functools.wraps` in the `sampler`, but this would then cause the signature to be that of the -sampler function which is called on the analytic pulse, below: - `(analytic_pulse: Callable, duration: int, *args, **kwargs)`` +sampler function which is called on the continuous pulse, below: + `(continuous_pulse: Callable, duration: int, *args, **kwargs)`` This is not correct for the sampler as the output sampled functions accept only a function. For the standard sampler we get around this by not using `functools.wraps` and explicitly defining our samplers such as `left`, `right` and `midpoint` and @@ -54,11 +54,11 @@ def custom_sampler(time, *args, **kwargs): However, in this case it will be missing documentation of the underlying sampling methods. We believe that the definition of custom samplers will be rather infrequent. -However, users will frequently apply sampler instances too analytic pulses. Therefore, a different -approach was required for sampled analytic functions (the output of an analytic pulse function +However, users will frequently apply sampler instances too continuous pulses. Therefore, a different +approach was required for sampled continuous functions (the output of an continuous pulse function decorated by a sampler instance). -A sampler instance is a decorator that may be used to wrap analytic pulse functions such as +A sampler instance is a decorator that may be used to wrap continuous pulse functions such as linear below: ```python @left @@ -81,20 +81,20 @@ def linear(times: np.ndarray, m: float, b: float) -> np.ndarray: If one calls help on `linear` they will find ``` linear(duration:int, *args, **kwargs) -> numpy.ndarray - Discretized analytic pulse function: `linear` using + Discretized continuous pulse function: `linear` using sampler: `_left`. - The first argument (time) of the analytic pulse function has been replaced with + The first argument (time) of the continuous pulse function has been replaced with a discretized `duration` of type (int). Args: duration (int) - *args: Remaining arguments of analytic pulse function. - See analytic pulse function documentation below. - **kwargs: Remaining kwargs of analytic pulse function. - See analytic pulse function documentation below. + *args: Remaining arguments of continuous pulse function. + See continuous pulse function documentation below. + **kwargs: Remaining kwargs of continuous pulse function. + See continuous pulse function documentation below. - Sampled analytic function: + Sampled continuous function: function linear in module test.python.pulse.test_samplers linear(x:numpy.ndarray, m:float, b:float) -> numpy.ndarray @@ -108,7 +108,7 @@ def linear(times: np.ndarray, m: float, b: float) -> np.ndarray: ``` This is partly because `functools.wraps` has been used on the underlying function. This in itself is not sufficient as the signature of the sampled function has -`duration`, whereas the signature of the analytic function is `time`. +`duration`, whereas the signature of the continuous function is `time`. This is acheived by removing `__wrapped__` set by `functools.wraps` in order to preserve the correct signature and also applying `_update_annotations` and `_update_docstring` @@ -116,7 +116,7 @@ def linear(times: np.ndarray, m: float, b: float) -> np.ndarray: docstring respectively. The user therefore has access to the correct sampled function docstring in its entirety, while -still seeing the signature for the analytic pulse function and all of its arguments. +still seeing the signature for the continuous pulse function and all of its arguments. """ import functools @@ -130,10 +130,10 @@ def linear(times: np.ndarray, m: float, b: float) -> np.ndarray: def _update_annotations(discretized_pulse: Callable) -> Callable: - """Update annotations of discretized analytic pulse function with duration. + """Update annotations of discretized continuous pulse function with duration. Args: - discretized_pulse: Discretized decorated analytic pulse. + discretized_pulse: Discretized decorated continuous pulse. """ undecorated_annotations = list(discretized_pulse.__annotations__.items()) decorated_annotations = undecorated_annotations[1:] @@ -143,10 +143,10 @@ def _update_annotations(discretized_pulse: Callable) -> Callable: def _update_docstring(discretized_pulse: Callable, sampler_inst: Callable) -> Callable: - """Update annotations of discretized analytic pulse function. + """Update annotations of discretized continuous pulse function. Args: - discretized_pulse: Discretized decorated analytic pulse. + discretized_pulse: Discretized decorated continuous pulse. sampler_inst: Applied sampler. """ wrapped_docstring = pydoc.render_doc(discretized_pulse, '%s') @@ -154,25 +154,25 @@ def _update_docstring(discretized_pulse: Callable, sampler_inst: Callable) -> Ca body = textwrap.indent(body, ' ') wrapped_docstring = header+body updated_ds = """ - Discretized analytic pulse function: `{analytic_name}` using + Discretized continuous pulse function: `{continuous_name}` using sampler: `{sampler_name}`. - The first argument (time) of the analytic pulse function has been replaced with + The first argument (time) of the continuous pulse function has been replaced with a discretized `duration` of type (int). Args: duration (int) - *args: Remaining arguments of analytic pulse function. - See analytic pulse function documentation below. - **kwargs: Remaining kwargs of analytic pulse function. - See analytic pulse function documentation below. + *args: Remaining arguments of continuous pulse function. + See continuous pulse function documentation below. + **kwargs: Remaining kwargs of continuous pulse function. + See continuous pulse function documentation below. - Sampled analytic function: + Sampled continuous function: - {analytic_doc} - """.format(analytic_name=discretized_pulse.__name__, + {continuous_doc} + """.format(continuous_name=discretized_pulse.__name__, sampler_name=sampler_inst.__name__, - analytic_doc=wrapped_docstring) + continuous_doc=wrapped_docstring) discretized_pulse.__doc__ = updated_ds return discretized_pulse @@ -181,7 +181,7 @@ def _update_docstring(discretized_pulse: Callable, sampler_inst: Callable) -> Ca def sampler(sample_function: Callable) -> Callable: """Sampler decorator base method. - Samplers are used for converting an analytic function to a discretized pulse. + Samplers are used for converting an continuous function to a discretized pulse. They operate on a function with the signature: `def f(times: np.ndarray, *args, **kwargs) -> np.ndarray` @@ -190,7 +190,7 @@ def sampler(sample_function: Callable) -> Callable: instance of `FunctionalPulse` with signature: `def g(duration: int, *args, **kwargs) -> SamplePulse` - Note if your analytic pulse function outputs a `complex` scalar rather than a + Note if your continuous pulse function outputs a `complex` scalar rather than a `np.array`, you should first vectorize it before applying a sampler. This class implements the sampler boilerplate for the sampler. @@ -199,17 +199,17 @@ def sampler(sample_function: Callable) -> Callable: sample_function: A sampler function to be decorated. """ - def generate_sampler(analytic_pulse: Callable) -> Callable: + def generate_sampler(continuous_pulse: Callable) -> Callable: """Return a decorated sampler function.""" - @functools.wraps(analytic_pulse) + @functools.wraps(continuous_pulse) def call_sampler(duration: int, *args, **kwargs) -> commands.SamplePulse: - """Replace the call to the analytic function with a call to the sampler applied + """Replace the call to the continuous function with a call to the sampler applied to the anlytic pulse function.""" - sampled_pulse = sample_function(analytic_pulse, duration, *args, **kwargs) + sampled_pulse = sample_function(continuous_pulse, duration, *args, **kwargs) return np.asarray(sampled_pulse, dtype=np.complex) - # Update type annotations for wrapped analytic function to be discrete + # Update type annotations for wrapped continuous function to be discrete call_sampler = _update_annotations(call_sampler) # Update docstring with that of the sampler and include sampled function documentation. call_sampler = _update_docstring(call_sampler, sample_function) @@ -223,7 +223,7 @@ def call_sampler(duration: int, *args, **kwargs) -> commands.SamplePulse: return generate_sampler -def left(analytic_pulse: Callable) -> Callable: +def left(continuous_pulse: Callable) -> Callable: r"""Left sampling strategy decorator. See `pulse.samplers.sampler` for more information. @@ -232,23 +232,23 @@ def left(analytic_pulse: Callable) -> Callable: $$\{f(t) \in \mathbb{C} | t \in \mathbb{Z} \wedge 0<=t<\texttt{duration}\}$$ Args: - analytic_pulse: To sample. + continuous_pulse: To sample. """ - def _left(analytic_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: + def _left(continuous_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: """Sampling strategy for decorator. Args: - analytic_pulse: Analytic pulse function to sample. + continuous_pulse: Continuous pulse function to sample. duration: Duration to sample for. - *args: Analytic pulse function args. - *kwargs: Analytic pulse function kwargs. + *args: Continuous pulse function args. + *kwargs: Continuous pulse function kwargs. """ times = np.arange(duration) - return analytic_pulse(times, *args, **kwargs) + return continuous_pulse(times, *args, **kwargs) - return sampler(_left)(analytic_pulse) + return sampler(_left)(continuous_pulse) -def right(analytic_pulse: Callable) -> Callable: +def right(continuous_pulse: Callable) -> Callable: r"""Right sampling strategy decorator. See `pulse.samplers.sampler` for more information. @@ -257,23 +257,23 @@ def right(analytic_pulse: Callable) -> Callable: $$\{f(t) \in \mathbb{C} | t \in \mathbb{Z} \wedge 0 np.ndarray: + def _right(continuous_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: """Sampling strategy for decorator. Args: - analytic_pulse: Analytic pulse function to sample. + continuous_pulse: Continuous pulse function to sample. duration: Duration to sample for. - *args: Analytic pulse function args. - *kwargs: Analytic pulse function kwargs. + *args: Continuous pulse function args. + *kwargs: Continuous pulse function kwargs. """ times = np.arange(1, duration+1) - return analytic_pulse(times, *args, **kwargs) + return continuous_pulse(times, *args, **kwargs) - return sampler(_right)(analytic_pulse) + return sampler(_right)(continuous_pulse) -def midpoint(analytic_pulse: Callable) -> Callable: +def midpoint(continuous_pulse: Callable) -> Callable: r"""Midpoint sampling strategy decorator. See `pulse.samplers.sampler` for more information. @@ -282,17 +282,17 @@ def midpoint(analytic_pulse: Callable) -> Callable: $$\{f(t+0.5) \in \mathbb{C} | t \in \mathbb{Z} \wedge 0<=t<\texttt{duration}\}$$ Args: - analytic_pulse: To sample. + continuous_pulse: To sample. """ - def _midpoint(analytic_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: + def _midpoint(continuous_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: """Sampling strategy for decorator. Args: - analytic_pulse: Analytic pulse function to sample. + continuous_pulse: Continuous pulse function to sample. duration: Duration to sample for. - *args: Analytic pulse function args. - *kwargs: Analytic pulse function kwargs. + *args: Continuous pulse function args. + *kwargs: Continuous pulse function kwargs. """ times = np.arange(1/2, duration + 1/2) - return analytic_pulse(times, *args, **kwargs) + return continuous_pulse(times, *args, **kwargs) - return sampler(_midpoint)(analytic_pulse) + return sampler(_midpoint)(continuous_pulse) diff --git a/test/python/pulse/test_samplers.py b/test/python/pulse/test_samplers.py index 6ec04f0dc4d6..e1fd56ec0c0b 100644 --- a/test/python/pulse/test_samplers.py +++ b/test/python/pulse/test_samplers.py @@ -29,7 +29,7 @@ def linear(times: np.ndarray, m: float, b: float = 0.1) -> np.ndarray: class TestSampler(QiskitTestCase): - """Test analytic pulse function samplers.""" + """Test continuous pulse function samplers.""" def test_left_sampler(self): """Test left sampler.""" From 568f635c884919ead8f18f44ab1e706bc70e6f6b Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Thu, 4 Apr 2019 11:00:04 -0400 Subject: [PATCH 19/28] Update sampler module structure to separate sampling functions and decorators. --- qiskit/pulse/samplers/__init__.py | 10 +++ .../sampler_decorators.py} | 55 ++++++---------- qiskit/pulse/samplers/sampler_strategies.py | 64 +++++++++++++++++++ 3 files changed, 93 insertions(+), 36 deletions(-) create mode 100644 qiskit/pulse/samplers/__init__.py rename qiskit/pulse/{samplers.py => samplers/sampler_decorators.py} (85%) create mode 100644 qiskit/pulse/samplers/sampler_strategies.py diff --git a/qiskit/pulse/samplers/__init__.py b/qiskit/pulse/samplers/__init__.py new file mode 100644 index 000000000000..15bdd23e42b4 --- /dev/null +++ b/qiskit/pulse/samplers/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""Module for Samplers.""" + +from .sampler_decorators import * diff --git a/qiskit/pulse/samplers.py b/qiskit/pulse/samplers/sampler_decorators.py similarity index 85% rename from qiskit/pulse/samplers.py rename to qiskit/pulse/samplers/sampler_decorators.py index 03dcce1025a9..3f39add1f271 100644 --- a/qiskit/pulse/samplers.py +++ b/qiskit/pulse/samplers/sampler_decorators.py @@ -7,7 +7,8 @@ # pylint: disable=missing-return-doc -"""Sampler module for sampling of continuous pulses to discrete pulses. +"""Sampler decorator module for sampling of continuous pulses to discrete pulses to be +exposed to user. Some atypical boilerplate has been added to solve the problem of decorators not preserving their wrapped function signatures. Below we explain the problem that samplers solve and how @@ -18,7 +19,7 @@ def f(times: np.ndarray, *args, **kwargs) -> np.ndarray: ... ``` -and returns a new function +and returns a new function: def f(duration: int, *args, **kwargs) -> SamplePulse: ... @@ -126,6 +127,7 @@ def linear(times: np.ndarray, m: float, b: float) -> np.ndarray: import numpy as np +from qiskit.pulse.samplers import sampler_strategies import qiskit.pulse.commands as commands @@ -223,6 +225,18 @@ def call_sampler(duration: int, *args, **kwargs) -> commands.SamplePulse: return generate_sampler +def left_sample_fun(continuous_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: + """Left sample a continuous function. + Args: + continuous_pulse: Continuous pulse function to sample. + duration: Duration to sample for. + *args: Continuous pulse function args. + *kwargs: Continuous pulse function kwargs. + """ + times = np.arange(duration) + return continuous_pulse(times, *args, **kwargs) + + def left(continuous_pulse: Callable) -> Callable: r"""Left sampling strategy decorator. @@ -234,18 +248,8 @@ def left(continuous_pulse: Callable) -> Callable: Args: continuous_pulse: To sample. """ - def _left(continuous_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: - """Sampling strategy for decorator. - Args: - continuous_pulse: Continuous pulse function to sample. - duration: Duration to sample for. - *args: Continuous pulse function args. - *kwargs: Continuous pulse function kwargs. - """ - times = np.arange(duration) - return continuous_pulse(times, *args, **kwargs) - return sampler(_left)(continuous_pulse) + return sampler(sampler_strategies.left_sample)(continuous_pulse) def right(continuous_pulse: Callable) -> Callable: @@ -259,18 +263,8 @@ def right(continuous_pulse: Callable) -> Callable: Args: continuous_pulse: To sample. """ - def _right(continuous_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: - """Sampling strategy for decorator. - Args: - continuous_pulse: Continuous pulse function to sample. - duration: Duration to sample for. - *args: Continuous pulse function args. - *kwargs: Continuous pulse function kwargs. - """ - times = np.arange(1, duration+1) - return continuous_pulse(times, *args, **kwargs) - return sampler(_right)(continuous_pulse) + return sampler(sampler_strategies.right_sample)(continuous_pulse) def midpoint(continuous_pulse: Callable) -> Callable: @@ -284,15 +278,4 @@ def midpoint(continuous_pulse: Callable) -> Callable: Args: continuous_pulse: To sample. """ - def _midpoint(continuous_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: - """Sampling strategy for decorator. - Args: - continuous_pulse: Continuous pulse function to sample. - duration: Duration to sample for. - *args: Continuous pulse function args. - *kwargs: Continuous pulse function kwargs. - """ - times = np.arange(1/2, duration + 1/2) - return continuous_pulse(times, *args, **kwargs) - - return sampler(_midpoint)(continuous_pulse) + return sampler(sampler_strategies.midpoint_sample)(continuous_pulse) diff --git a/qiskit/pulse/samplers/sampler_strategies.py b/qiskit/pulse/samplers/sampler_strategies.py new file mode 100644 index 000000000000..9d1433207c64 --- /dev/null +++ b/qiskit/pulse/samplers/sampler_strategies.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +# pylint: disable=missing-return-doc + +"""Sampler strategy module for sampler functions. + +Sampler functions have signature. + ```python + def sampler_function(continuous_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: + ... + ``` +where the supplied `continuous_pulse` is a function with signature: + ```python + def f(times: np.ndarray, *args, **kwargs) -> np.ndarray: + ... + ``` +The sampler will call the `continuous_pulse` function with a set of times it will decide +according to the sampling strategy it implments along with the passed `args` and `kwargs`. +""" + +from typing import Callable + +import numpy as np + + +def left_sample(continuous_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: + """Left sample a continuous function. + Args: + continuous_pulse: Continuous pulse function to sample. + duration: Duration to sample for. + *args: Continuous pulse function args. + **kwargs: Continuous pulse function kwargs. + """ + times = np.arange(duration) + return continuous_pulse(times, *args, **kwargs) + + +def right_sample(continuous_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: + """Sampling strategy for decorator. + Args: + continuous_pulse: Continuous pulse function to sample. + duration: Duration to sample for. + *args: Continuous pulse function args. + **kwargs: Continuous pulse function kwargs. + """ + times = np.arange(1, duration+1) + return continuous_pulse(times, *args, **kwargs) + + +def midpoint_sample(continuous_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: + """Sampling strategy for decorator. + Args: + continuous_pulse: Continuous pulse function to sample. + duration: Duration to sample for. + *args: Continuous pulse function args. + **kwargs: Continuous pulse function kwargs. + """ + times = np.arange(1/2, duration + 1/2) + return continuous_pulse(times, *args, **kwargs) From 12f635cfc3f918ab8051bb0986d7d10d961fc077 Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Thu, 4 Apr 2019 14:08:48 -0400 Subject: [PATCH 20/28] Remove sampler prefix to sampler module's modules. --- qiskit/pulse/samplers/__init__.py | 2 +- qiskit/pulse/samplers/{sampler_decorators.py => decorators.py} | 0 qiskit/pulse/samplers/{sampler_strategies.py => strategies.py} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename qiskit/pulse/samplers/{sampler_decorators.py => decorators.py} (100%) rename qiskit/pulse/samplers/{sampler_strategies.py => strategies.py} (100%) diff --git a/qiskit/pulse/samplers/__init__.py b/qiskit/pulse/samplers/__init__.py index 15bdd23e42b4..718a5e487998 100644 --- a/qiskit/pulse/samplers/__init__.py +++ b/qiskit/pulse/samplers/__init__.py @@ -7,4 +7,4 @@ """Module for Samplers.""" -from .sampler_decorators import * +from .decorators import * diff --git a/qiskit/pulse/samplers/sampler_decorators.py b/qiskit/pulse/samplers/decorators.py similarity index 100% rename from qiskit/pulse/samplers/sampler_decorators.py rename to qiskit/pulse/samplers/decorators.py diff --git a/qiskit/pulse/samplers/sampler_strategies.py b/qiskit/pulse/samplers/strategies.py similarity index 100% rename from qiskit/pulse/samplers/sampler_strategies.py rename to qiskit/pulse/samplers/strategies.py From a14489c3e9165c8dbd93105e581215ec9cdfe32a Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Thu, 4 Apr 2019 14:10:04 -0400 Subject: [PATCH 21/28] Update small bug from rename --- qiskit/pulse/samplers/decorators.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/pulse/samplers/decorators.py b/qiskit/pulse/samplers/decorators.py index 3f39add1f271..c6babc794672 100644 --- a/qiskit/pulse/samplers/decorators.py +++ b/qiskit/pulse/samplers/decorators.py @@ -127,7 +127,7 @@ def linear(times: np.ndarray, m: float, b: float) -> np.ndarray: import numpy as np -from qiskit.pulse.samplers import sampler_strategies +from qiskit.pulse.samplers import strategies import qiskit.pulse.commands as commands @@ -249,7 +249,7 @@ def left(continuous_pulse: Callable) -> Callable: continuous_pulse: To sample. """ - return sampler(sampler_strategies.left_sample)(continuous_pulse) + return sampler(strategies.left_sample)(continuous_pulse) def right(continuous_pulse: Callable) -> Callable: @@ -264,7 +264,7 @@ def right(continuous_pulse: Callable) -> Callable: continuous_pulse: To sample. """ - return sampler(sampler_strategies.right_sample)(continuous_pulse) + return sampler(strategies.right_sample)(continuous_pulse) def midpoint(continuous_pulse: Callable) -> Callable: @@ -278,4 +278,4 @@ def midpoint(continuous_pulse: Callable) -> Callable: Args: continuous_pulse: To sample. """ - return sampler(sampler_strategies.midpoint_sample)(continuous_pulse) + return sampler(strategies.midpoint_sample)(continuous_pulse) From 10047a65c3d6a64e40cbafc30e70ade3250c13b2 Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Thu, 4 Apr 2019 15:25:27 -0400 Subject: [PATCH 22/28] Change dtype casting to np.complex_ --- qiskit/pulse/samplers/decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/pulse/samplers/decorators.py b/qiskit/pulse/samplers/decorators.py index c6babc794672..bfa27cd10f64 100644 --- a/qiskit/pulse/samplers/decorators.py +++ b/qiskit/pulse/samplers/decorators.py @@ -209,7 +209,7 @@ def call_sampler(duration: int, *args, **kwargs) -> commands.SamplePulse: """Replace the call to the continuous function with a call to the sampler applied to the anlytic pulse function.""" sampled_pulse = sample_function(continuous_pulse, duration, *args, **kwargs) - return np.asarray(sampled_pulse, dtype=np.complex) + return np.asarray(sampled_pulse, dtype=np.complex_) # Update type annotations for wrapped continuous function to be discrete call_sampler = _update_annotations(call_sampler) From c148c74996b9068b4679390bd76ad06f9006251e Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Fri, 5 Apr 2019 16:30:39 -0400 Subject: [PATCH 23/28] remove accidental remote simulator file. --- test/python/test_remote_simulator.py | 241 --------------------------- 1 file changed, 241 deletions(-) delete mode 100644 test/python/test_remote_simulator.py diff --git a/test/python/test_remote_simulator.py b/test/python/test_remote_simulator.py deleted file mode 100644 index 427a4c4d0146..000000000000 --- a/test/python/test_remote_simulator.py +++ /dev/null @@ -1,241 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright 2018, IBM. -# -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. - -# pylint: disable=missing-docstring,broad-except -# pylint: disable=redefined-builtin -# pylint: disable=too-many-function-args - -"""IBMQ Remote Simulator Tests""" - -import os -import unittest -from qiskit import (ClassicalRegister, QuantumCircuit, QuantumRegister, compile) - -from qiskit import IBMQ, Aer -from qiskit.qasm import pi -from .common import requires_qe_access, JobTestCase, slow_test - - -class TestBackendQobj(JobTestCase): - """Qiskit backend qobj test. Compares remote simulator as - configured in environment variables 'IBMQ_QOBJ_DEVICE', - 'IBMQ_TOKEN' and 'IBMQ_QOBJ_URL' against local simulator - 'local_qasm_simulator' as ground truth. - """ - - def setUp(self): - super().setUp() - self._testing_device = os.getenv('IBMQ_QOBJ_DEVICE', None) - self._qe_token = os.getenv('IBMQ_TOKEN', None) - self._qe_url = os.getenv('IBMQ_QOBJ_URL') - if not self._testing_device or not self._qe_token or not self._qe_url: - self.skipTest("No credentials or testing device available for " - "testing Qobj capabilities.") - - IBMQ.use_account(self._qe_token, self._qe_url) - self._local_backend = Aer.get_backend('qasm_simulator_py') - self._remote_backend = IBMQ.get_backend(self._testing_device) - self.log.info('Remote backend: %s', self._remote_backend.name()) - self.log.info('Local backend: %s', self._local_backend.name()) - - @slow_test - @requires_qe_access - def test_operational(self): - """Test if backend is operational. - """ - self.assertTrue(self._remote_backend.status()['operational']) - - @slow_test - @requires_qe_access - def test_allow_qobj(self): - """Test if backend support Qobj. - """ - self.assertTrue(self._remote_backend.configuration()['allow_q_object']) - - @slow_test - @requires_qe_access - def test_one_qubit_no_operation(self): - """Test one circuit, one register, in-order readout. - """ - qr = QuantumRegister(1) - cr = ClassicalRegister(1) - circ = QuantumCircuit(qr, cr) - circ.measure(qr[0], cr[0]) - - qobj = compile(circ, self._remote_backend) - result_remote = self._remote_backend.run(qobj).result() - result_local = self._local_backend.run(qobj).result() - self.assertDictAlmostEqual(result_remote.get_counts(circ), - result_local.get_counts(circ), delta=50) - - @slow_test - @requires_qe_access - def test_one_qubit_operation(self): - """Test one circuit, one register, in-order readout. - """ - qr = QuantumRegister(1) - cr = ClassicalRegister(1) - circ = QuantumCircuit(qr, cr) - circ.x(qr[0]) - circ.measure(qr[0], cr[0]) - - qobj = compile(circ, self._remote_backend) - result_remote = self._remote_backend.run(qobj).result() - result_local = self._local_backend.run(qobj).result() - self.assertDictAlmostEqual(result_remote.get_counts(circ), - result_local.get_counts(circ), delta=50) - - @slow_test - @requires_qe_access - def test_simple_circuit(self): - """Test one circuit, one register, in-order readout. - """ - qr = QuantumRegister(4) - cr = ClassicalRegister(4) - circ = QuantumCircuit(qr, cr) - circ.x(qr[0]) - circ.x(qr[2]) - circ.measure(qr[0], cr[0]) - circ.measure(qr[1], cr[1]) - circ.measure(qr[2], cr[2]) - circ.measure(qr[3], cr[3]) - - qobj = compile(circ, self._remote_backend) - result_remote = self._remote_backend.run(qobj).result() - result_local = self._local_backend.run(qobj).result() - self.assertDictAlmostEqual(result_remote.get_counts(circ), - result_local.get_counts(circ), delta=50) - - @slow_test - @requires_qe_access - def test_readout_order(self): - """Test one circuit, one register, out-of-order readout. - """ - qr = QuantumRegister(4) - cr = ClassicalRegister(4) - circ = QuantumCircuit(qr, cr) - circ.x(qr[0]) - circ.x(qr[2]) - circ.measure(qr[0], cr[2]) - circ.measure(qr[1], cr[0]) - circ.measure(qr[2], cr[1]) - circ.measure(qr[3], cr[3]) - - qobj_remote = compile(circ, self._remote_backend) - qobj_local = compile(circ, self._local_backend) - result_remote = self._remote_backend.run(qobj_remote).result() - result_local = self._local_backend.run(qobj_local).result() - self.assertDictAlmostEqual(result_remote.get_counts(circ), - result_local.get_counts(circ), delta=50) - - @slow_test - @requires_qe_access - def test_multi_register(self): - """Test one circuit, two registers, out-of-order readout. - """ - qr1 = QuantumRegister(2) - qr2 = QuantumRegister(2) - cr1 = ClassicalRegister(3) - cr2 = ClassicalRegister(1) - circ = QuantumCircuit(qr1, qr2, cr1, cr2) - circ.h(qr1[0]) - circ.cx(qr1[0], qr2[1]) - circ.h(qr2[0]) - circ.cx(qr2[0], qr1[1]) - circ.x(qr1[1]) - circ.measure(qr1[0], cr2[0]) - circ.measure(qr1[1], cr1[0]) - circ.measure(qr1[1], cr2[0]) - circ.measure(qr1[1], cr1[2]) - circ.measure(qr2[0], cr1[2]) - circ.measure(qr2[1], cr1[1]) - - qobj = compile(circ, self._remote_backend) - result_remote = self._remote_backend.run(qobj).result() - result_local = self._local_backend.run(qobj).result() - self.assertDictAlmostEqual(result_remote.get_counts(circ), - result_local.get_counts(circ), delta=50) - - @slow_test - @requires_qe_access - def test_multi_circuit(self): - """Test two circuits, two registers, out-of-order readout. - """ - qr1 = QuantumRegister(2) - qr2 = QuantumRegister(2) - cr1 = ClassicalRegister(3) - cr2 = ClassicalRegister(1) - circ1 = QuantumCircuit(qr1, qr2, cr1, cr2) - circ1.h(qr1[0]) - circ1.cx(qr1[0], qr2[1]) - circ1.h(qr2[0]) - circ1.cx(qr2[0], qr1[1]) - circ1.x(qr1[1]) - circ1.measure(qr1[0], cr2[0]) - circ1.measure(qr1[1], cr1[0]) - circ1.measure(qr1[0], cr2[0]) - circ1.measure(qr1[1], cr1[2]) - circ1.measure(qr2[0], cr1[2]) - circ1.measure(qr2[1], cr1[1]) - circ2 = QuantumCircuit(qr1, qr2, cr1) - circ2.h(qr1[0]) - circ2.cx(qr1[0], qr1[1]) - circ2.h(qr2[1]) - circ2.cx(qr2[1], qr1[1]) - circ2.measure(qr1[0], cr1[0]) - circ2.measure(qr1[1], cr1[1]) - circ2.measure(qr1[0], cr1[2]) - circ2.measure(qr2[1], cr1[2]) - - qobj = compile([circ1, circ2], self._remote_backend) - result_remote = self._remote_backend.run(qobj).result() - result_local = self._local_backend.run(qobj).result() - self.assertDictAlmostEqual(result_remote.get_counts(circ1), - result_local.get_counts(circ1), delta=50) - self.assertDictAlmostEqual(result_remote.get_counts(circ2), - result_local.get_counts(circ2), delta=50) - - @slow_test - @requires_qe_access - def test_conditional_operation(self): - """Test conditional operation. - """ - qr = QuantumRegister(4) - cr = ClassicalRegister(4) - circ = QuantumCircuit(qr, cr) - circ.x(qr[0]) - circ.x(qr[2]) - circ.measure(qr[0], cr[0]) - circ.x(qr[0]).c_if(cr, 1) - - qobj = compile(circ, self._remote_backend) - result_remote = self._remote_backend.run(qobj).result() - result_local = self._local_backend.run(qobj).result() - self.assertDictAlmostEqual(result_remote.get_counts(circ), - result_local.get_counts(circ), delta=50) - - @slow_test - @requires_qe_access - def test_atlantic_circuit(self): - """Test Atlantis deterministic ry operation. - """ - qr = QuantumRegister(3) - cr = ClassicalRegister(3) - circ = QuantumCircuit(qr, cr) - circ.ry(pi, qr[0]) - circ.ry(pi, qr[2]) - circ.measure(qr, cr) - - qobj = compile(circ, self._remote_backend) - result_remote = self._remote_backend.run(qobj).result() - result_local = self._local_backend.run(qobj).result() - self.assertDictAlmostEqual(result_remote.get_counts(circ), - result_local.get_counts(circ), delta=50) - - -if __name__ == '__main__': - unittest.main(verbosity=2) From 7cdff430dcea09b25615bbb067b3fac49562d8f0 Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Mon, 8 Apr 2019 14:58:51 -0400 Subject: [PATCH 24/28] Removed required default parameters. --- qiskit/providers/models/pulsedefaults.py | 14 +++++++------- .../default_pulse_configuration_schema.json | 8 -------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/qiskit/providers/models/pulsedefaults.py b/qiskit/providers/models/pulsedefaults.py index 8ba4ce7af5f9..4d2d069f1003 100644 --- a/qiskit/providers/models/pulsedefaults.py +++ b/qiskit/providers/models/pulsedefaults.py @@ -56,17 +56,17 @@ class PulseDefaultsSchema(BaseSchema): """Schema for PulseDefaults.""" # Required properties. - qubit_freq_est = List(Number(), required=True, + qubit_freq_est = List(Number(), required=False, validate=Length(min=1)) - meas_freq_est = List(Number(), required=True, + meas_freq_est = List(Number(), required=False, validate=Length(min=1)) - buffer = Integer(required=True, validate=Range(min=0)) - pulse_library = Nested(PulseLibraryItemSchema, required=True, many=True) - meas_kernel = Nested(MeasurementKernelSchema, required=True) - discriminator = Nested(DiscriminatorSchema, required=True) + buffer = Integer(required=False, validate=Range(min=0), missing=0) + pulse_library = Nested(PulseLibraryItemSchema, required=True, many=True, missing=lambda: []) + meas_kernel = Nested(MeasurementKernelSchema, required=False) + discriminator = Nested(DiscriminatorSchema, required=False) # Optional properties. - cmd_def = Nested(PulseCommandSchema, many=True) + cmd_def = Nested(PulseCommandSchema, many=True, required=True, missing=lambda: []) @bind_schema(PulseLibraryItemSchema) diff --git a/qiskit/schemas/default_pulse_configuration_schema.json b/qiskit/schemas/default_pulse_configuration_schema.json index 1b651280dfbd..ee9a74d9eb93 100644 --- a/qiskit/schemas/default_pulse_configuration_schema.json +++ b/qiskit/schemas/default_pulse_configuration_schema.json @@ -140,13 +140,5 @@ "type": "array" } }, - "required": [ - "qubit_freq_est", - "meas_freq_est", - "pulse_library", - "meas_kernel", - "discriminator", - "buffer" - ], "type": "object" } From b470ad3adf9300006b71d1f12cff3281cd12cb81 Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Mon, 8 Apr 2019 15:44:56 -0400 Subject: [PATCH 25/28] Update changelog. Update defaults model validation to add nonetypes. --- CHANGELOG.rst | 1 + qiskit/providers/models/pulsedefaults.py | 19 +++++-------------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d6b3e949cbf4..ea8e06f38384 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -68,6 +68,7 @@ Added Changed ------- +- Backend defaults values are no longer required (). - The most connected subset in DenseLayout is now reduced bandwidth (#2021). - plot_histogram now allows sorting by Hamming distance from target_string (#2064). - FunctionalPulse is no longer a class and instead is a decorator, `functional_pulse` diff --git a/qiskit/providers/models/pulsedefaults.py b/qiskit/providers/models/pulsedefaults.py index 4d2d069f1003..e961a30cd579 100644 --- a/qiskit/providers/models/pulsedefaults.py +++ b/qiskit/providers/models/pulsedefaults.py @@ -57,13 +57,13 @@ class PulseDefaultsSchema(BaseSchema): # Required properties. qubit_freq_est = List(Number(), required=False, - validate=Length(min=1)) + validate=Length(min=1), missing=None) meas_freq_est = List(Number(), required=False, - validate=Length(min=1)) + validate=Length(min=1), missing=None) buffer = Integer(required=False, validate=Range(min=0), missing=0) pulse_library = Nested(PulseLibraryItemSchema, required=True, many=True, missing=lambda: []) - meas_kernel = Nested(MeasurementKernelSchema, required=False) - discriminator = Nested(DiscriminatorSchema, required=False) + meas_kernel = Nested(MeasurementKernelSchema, required=False, missing=None) + discriminator = Nested(DiscriminatorSchema, required=False, missing=None) # Optional properties. cmd_def = Nested(PulseCommandSchema, many=True, required=True, missing=lambda: []) @@ -140,13 +140,4 @@ class PulseDefaults(BaseModel): discriminator (Discriminator): Default discriminator. """ - def __init__(self, qubit_freq_est, meas_freq_est, buffer, pulse_library, - meas_kernel, discriminator, **kwargs): - self.qubit_freq_est = qubit_freq_est - self.meas_freq_est = meas_freq_est - self.buffer = buffer - self.pulse_library = pulse_library - self.meas_kernel = meas_kernel - self.discriminator = discriminator - - super().__init__(**kwargs) + pass From f619d29806fe2389553d449f2ddd07ddbd756206 Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Mon, 8 Apr 2019 16:02:18 -0400 Subject: [PATCH 26/28] Update changelog. --- CHANGELOG.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ea8e06f38384..1c41d9d9a76c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -22,6 +22,7 @@ The format is based on `Keep a Changelog`_. Added ----- + - `meas_level` to result schema (#2085). - Core StochasticSwap routine implemented in Cython (#1789). - New EnlargeWithAncilla pass for adding ancilla qubits after a Layout @@ -62,13 +63,13 @@ Added in terms of other, simpler instructions (#1816). - Added an ``Instruction.mirror()`` method that mirrors a composite instruction (reverses its sub-instructions) (#1816). -- Added a ``NoiseAdaptiveLayout`` pass to compute a backend calibration-data aware initial +- Added a ``NoiseAdaptiveLayout`` pass to compute a backend calibration-data aware initial qubit layout. (#2089) Changed ------- -- Backend defaults values are no longer required (). +- Backend defaults values are no longer required (#2101). - The most connected subset in DenseLayout is now reduced bandwidth (#2021). - plot_histogram now allows sorting by Hamming distance from target_string (#2064). - FunctionalPulse is no longer a class and instead is a decorator, `functional_pulse` From 2a954ecae1a7606fb022cc3b1c85545037a11ff5 Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Wed, 10 Apr 2019 13:13:15 -0400 Subject: [PATCH 27/28] Readd parameters to be required. --- qiskit/providers/models/pulsedefaults.py | 29 +++++++++++-------- .../default_pulse_configuration_schema.json | 6 ++++ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/qiskit/providers/models/pulsedefaults.py b/qiskit/providers/models/pulsedefaults.py index e961a30cd579..cff3bf27950f 100644 --- a/qiskit/providers/models/pulsedefaults.py +++ b/qiskit/providers/models/pulsedefaults.py @@ -56,17 +56,15 @@ class PulseDefaultsSchema(BaseSchema): """Schema for PulseDefaults.""" # Required properties. - qubit_freq_est = List(Number(), required=False, - validate=Length(min=1), missing=None) - meas_freq_est = List(Number(), required=False, - validate=Length(min=1), missing=None) - buffer = Integer(required=False, validate=Range(min=0), missing=0) - pulse_library = Nested(PulseLibraryItemSchema, required=True, many=True, missing=lambda: []) - meas_kernel = Nested(MeasurementKernelSchema, required=False, missing=None) - discriminator = Nested(DiscriminatorSchema, required=False, missing=None) + qubit_freq_est = List(Number(), required=True, validate=Length(min=1)) + meas_freq_est = List(Number(), required=True, validate=Length(min=1)) + buffer = Integer(required=False, validate=Range(min=0)) + pulse_library = Nested(PulseLibraryItemSchema, required=True, many=True) + cmd_def = Nested(PulseCommandSchema, many=True, required=True) # Optional properties. - cmd_def = Nested(PulseCommandSchema, many=True, required=True, missing=lambda: []) + meas_kernel = Nested(MeasurementKernelSchema) + discriminator = Nested(DiscriminatorSchema) @bind_schema(PulseLibraryItemSchema) @@ -136,8 +134,15 @@ class PulseDefaults(BaseModel): in GHz. buffer (int): Default buffer time (in units of dt) between pulses. pulse_library (list[PulseLibraryItem]): Backend pulse library. - meas_kernel (MeasurementKernel): Default measurement kernel. - discriminator (Discriminator): Default discriminator. + cmd_def (list[PulseCommand]): Backend command definition. """ - pass + def __init__(self, qubit_freq_est, meas_freq_est, buffer, + pulse_library, cmd_def, **kwargs): + self.qubit_freq_est = qubit_freq_est + self.meas_freq_est = meas_freq_est + self.buffer = buffer + self.pulse_library = pulse_library + self.cmd_def = cmd_def + + super().__init__(**kwargs) diff --git a/qiskit/schemas/default_pulse_configuration_schema.json b/qiskit/schemas/default_pulse_configuration_schema.json index ee9a74d9eb93..4389dcb4fcc3 100644 --- a/qiskit/schemas/default_pulse_configuration_schema.json +++ b/qiskit/schemas/default_pulse_configuration_schema.json @@ -140,5 +140,11 @@ "type": "array" } }, + "required": [ + "qubit_freq_est", + "meas_freq_est", + "pulse_library", + "buffer" + ], "type": "object" } From 7410f93286a4f6f09ec55e328484585fd3e661e1 Mon Sep 17 00:00:00 2001 From: Thomas Alexander Date: Wed, 10 Apr 2019 20:45:27 -0400 Subject: [PATCH 28/28] Update docstring and delete extra function following review. --- qiskit/pulse/samplers/decorators.py | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/qiskit/pulse/samplers/decorators.py b/qiskit/pulse/samplers/decorators.py index bfa27cd10f64..4f82cef05c84 100644 --- a/qiskit/pulse/samplers/decorators.py +++ b/qiskit/pulse/samplers/decorators.py @@ -45,7 +45,7 @@ def left(continuous_pulse: Callable, duration: int, *args, **kwargs) For the standard sampler we get around this by not using `functools.wraps` and explicitly defining our samplers such as `left`, `right` and `midpoint` and calling `sampler` internally on the function that implements the sampling schemes such as -`_left`, `_right` and `_midpoint` respectively. See `left` for an example of this. +`left_sample`, `right_sample` and `midpoint_sample` respectively. See `left` for an example of this. In this way our standard samplers will expose the proper help signature, but a user can still create their own sampler with @@ -187,13 +187,13 @@ def sampler(sample_function: Callable) -> Callable: They operate on a function with the signature: `def f(times: np.ndarray, *args, **kwargs) -> np.ndarray` - Where `times` is a numpy array of floats with length `n_times` and the output array - is a complex numpy array with length `n_times`. The output of the decorator is an + Where `times` is a numpy array of floats with length n_times and the output array + is a complex numpy array with length n_times. The output of the decorator is an instance of `FunctionalPulse` with signature: `def g(duration: int, *args, **kwargs) -> SamplePulse` Note if your continuous pulse function outputs a `complex` scalar rather than a - `np.array`, you should first vectorize it before applying a sampler. + `np.ndarray`, you should first vectorize it before applying a sampler. This class implements the sampler boilerplate for the sampler. @@ -225,18 +225,6 @@ def call_sampler(duration: int, *args, **kwargs) -> commands.SamplePulse: return generate_sampler -def left_sample_fun(continuous_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray: - """Left sample a continuous function. - Args: - continuous_pulse: Continuous pulse function to sample. - duration: Duration to sample for. - *args: Continuous pulse function args. - *kwargs: Continuous pulse function kwargs. - """ - times = np.arange(duration) - return continuous_pulse(times, *args, **kwargs) - - def left(continuous_pulse: Callable) -> Callable: r"""Left sampling strategy decorator.