Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Insertion ordered parameters #3910

Closed
wants to merge 11 commits into from
3 changes: 2 additions & 1 deletion qiskit/circuit/parametertable.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"""
Look-up table for variable parameters in QuantumCircuit.
"""
from collections import OrderedDict
from collections.abc import MutableMapping

from .instruction import Instruction
Expand All @@ -27,7 +28,7 @@ def __init__(self, *args, **kwargs):
the structure of _table is,
{var_object: [(instruction_object, parameter_index), ...]}
"""
self._table = dict(*args, **kwargs)
self._table = OrderedDict(*args, **kwargs) # replace by dict() when Python 3.5 reaches EOL

def __getitem__(self, key):
return self._table[key]
Expand Down
20 changes: 13 additions & 7 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,37 +568,43 @@ def _check_cargs(self, cargs):
if not all(self.has_register(i.register) for i in cargs):
raise CircuitError("register not in this circuit")

def to_instruction(self, parameter_map=None):
def to_instruction(self, parameter_map=None, sort_parameters_by_name=True):
"""Create an Instruction out of this circuit.

Args:
parameter_map(dict): For parameterized circuits, a mapping from
parameters in the circuit to parameters to be used in the
instruction. If None, existing circuit parameters will also
parameterize the instruction.
sort_parameters_by_name (bool): If True, the parameters in the circuit are sorted by
name before being added to the gate. Otherwise, the order of the circuit is used,
i.e. insertion-ordered by default.

Returns:
Instruction: a composite instruction encapsulating this circuit
(can be decomposed back)
"""
from qiskit.converters.circuit_to_instruction import circuit_to_instruction
return circuit_to_instruction(self, parameter_map)
return circuit_to_instruction(self, parameter_map, sort_parameters_by_name)

def to_gate(self, parameter_map=None):
def to_gate(self, parameter_map=None, sort_parameters_by_name=True):
"""Create a Gate out of this circuit.

Args:
parameter_map(dict): For parameterized circuits, a mapping from
parameters in the circuit to parameters to be used in the
gate. If None, existing circuit parameters will also
parameterize the gate.
sort_parameters_by_name (bool): If True, the parameters in the circuit are sorted by
name before being added to the gate. Otherwise, the order of the circuit is used,
i.e. insertion-ordered by default.

Returns:
Gate: a composite gate encapsulating this circuit
(can be decomposed back)
"""
from qiskit.converters.circuit_to_gate import circuit_to_gate
return circuit_to_gate(self, parameter_map)
return circuit_to_gate(self, parameter_map, sort_parameters_by_name)

def decompose(self):
"""Call a decomposition pass on this circuit,
Expand Down Expand Up @@ -1238,7 +1244,7 @@ def from_qasm_str(qasm_str):
@property
def parameters(self):
"""Convenience function to get the parameters defined in the parameter table."""
return set(self._parameter_table.keys())
return list(self._parameter_table.keys())

def bind_parameters(self, value_dict):
"""Assign parameters to values yielding a new circuit.
Expand All @@ -1255,7 +1261,7 @@ def bind_parameters(self, value_dict):
new_circuit = self.copy()
unrolled_value_dict = self._unroll_param_dict(value_dict)

if unrolled_value_dict.keys() > self.parameters:
if unrolled_value_dict.keys() > set(self.parameters):
raise CircuitError('Cannot bind parameters ({}) not present in the circuit.'.format(
[str(p) for p in value_dict.keys() - self.parameters]))

Expand All @@ -1267,7 +1273,7 @@ def bind_parameters(self, value_dict):
return new_circuit

def _unroll_param_dict(self, value_dict):
unrolled_value_dict = {}
unrolled_value_dict = OrderedDict() # replace by dict() when Python 3.5 reaches EOL
for (param, value) in value_dict.items():
if isinstance(param, ParameterExpression):
unrolled_value_dict[param] = value
Expand Down
2 changes: 1 addition & 1 deletion qiskit/compiler/assemble.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ def _expand_parameters(circuits, run_config):

all_bind_parameters = [bind.keys()
for bind in parameter_binds]
all_circuit_parameters = [circuit.parameters for circuit in circuits]
all_circuit_parameters = [set(circuit.parameters) for circuit in circuits]

# Collect set of all unique parameters across all circuits and binds
unique_parameters = {param
Expand Down
30 changes: 21 additions & 9 deletions qiskit/converters/circuit_to_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@

"""Helper function for converting a circuit to a gate"""

from collections import OrderedDict

from qiskit.circuit.gate import Gate
from qiskit.circuit.quantumregister import QuantumRegister, Qubit
from qiskit.exceptions import QiskitError


def circuit_to_gate(circuit, parameter_map=None):
def circuit_to_gate(circuit, parameter_map=None, sort_parameters_by_name=True):
"""Build a ``Gate`` object from a ``QuantumCircuit``.

The gate is anonymous (not tied to a named quantum register),
Expand All @@ -32,6 +34,9 @@ def circuit_to_gate(circuit, parameter_map=None):
parameters in the circuit to parameters to be used in the gate.
If None, existing circuit parameters will also parameterize the
Gate.
sort_parameters_by_name (bool): If True, the parameters in the circuit are sorted by name
before being added to the gate. Otherwise, the order of the circuit is used, i.e.
insertion-ordered by default.

