Skip to content

Commit

Permalink
basicaer handling of global_phase. (#4915)
Browse files Browse the repository at this point in the history
* basicaer handling of global_phase.

Also, adapted some transpiler passes to preserve global phase in dag.

* catch non-float global_phase

* update state vector simulator

* linting

* blank line needed

* update Decompose pass for global phase

* update transpiler passes which create new dagcircuits.

This preserves global phase in newly created dagcircuit.

* Drop support for python 3.5 (#4926)

This commit drops support for running with python 3.5. It marks the
minimum supported version of the package as python 3.6, removes python
3.5 package pins, removes the 3.5 CI jobs, and removes the warning on
python 3.5. Looking at the PyPI stats since the deprecation period
started the number of users on Python 3.5 has diminished significantly,
but not disappeared. There were 783 downloads with pip from pypi out of
total of 25782 total pip downloads in the last 30 days. Compared to the
roughly 10% figure when we deprecated Python 3.5.

Merging this means we can not release until after the documented EoL
date for Python 3.5 support of September 13. This shouldn't be a problem
because with #4767 we will need to coordinate the release of all the
qiskit elements and are planning to do that after 09/13/2020.

It's worth noting that we should start planning to deprecate python 3.6
support sooner rather than later it goes EoL upstream at the end of
next year [1] and some of our other upstream dependencies (mainly numpy
et al) are going to remove support before the upstream Python EoL date
[2].

[1] https://devguide.python.org/#branchstatus
[2] https://numpy.org/neps/nep-0029-deprecation_policy.html

Co-authored-by: Kevin Krsulich <kevin.krsulich@ibm.com>

* Explicity set encoding of release notes via reno config (#5013)

The recent reno release 3.2.0 included a new feature [1] for setting the
character encoding that reno uses for all it's files. [2] This commit
sets this option in the reno config file to make the release note files
explicitly utf8. This is important (especially for windows users)
because we have literal inlines of the text drawer in some release notes
which use utf8 characters. This should avoid issues for users who's
system encoding is not compatible with the text drawer output in the
release notes.

[1] https://opendev.org/openstack/reno/commit/984bcba17e4e0b46763f42015d09680e5c5d5a04
[2] https://docs.openstack.org/reno/latest/user/usage.html#configuring-reno

* Fix parameterized Gate.definition to have valid ParameterTable. (#4945)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>

* Allow ParameterExpressions to be cast to ints (#5001)

- Resolves #4978

Co-authored-by: Kevin Krsulich <kevin.krsulich@ibm.com>

* Add tutorials job to CI (#4907)

* Add tutorials job to CI

This commit adds a new job to run the qiskit tutorials in CI. The
tutorials are used as a form of upgrade testing to ensure that for key
examples Qiskit as a whole is N and N+1 releases. This caused friction
during the qiskit 0.20.0 release because the tutorials were never
updated to stop using deprecated code (or in the case of other elements
by merging backwards incompatible changes). To ensure there aren't any
surprises at the last minute when we run the tutorials with a proposed
new metapackage this commit adds a job to CI to ensure that they always
run with terra changes. It means for PRs that change an api (after a
deprecation cycle) the tutorial will need to be updated first. This will
ensure that users will have an upgrade path because CI in
qiskit/qiskit-tutorials runs with the metapackage.

* Install pandoc in tutorials job

* Remove aqua tutorials from ci job for run time

* Fix rm path and speed up tutorials clone

* Try pinning matplotlib

* Try fixing archive path

* Add back aqua tutorials

Co-authored-by: Luciano Bello <luciano.bello@ibm.com>
Co-authored-by: Kevin Krsulich <kevin.krsulich@ibm.com>

* Fix/5015 QuantumCircuit __eq__ should always return a Bool (#5016)

* Check if circuit is an instance of QuantumObject before comparing it
Added a few tests

* Fixed lint error

Co-authored-by: Kevin Krsulich <kevin.krsulich@ibm.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>

* fixed issue 4936 by updating the docstring (#5014)

Co-authored-by: Luciano Bello <luciano.bello@ibm.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>

* fix bux in optimize_1q_gates

* minor linting

* fix test

Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
Co-authored-by: Kevin Krsulich <kevin.krsulich@ibm.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Andrew Meyer <andrewm1100@gmail.com>
Co-authored-by: Luciano Bello <luciano.bello@ibm.com>
Co-authored-by: Raul Otaolea <raul.otaolea@gmail.com>
Co-authored-by: Tanya Garg <62295887+tgag17@users.noreply.github.com>
  • Loading branch information
8 people authored Sep 24, 2020
1 parent b0be96e commit ee854fb
Show file tree
Hide file tree
Showing 20 changed files with 138 additions and 53 deletions.
5 changes: 4 additions & 1 deletion qiskit/assembler/assemble_circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def _assemble_circuit(
memory_slots=memory_slots,
creg_sizes=creg_sizes,
name=circuit.name,
global_phase=circuit.global_phase)
global_phase=float(circuit.global_phase))

# TODO: why do we need n_qubits and memory_slots in both the header and the config
config = QasmQobjExperimentConfig(n_qubits=num_qubits, memory_slots=memory_slots)
Expand Down Expand Up @@ -113,6 +113,9 @@ def _assemble_circuit(
# measurement result may be needed for a conditional gate.
if instruction.name == "measure" and is_conditional_experiment:
instruction.register = clbit_indices
if op_context[0].definition is not None and op_context[0].definition.global_phase:
# pylint: disable=no-member
header.global_phase += float(op_context[0].definition.global_phase)

# To convert to a qobj-style conditional, insert a bfunc prior
# to the conditional instruction to map the creg ?= val condition
Expand Down
16 changes: 16 additions & 0 deletions qiskit/dagcircuit/dagcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,19 @@ def _add_op_node(self, op, qargs, cargs):
new_node._node_id = node_index
return node_index

def _copy_circuit_metadata(self):
"""Return a copy of source_dag with metadata but empty."""
target_dag = DAGCircuit()
target_dag.name = self.name
target_dag._global_phase = self._global_phase

for qreg in self.qregs.values():
target_dag.add_qreg(qreg)
for creg in self.cregs.values():
target_dag.add_creg(creg)

return target_dag

def apply_operation_back(self, op, qargs=None, cargs=None, condition=None):
"""Apply an operation to the output of the circuit.
Expand Down Expand Up @@ -883,6 +896,9 @@ def substitute_node_with_dag(self, node, input_dag, wires=None):
in_dag.apply_operation_back(replay_node.op, replay_node.qargs,
replay_node.cargs)

if in_dag.global_phase:
self.global_phase += in_dag.global_phase

if wires is None:
wires = in_dag.wires

Expand Down
3 changes: 3 additions & 0 deletions qiskit/providers/basicaer/qasm_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ def run_experiment(self, experiment):
self._classical_memory = 0
self._classical_register = 0
self._sample_measure = False
global_phase = experiment.header.global_phase
# Validate the dimension of initial statevector if set
self._validate_initial_statevector()
# Get the seed looking in circuit, qobj, and then random.
Expand Down Expand Up @@ -485,6 +486,8 @@ def run_experiment(self, experiment):
shots = self._shots
for _ in range(shots):
self._initialize_statevector()
# apply global_phase
self._statevector *= np.exp(1j * global_phase)
# Initialize classical memory to all 0
self._classical_memory = 0
self._classical_register = 0
Expand Down
4 changes: 4 additions & 0 deletions qiskit/providers/basicaer/unitary_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ def __init__(self, configuration=None, provider=None):
self._number_of_qubits = 0
self._initial_unitary = None
self._chop_threshold = 1e-15
self._global_phase = 0

def _add_unitary(self, gate, qubits):
"""Apply an N-qubit unitary matrix.
Expand Down Expand Up @@ -200,6 +201,8 @@ def _initialize_unitary(self):
def _get_unitary(self):
"""Return the current unitary"""
unitary = np.reshape(self._unitary, 2 * [2 ** self._number_of_qubits])
if self._global_phase:
unitary *= np.exp(1j * float(self._global_phase))
unitary[abs(unitary) < self._chop_threshold] = 0.0
return unitary

Expand Down Expand Up @@ -302,6 +305,7 @@ def run_experiment(self, experiment):
"""
start = time.time()
self._number_of_qubits = experiment.header.n_qubits
self._global_phase = experiment.header.global_phase

# Validate the dimension of initial unitary if set
self._validate_initial_unitary()
Expand Down
6 changes: 4 additions & 2 deletions qiskit/transpiler/passes/basis/basis_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,12 @@ def run(self, dag):
bound_target_dag = circuit_to_dag(target_circuit)
else:
bound_target_dag = target_dag

if bound_target_dag.global_phase:
dag.global_phase += bound_target_dag.global_phase
if (len(bound_target_dag.op_nodes()) == 1
and len(bound_target_dag.op_nodes()[0].qargs) == len(node.qargs)):
dag.substitute_node(node, bound_target_dag.op_nodes()[0].op, inplace=True)
dag_op = bound_target_dag.op_nodes()[0].op
dag.substitute_node(node, dag_op, inplace=True)
else:
dag.substitute_node_with_dag(node, bound_target_dag)
else:
Expand Down
2 changes: 2 additions & 0 deletions qiskit/transpiler/passes/basis/decompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ def run(self, dag: DAGCircuit) -> DAGCircuit:
# opaque or built-in gates are not decomposable
if not node.op.definition:
continue
if node.op.definition.global_phase:
dag.global_phase += node.op.definition.global_phase
# TODO: allow choosing among multiple decomposition rules
rule = node.op.definition.data

Expand Down
1 change: 1 addition & 0 deletions qiskit/transpiler/passes/layout/apply_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,6 @@ def run(self, dag):
if node.type == 'op':
qargs = [q[layout[qarg]] for qarg in node.qargs]
new_dag.apply_operation_back(node.op, qargs, node.cargs)
new_dag._global_phase = dag._global_phase

return new_dag
7 changes: 1 addition & 6 deletions qiskit/transpiler/passes/optimization/consolidate_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@


from qiskit.circuit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit.dagcircuit import DAGCircuit
from qiskit.quantum_info.operators import Operator
from qiskit.quantum_info.synthesis import TwoQubitBasisDecomposer
from qiskit.extensions import UnitaryGate
Expand Down Expand Up @@ -75,11 +74,7 @@ def run(self, dag):
if self.decomposer is None:
return dag

new_dag = DAGCircuit()
for qreg in dag.qregs.values():
new_dag.add_qreg(qreg)
for creg in dag.cregs.values():
new_dag.add_creg(creg)
new_dag = dag._copy_circuit_metadata()

# compute ordered indices for the global circuit wires
global_index_map = {wire: idx for idx, wire in enumerate(dag.qubits)}
Expand Down
7 changes: 6 additions & 1 deletion qiskit/transpiler/passes/optimization/optimize_1q_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def run(self, dag):
for run in runs:
right_name = "u1"
right_parameters = (0, 0, 0) # (theta, phi, lambda)

right_global_phase = 0
for current_node in run:
left_name = current_node.name
if (current_node.condition is not None
Expand All @@ -77,6 +77,9 @@ def run(self, dag):
else:
left_name = "u1" # replace id with u1
left_parameters = (0, 0, 0)
if (current_node.op.definition is not None and
current_node.op.definition.global_phase):
right_global_phase += current_node.op.definition.global_phase
# If there are any sympy objects coming from the gate convert
# to numpy.
left_parameters = tuple([float(x) for x in left_parameters])
Expand Down Expand Up @@ -206,6 +209,8 @@ def run(self, dag):
else:
raise TranspilerError('It was not possible to use the basis %s' % self.basis)

dag.global_phase += right_global_phase

if right_name != 'nop':
dag.substitute_node(run[0], new_op, inplace=True)

Expand Down
6 changes: 1 addition & 5 deletions qiskit/transpiler/passes/routing/basic_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,7 @@ def run(self, dag):
TranspilerError: if the coupling map or the layout are not
compatible with the DAG.
"""
new_dag = DAGCircuit()
for qreg in dag.qregs.values():
new_dag.add_qreg(qreg)
for creg in dag.cregs.values():
new_dag.add_creg(creg)
new_dag = dag._copy_circuit_metadata()

if len(dag.qregs) != 1 or dag.qregs.get('q', None) is None:
raise TranspilerError('Basic swap runs on physical circuits only')
Expand Down
17 changes: 1 addition & 16 deletions qiskit/transpiler/passes/routing/sabre_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from itertools import cycle
import numpy as np

from qiskit.dagcircuit import DAGCircuit
from qiskit.circuit.library.standard_gates import SwapGate
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.exceptions import TranspilerError
Expand Down Expand Up @@ -145,7 +144,7 @@ def run(self, dag):
rng = np.random.default_rng(self.seed)

# Preserve input DAG's name, regs, wire_map, etc. but replace the graph.
mapped_dag = _copy_circuit_metadata(dag)
mapped_dag = dag._copy_circuit_metadata()

# Assume bidirectional couplings, fixing gate direction is easy later.
self.coupling_map.make_symmetric()
Expand Down Expand Up @@ -335,20 +334,6 @@ def _score_heuristic(self, heuristic, front_layer, extended_set, layout, swap_qu
raise TranspilerError('Heuristic %s not recognized.' % heuristic)


def _copy_circuit_metadata(source_dag):
"""Return a copy of source_dag with metadata but empty.
"""
target_dag = DAGCircuit()
target_dag.name = source_dag.name

for qreg in source_dag.qregs.values():
target_dag.add_qreg(qreg)
for creg in source_dag.cregs.values():
target_dag.add_creg(creg)

return target_dag


def _transform_gate_for_layout(op_node, layout):
"""Return node implementing a virtual op on given layout."""
mapped_op_node = deepcopy(op_node)
Expand Down
7 changes: 1 addition & 6 deletions qiskit/transpiler/passes/routing/stochastic_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,12 +299,7 @@ def _mapper(self, circuit_graph, coupling_graph, trials=20):

# Construct an empty DAGCircuit with the same set of
# qregs and cregs as the input circuit
dagcircuit_output = DAGCircuit()
dagcircuit_output.name = circuit_graph.name
for qreg in circuit_graph.qregs.values():
dagcircuit_output.add_qreg(qreg)
for creg in circuit_graph.cregs.values():
dagcircuit_output.add_creg(creg)
dagcircuit_output = circuit_graph._copy_circuit_metadata()

logger.debug("trivial_layout = %s", layout)

Expand Down
8 changes: 1 addition & 7 deletions qiskit/transpiler/passes/utils/merge_adjacent_barriers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"""Return a circuit with any adjacent barriers merged together."""

from qiskit.transpiler.basepasses import TransformationPass
from qiskit.dagcircuit import DAGCircuit
from qiskit.circuit.barrier import Barrier


Expand Down Expand Up @@ -59,12 +58,7 @@ def run(self, dag):
return dag

# add the merged barriers to a new DAG
new_dag = DAGCircuit()

for qreg in dag.qregs.values():
new_dag.add_qreg(qreg)
for creg in dag.cregs.values():
new_dag.add_creg(creg)
new_dag = dag._copy_circuit_metadata()

# go over current nodes, and add them to the new dag
for node in dag.topological_op_nodes():
Expand Down
9 changes: 1 addition & 8 deletions qiskit/transpiler/passes/utils/remove_final_measurements.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"""Remove final measurements and barriers at the end of a circuit."""

from qiskit.transpiler.basepasses import TransformationPass
from qiskit.dagcircuit import DAGCircuit


class RemoveFinalMeasurements(TransformationPass):
Expand Down Expand Up @@ -54,8 +53,6 @@ def run(self, dag):
if not final_ops:
return dag

new_dag = DAGCircuit()

for node in final_ops:
for carg in node.cargs:
# Add the clbit that was attached to the measure we are removing
Expand All @@ -75,11 +72,7 @@ def run(self, dag):
if val in cregs_to_remove and cregs_to_remove[val] == val.size:
del dag.cregs[key]

# Fill new DAGCircuit
for qreg in dag.qregs.values():
new_dag.add_qreg(qreg)
for creg in dag.cregs.values():
new_dag.add_creg(creg)
new_dag = dag._copy_circuit_metadata()

for node in dag.topological_op_nodes():
# copy the condition over too
Expand Down
31 changes: 31 additions & 0 deletions test/python/basicaer/test_statevector_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,37 @@ def test_unitary(self):
fidelity = state_fidelity(psi_target, psi_out)
self.assertGreater(fidelity, 0.999)

def test_global_phase(self):
"""Test global_phase"""
n_qubits = 4
qr = QuantumRegister(n_qubits)
circ = QuantumCircuit(qr)
circ.x(qr)
circ.global_phase = 0.5
self.circuit = circ
result = super().test_run_circuit()
actual = result.get_statevector(self.circuit)
expected = np.exp(1j * circ.global_phase) * np.repeat([[0], [1]], [n_qubits**2-1, 1])
self.assertTrue(np.allclose(actual, expected))

def test_global_phase_composite(self):
"""Test global_phase"""
n_qubits = 4
qr = QuantumRegister(n_qubits)
circ = QuantumCircuit(qr)
circ.x(qr)
circ.global_phase = 0.5
gate = circ.to_gate()

comp = QuantumCircuit(qr)
comp.append(gate, qr)
comp.global_phase = 0.1
self.circuit = comp
result = super().test_run_circuit()
actual = result.get_statevector(self.circuit)
expected = np.exp(1j * 0.6) * np.repeat([[0], [1]], [n_qubits**2-1, 1])
self.assertTrue(np.allclose(actual, expected))


if __name__ == '__main__':
unittest.main()
9 changes: 9 additions & 0 deletions test/python/circuit/test_controlled_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,15 @@ def test_open_controlled_unitary_z(self, num_ctrl_qubits, ctrl_state):
ref_mat = _compute_control_matrix(umat, num_ctrl_qubits, ctrl_state=ctrl_state)
self.assertEqual(Operator(cugate), Operator(ref_mat))

def test_controlled_controlled_rz(self):
"""Test that UnitaryGate with control returns params."""
qc = QuantumCircuit(1)
qc.rz(0.2, 0)
controlled = QuantumCircuit(2)
controlled.compose(qc.control(), inplace=True)
self.assertEqual(Operator(controlled), Operator(CRZGate(0.2)))
self.assertEqual(Operator(controlled), Operator(RZGate(0.2).control()))

def test_controlled_controlled_unitary(self):
"""Test that global phase in iso decomposition of unitary is handled."""
umat = np.array([[1, 0], [0, -1]])
Expand Down
2 changes: 1 addition & 1 deletion test/python/circuit/test_extensions_standard.py
Original file line number Diff line number Diff line change
Expand Up @@ -1381,7 +1381,7 @@ def test_to_matrix_op(self):
from qiskit.quantum_info import Operator
from qiskit.circuit.library.standard_gates.ms import MSGate

params = [0.1 * i for i in range(10)]
params = [0.1 * i for i in range(1, 11)]
gate_class_list = Gate.__subclasses__() + ControlledGate.__subclasses__()
for gate_class in gate_class_list:
sig = signature(gate_class)
Expand Down
18 changes: 18 additions & 0 deletions test/python/transpiler/test_basis_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -643,3 +643,21 @@ def test_cx_bell_to_iswap(self):
expected_dag = circuit_to_dag(expected)

self.assertEqual(out_dag, expected_dag)

def test_global_phase(self):
"""Verify global phase preserved in basis translation"""
circ = QuantumCircuit(1)
gate_angle = pi / 5
circ_angle = pi / 3
circ.rz(gate_angle, 0)
circ.global_phase = circ_angle
in_dag = circuit_to_dag(circ)
out_dag = BasisTranslator(std_eqlib, ['u1']).run(in_dag)

qr = QuantumRegister(1, 'q')
expected = QuantumCircuit(qr)
expected.u1(gate_angle, qr)
expected.global_phase = circ_angle - gate_angle / 2
expected_dag = circuit_to_dag(expected)
self.assertEqual(out_dag, expected_dag)
self.assertEqual(float(out_dag.global_phase), float(expected_dag.global_phase))
Loading

0 comments on commit ee854fb

Please sign in to comment.