From 1b2cbbd8433e53979cc1b959cc1325f279cc17ac Mon Sep 17 00:00:00 2001 From: Jun Doi Date: Thu, 22 Aug 2024 18:10:35 +0900 Subject: [PATCH] Fix parameter array initialization for runtime parameter binding (#2209) * Fix parameter bind * lint * add test case --- ...x_parameter_bind_rpb-23d8bfbb8ee8d0f4.yaml | 7 ++ src/controllers/controller_execute.hpp | 8 ++- src/noise/noise_model.hpp | 36 ++++++---- src/simulators/circuit_executor.hpp | 65 +++++++++---------- src/simulators/parallel_state_executor.hpp | 5 +- .../backends/test_runtime_parameterization.py | 38 +++++++++++ 6 files changed, 111 insertions(+), 48 deletions(-) create mode 100644 releasenotes/notes/fix_parameter_bind_rpb-23d8bfbb8ee8d0f4.yaml diff --git a/releasenotes/notes/fix_parameter_bind_rpb-23d8bfbb8ee8d0f4.yaml b/releasenotes/notes/fix_parameter_bind_rpb-23d8bfbb8ee8d0f4.yaml new file mode 100644 index 0000000000..7ae71867ed --- /dev/null +++ b/releasenotes/notes/fix_parameter_bind_rpb-23d8bfbb8ee8d0f4.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Runtime parameter binding did not populate default parameters + to all parameters in buffer, this caused issue when some of the + parameter was not bound in gates. + This fix copies default parameters in gates. diff --git a/src/controllers/controller_execute.hpp b/src/controllers/controller_execute.hpp index fc217de84f..f0563ea134 100644 --- a/src/controllers/controller_execute.hpp +++ b/src/controllers/controller_execute.hpp @@ -120,7 +120,13 @@ Result controller_execute(std::vector> &input_circs, R"(Invalid parameterized qobj: instruction param position out of range)"); } // resize parameter array - op.params.resize(op.params.size() * num_params); + uint_t stride = op.params.size(); + op.params.resize(stride * num_params); + // populate default params to allocated params + for (size_t j = 1; j < num_params; j++) { + for (size_t k = 0; k < stride; k++) + op.params[k + stride * j] = op.params[k]; + } op.has_bind_params = true; } uint_t stride = op.params.size() / num_params; diff --git a/src/noise/noise_model.hpp b/src/noise/noise_model.hpp index 50d0172397..1fccba785c 100644 --- a/src/noise/noise_model.hpp +++ b/src/noise/noise_model.hpp @@ -65,6 +65,7 @@ class NoiseModel { const Method method = Method::circuit, bool sample_at_runtime = false) const; + void set_config(const Circuit &circ, const Method method) const; NoiseOps sample_noise_at_runtime(const Operations::Op &op, RngEngine &rng) const; @@ -148,7 +149,8 @@ class NoiseModel { NoiseOps sample_noise_op(const Operations::Op &op, RngEngine &rng, const Method method, const reg_t &mapping, - bool sample_at_runtime) const; + bool sample_at_runtime, + bool param_bind = false) const; // Sample noise for the current operation void sample_readout_noise(const Operations::Op &op, NoiseOps &noise_after, @@ -167,7 +169,8 @@ class NoiseModel { // Sample noise for the current operation NoiseOps sample_noise_helper(const Operations::Op &op, RngEngine &rng, const Method method, const reg_t &mapping, - bool sample_at_runtime) const; + bool sample_at_runtime, + bool param_bind = false) const; // Add a local quantum error to the noise model for specific qubits void add_local_quantum_error(const QuantumError &error, @@ -283,6 +286,15 @@ Circuit NoiseModel::sample_noise(const Circuit &circ, RngEngine &rng, return sample_noise_circuit(circ, rng, method, sample_at_runtime); } +void NoiseModel::set_config(const Circuit &circ, const Method method) const { + // Qubit mapping + reg_t mapping; + if (circ.remapped_qubits) + mapping = reg_t(circ.qubits().cbegin(), circ.qubits().cend()); + circ_method_ = method; + circ_mapping_ = mapping; +} + NoiseModel::NoiseOps NoiseModel::sample_noise_at_runtime(const Operations::Op &op, RngEngine &rng) const { @@ -382,7 +394,8 @@ Circuit NoiseModel::sample_noise_circuit(const Circuit &circ, RngEngine &rng, default: if (noise_active) { NoiseOps noisy_op = - sample_noise_op(op, rng, method, mapping, sample_at_runtime); + sample_noise_op(op, rng, method, mapping, sample_at_runtime, + (circ.num_bind_params > 0)); noisy_circ.ops.insert(noisy_circ.ops.end(), noisy_op.begin(), noisy_op.end()); } @@ -399,13 +412,12 @@ Circuit NoiseModel::sample_noise_circuit(const Circuit &circ, RngEngine &rng, return noisy_circ; } -NoiseModel::NoiseOps NoiseModel::sample_noise_op(const Operations::Op &op, - RngEngine &rng, - const Method method, - const reg_t &mapping, - bool sample_at_runtime) const { - auto noise_ops = - sample_noise_helper(op, rng, method, mapping, sample_at_runtime); +NoiseModel::NoiseOps +NoiseModel::sample_noise_op(const Operations::Op &op, RngEngine &rng, + const Method method, const reg_t &mapping, + bool sample_at_runtime, bool param_bind) const { + auto noise_ops = sample_noise_helper(op, rng, method, mapping, + sample_at_runtime, param_bind); // If original op is conditional, make all the noise operations also // conditional @@ -551,7 +563,7 @@ void NoiseModel::add_nonlocal_quantum_error( NoiseModel::NoiseOps NoiseModel::sample_noise_helper(const Operations::Op &op, RngEngine &rng, const Method method, const reg_t &mapping, - bool sample_at_runtime) const { + bool sample_at_runtime, bool param_bind) const { // Return operator set NoiseOps noise_before; NoiseOps noise_after; @@ -583,7 +595,7 @@ NoiseModel::sample_noise_helper(const Operations::Op &op, RngEngine &rng, std::make_move_iterator(noise_after.end())); if (op.type != Operations::OpType::measure && noise_ops.size() == 2 && - noise_ops[0].qubits == noise_ops[1].qubits) { + noise_ops[0].qubits == noise_ops[1].qubits && !param_bind) { // Try and fuse operations // If either are superoperators combine superoperators // else if either are unitaries combine unitaries diff --git a/src/simulators/circuit_executor.hpp b/src/simulators/circuit_executor.hpp index 39f9a62cff..ddfee144c1 100644 --- a/src/simulators/circuit_executor.hpp +++ b/src/simulators/circuit_executor.hpp @@ -205,7 +205,8 @@ class Executor : public Base { void run_circuit_with_parameter_binding(state_t &state, OpItr first, OpItr last, ExperimentResult &result, RngEngine &rng, const uint_t iparam, - bool final_op); + const Noise::NoiseModel *noise, + bool sample_noise, bool final_op); template void measure_sampler(InputIterator first_meas, InputIterator last_meas, @@ -802,9 +803,9 @@ void Executor::run_circuit_with_sampling(Circuit &circ, state.initialize_creg(circ.num_memory, circ.num_registers); if (circ.num_bind_params > 1) { - run_circuit_with_parameter_binding(state, circ.ops.cbegin(), - circ.ops.cbegin() + first_meas, - result, rng, iparam, final_ops); + run_circuit_with_parameter_binding( + state, circ.ops.cbegin(), circ.ops.cbegin() + first_meas, result, + rng, iparam, nullptr, false, final_ops); } else { state.apply_ops(circ.ops.cbegin(), circ.ops.cbegin() + first_meas, result, rng, final_ops); @@ -846,7 +847,11 @@ void Executor::run_circuit_shots( shot_end[distributed_rank_] - shot_begin[distributed_rank_]; int max_matrix_qubits = 1; - if (!sample_noise) { + if (sample_noise) { + if (circ.num_bind_params > 1) { + noise.set_config(circ, Noise::NoiseModel::Method::circuit); + } + } else { Noise::NoiseModel dummy_noise; state_t dummy_state; ExperimentResult fusion_result; @@ -905,7 +910,7 @@ void Executor::run_circuit_shots( ExperimentResult &result = par_results[i][iparam]; Circuit circ_opt; - if (sample_noise) { + if (sample_noise && (circ.num_bind_params == 1)) { Noise::NoiseModel dummy_noise; circ_opt = noise.sample_noise(circ, rng); fusion_pass.optimize_circuit(circ_opt, dummy_noise, state.opset(), @@ -927,25 +932,20 @@ void Executor::run_circuit_shots( state.initialize_qreg(circ.num_qubits); state.initialize_creg(circ.num_memory, circ.num_registers); - if (sample_noise) { - if (circ.num_bind_params > 1) { - run_circuit_with_parameter_binding(state, circ_opt.ops.cbegin(), - circ_opt.ops.cend(), result, rng, - iparam, true); - } else { + if (circ.num_bind_params > 1) { + run_circuit_with_parameter_binding(state, circ.ops.cbegin(), + circ.ops.cend(), result, rng, iparam, + &noise, sample_noise, true); + } else { + if (sample_noise) { state.apply_ops(circ_opt.ops.cbegin(), circ_opt.ops.cend(), result, rng, true); - } - } else { - if (circ.num_bind_params > 1) { - run_circuit_with_parameter_binding(state, circ.ops.cbegin(), - circ.ops.cend(), result, rng, - iparam, true); } else { state.apply_ops(circ.ops.cbegin(), circ.ops.cend(), result, rng, true); } } + if (distributed_procs_ > 1) { // save creg to be gathered cregs[shot_index] = state.creg(); @@ -1002,29 +1002,28 @@ void Executor::run_circuit_shots( template void Executor::run_circuit_with_parameter_binding( state_t &state, OpItr first, OpItr last, ExperimentResult &result, - RngEngine &rng, const uint_t iparam, bool final_op) { - OpItr op_begin = first; + RngEngine &rng, const uint_t iparam, const Noise::NoiseModel *noise, + bool sample_noise, bool final_op) { OpItr op = first; while (op != last) { + Operations::Op top; + std::vector ops; // run with parameter bind if (op->has_bind_params) { - if (op_begin != op) { - // run ops before this - state.apply_ops(op_begin, op, result, rng, false); - } - - std::vector binded_op(1); - binded_op[0] = Operations::bind_parameter(*op, iparam, num_bind_params_); - state.apply_ops(binded_op.cbegin(), binded_op.cend(), result, rng, - final_op && (op == last - 1)); - op_begin = op + 1; + top = Operations::bind_parameter(*op, iparam, num_bind_params_); + } else { + top = *op; + } + if (sample_noise) { + ops = noise->sample_noise_at_runtime(top, rng); + } else { + ops.push_back(top); } + state.apply_ops(ops.cbegin(), ops.cend(), result, rng, + final_op && (op == last - 1)); op++; } - if (op_begin != last) { - state.apply_ops(op_begin, last, result, rng, final_op); - } } template diff --git a/src/simulators/parallel_state_executor.hpp b/src/simulators/parallel_state_executor.hpp index 0a3b390d35..61658ae149 100644 --- a/src/simulators/parallel_state_executor.hpp +++ b/src/simulators/parallel_state_executor.hpp @@ -924,8 +924,9 @@ void ParallelStateExecutor::apply_cache_blocking_ops( // fecth chunk in cache if (Base::states_[iChunk].qreg().fetch_chunk()) { if (Base::num_bind_params_ > 1) { - Base::run_circuit_with_parameter_binding( - Base::states_[iChunk], first, last, result, rng, iparam, false); + Base::run_circuit_with_parameter_binding(Base::states_[iChunk], first, + last, result, rng, iparam, + nullptr, false, false); } else { Base::states_[iChunk].apply_ops(first, last, result, rng, false); } diff --git a/test/terra/backends/test_runtime_parameterization.py b/test/terra/backends/test_runtime_parameterization.py index e1940fe727..c3385a0724 100644 --- a/test/terra/backends/test_runtime_parameterization.py +++ b/test/terra/backends/test_runtime_parameterization.py @@ -754,6 +754,44 @@ def test_kraus_noise_with_shot_branching(self, method, device): self.assertEqual(counts, counts_pre_bind) + @supported_methods(SUPPORTED_METHODS) + def test_bind_some_param(self, method, device): + """Test parameterized circuit with gate with defaul values""" + shots = 1000 + backend = self.backend(method=method, device=device) + circuit = QuantumCircuit(2) + theta = Parameter("theta") + theta_squared = theta * theta + circuit.h(0) + circuit.rx(theta, 0) + circuit.cx(0, 1) + circuit.rz(theta_squared, 1) + circuit.u(theta, theta_squared, 1.5708, 1) + circuit.measure_all() + parameter_binds = [{theta: [0, pi, 2 * pi]}] + + result = backend.run( + circuit, + shots=shots, + parameter_binds=parameter_binds, + shot_branching_enable=False, + runtime_parameter_bind_enable=True, + ).result() + self.assertSuccess(result) + counts = result.get_counts() + + result_pre_bind = backend.run( + circuit, + shots=shots, + parameter_binds=parameter_binds, + shot_branching_enable=False, + runtime_parameter_bind_enable=False, + ).result() + self.assertSuccess(result_pre_bind) + counts_pre_bind = result_pre_bind.get_counts() + + self.assertEqual(counts, counts_pre_bind) + if __name__ == "__main__": unittest.main()