Raises:
QiskitError: if circuit is non-unitary or if
Expand All @@ -51,19 +56,26 @@ def circuit_to_gate(circuit, parameter_map=None):
raise QiskitError('One or more instructions in this instruction '
'cannot be converted to a gate')

parameter_dict = OrderedDict() # replace by dict() when Python 3.5 reaches EOL
if parameter_map is None:
parameter_dict = {p: p for p in circuit.parameters}
parameter_dict.update(zip(circuit.parameters, circuit.parameters))
else:
parameter_dict = circuit._unroll_param_dict(parameter_map)

if parameter_dict.keys() != circuit.parameters:
raise QiskitError(('parameter_map should map all circuit parameters. '
'Circuit parameters: {}, parameter_map: {}').format(
circuit.parameters, parameter_dict))
unrolled_parameter_map = circuit._unroll_param_dict(parameter_map)
if unrolled_parameter_map.keys() != set(circuit.parameters):
raise QiskitError(('parameter_map should map all circuit parameters. '
'Circuit parameters: {}, parameter_map: {}').format(
circuit.parameters, parameter_dict))
for parameter in circuit.parameters:
parameter_dict[parameter] = unrolled_parameter_map[parameter]

if sort_parameters_by_name:
gate_parameters = sorted(parameter_dict.values(), key=lambda p: p.name)
else:
gate_parameters = list(parameter_dict.values())

gate = Gate(name=circuit.name,
num_qubits=sum([qreg.size for qreg in circuit.qregs]),
params=sorted(parameter_dict.values(), key=lambda p: p.name))
params=gate_parameters)
gate.condition = None

def find_bit_position(bit):
Expand Down
30 changes: 21 additions & 9 deletions qiskit/converters/circuit_to_instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@

"""Helper function for converting a circuit to an instruction."""

from collections import OrderedDict

from qiskit.exceptions import QiskitError
from qiskit.circuit.instruction import Instruction
from qiskit.circuit.quantumregister import QuantumRegister, Qubit
from qiskit.circuit.classicalregister import ClassicalRegister


def circuit_to_instruction(circuit, parameter_map=None):
def circuit_to_instruction(circuit, parameter_map=None, sort_parameters_by_name=True):
"""Build an ``Instruction`` object from a ``QuantumCircuit``.

The instruction is anonymous (not tied to a named quantum register),
Expand All @@ -33,6 +35,9 @@ def circuit_to_instruction(circuit, parameter_map=None):
parameters in the circuit to parameters to be used in the instruction.
If None, existing circuit parameters will also parameterize the
instruction.
sort_parameters_by_name (bool): If True, the parameters in the circuit are sorted by name
before being added to the gate. Otherwise, the order of the circuit is used, i.e.
insertion-ordered by default.

Raises:
QiskitError: if parameter_map is not compatible with circuit
Expand All @@ -59,20 +64,27 @@ def circuit_to_instruction(circuit, parameter_map=None):
circuit_to_instruction(circ)
"""

parameter_dict = OrderedDict() # replace by dict() when Python 3.5 reaches EOL
if parameter_map is None:
parameter_dict = {p: p for p in circuit.parameters}
parameter_dict.update(zip(circuit.parameters, circuit.parameters))
else:
parameter_dict = circuit._unroll_param_dict(parameter_map)

if parameter_dict.keys() != circuit.parameters:
raise QiskitError(('parameter_map should map all circuit parameters. '
'Circuit parameters: {}, parameter_map: {}').format(
circuit.parameters, parameter_dict))
unrolled_parameter_map = circuit._unroll_param_dict(parameter_map)
if unrolled_parameter_map.keys() != set(circuit.parameters):
raise QiskitError(('parameter_map should map all circuit parameters. '
'Circuit parameters: {}, parameter_map: {}').format(
circuit.parameters, parameter_dict))
for parameter in circuit.parameters:
parameter_dict[parameter] = unrolled_parameter_map[parameter]

if sort_parameters_by_name:
gate_parameters = sorted(parameter_dict.values(), key=lambda p: p.name)
else:
gate_parameters = list(parameter_dict.values())

instruction = Instruction(name=circuit.name,
num_qubits=sum([qreg.size for qreg in circuit.qregs]),
num_clbits=sum([creg.size for creg in circuit.cregs]),
params=sorted(parameter_dict.values(), key=lambda p: p.name))
params=gate_parameters)
instruction.condition = None

def find_bit_position(bit):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
features:
- |
Since Python 3.6 dictionaries are insertion ordered, i.e. retrieving the keys of a
dictionary will return an iterable mirroring the order in which the keys are inserted.
In the QuantumCircuit, Parameters are stored in a dictionary so the information of
the insertion order is present.
Currently, we discard this information at two points:
1) if ``QuantumCircuit.parameters`` is called we return a set of the parameters,
which can change the order
2) if we create a ``Gate`` or ``Instruction`` out of a circuit, the parameters are
actively sorted by name
The new behaviour ensures that parameters are insertion-sorted by
1) returning a list, not set, of the parameter dictionary keys
2) not re-sorting the parameters of the circuit
This feature mirrors the behaviour some users might expect and is necessary for changes
in Aqua introduced by the Ansatz object.
Loading