Skip to content

Commit

Permalink
Support parameterized global phase (Qiskit#1814)
Browse files Browse the repository at this point in the history
* Support parameterized global phase

Though `ParameterExpression` can be set to `QuantumCircuit.global_phase`,
Aer does not bind parameter values to it in simulation phase. This commit fixes
this problem by resolving values of `global_phase` with specified `parameter_binds`
in `AerSimulator.run`.

* define AER::CONFIG::GLOBAL_PHASE_POS and its pybind
* fix lint issues
---------
Co-authored-by: Jun Doi <[email protected]>
  • Loading branch information
hhorii committed Jun 7, 2023
1 parent fc5c11b commit 033e601
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 51 deletions.
8 changes: 7 additions & 1 deletion qiskit_aer/backends/aer_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,13 @@ def assemble_circuit(circuit: QuantumCircuit):

qreg_sizes = []
creg_sizes = []
global_phase = float(circuit.global_phase)
if (
isinstance(circuit.global_phase, ParameterExpression)
and len(circuit.global_phase.parameters) > 0
):
global_phase = 0.0
else:
global_phase = float(circuit.global_phase)

for qreg in circuit.qregs:
qreg_sizes.append([qreg.name, qreg.size])
Expand Down
47 changes: 28 additions & 19 deletions qiskit_aer/backends/aerbackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
from .aer_compiler import compile_circuit, assemble_circuits, generate_aer_config
from .backend_utils import format_save_type, circuit_optypes

# pylint: disable=import-error, no-name-in-module
from .controller_wrappers import AerConfig

# Logger
logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -81,28 +84,34 @@ def __init__(

def _convert_circuit_binds(self, circuit, binds):
parameterizations = []

def append_param_values(index, bind_pos, param):
if param in binds:
parameterizations.append([(index, bind_pos), binds[param]])
elif isinstance(param, ParameterExpression):
# If parameter expression has no unbound parameters
# it's already bound and should be skipped
if not param.parameters:
return
if not binds:
raise AerError("The element of parameter_binds is empty.")
len_vals = len(next(iter(binds.values())))
bind_list = [
{
parameter: binds[parameter][i]
for parameter in param.parameters & binds.keys()
}
for i in range(len_vals)
]
bound_values = [float(param.bind(x)) for x in bind_list]
parameterizations.append([(index, bind_pos), bound_values])

append_param_values(AerConfig.GLOBAL_PHASE_POS, -1, circuit.global_phase)

for index, instruction in enumerate(circuit.data):
if instruction.operation.is_parameterized():
for bind_pos, param in enumerate(instruction.operation.params):
if param in binds:
parameterizations.append([(index, bind_pos), binds[param]])
elif isinstance(param, ParameterExpression):
# If parameter expression has no unbound parameters
# it's already bound and should be skipped
if not param.parameters:
continue
if not binds:
raise AerError("The element of parameter_binds is empty.")
len_vals = len(next(iter(binds.values())))
bind_list = [
{
parameter: binds[parameter][i]
for parameter in param.parameters & binds.keys()
}
for i in range(len_vals)
]
bound_values = [float(param.bind(x)) for x in bind_list]
parameterizations.append([(index, bind_pos), bound_values])
append_param_values(index, bind_pos, param)
return parameterizations

def _convert_binds(self, circuits, parameter_binds):
Expand Down
3 changes: 3 additions & 0 deletions qiskit_aer/backends/wrappers/aer_controller_binding.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ void bind_aer_controller(MODULE m) {

// system configurations
aer_config.def_readwrite("library_dir", &Config::library_dir);
aer_config.def_property_readonly_static(
"GLOBAL_PHASE_POS",
[](const py::object &) { return Config::GLOBAL_PHASE_POS; });
aer_config.def_readwrite("parameterizations", &Config::param_table);
aer_config.def_property(
"n_qubits", [](const Config &config) { return config.n_qubits.val; },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
fixes:
- |
:class:`~qiskit.circuit.QuantumCircuit` supports parameterization for its `global_phase`.
However, Aer has not allowed such parameterization and failed when transpiler generates
parameterized global phases. This commit supports parameterization of `global_phase` and
resolve issues related to https://github.com/Qiskit/qiskit-aer/issues/1795,
https://github.com/Qiskit/qiskit-aer/issues/1781, and https://github.com/Qiskit/qiskit-aer/issues/1798.
38 changes: 23 additions & 15 deletions src/controllers/controller_execute.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Result controller_execute(std::vector<Circuit> &input_circs,
// i is the instruction index in the experiment
// j is the param index in the instruction
// pars = [par0, par1, ...] is a list of different parameterizations
using pos_t = std::pair<uint_t, uint_t>;
using pos_t = std::pair<int_t, int_t>;
using exp_params_t = std::vector<std::pair<pos_t, std::vector<double>>>;
std::vector<exp_params_t> param_table = config.param_table;

Expand Down Expand Up @@ -101,21 +101,29 @@ Result controller_execute(std::vector<Circuit> &input_circs,
const auto instr_pos = params.first.first;
const auto param_pos = params.first.second;
// Validation
if (instr_pos >= num_instr) {
throw std::invalid_argument(
R"(Invalid parameterized qobj: instruction position out of range)");
if (instr_pos == AER::Config::GLOBAL_PHASE_POS) {
// negative position is for global phase
circ.global_phase_angle = params.second[j];
} else {
if (instr_pos >= num_instr) {
std::cout << "Invalid parameterization: instruction position "
"out of range: "
<< instr_pos << std::endl;
throw std::invalid_argument(
R"(Invalid parameterization: instruction position out of range)");
}
auto &op = param_circ.ops[instr_pos];
if (param_pos >= op.params.size()) {
throw std::invalid_argument(
R"(Invalid parameterization: instruction param position out of range)");
}
if (j >= params.second.size()) {
throw std::invalid_argument(
R"(Invalid parameterization: parameterization value out of range)");
}
// Update the param
op.params[param_pos] = params.second[j];
}
auto &op = param_circ.ops[instr_pos];
if (param_pos >= op.params.size()) {
throw std::invalid_argument(
R"(Invalid parameterized qobj: instruction param position out of range)");
}
if (j >= params.second.size()) {
throw std::invalid_argument(
R"(Invalid parameterized qobj: parameterization value out of range)");
}
// Update the param
op.params[param_pos] = params.second[j];
}
// Run truncation.
// TODO: Truncation should be performed and parameters should be
Expand Down
4 changes: 3 additions & 1 deletion src/framework/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@ struct Config {

// system configurations
std::string library_dir = "";
using pos_t = std::pair<uint_t, uint_t>;
const static int_t GLOBAL_PHASE_POS =
-1; // special param position for global phase
using pos_t = std::pair<int_t, int_t>;
using exp_params_t = std::vector<std::pair<pos_t, std::vector<double>>>;
std::vector<exp_params_t> param_table;
optional<uint_t> n_qubits;
Expand Down
35 changes: 20 additions & 15 deletions src/framework/qobj.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ Qobj::Qobj(const inputdata_t &input) {
// i is the instruction index in the experiment
// j is the param index in the instruction
// pars = [par0, par1, ...] is a list of different parameterizations
using pos_t = std::pair<uint_t, uint_t>;
using pos_t = std::pair<int_t, int_t>;
using exp_params_t = std::vector<std::pair<pos_t, std::vector<double>>>;
std::vector<exp_params_t> param_table;
Parser<json_t>::get_value(param_table, "parameterizations", config);
Expand Down Expand Up @@ -149,21 +149,26 @@ Qobj::Qobj(const inputdata_t &input) {
const auto instr_pos = params.first.first;
const auto param_pos = params.first.second;
// Validation
if (instr_pos >= num_instr) {
throw std::invalid_argument(
R"(Invalid parameterized qobj: instruction position out of range)");
if (instr_pos == AER::Config::GLOBAL_PHASE_POS) {
// negative position is for global phase
circuit.global_phase_angle = params.second[j];
} else {
if (instr_pos >= num_instr) {
throw std::invalid_argument(
R"(Invalid parameterized qobj: instruction position out of range)");
}
auto &op = param_circuit.ops[instr_pos];
if (param_pos >= op.params.size()) {
throw std::invalid_argument(
R"(Invalid parameterized qobj: instruction param position out of range)");
}
if (j >= params.second.size()) {
throw std::invalid_argument(
R"(Invalid parameterized qobj: parameterization value out of range)");
}
// Update the param
op.params[param_pos] = params.second[j];
}
auto &op = param_circuit.ops[instr_pos];
if (param_pos >= op.params.size()) {
throw std::invalid_argument(
R"(Invalid parameterized qobj: instruction param position out of range)");
}
if (j >= params.second.size()) {
throw std::invalid_argument(
R"(Invalid parameterized qobj: parameterization value out of range)");
}
// Update the param
op.params[param_pos] = params.second[j];
}
// Run truncation.
// TODO: Truncation should be performed and parameters should be
Expand Down
21 changes: 21 additions & 0 deletions test/terra/backends/test_parameterized_qobj.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,27 @@ def test_parameters_with_barrier(self):
self.assertSuccess(res)
self.assertEqual(res.get_counts(), {"111": 1024})

def test_global_phase_parameters(self):
"""Test parameterized global phase"""
backend = AerSimulator()

x = Parameter("x")
circuit = QuantumCircuit(1)
circuit.u(x, x, x, [0])
circuit.measure_all()

parameter_binds = [{x: [1, 2, 3]}]
res = backend.run(
[circuit], shots=1024, parameter_binds=parameter_binds, seed_simulator=100
).result()

self.assertSuccess(res)

circuits = [circuit.bind_parameters({x: v}) for v in [1, 2, 3]]
expected = backend.run(circuits, shots=1024, seed_simulator=100).result()

self.assertEqual(res.get_counts(), expected.get_counts())


if __name__ == "__main__":
unittest.main()

0 comments on commit 033e601

Please sign in to comment.